Fix retained background lifecycle

This commit is contained in:
gamer147
2026-07-12 00:00:45 -04:00
parent fb3a7206ce
commit 96e5306148
13 changed files with 195 additions and 42 deletions

View File

@@ -403,9 +403,31 @@ correct `dst`/`w`/`h`. Ancillary per-object tables observed: `ctx+0x14d54` (obj
Worker functions decoded + annotated in the Ghidra project (updated 2026-07-09): `gfx_object_erase`(`0x47d850`),
`gfx_object_erase_range`(`0x47d8b0`), `gfx_object_query_source_slot`(`0x47f280`),
`gfx_object_get_or_create`(`0x47ddb0`, inserts a zeroed default via
`gfx_object_init_default`@`0x472810`), the setters `gfx_set_vec18/24/16c`(`0x47e960/e910/e800`), the getters
`gfx_object_init_default`@`0x472810`; critically, source slot `obj+4` defaults to **0**, while only an absent
map entry queries as `-1`), the setters `gfx_set_vec18/24/16c`(`0x47e960/e910/e800`), the getters
`gfx_get_vec18/24`(`0x47f360/f2e0`).
**Page-58 lifecycle correction (2026-07-11).** Locator `SC0000 P058` resolves to `wait@0x6545`; the loader at
`0x6470..0x6478` resolves resource `0x7a` to `EV050EA.AGF`. The port loaded and bound it correctly, but handle
`0xcb2a` inherited an earlier translation target `(-100,0)`, rotation `-90 degrees`, and alpha endpoint zero.
The preceding query-gated cleanup had skipped this created-but-unbound object because the port initialized
`SourceSlot=-1`. Native `gfx_object_init_default` zeroes `obj+4`, so op `0x215` returns slot 0, the cleanup
erases the object, and the later bind recreates identity state. Matching that default leaves EV050EA centered,
unrotated, and opaque in the synchronized page-58 compositor trace. The adjacent op `0x242(handle,0)` is not
a reset: `op_0x242_set_object_field2d0@0x4249d0` calls `gfx_object_set_field2d0@0x47f1a0`, which only writes
the supplied value at `obj+0x2d0`.
**Page-89 surface-lifetime correction (2026-07-11).** Locator `SC0000 P089` resolves to
`wait@0x89e9`; the loader at `0x8976..0x898a` resolves resource `0x6a` to `BG004D.AGF`. The background
was bound but composited at `(400,650)`. The remaining 550 pixels were intentional retained motion from
BG001A; the incorrect `(400,600)` component came from the host keeping slot 0's boot-time 800x600 dimensions
after op `0x1fa` released that surface. Native op `0x1fa` frees and nulls `ctx+0x52bd4[slot]`, so a subsequent
op `0x208` size query of the released slot yields no surface dimensions. Clearing the host slot resource and
dimensions on release makes the script's existing-object geometry path compute base `(0,-500)`; after the
retained `(0,550)` translation, BG004D lands at `(0,50)`. A synchronized run at the exact wait confirms the
background and character layers together. This path also established that op `0x1ff` directly replaces the
current translation matrix at `obj+0x16c`; it is now modeled rather than stored in an inert side vector.
#### The `0x21c0x243` sprite transform / ANIMATION cluster (2026-07-10, partial implementation)
The scene-completeness tracker (`tools/scene_opcode_coverage.py`) flagged a dense band of GAP ops in
@@ -918,8 +940,8 @@ negative operands in `SetColorAnimResolved`, samples packed ARGB before blend se
the existing mode-specific path. Exact AE001H, mode-0 RGB-modulation, and mode-1 alpha regressions cover the
contract; the white pulse is removed without suppressing the scripted channel.
**Resolved 2026-07-11:** `0x236` is the movie-to-retained-surface path described below. The unclassified
`0x242/0x23d/0x20a/0x20e` tail (2-arg flags / inline) remains GAP and outside this slice.
**Resolved 2026-07-11:** `0x236` is the movie-to-retained-surface path described below. `0x242` is now
classified as the retained-object `+0x2d0` field setter described above; `0x23d/0x20a/0x20e` remain GAP.
### Movie-to-surface opcode `0x236` (2026-07-11)

View File

