docs(gfx): settle render drift as native cmd-buffer op 0x215 (Ghidra), align docs

Read op 0x215's real handler FUN_0042a0b0 (resolved via the dispatch table
ctx[0x26c93+op]; Kelebek's 0x421160 is VA-drift). It writes cmd-type 5 into the
current gfx-object record and returns a std::map::find over an engine-internal
registry populated by sibling gfx ops (0x1a2 hash insert). The return is native
command-buffer state, not the VM global bank -> seeding story-state cannot fix
the drift. Verdict: (b) a genuine native op, NOT (a) state-divergence.

Reconcile the previously contradictory drift accounts onto one canonical home
(engine-re.md op 0x215), with opcodes.toml carrying the opcode-level semantics
and phase-a-slice-plan / tools-reference / frida README corrected to point at it
instead of repeating the disproven state-divergence conclusion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-07-07 16:40:59 -04:00
parent a6e0bec5c3
commit 5b07f4ac2b
7 changed files with 80 additions and 32 deletions

View File

@@ -273,28 +273,28 @@ measured `0×0`, and the anchor-preserve math (`base' = center (w_new/2, h_n
corruption. Fix: seed `_slotDims[0] = (800,600)` (and record `create-texture(w,h)` dims) so the first CG's
anchor stays an identity. This is the faithful stand-in for the skipped boot-time primary-surface creation.
**Post-opening bg/sprite drift — RESOLVED as a STATE-DIVERGENCE artifact, NOT a missing native op
(2026-07-06/07).** Symptom: everything blits through slot 0 as an immediate-mode canvas; the anchor-preserve
base globals **accumulate drift** across differently-sized textures (`BG030A→(300,500)`, next→`(450,100)`,
`(800,350)`… marching bottom-right). We reverse-engineered the whole chain (systematic-debugging):
1. Root cause traced to **`0x215` = native graphics-object query** (opcodes.toml `query-gfx-object?`), which we
stub → `label_12649` takes the wrong branch → all draws collapse onto slot 0 → anchor-preserve reads foreign
textures → drift.
2. **Engine now statically analyzable (major, general unlock):** `SYS4AB.BIN` = `XOR-0xFF(AGE.EXE)` (dead end),
but `AGE.EXE` unpacks **in-place at 0x400000** in the live process → `tools/frida/dump_engine.py`
`build/engine-dump/` (validated via AGF-decoder landmark; interpreter confirmed to run from the module, so
handlers are hookable). Handler ABI + object-record layout (`[esi+0x53d64]`, 120B/rec, cmd-type at rec+0x24)
decoded. See `docs/vm-mapping-plan.md` appendix + `tools/frida/README.md`.
3. **Live capture verdict (the resolution):** `tools/frida/capture_gfx_objects.py` polled the object-record
array through the **real** opening — it held only **3 persistent UI objects, ZERO CG objects**. So the real
game does **not** draw the opening CGs via the `0x2120x21a` positioned-object path our headless VM uses;
with proper state it takes a different (direct) branch that we already render correctly. **⇒ the drift is
downstream of our unseeded headless VM taking `label_12649`'s else-branch (compute-from-drifting-base) where
the real game hits the if-branch (stored/record geometry). The fix is the Phase B state/choices-flow work,
not a separate native-op subsystem.** Seeding real per-scene/object state makes `label_12649` branch right.
Fades/alpha (`AE*`, `0x202/0x203`) + green chromakey + true multi-surface remain deferred; the compositor is
built to accept alpha later. The full-screen opening path is correct and unaffected. **Native gfx-op modeling
is only needed for scenes that genuinely use runtime-positioned sprites — revisit later with the dump in hand.**
**Post-opening bg/sprite drift — root cause SETTLED as (b) a native command-buffer op via Ghidra
(2026-07-07). ⚠ Corrects an earlier wrong "state-divergence" verdict here.** Symptom (screenshot
`Screenshot 2026-07-06 211353.png`): everything blits through slot 0 as an immediate-mode canvas; the
anchor-preserve base globals **accumulate drift** across differently-sized textures (`BG030A→(300,500)`,
next→`(450,100)`, →`(800,350)`… marching bottom-right; the background ends up pinned off-centre / bottom-right
with the rest of the screen grey). Root cause = the stubbed native op **`0x215`** collapsing every draw onto
slot 0 (its return drives `label_12649`'s slot-select).
**The canonical decode + verdict now lives in `docs/engine-re.md` (op `0x215` section)** — don't duplicate it
here. In brief: `0x215`'s real handler `FUN_0042a0b0` (Ghidra) writes cmd-type 5 into the current gfx-object
record and returns a **`std::map::find`** over an engine-internal command-buffer registry (populated by sibling
gfx ops like `0x1a2`). That return is **native command-buffer state, not the VM global bank** → seeding
story-state **cannot** fix it. So this is **(b) a genuine native op**, *not* (a) the Phase-B state-divergence
problem. The prior conclusion in this doc — grounded in a 2/s `capture_gfx_objects.py` poll of the object-*record*
array — was wrong: it observed the wrong structure (not the lookup map) and can't rule out transient records.
**Fix (Frida-free, implementation not RE):** model the gfx command-buffer host-side (record array +
handle→object hash registry) and *execute* the gfx ops (`0x1a2`/`0x215`/`0x2120x21a`) instead of stubbing;
the inserts are bytecode-driven, so the state rebuilds from the same scripts. Fades/alpha (`AE*`,
`0x202/0x203`) + green chromakey + true multi-surface remain deferred; the compositor is built to accept alpha
later. The full-screen event-CG opening path (`EV052*` at `(0,0)`) is correct and unaffected — the drift is
specifically the `BG*`/sprite draws routed through `label_12649`.
---