fix(gfx): op 0x215 query registry is separate from the geometry store
The retained-mode "2nd CG renders off-screen" bug: GfxState conflated two distinct native structures. It assigned a fabricated AcquireSlot() slot on every GetOrCreate (called by all geometry/draw ops) and returned it from QuerySlot (op 0x215). But Ghidra (gfx_op_0x215_register_query @0x42a0b0 / gfx_op_0x1a2_registry_insert @0x42d360) shows 0x215 does map.find(handle) over a registry populated ONLY by op 0x1a2 -- it never allocates a slot. So a CG handle (never 0x1a2-registered) read back as "existing", took the existing branch of label_12649, ran get-texture-size on the wrong slot (0), got size 0, and computed dst = pos(0,0) - (w/2,h) = (-400,-600) -> off-screen. The real engine returns -1 -> the fresh branch -> anchor from the INIT2 arrays -> dst=(0,0). Fix: GfxState keeps a separate _registry (HashSet) populated only by Register() (op 0x1a2); QuerySlot returns the handle if registered else -1, and no longer consults the geometry store or invents slots. Drop AcquireSlot / GfxObject.Slot / the free-list. Verified: Age.Cli gfx --boot SC0000.BIN -> all event CGs dst=(0,0), zero (-400,-600) draws; Godot --boot pages 1/2/4 render opening CGs full-screen; engine 44/44; sweep parity 284 exit / 13 STEP-LIMIT unchanged. Docs: engine-re.md (query-registry-vs-geometry-store section), opcodes.toml 0x1a2/0x215 rebuilt; Ghidra helpers gfx_registry_map_find/hash_insert annotated + saved. Tests rewritten to the native contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -257,8 +257,9 @@ public sealed class VirtualMachine
|
||||
{
|
||||
var o = Gfx.GetOrCreate(Read(a[0])); o.Field68 = Read(a[1]); o.Field6c = Read(a[2]); return pc + 1;
|
||||
}
|
||||
case "gfx-cmd-register": // 0x1a2 (val) — register/insert
|
||||
Gfx.GetOrCreate(Read(a[0])); return pc + 1;
|
||||
case "gfx-cmd-register": // 0x1a2 (handle) — insert into the op-0x215 query registry (native
|
||||
// FUN_0042d360 -> FUN_0042cf70 hash insert; the ONLY populator of that map)
|
||||
Gfx.Register(Read(a[0])); return pc + 1;
|
||||
case "gfx-elem-erase": // 0x1f7 (handle)(count) — erase registry range (teardown, NOT create)
|
||||
Gfx.EraseRange(Read(a[0]), Read(a[1])); return pc + 1;
|
||||
case "gfx-elem-release": // 0x1fa (handle)
|
||||
|
||||
Reference in New Issue
Block a user