@@ -166,10 +166,10 @@ SC0000 label_1235a reaches this when 0x1c7/0x1cc are zero. Native run-state bit
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: handler gfx_op_0x1fd_set_vec_scaled@0x422650 fetches operands 2..4, divides each by the 100.0 constant, and calls gfx_object_set_scale_current@0x47e6b0. The worker gets/creates the object, marks obj+0x68, and calls matrix4_make_scale on obj+0x6c. SC0000 sets AE001D handles to 210/210/100 and 240/240/100; without this setter their 800x800 alpha circles remain below the viewport. Both functions annotated and /v2 saved 2026-07-11.
### 0x1ff `set-gfx-geom3-c` (set-gfx-geom3-c, argc 4)
- **summary:** 0x1ff (handle)(a)(b)(c) — gfx cmd-type 9. Handler gfx_op_0x1ff_set_geom3 @0x4227b0: SETS a 3-vector (int→float a,b,c) on object `handle` via native worker FUN_0047e800 (sibling of 0x217/0x219, a distinct per-object vector). See docs/engine-re.md gfx op-contract table.
### 0x1ff `set-current-translation` (set-gfx-geom3-c, argc 4)
- **summary:** 0x1ff (handle)(x)(y)(z) — immediately replace the retained object's current translation matrix at obj+0x16c. This is the direct-current companion to 0x220's delayed target at obj+0x1ac.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra handler 0x4227b0 (dispatch ctx[0x26c93+0x1ff]); FUN_0047e800(op1,(float)op2,(float)op3,(float)op4).
- **evidence:** Ghidra /v2: handler gfx_op_0x1ff_set_geom3@0x4227b0 converts operands 2..4 to float and calls gfx_object_set_translation_current@0x47e800. The worker gets/creates the object, marks obj+0x168, and calls matrix4_make_translation on obj+0x16c. SC0000 uses (0,0,0) before animating BG001A. Annotated and saved 2026-07-11.
### 0x202 `gfx-blit-color` (gfx-blit-color, argc 5)
- **summary:** 0x202 (handle)(delay_ms)(duration_ms)(alpha)(color) — arm the one-shot packed-ARGB channel. Worker gfx_op_0x202_worker_set_color_anim @0x47ea00 resets shared start obj+0x34, writes delay +0x38, duration +0x4c, and target +0x64. gfx_object_apply_transform_channels @0x472f00 linearly interpolates each byte from current +0x60 on frame clock ctx+0xb550, commits the target, clears timing, writes target -1, and clears the one-shot active bit when all sibling channels finish. Negative alpha/RGB independently preserve current bytes. Implemented in GfxState with synchronized current/target timeline evidence; draw-string 0x204/0x7a remains separate.
@@ -206,9 +206,9 @@ Native handler gfx_op_0x20c_present_frame -> gfx_render_frame @0x4820b0. This is
- **evidence:** Ghidra handler 0x423110; writes obj+0x68/+0x6c from operands 2/3, obj from ctx+0x14d54[operand1*4].
### 0x215 `query-gfx-object?` (query-gfx-object?, argc 2)
- **summary:** 0x215 (out_slot)(handle) — query the retained gfx-object map. Handler gfx_op_0x215_query_source_slot@0x42a0b0 calls gfx_object_query_source_slot@0x47f280 with owner ctx+0x46614. The worker searches owner+0x408, the same map populated by geometry/draw workers, and returns obj+4: the live source-surface slot written by draw-texture, or -1 if absent. A geometry-only object remains unbound and returns -1. SC0000 uses a successful result for existing-object geometry and query-guarded teardown: op 0x1f7 erases the object group and op 0x1fa releases this slot. VM fix 2026-07-09 restores AE001H magic-circle cleanup.
- **summary:** 0x215 (out_slot)(handle) — query the retained gfx-object map. Handler gfx_op_0x215_query_source_slot@0x42a0b0 calls gfx_object_query_source_slot@0x47f280 with owner ctx+0x46614. The worker searches owner+0x408, the same map populated by geometry/draw workers, and returns obj+4: the live source-surface slot written by draw-texture, or -1 if absent. gfx_object_init_default zeroes obj+4, so a geometry/animation-created but draw-unbound object returns slot 0, not -1. SC0000 uses a successful result for query-guarded teardown: op 0x1f7 erases the object group and op 0x1fa releases this slot. Matching the zero default prevents stale transforms from surviving into the EV050EA CG at SC0000 page 58.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra asm: handler 0x42a0b0 passes ECX=ctx+0x46614 to 0x47f280. That worker searches ECX+0x408 and returns resolved object+4. gfx_object_bind_draw@0x47e870, called with the same owner, get-or-creates in owner+0x408 and writes source slot to object+4. SC0000 post-effect cleanup 0x3321 queries G[0x62457], sign-tests, then executes 0x1f7(handle,10) + 0x1fa(returned_slot); AE001H res 0x37 was bound to that handle.
- **evidence:** Ghidra: handler 0x42a0b0 passes ECX=ctx+0x46614 to 0x47f280, which searches ECX+0x408 and returns object+4. gfx_object_bind_draw@0x47e870 writes the bound slot there; gfx_object_init_default@0x472810 explicitly writes zero to dword index 1. SC0000 page-58 trace: the old -1 default skipped cleanup of transform-created handle 0xcb2a, then EV050EA inherited translation (-100,0), rotation -90, and alpha 0; the native zero default makes the guard succeed and the reused object is identity/opaque.
### 0x216 `query-gfx-field?` (query-gfx-field?, argc 2)
- **summary:** 0x216 (out)(idx) — gfx cmd-type 5. Handler gfx_op_0x216_query_table46d14 @0x42a0f0: out = *(ctx+0x46d14 + idx*0x14). A per-object field query over a stride-0x14 table. See docs/engine-re.md gfx op-contract table.
@@ -316,6 +316,11 @@ The handler requires an existing destination texture, allocates/reuses a 0x478-b
- **summary:** 0x23f query-object (out)(handle): return object status (FUN_0042a520; -1 if none). C# VM: 0 if the object exists else -1. See docs/engine-re.md §SC0000 anim cluster.
- **grounding:** source=kelebek, confidence=low
### 0x242 `set-gfx-field2d0` (set-gfx-field2d0, argc 2)
- **summary:** 0x242 (handle)(value) — command type 5. Get-or-create the retained gfx object and write value to obj+0x2d0. SC0000's common CG loader passes zero after draw binding. This field does not reset transform or color channels; its downstream purpose remains unknown.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x242_set_object_field2d0@0x4249d0 fetches operands 2 and 1 and calls gfx_object_set_field2d0@0x47f1a0; the worker calls gfx_object_get_or_create then stores operand 2 at returned object+0x2d0.
### 0x243 `reset-anim-clock` (reset-anim-clock, argc 0)
- **summary:** Reset the native global animation-service elapsed and duration fields to zero when service flag bit 1 is clear.
- **grounding:** source=investigation, confidence=high
@@ -1077,10 +1082,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it "
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x242 `u00422D60` (u00422D60, argc 2)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x248 `u00422E80` (u00422E80, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=low

View File

@@ -1445,3 +1445,27 @@ around the wait. Page number is deliberately only the friendly coordinate becaus
shift ordinals; the script/offset remains authoritative. A live SC0000 run verified page 1 as
`show-text@0x834`, string `0x14963`, and `wait-for-input@0x83c`. Validation: focused Python tests,
engine **152/152**, zero-warning Godot build, threaded `SELFTEST OK`, and the live lookup all pass.
### SC0000 page-58 EV050EA background restored (2026-07-11)
The first user-supplied locator, `SC0000 P058 / wait@0x6545 / text@0x653d`, led directly to the missing
full-screen CG. Resource `0x7a` resolves to `EV050EA.AGF` and was loaded into slot 5, but retained handle
`0xcb2a` inherited an older `(-100,0)` translation, `-90 degree` rotation, and alpha-zero endpoint. Native
RE showed why cleanup diverged: `gfx_object_init_default` zeroes source slot `obj+4`, so op `0x215` returns
0 for a created-but-unbound object; only a missing handle returns -1. The port's `SourceSlot=-1` default made
the sign-tested cleanup skip that object. The model now uses native slot 0, allowing erase/recreate to restore
identity state. A lifecycle regression reproduces the transform-create, query-cleanup, and CG-reuse sequence.
Validation: engine **153/153**, zero-warning Godot build, and a synchronized real-scene compositor trace at
page 58 showing `EV050EA.AGF` at `(0,0)`, scale 1, translation 0, rotation 0, and opacity 1.
### SC0000 page-89 BG004D background restored (2026-07-11)
Locator `SC0000 P089 / wait@0x89e9 / text@0x89e1` identified `BG004D.AGF` as loaded but offscreen at
`(400,650)`. The prior BG001A animation intentionally leaves translation y=550; the erroneous extra
`(400,600)` came from `GodotAdvHost` retaining slot 0's seeded 800x600 dimensions after op `0x1fa` released
the surface. Native release frees and nulls the slot. Static releases now remove the host resource and size
state (while the existing in-flight movie retention remains unchanged), and the CLI graphics trace mirrors
that lifecycle. The loader consequently builds base `(0,-500)`, so the retained translation places BG004D
at `(0,50)`. Op `0x1ff` was also completed as the native immediate current-translation setter at obj+0x16c.
Validation: engine **155/155**, zero-warning Godot and CLI builds, and a full synchronized run reaching the
exact page-89 wait with `BG004D.AGF` drawn at `(0,50)` alongside the character sprite.