From 0941be549c273ae4618bcce36e6469606436b295 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sun, 2 Aug 2026 23:50:45 -0400 Subject: [PATCH] Extract VM retained object handler --- docs/PROJECT-STRUCTURE.md | 4 +- docs/remake-architecture-and-roadmap.md | 12 ++- .../Vm/VirtualMachine.RetainedObjects.cs | 90 +++++++++++++++++++ engine/Age.Engine/Vm/VirtualMachine.cs | 77 ++++------------ 4 files changed, 121 insertions(+), 62 deletions(-) create mode 100644 engine/Age.Engine/Vm/VirtualMachine.RetainedObjects.cs diff --git a/docs/PROJECT-STRUCTURE.md b/docs/PROJECT-STRUCTURE.md index 8e42bd0..8c4fe4b 100644 --- a/docs/PROJECT-STRUCTURE.md +++ b/docs/PROJECT-STRUCTURE.md @@ -180,7 +180,9 @@ positioned movie playback, movie surface metadata/activity queries, and movie-ma likewise retains and routes the movie labels. `engine/Age.Engine/Vm/VirtualMachine.Surface.cs` owns surface allocation/loading, texture binding and sizing, mutable surface fill/copy, render-target control, and transient surface release; its labels remain at their existing dispatcher positions around the retained numeric-glyph and -object cases. +object cases. `engine/Age.Engine/Vm/VirtualMachine.RetainedObjects.cs` owns retained-object registry queries, +default-slot and geometry mutation, direct and range transforms, clone, and erase dispatch; animation, surface, +ADV-binding, and presentation labels remain in their respective dispatcher groups. The disposable `build/page-map-.jsonl` files are produced by editor/development Godot runs and map runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports diff --git a/docs/remake-architecture-and-roadmap.md b/docs/remake-architecture-and-roadmap.md index 3d8af1f..68cc79c 100644 --- a/docs/remake-architecture-and-roadmap.md +++ b/docs/remake-architecture-and-roadmap.md @@ -642,6 +642,13 @@ do not mix mechanical moves with semantic changes. existing positions, including the separate routing groups around numeric-glyph and retained-object cases; public VM behavior and case-body logic remain unchanged. Runtime validation remains green. + The fourth bounded `VirtualMachine.Step` extraction moved retained-object registry queries, default-slot and + geometry mutation, direct and embedded-range transforms, clone, and erase dispatch into + `engine/Age.Engine/Vm/VirtualMachine.RetainedObjects.cs`. The top-level dispatcher retains all labels at their + existing positions and routes the separated groups through `StepRetainedObject`; timed/cyclic animation, + color, surface release, ADV binding, and presentation behavior remain outside this handler. Runtime validation + remains green. + **Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after each domain move. @@ -1014,7 +1021,8 @@ layer's rendering diverges from ADV; save layout. Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState` domains isolated and the audio, movie, and surface/texture `VirtualMachine.Step` families routed through domain -handlers, extract the retained-object query/mutation and transform opcode family next without replacing the -proven dispatcher or changing public types, commands, and generated output. +handlers, with retained-object query/mutation and transform dispatch now isolated as well, extract the retained +animation/color/spritesheet opcode family next without replacing the proven dispatcher or changing public types, +commands, and generated output. Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does not replace Phase B gameplay validation or the open cross-platform gates. diff --git a/engine/Age.Engine/Vm/VirtualMachine.RetainedObjects.cs b/engine/Age.Engine/Vm/VirtualMachine.RetainedObjects.cs new file mode 100644 index 0000000..9b6fec9 --- /dev/null +++ b/engine/Age.Engine/Vm/VirtualMachine.RetainedObjects.cs @@ -0,0 +1,90 @@ +using Age.Engine.Model; + +namespace Age.Engine.Vm; + +public sealed partial class VirtualMachine +{ + private int StepRetainedObject(string label, IReadOnlyList a, int pc) + { + switch (label) + { + case "clear-retained-gfx-objects": // 0x1f6: erase object records, but preserve surfaces + Gfx.ClearRetainedObjects(); return pc + 1; + case "query-gfx-object?": // 0x215 (out)(handle) -> slot | -1 + if (_diagSetTexture) // reuse the flag: show what the slot query returns (grey-BG slot dig) + { + long h = Read(a[1]); + System.Console.Error.WriteLine($"[query] handle=0x{h:x} handleOp=(type={a[1].Type} val=0x{a[1].Value:x}) " + + $"-> QuerySlot={Gfx.QuerySlot(h)} objectPresent={Gfx.TryGet(h) != null}"); + } + Write(a[0], Gfx.QuerySlot(Read(a[1]))); return pc + 1; + case "query-gfx-field?": // 0x216 (out)(idx) + Write(a[0], Gfx.QueryField(Read(a[1]))); return pc + 1; + case "get-gfx-geom3?": // 0x218 (handle)(outA)(outB)(outC) <- V18 + { + var v = Gfx.TryGet(Read(a[0]))?.V18 ?? default; + Write(a[1], v.X); Write(a[2], v.Y); Write(a[3], v.Z); return pc + 1; + } + case "get-gfx-geom3-b?": // 0x21a (handle)(outA)(outB)(outC) <- V24 + { + var v = Gfx.TryGet(Read(a[0]))?.V24 ?? default; + Write(a[1], v.X); Write(a[2], v.Y); Write(a[3], v.Z); return pc + 1; + } + case "set-gfx-geom3": // 0x217 (handle)(a)(b)(c) -> V18 + Gfx.SetObjectAnchor(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1; + case "set-gfx-geom3-b": // 0x219 (handle)(a)(b)(c) -> V24 + Gfx.SetObjectPosition(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1; + case "u0041AF00": // 0x80: default object slot substituted by native op 0x1d9 + case "set-default-gfx-object-slot": + Gfx.SetDefaultObjectSlot((int)Read(a[0])); return pc + 1; + + // ---- SC0000 anim/transform/spritesheet cluster (docs/engine-re.md §"SC0000 anim ... cluster") ---- + case "u00421DD0": // 0x22f set-position: (handle)(op2)(x)(y)(z) -> base position (direct set) + Gfx.SetObjectPosition(Read(a[0]), (Read(a[2]), Read(a[3]), Read(a[4]))); return pc + 1; + case "u004219E0": // pre-reference compatibility + case "set-gfx-range-transform": // 0x229 (first)(count)(anchor x/y/z) + Gfx.SetRangeTransform(Read(a[0]), Read(a[1]), (Read(a[2]), Read(a[3]), Read(a[4]))); + return pc + 1; + case "u00421A90": // pre-reference compatibility + case "set-gfx-range-scale-current": // 0x22a (sx%)(sy%)(sz%) + Gfx.SetRangeScaleCurrent((Read(a[0]), Read(a[1]), Read(a[2]))); return pc + 1; + case "u00421BD0": // pre-reference compatibility + case "set-gfx-range-translation-current": // 0x22c (tx)(ty)(tz) + Gfx.SetRangeTranslationCurrent((Read(a[0]), Read(a[1]), Read(a[2]))); return pc + 1; + case "u00421C60": // pre-reference compatibility + case "set-gfx-range-scale-target": // 0x22d (delay)(duration)(sx%)(sy%)(sz%) + Gfx.SetRangeScaleChannel(Read(a[0]), Read(a[1]), (Read(a[2]), Read(a[3]), Read(a[4]))); + return pc + 1; + case "u00421940": // 0x228: (succ)(handle)(outX)(outY)(outZ) <- target translation matrix + { + if (Gfx.TryQueryTranslationTarget(Read(a[1]), out var v)) + { + Write(a[2], (long)v.X); Write(a[3], (long)v.Y); Write(a[4], (long)v.Z); + Write(a[0], 0); + } + else Write(a[0], 1); // native missing-object path leaves output operands untouched + return pc + 1; + } + case "set-gfx-geom3-c": // 0x1ff: set current translation matrix + Gfx.SetCurrentTranslation(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1; + case "u00420620": // upstream ABI label + case "gfx-set-scale-current": // 0x1fd (handle)(sx%)(sy%)(sz%) -> current scale matrix + Gfx.SetCurrentScale(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1; + case "set-current-rotation-axis-angle": // 0x1fe (handle)(axis x/y/z)(angle degrees) + Gfx.SetCurrentRotation(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3])), Read(a[4])); + return pc + 1; + case "gfx-elem-erase": // 0x1f7 (handle)(count) — erase retained-object range + { + long first = Read(a[0]), count = Read(a[1]); + Gfx.EraseRange(first, count); + foreach (int layoutSlot in TextHistory.LayoutsCoveredByTextObjectErase(first, count)) + _host.ClearRenderedAdvTextLayout(layoutSlot); + return pc + 1; + } + case "clone-gfx-object": // 0x21d (source handle)(destination handle) + Gfx.CloneObject(Read(a[0]), Read(a[1])); return pc + 1; + default: + throw new InvalidOperationException($"Non-retained-object opcode routed to retained-object handler: {label}"); + } + } +} diff --git a/engine/Age.Engine/Vm/VirtualMachine.cs b/engine/Age.Engine/Vm/VirtualMachine.cs index 7760015..cc39d7c 100644 --- a/engine/Age.Engine/Vm/VirtualMachine.cs +++ b/engine/Age.Engine/Vm/VirtualMachine.cs @@ -2490,8 +2490,8 @@ public sealed partial class VirtualMachine case "u00420D50": case "copy-surface-rect": return StepSurface(label, a, pc); - case "clear-retained-gfx-objects": // 0x1f6: erase object records, but preserve surfaces - Gfx.ClearRetainedObjects(); return pc + 1; + case "clear-retained-gfx-objects": + return StepRetainedObject(label, a, pc); case "select-render-target": case "clear-render-target": case "release-transient-surfaces": @@ -2528,51 +2528,27 @@ public sealed partial class VirtualMachine case "u00422B80": case "play-movie-to-surface-at-position": return StepMovie(label, ins, pc); - case "query-gfx-object?": // 0x215 (out)(handle) -> slot | -1 - if (_diagSetTexture) // reuse the flag: show what the slot query returns (grey-BG slot dig) - { - long h = Read(a[1]); - System.Console.Error.WriteLine($"[query] handle=0x{h:x} handleOp=(type={a[1].Type} val=0x{a[1].Value:x}) " + - $"-> QuerySlot={Gfx.QuerySlot(h)} objectPresent={Gfx.TryGet(h) != null}"); - } - Write(a[0], Gfx.QuerySlot(Read(a[1]))); return pc + 1; - case "query-gfx-field?": // 0x216 (out)(idx) - Write(a[0], Gfx.QueryField(Read(a[1]))); return pc + 1; - case "get-gfx-geom3?": // 0x218 (handle)(outA)(outB)(outC) <- V18 - { - var v = Gfx.TryGet(Read(a[0]))?.V18 ?? default; - Write(a[1], v.X); Write(a[2], v.Y); Write(a[3], v.Z); return pc + 1; - } - case "get-gfx-geom3-b?": // 0x21a (handle)(outA)(outB)(outC) <- V24 - { - var v = Gfx.TryGet(Read(a[0]))?.V24 ?? default; - Write(a[1], v.X); Write(a[2], v.Y); Write(a[3], v.Z); return pc + 1; - } - case "set-gfx-geom3": // 0x217 (handle)(a)(b)(c) -> V18 - Gfx.SetObjectAnchor(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1; - case "set-gfx-geom3-b": // 0x219 (handle)(a)(b)(c) -> V24 - Gfx.SetObjectPosition(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1; + case "query-gfx-object?": + case "query-gfx-field?": + case "get-gfx-geom3?": + case "get-gfx-geom3-b?": + case "set-gfx-geom3": + case "set-gfx-geom3-b": case "u0041AF00": // 0x80: default object slot substituted by native op 0x1d9 case "set-default-gfx-object-slot": - Gfx.SetDefaultObjectSlot((int)Read(a[0])); return pc + 1; + return StepRetainedObject(label, a, pc); // ---- SC0000 anim/transform/spritesheet cluster (docs/engine-re.md §"SC0000 anim ... cluster") ---- case "u00421DD0": // 0x22f set-position: (handle)(op2)(x)(y)(z) -> base position (direct set) - Gfx.SetObjectPosition(Read(a[0]), (Read(a[2]), Read(a[3]), Read(a[4]))); return pc + 1; case "u004219E0": // pre-reference compatibility case "set-gfx-range-transform": // 0x229 (first)(count)(anchor x/y/z) - Gfx.SetRangeTransform(Read(a[0]), Read(a[1]), (Read(a[2]), Read(a[3]), Read(a[4]))); - return pc + 1; case "u00421A90": // pre-reference compatibility case "set-gfx-range-scale-current": // 0x22a (sx%)(sy%)(sz%) - Gfx.SetRangeScaleCurrent((Read(a[0]), Read(a[1]), Read(a[2]))); return pc + 1; case "u00421BD0": // pre-reference compatibility case "set-gfx-range-translation-current": // 0x22c (tx)(ty)(tz) - Gfx.SetRangeTranslationCurrent((Read(a[0]), Read(a[1]), Read(a[2]))); return pc + 1; case "u00421C60": // pre-reference compatibility case "set-gfx-range-scale-target": // 0x22d (delay)(duration)(sx%)(sy%)(sz%) - Gfx.SetRangeScaleChannel(Read(a[0]), Read(a[1]), (Read(a[2]), Read(a[3]), Read(a[4]))); - return pc + 1; + return StepRetainedObject(label, a, pc); case "u004223C0": // 0x239 spritesheet cell: (handle)(delay)(duration)(frame count)(columns)(cell) Gfx.SetSrcRect(Read(a[0]), Read(a[3]), Read(a[4]), Read(a[5]), 0); return pc + 1; case "reset-gfx-cyclic-animations": // 0x230: stop all five retained looping channels @@ -2586,29 +2562,18 @@ public sealed partial class VirtualMachine Gfx.SetScaleCycle(Read(a[0]), Read(a[1]), (Read(a[2]), Read(a[3]), Read(a[4]))); return pc + 1; case "u00421940": // 0x228: (succ)(handle)(outX)(outY)(outZ) <- target translation matrix - { - if (Gfx.TryQueryTranslationTarget(Read(a[1]), out var v)) - { - Write(a[2], (long)v.X); Write(a[3], (long)v.Y); Write(a[4], (long)v.Z); - Write(a[0], 0); - } - else Write(a[0], 1); // native missing-object path leaves output operands untouched - return pc + 1; - } + return StepRetainedObject(label, a, pc); case "u00422930": case "query-surface-stop-time-ms": case "query-movie-surface-active": return StepMovie(label, ins, pc); case "sample-frame-time": // 0x23c: previous <- current; current <- monotonic time Gfx.SampleFrameTime(_host.InputClockMilliseconds); return pc + 1; - case "set-gfx-geom3-c": // 0x1ff: set current translation matrix - Gfx.SetCurrentTranslation(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1; + case "set-gfx-geom3-c": case "u00420620": // upstream ABI label case "gfx-set-scale-current": // 0x1fd (handle)(sx%)(sy%)(sz%) -> current scale matrix - Gfx.SetCurrentScale(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1; - case "set-current-rotation-axis-angle": // 0x1fe (handle)(axis x/y/z)(angle degrees) - Gfx.SetCurrentRotation(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3])), Read(a[4])); - return pc + 1; + case "set-current-rotation-axis-angle": + return StepRetainedObject(label, a, pc); case "set-adv-wait-indicator-handle": // 0x212 (layout)(retained handle) { int requestedSlot = (int)Read(a[0]); @@ -2625,18 +2590,12 @@ public sealed partial class VirtualMachine TextHistory.SetTextObjectRange((int)Read(a[0]), Read(a[1]), Read(a[2])); return pc + 1; } - case "gfx-elem-erase": // 0x1f7 (handle)(count) — erase retained-object range - { - long first = Read(a[0]), count = Read(a[1]); - Gfx.EraseRange(first, count); - foreach (int layoutSlot in TextHistory.LayoutsCoveredByTextObjectErase(first, count)) - _host.ClearRenderedAdvTextLayout(layoutSlot); - return pc + 1; - } + case "gfx-elem-erase": + return StepRetainedObject(label, a, pc); case "gfx-elem-release": // 0x1fa (surface slot) _host.ReleaseSurface((int)Read(a[0])); Gfx.ClearSurface((int)Read(a[0])); return pc + 1; - case "clone-gfx-object": // 0x21d (source handle)(destination handle) - Gfx.CloneObject(Read(a[0]), Read(a[1])); return pc + 1; + case "clone-gfx-object": + return StepRetainedObject(label, a, pc); case "gfx-blit-color": // 0x202 (handle)(delay)(duration)(alpha)(color) — one-shot color Gfx.SetAnimatedObjectColorResolved(Read(a[0]), Read(a[1]), Read(a[2]), Read(a[3]), Read(a[4])); return pc + 1;