Map legacy surface fades and cyclic reset

This commit is contained in:
gamer147
2026-07-29 10:34:03 -04:00
parent 1678459b05
commit e70b506ec4
5 changed files with 155 additions and 69 deletions

View File

@@ -1355,6 +1355,24 @@ channel, samples the exact triangular phase on the shared frame clock, and compo
T(anchor)`. It remains frame-driven while visible and survives native-style object cloning independently
of the immediate and delayed one-shot scale fields.
**Cyclic-channel reset follow-up (2026-07-29):** opcode `0x230` is the shared reset for this looping
animation family. `op_0x230_reset_gfx_cyclic_animations@0x423ba0` takes one retained-object handle and
calls `gfx_object_reset_cyclic_animation_channels@0x47ee60`. The worker gets or creates the object, clears
active flag bit 2, and zeroes five start/period pairs:
| channel field | start | period |
|---|---:|---:|
| looping color | `obj+0x20c` | `obj+0x220` |
| cyclic scale | `obj+0x210` | `obj+0x224` |
| cyclic rotation | `obj+0x214` | `obj+0x228` |
| second cyclic matrix | `obj+0x218` | `obj+0x22c` |
| looping source rectangle | `obj+0x21c` | `obj+0x230` |
It does not change the object's current/base transforms. Five DEBUGADV calls reset an effect object before
demonstrating a new looping asset; FIELD's sole ordinary call resets the temporary moving-unit handle
between its one-shot translation setup and `0x239` source-cell animation. This is a bounded retained-state
operation and is the recommended follow-up after the black-fade pair below.
**Follow-up resolution (2026-07-10):** `0x21f` is the one-shot axis-angle channel and is implemented with
affine rasterization. `0x223` is **not affine**: `gfx_queue_surface_alpha_transition` (`0x47f440`) inserts
a type-0 command-map record keyed by arg 1: start `+4`, delay/duration `+8/+0xc`, target surface slot `+0x10`,
@@ -3154,6 +3172,32 @@ retained objects when `0x20c` targets an offscreen surface, composites the captu
source on the shared frame clock, blocks the VM through the endpoint, and only then permits the following
surface release/root reload. Headless hosts retain their non-rendering no-op policy.
#### Single-surface black/white fade family — `0x21``0x24` (2026-07-29)
The widest remaining opcode `0x22` is not a new renderer. It and `0x21` are the single-surface siblings of
the already implemented mode-4 `0x25` transition. Dispatch-table resolution gives:
| op | handler | transition mode | endpoint |
|---|---|---:|---|
| `0x21` | `op_0x21_fade_surface_in_from_black@0x41cb00` | 0 | captured surface |
| `0x22` | `op_0x22_fade_surface_out_to_black@0x41cbc0` | 1 | black |
| `0x23` | `op_0x23_fade_surface_in_from_white@0x41cc80` | 2 | captured surface |
| `0x24` | `op_0x24_fade_surface_out_to_white@0x41cd40` | 3 | white |
All four take `(surface_slot, timing_argument)`, set run-state bit 8, and use the exact `0x25` timing
conversion: arguments up to 64 use `argument` milliseconds per tick with alpha step 16; larger arguments
use `argument/16` milliseconds with step 1. Modes 0/2 start with an opaque solid scratch surface over the
captured frame and lower its alpha to zero. Modes 1/3 keep the captured frame opaque and raise the
black/white scratch alpha to 255. Re-entry finalizes the named endpoint before script execution resumes.
Himegari uses only the black pair: three `0x21` calls and eight `0x22` calls, all `(1,30)` and therefore
about 480 ms. The scripts first select surface 1, clear it, publish a complete retained frame through
`0x20c`, restore the backbuffer, then fade. `0x21` is the menu-entry reveal; `0x22` is the corresponding
menu/scene exit to black. Together they close 11 of the 39 remaining skipped instructions across seven
distinct scripts. Implementation should extend the existing `LegacyScreenTransition` host path with an
explicit solid-color endpoint; treating a missing numbered surface as black is unsafe because the current
mode-4 fallback snapshots the live retained frame.
The same manual pass exposed a resource-addressing issue after `0x6c` was fixed: ROOM did execute
`play-voice`, but `GodotAdvHost` attempted only inferred SC-section resolution. ROOM's voice operands
`0x3365..0x3376` are universal packed SYS4INI indices (for example `0x3365` is `EUA0016.OGG`). The first

View File

@@ -652,13 +652,37 @@ SC0000 label_1235a reaches this when 0x1c7/0x1cc are zero. Native run-state bit
## draw
### 0x21 `fade-surface-in-from-black` (u00418860, argc 2)
- **summary:** (surface_slot)(timing_argument) — block while fading from black to a captured full-frame surface. The captured surface remains the terminal frame.
- **grounding:** source=investigation, confidence=high
- **depends on:** 0x20c, 0x20d
- **evidence:** Ghidra /v2: dispatch slot 0x21 resolves to op_0x21_fade_surface_in_from_black@0x41cb00. First entry sets run-state bit 8, converts timing <=64 to interval=argument ms and alpha step=16 (otherwise interval=argument/16 and step=1), then starts screen_transition_begin@0x439da0 mode 0 with the operand surface. screen_transition_tick@0x43a7a0 draws that surface below an opaque-black scratch surface whose alpha falls 255->0; re-entry finalizes the operand surface. Corpus: 3 sites in EVOLVE, SELSTAGE, and STUDY, all (1,30), approximately 480 ms.
### 0x22 `fade-surface-out-to-black` (u00418920, argc 2)
- **summary:** (surface_slot)(timing_argument) — block while fading a captured full-frame surface to black. Black remains the terminal frame.
- **grounding:** source=investigation, confidence=high
- **depends on:** 0x20c, 0x20d
- **evidence:** Ghidra /v2: dispatch slot 0x22 resolves to op_0x22_fade_surface_out_to_black@0x41cbc0. It uses the same timer/alpha-step conversion as 0x21 and 0x25, then starts screen_transition_begin@0x439da0 mode 1. screen_transition_tick@0x43a7a0 keeps the operand surface opaque and raises the black scratch-surface alpha 0->255; re-entry finalizes a black endpoint. Corpus: 8 sites across ALCHEMY, CHMENU, EVOLVE, FORT, SELSTAGE, STUDY, and SUMMON, all (1,30), approximately 480 ms.
### 0x23 `fade-surface-in-from-white` (u004189D0, argc 2)
- **summary:** (surface_slot)(timing_argument) — broader-AGE mode-2 sibling of 0x21: block while fading from white to a captured full-frame surface.
- **grounding:** source=investigation, confidence=high
- **depends on:** 0x20c, 0x20d
- **evidence:** Ghidra /v2: dispatch slot 0x23 resolves to op_0x23_fade_surface_in_from_white@0x41cc80 and starts screen_transition_begin@0x439da0 mode 2 with the same timing conversion as 0x21/0x22/0x25. Not observed in Himegari's script corpus.
### 0x24 `fade-surface-out-to-white` (u00418A90, argc 2)
- **summary:** (surface_slot)(timing_argument) — broader-AGE mode-3 sibling of 0x22: block while fading a captured full-frame surface to white.
- **grounding:** source=investigation, confidence=high
- **depends on:** 0x20c, 0x20d
- **evidence:** Ghidra /v2: dispatch slot 0x24 resolves to op_0x24_fade_surface_out_to_white@0x41cd40 and starts screen_transition_begin@0x439da0 mode 3 with the same timing conversion as 0x21/0x22/0x25. Not observed in Himegari's script corpus.
### 0x25 `crossfade-surfaces` (u00418B40, argc 3)
- **summary:** (source_surface)(target_surface)(interval_argument) — blocking legacy full-frame mode-4 alpha transition. The native engine composites the captured target surface over the captured source while an 8-bit alpha accumulator advances from 0 to 256.
- **grounding:** source=investigation, confidence=high
- **depends on:** 0x20c, 0x20d
- **evidence:** Ghidra /v2: dispatch handler op_0x25_handler@0x41ce00 sets run-state bit 8, converts operand 3 to timer interval/alpha step (<=64: interval=arg ms and step=16; >64: interval=arg/16 ms and step=1), calls screen_transition_begin@0x439da0 with mode 4, and later screen_transition_finalize@0x4399c0. engine_main_tick_with_exception_policy polls interval_timer_poll_elapsed_steps@0x44d080 and calls screen_transition_tick@0x43a7a0; mode 4 draws source then target with progress alpha and commits target at 0x100. Thus ROOM argument 10 is about 160 ms and argument 30 about 480 ms. ROOM sites: 0x12f,0x7c6,0x856 use (1,2,10); 0x8dc uses (1,2,30). The step branch was corrected after manual timing validation; /v2 annotated and saved 2026-07-21.
The handler uses an alpha step of 1 and timer interval=argument when argument <=64. Above 64 it uses step=16 and interval=argument/16. The main loop polls that timer, advances by skipped intervals plus the current interval, and does not resume the script until the target endpoint has been presented. ROOM uses (1,2,10) for button/character entry and exit fades and (1,2,30) for the final fade before returning to TITLE. The port captures retained-frame snapshots whenever op 0x20c presents to a selected offscreen render target, then reproduces this blocking alpha lifecycle in the interactive host.
The handler uses an alpha step of 16 and timer interval=argument when argument <=64. Above 64 it uses step=1 and interval=argument/16. The main loop polls that timer, advances by skipped intervals plus the current interval, and does not resume the script until the target endpoint has been presented. ROOM uses (1,2,10) for button/character entry and exit fades and (1,2,30) for the final fade before returning to TITLE. The port captures retained-frame snapshots whenever op 0x20c presents to a selected offscreen render target, then reproduces this blocking alpha lifecycle in the interactive host.
### 0x80 `set-default-gfx-object-slot` (u0041AF00, argc 1)
- **summary:** (slot) - select the retained graphics-object slot used when op 0x1d9 receives explicit slot zero.
@@ -783,7 +807,7 @@ The worker clips the paired source and destination rectangles against both surfa
- **summary:** Present the composited frame; label_1235a uses this on the read/message-skip branch to expose the completed foreground endpoint immediately.
- **grounding:** source=investigation, confidence=high
- **depends on:** 0x223, 0x1c7, 0x1cc
- **depended on by:** 0x25, 0x20d, 0x223
- **depended on by:** 0x21, 0x22, 0x23, 0x24, 0x25, 0x20d, 0x223
- **evidence:** Ghidra: dispatch table FUN_00413860 param_1[0x26e9f]=gfx_op_0x20c_present_frame; 0x26e9f-0x26c93=0x20c. 2026-07-08.
Native handler gfx_op_0x20c_present_frame -> gfx_render_frame @0x4820b0. This is an explicit retained-state publication boundary, not a continuously visible object-store mutation. The read/message-skip branch resets the animation service then presents; the port publishes and snaps pending 0x223 state here. Normal playback branches to 0x21c, which owns repeated render/wait/resume. Headless hosts remain non-blocking.
@@ -792,7 +816,7 @@ Native handler gfx_op_0x20c_present_frame -> gfx_render_frame @0x4820b0. This is
- **summary:** Select an offscreen surface slot as Direct3D render target 0, or restore the device backbuffer when the operand is at least 1000.
- **grounding:** source=investigation, confidence=high
- **depends on:** 0x20c, 0x1c7, 0x1cc
- **depended on by:** 0x25
- **depended on by:** 0x21, 0x22, 0x23, 0x24, 0x25
- **evidence:** Ghidra /v2: op_0x20d_select_render_target@0x422e10 passes operand 1 and retained-gfx owner ctx+0x46614 to retained_gfx_select_render_target@0x479660. Its D3D calls resolve texture level 0 or backbuffer 0, then invoke device vtable +0x94 SetRenderTarget and store selected slot at owner+0xb530. DATA1: 113 calls in 24 scripts.
For slots below 1000 the native worker obtains that surface's level-0 D3D texture surface and calls IDirect3DDevice9::SetRenderTarget(0,...). The >=1000 path obtains backbuffer 0 and records current target -1. Himegari commonly follows a freshly created blank surface selection with 0x20e before drawing into it.
@@ -909,6 +933,11 @@ Implemented through IHost.PlayModalMovieToSurface. Its operand uses the same nat
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: gfx_op_0x22f_set_position_anim@0x423b00 fetches x/y/z as floats and passes all five operands to gfx_worker_set_translation@0x472e90. The worker builds the current translation matrix with x/y/z and stores operand 2 in the channel record. This is distinct from the shared range transform at 0x229. The C# handler sets V24 directly and is therefore position-correct but does not yet model operand 2.
### 0x230 `reset-gfx-cyclic-animations` (u00421E70, argc 1)
- **summary:** (handle) — get or create a retained gfx object, clear its cyclic-animation active flag, and zero all five looping-channel start/period pairs without changing current/base transforms.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x230_reset_gfx_cyclic_animations@0x423ba0 fetches the handle and calls gfx_object_reset_cyclic_animation_channels@0x47ee60. The worker clears object flag bit 2 and zeroes starts obj+0x20c/+0x210/+0x214/+0x218/+0x21c plus periods obj+0x220/+0x224/+0x228/+0x22c/+0x230, covering looping color, scale, rotation, the second cyclic matrix, and source-rectangle channels. Corpus: five DEBUGADV effect demonstrations plus one FIELD movement setup before 0x239.
### 0x231 `animate-gfx-srcrect-loop` (u00421EA0, argc 4)
- **summary:** (handle)(frame_period_ms)(frame_count)(column_count) — loop row-major through the spritesheet. Every sample preserves draw-texture's source-rectangle width/height; frame=floor((shared_frame_time-object_start)/frame_period)%frame_count, src offset=(frame%columns*width, frame/columns*height). All objects use the retained manager's shared current/previous millisecond timestamps but retain their own start and period. The native consumer raises redraw dirty only when the current and previous samples select different cells. Worker gfx_worker_anim_srcrect @0x47eec0; consumer gfx_object_anim_interpolate @0x473ed0.
- **grounding:** source=investigation, confidence=high
@@ -1343,24 +1372,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting
- **grounding:** source=kelebek, confidence=low
- **evidence:** Not observed in Himegari's script corpus; ABI label/argc come from Kelebek's AGE table.
### 0x21 `u00418860` (u00418860, argc 2)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x22 `u00418920` (u00418920, argc 2)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x23 `u004189D0` (u004189D0, argc 2)
- **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it.
- **grounding:** source=kelebek, confidence=low
- **evidence:** Not observed in Himegari's script corpus; ABI label/argc come from Kelebek's AGE table.
### 0x24 `u00418A90` (u00418A90, argc 2)
- **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it.
- **grounding:** source=kelebek, confidence=low
- **evidence:** Not observed in Himegari's script corpus; ABI label/argc come from Kelebek's AGE table.
### 0x26 `u00418C00` (u00418C00, argc 4)
- **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it.
- **grounding:** source=kelebek, confidence=low
@@ -2103,10 +2114,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting
- **grounding:** source=kelebek, confidence=low
- **evidence:** Not observed in Himegari's script corpus; ABI label/argc come from Kelebek's AGE table.
### 0x230 `u00421E70` (u00421E70, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x235 `u00422100` (u00422100, argc 5)
- **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it.
- **grounding:** source=kelebek, confidence=low

View File

@@ -823,9 +823,9 @@ writer updates only the nine audio keys and preserves every unrelated option. Ra
native `2 ↔ -1` band toggling. Godot applies
gains through its nested audio buses, so master and category volume compose, and route changes immediately
stop/mute active playback according to the native worker distinctions. All 37 CONFIG sites now dispatch,
reducing the effectful fallthrough inventory from 22 to 18 distinct opcodes. Seven focused tests cover
reducing the effectful fallthrough inventory from 22 to 18 distinct opcodes. Eight focused tests cover
native defaults, category isolation, getter/setter round trips, idempotent routes, cross-VM state, invalid
selectors, path resolution, CP932 INI preservation, and persistence. All 475 engine tests, the
selectors, native and redirected path resolution, CP932 INI preservation, and persistence. All 476 engine tests, the
warning-free Godot build, opcode lint, and the Himegari-targeted threaded selftest pass.
**Post-tranche rerank:** a fresh 481-script aggregate confirms exactly 18 effectful gaps totaling only
@@ -834,8 +834,18 @@ warning-free Godot build, opcode lint, and the Himegari-targeted threaded selfte
sites. CONFIG itself is now 1659/1665 instructions implemented, with four unrelated calls remaining
(`0x142` twice, `0xb7`, and `0xb8`).
**NEXT:** investigate `0x22` as the widest remaining independent opcode, with `0x230` as the next
candidate if `0x22` proves to own a larger subsystem.
**Next-opcode investigation complete (2026-07-29):** `0x22` is the mode-1 half of AGE's blocking
single-surface black-fade pair, not a larger subsystem. `0x21(surface,timing)` fades black to the captured
surface; `0x22(surface,timing)` fades the captured surface to black. They share `0x25`'s already-modeled
timer conversion and full-frame snapshot lifecycle. All 11 combined Himegari sites use `(1,30)`, about
480 ms, across menu entry/exit paths. The existing `LegacyScreenTransition` host seam can own the pair,
but it needs an explicit black endpoint rather than its mode-4 missing-surface fallback.
The follow-up `0x230(handle)` is also decoded: it clears the retained object's cyclic active flag and five
looping-channel start/period pairs without changing current/base transforms. Its six calls are five
DEBUGADV effect demonstrations plus one FIELD movement setup.
**NEXT:** implement `0x21` and `0x22` together as one black-fade slice, then implement `0x230`.
## Later Phase B breadth