From f85e8643a009605907bb417c0e9f9e42c5bcf8a4 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Mon, 3 Aug 2026 00:04:52 -0400 Subject: [PATCH] Extract VM ADV text opcode handler --- docs/PROJECT-STRUCTURE.md | 3 + docs/remake-architecture-and-roadmap.md | 11 +- .../Age.Engine/Vm/VirtualMachine.AdvText.cs | 187 ++++++++++++++++++ engine/Age.Engine/Vm/VirtualMachine.cs | 156 ++------------- 4 files changed, 214 insertions(+), 143 deletions(-) create mode 100644 engine/Age.Engine/Vm/VirtualMachine.AdvText.cs diff --git a/docs/PROJECT-STRUCTURE.md b/docs/PROJECT-STRUCTURE.md index 507aea5..6e9d784 100644 --- a/docs/PROJECT-STRUCTURE.md +++ b/docs/PROJECT-STRUCTURE.md @@ -187,6 +187,9 @@ ADV-binding, and presentation labels remain in their respective dispatcher group transforms, per-object animation control, frame-time sampling, and the shared animation-clock opcode handler. `engine/Age.Engine/Vm/VirtualMachine.Presentation.cs` owns queued surface-alpha transitions, frame and object-range publication, skip-aware blocking fades/crossfades, foreground-transition waits, and graphics command-queue clear. +`engine/Age.Engine/Vm/VirtualMachine.AdvText.cs` owns live ADV text emission, layout/cursor/wait-indicator state, +text style and glyph-delay control, direct surface-string and retained numeric-glyph rendering, and retained +text/wait-object bindings; text history and input/skip/auto services remain separate. 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 18f8783..118038a 100644 --- a/docs/remake-architecture-and-roadmap.md +++ b/docs/remake-architecture-and-roadmap.md @@ -661,6 +661,13 @@ do not mix mechanical moves with semantic changes. labels and aliases at their existing positions and routes them through guarded `StepPresentation`; movie-mask transition dispatch remains with the movie handler. Runtime validation remains green. + The seventh bounded `VirtualMachine.Step` extraction moved live ADV text emission, layout/cursor/wait-indicator + state, text style and glyph-delay control, direct surface-string and retained numeric-glyph rendering, and + retained text/wait-object bindings into `engine/Age.Engine/Vm/VirtualMachine.AdvText.cs`. The top-level + dispatcher retains all labels at their existing positions and routes the separated groups through guarded + `StepAdvText`; the complete instruction remains available for emission and layout-reset bytecode offsets. + Text-history and input/skip/auto services remain outside the 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. @@ -1034,7 +1041,7 @@ Continue step 2 of the **codebase consolidation** maintenance slice: behavior-ne by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState` domains isolated and the audio, movie, surface/texture, retained-object, and animation `VirtualMachine.Step` families routed through domain handlers, with retained presentation/transition dispatch now isolated as well, -extract the ADV text-layout/rendering opcode family next without replacing the proven dispatcher or changing -public types, commands, and generated output. +and ADV text-layout/rendering dispatch isolated, extract the ADV text-history 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.AdvText.cs b/engine/Age.Engine/Vm/VirtualMachine.AdvText.cs new file mode 100644 index 0000000..0d9309a --- /dev/null +++ b/engine/Age.Engine/Vm/VirtualMachine.AdvText.cs @@ -0,0 +1,187 @@ +using Age.Engine.Hosting; +using Age.Engine.Model; + +namespace Age.Engine.Vm; + +public sealed partial class VirtualMachine +{ + private int StepAdvText(string label, Instruction ins, int pc) + { + var a = ins.Args; + switch (label) + { + case "show-text": + RefreshAdvReadSkipState(); + foreach (var o in a) + { + if (!IsStr(o)) continue; + int off = o.Type == T_STR ? (int)o.Value : ins.Offset; + _cur.EmitSeen.TryGetValue(off, out var c); c++; _cur.EmitSeen[off] = c; + if (c > _o.EmitCap) { HaltReason = $"LOOP:line@0x{off:x}×{c}"; return HALT; } + string text = ReadStr(o); + Emitted.Add((off, text, _cur.Script.Name)); + int layoutSlot = a.Count > 0 ? (int)Read(a[0]) : 0; + string[] scriptStack; + lock (_debugControlLock) scriptStack = _activeFrameNames.ToArray(); + var liveRun = new AdvLiveTextRun( + off, TextHistory.GetLayoutSnapshot(layoutSlot), _advTextStyle, text, scriptStack); + TextHistory.AppendText(layoutSlot, off, text, _advTextStyle); + AdvTextLayoutPresentationBinding binding = + TextHistory.GetPresentationBinding(liveRun.Layout.Slot); + AdvRetainedTextRunResult? retained = _host.ShowText( + Gfx, binding, liveRun, _messageGlyphDelayMilliseconds); + if (retained is { } result) + TextHistory.SetCursor( + result.LayoutSlot, result.CursorX, result.CursorY); + } + return pc + 1; + case "define-adv-text-layout": // 0x70: configure layout and begin a logical retained group + TextHistory.DefineLayout((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), + (int)Read(a[3]), (int)Read(a[4])); + return pc + 1; + case "reset-adv-text-layout": // 0x71: reset layout and begin the next logical retained group + { + int requestedSlot = (int)Read(a[0]); + TextHistory.ResetLayout(requestedSlot); + var layout = TextHistory.GetLayoutSnapshot(requestedSlot); + AdvTextLayoutPresentationBinding binding = + TextHistory.GetPresentationBinding(layout.Slot); + if (binding.FirstObjectHandle >= 0 && binding.ObjectCapacity > 0) + Gfx.EraseRange(binding.FirstObjectHandle, binding.ObjectCapacity); + _host.SetAdvTextCursor(layout.Slot, layout.CursorX, layout.CursorY); + _host.ResetRenderedAdvTextLayout(Gfx, binding); + _cur.ReadMessageOffset = ins.Offset; + _sharedProfile.ReadText.CommitPending(); + RefreshAdvReadSkipState(); + return pc + 1; + } + case "set-adv-text-reset-cursor": // 0x79: configure cursor restored by a later 0x71 + TextHistory.SetResetCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); + return pc + 1; + case "set-adv-text-cursor": // 0x7a (layout slot, x, y); slot 0 means current natively + TextHistory.SetCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); + _host.SetAdvTextCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); return pc + 1; + case "set-adv-text-bounds": // 0x1c1: layout-local right/bottom overflow boundaries + TextHistory.SetBounds((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); + return pc + 1; + case "configure-adv-wait-indicator": // 0x73: per-layout animated input-wait marker + _host.ConfigureAdvWaitIndicator(new AdvWaitIndicatorConfig( + (int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]), + (int)Read(a[4]), (int)Read(a[5]), (int)Read(a[6]), (int)Read(a[7]), + (int)Read(a[8]), Read(a[9]))); + return pc + 1; + case "u0041B9F0": + case "set-adv-wait-indicator-enabled": // 0x1ce: explicit marker service start/stop + _host.SetAdvWaitIndicatorEnabled(Read(a[0]) != 0); + return pc + 1; + case "u00420CE0": + case "publish-adv-text-layout": // 0x20a: publish layout and current marker frame if active + { + int requestedSlot = (int)Read(a[0]); + AdvTextLayoutPresentationBinding binding = + TextHistory.GetPresentationBinding(requestedSlot); + _host.PublishAdvTextLayout(Gfx, binding); + return pc + 1; + } + case "draw-string": // 0x204 (surface slot, x, y, string) + _host.DrawStringToSurface((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), ReadStr(a[3]), + _advTextStyle); + return pc + 1; + case "u00420A60": // pre-reference compatibility + case "draw-formatted-integer": // 0x205 (surface slot, x, y, value, field width, flags) + { + int x = (int)Read(a[1]); + string text = FormatIntegerForSurface( + ref x, unchecked((int)Read(a[3])), (int)Read(a[4]), (int)Read(a[5])); + _host.DrawStringToSurface((int)Read(a[0]), x, (int)Read(a[2]), text, _advTextStyle); + return pc + 1; + } + case "set-font-size": + _advTextStyle = _advTextStyle with { PrimaryFontSize = (int)Read(a[0]) }; return pc + 1; + case "set-ruby-font-size": + _advTextStyle = _advTextStyle with { RubyFontSize = (int)Read(a[0]) }; return pc + 1; + case "u0041B3D0": + case "set-text-line-spacing": + _advTextStyle = _advTextStyle with { LineSpacing = (int)Read(a[0]) }; return pc + 1; + case "set-font-bold": + _advTextStyle = _advTextStyle with { Bold = Read(a[0]) != 0 }; return pc + 1; + case "set-text-color": + _advTextStyle = _advTextStyle with { TextColor = Read(a[0]) }; return pc + 1; + case "set-text-effect-color": + _advTextStyle = _advTextStyle with { EffectColor = Read(a[0]) }; return pc + 1; + case "set-text-render-mode": + _advTextStyle = _advTextStyle with { RenderMode = (int)Read(a[0]) }; return pc + 1; + case "set-text-effect-offset": + _advTextStyle = _advTextStyle with + { + EffectOffsetX = (int)Read(a[0]), + EffectOffsetY = (int)Read(a[1]) + }; + return pc + 1; + case "set-adv-text-layout-origin": // 0x198; slot 0 selects the current layout + case "u0041B540": + TextHistory.SetLayoutOrigin((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); + return pc + 1; + case "get-message-glyph-delay": // 0x7f + case "u00414C60": + Write(a[0], _messageGlyphDelayMilliseconds); return pc + 1; + case "set-message-glyph-delay": // 0x1b5 + case "u0041B5F0": + _messageGlyphDelayMilliseconds = System.Math.Max(0, (int)Read(a[0])); + _host.SetMessageGlyphDelayMilliseconds(_messageGlyphDelayMilliseconds); + return pc + 1; + case "end-text-line": + TextHistory.EndLine((int)Read(a[0]), _advTextStyle); return pc + 1; + case "set-font": + _advTextStyle = _advTextStyle with { FontFace = ReadStr(a[0]) }; return pc + 1; + case "comment": case "display-furigana": case "dev_ukn": + return pc + 1; + case "u0041F3A0": + case "register-numeric-glyph-style": // 0x13a: (style)(surface)(atlas x/y)(digit w/h) + { + int styleIndex = unchecked((int)Read(a[0])); + if (!Gfx.RegisterNumericGlyphStyle(styleIndex, unchecked((int)Read(a[1])), + unchecked((int)Read(a[2])), unchecked((int)Read(a[3])), + unchecked((int)Read(a[4])), unchecked((int)Read(a[5])))) + { + HaltReason ??= $"numeric-glyph-style-index-out-of-range:{styleIndex}"; + return HALT; + } + return pc + 1; + } + case "u00422460": + case "draw-decimal-glyphs": // 0x23b: retained decimal glyph draw + { + int styleIndex = unchecked((int)Read(a[1])); + if (!Gfx.DrawDecimalGlyphs(Read(a[0]), styleIndex, unchecked((int)Read(a[2])), + unchecked((int)Read(a[3])), unchecked((int)Read(a[4])), + unchecked((int)Read(a[5])), unchecked((int)Read(a[6])))) + { + HaltReason ??= (uint)styleIndex >= 11 + ? $"numeric-glyph-style-index-out-of-range:{styleIndex}" + : $"numeric-glyph-style-unregistered:{styleIndex}"; + return HALT; + } + return pc + 1; + } + case "set-adv-wait-indicator-handle": // 0x212 (layout)(retained handle) + { + int requestedSlot = (int)Read(a[0]); + TextHistory.SetWaitIndicatorObjectHandle(requestedSlot, Read(a[1])); + AdvTextLayoutSnapshot layout = + TextHistory.GetLayoutSnapshot(requestedSlot); + _host.BindAdvWaitIndicator( + TextHistory.GetPresentationBinding(layout.Slot), + layout); + return pc + 1; + } + case "set-adv-text-object-range": // 0x213 (layout)(first handle)(count) + { + TextHistory.SetTextObjectRange((int)Read(a[0]), Read(a[1]), Read(a[2])); + return pc + 1; + } + default: + throw new InvalidOperationException($"Non-ADV-text opcode routed to ADV-text handler: {label}"); + } + } +} diff --git a/engine/Age.Engine/Vm/VirtualMachine.cs b/engine/Age.Engine/Vm/VirtualMachine.cs index d669fba..6c82b98 100644 --- a/engine/Age.Engine/Vm/VirtualMachine.cs +++ b/engine/Age.Engine/Vm/VirtualMachine.cs @@ -1963,91 +1963,20 @@ public sealed partial class VirtualMachine return pc + 1; } case "show-text": - RefreshAdvReadSkipState(); - foreach (var o in a) - { - if (!IsStr(o)) continue; - int off = o.Type == T_STR ? (int)o.Value : ins.Offset; - _cur.EmitSeen.TryGetValue(off, out var c); c++; _cur.EmitSeen[off] = c; - if (c > _o.EmitCap) { HaltReason = $"LOOP:line@0x{off:x}×{c}"; return HALT; } - string text = ReadStr(o); - Emitted.Add((off, text, _cur.Script.Name)); - int layoutSlot = a.Count > 0 ? (int)Read(a[0]) : 0; - string[] scriptStack; - lock (_debugControlLock) scriptStack = _activeFrameNames.ToArray(); - var liveRun = new AdvLiveTextRun( - off, TextHistory.GetLayoutSnapshot(layoutSlot), _advTextStyle, text, scriptStack); - TextHistory.AppendText(layoutSlot, off, text, _advTextStyle); - AdvTextLayoutPresentationBinding binding = - TextHistory.GetPresentationBinding(liveRun.Layout.Slot); - AdvRetainedTextRunResult? retained = _host.ShowText( - Gfx, binding, liveRun, _messageGlyphDelayMilliseconds); - if (retained is { } result) - TextHistory.SetCursor( - result.LayoutSlot, result.CursorX, result.CursorY); - } - return pc + 1; - case "define-adv-text-layout": // 0x70: configure layout and begin a logical retained group - TextHistory.DefineLayout((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), - (int)Read(a[3]), (int)Read(a[4])); - return pc + 1; - case "reset-adv-text-layout": // 0x71: reset layout and begin the next logical retained group - { - int requestedSlot = (int)Read(a[0]); - TextHistory.ResetLayout(requestedSlot); - var layout = TextHistory.GetLayoutSnapshot(requestedSlot); - AdvTextLayoutPresentationBinding binding = - TextHistory.GetPresentationBinding(layout.Slot); - if (binding.FirstObjectHandle >= 0 && binding.ObjectCapacity > 0) - Gfx.EraseRange(binding.FirstObjectHandle, binding.ObjectCapacity); - _host.SetAdvTextCursor(layout.Slot, layout.CursorX, layout.CursorY); - _host.ResetRenderedAdvTextLayout(Gfx, binding); - _cur.ReadMessageOffset = ins.Offset; - _sharedProfile.ReadText.CommitPending(); - RefreshAdvReadSkipState(); - return pc + 1; - } - case "set-adv-text-reset-cursor": // 0x79: configure cursor restored by a later 0x71 - TextHistory.SetResetCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); - return pc + 1; - case "set-adv-text-cursor": // 0x7a (layout slot, x, y); slot 0 means current natively - TextHistory.SetCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); - _host.SetAdvTextCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); return pc + 1; - case "set-adv-text-bounds": // 0x1c1: layout-local right/bottom overflow boundaries - TextHistory.SetBounds((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); - return pc + 1; - case "configure-adv-wait-indicator": // 0x73: per-layout animated input-wait marker - _host.ConfigureAdvWaitIndicator(new AdvWaitIndicatorConfig( - (int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]), - (int)Read(a[4]), (int)Read(a[5]), (int)Read(a[6]), (int)Read(a[7]), - (int)Read(a[8]), Read(a[9]))); - return pc + 1; + case "define-adv-text-layout": + case "reset-adv-text-layout": + case "set-adv-text-reset-cursor": + case "set-adv-text-cursor": + case "set-adv-text-bounds": + case "configure-adv-wait-indicator": case "u0041B9F0": case "set-adv-wait-indicator-enabled": // 0x1ce: explicit marker service start/stop - _host.SetAdvWaitIndicatorEnabled(Read(a[0]) != 0); - return pc + 1; case "u00420CE0": case "publish-adv-text-layout": // 0x20a: publish layout and current marker frame if active - { - int requestedSlot = (int)Read(a[0]); - AdvTextLayoutPresentationBinding binding = - TextHistory.GetPresentationBinding(requestedSlot); - _host.PublishAdvTextLayout(Gfx, binding); - return pc + 1; - } - case "draw-string": // 0x204 (surface slot, x, y, string) - _host.DrawStringToSurface((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), ReadStr(a[3]), - _advTextStyle); - return pc + 1; + case "draw-string": case "u00420A60": // pre-reference compatibility case "draw-formatted-integer": // 0x205 (surface slot, x, y, value, field width, flags) - { - int x = (int)Read(a[1]); - string text = FormatIntegerForSurface( - ref x, unchecked((int)Read(a[3])), (int)Read(a[4]), (int)Read(a[5])); - _host.DrawStringToSurface((int)Read(a[0]), x, (int)Read(a[2]), text, _advTextStyle); - return pc + 1; - } + return StepAdvText(label, ins, pc); case "wait-for-input": RefreshAdvReadSkipState(); // Faithful headless: no player => halt here rather than plow past every prompt (see VmOptions). @@ -2387,31 +2316,17 @@ public sealed partial class VirtualMachine case "clear-text-history": // 0x85: bound the backlog to the current ordinary ADV block TextHistory.Clear(); return pc + 1; case "set-font-size": - _advTextStyle = _advTextStyle with { PrimaryFontSize = (int)Read(a[0]) }; return pc + 1; case "set-ruby-font-size": - _advTextStyle = _advTextStyle with { RubyFontSize = (int)Read(a[0]) }; return pc + 1; case "u0041B3D0": case "set-text-line-spacing": - _advTextStyle = _advTextStyle with { LineSpacing = (int)Read(a[0]) }; return pc + 1; case "set-font-bold": - _advTextStyle = _advTextStyle with { Bold = Read(a[0]) != 0 }; return pc + 1; case "set-text-color": - _advTextStyle = _advTextStyle with { TextColor = Read(a[0]) }; return pc + 1; case "set-text-effect-color": - _advTextStyle = _advTextStyle with { EffectColor = Read(a[0]) }; return pc + 1; case "set-text-render-mode": - _advTextStyle = _advTextStyle with { RenderMode = (int)Read(a[0]) }; return pc + 1; case "set-text-effect-offset": - _advTextStyle = _advTextStyle with - { - EffectOffsetX = (int)Read(a[0]), - EffectOffsetY = (int)Read(a[1]) - }; - return pc + 1; case "set-adv-text-layout-origin": // 0x198; slot 0 selects the current layout case "u0041B540": - TextHistory.SetLayoutOrigin((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); - return pc + 1; + return StepAdvText(label, ins, pc); case "get-message-window-alpha": // 0x131: process-owned message:MesWinAlpha setting case "u00415F70": Write(a[0], _messageWindowAlphaSetting); return pc + 1; @@ -2434,21 +2349,16 @@ public sealed partial class VirtualMachine return pc + 1; case "get-message-glyph-delay": // 0x7f case "u00414C60": - Write(a[0], _messageGlyphDelayMilliseconds); return pc + 1; case "set-message-glyph-delay": // 0x1b5 case "u0041B5F0": - _messageGlyphDelayMilliseconds = System.Math.Max(0, (int)Read(a[0])); - _host.SetMessageGlyphDelayMilliseconds(_messageGlyphDelayMilliseconds); - return pc + 1; + return StepAdvText(label, ins, pc); case "u00415BF0": case "reset-message-skip-input": // 0x101 clears transient input/run bits, not op 0x88 state return pc + 1; case "end-text-line": - TextHistory.EndLine((int)Read(a[0]), _advTextStyle); return pc + 1; case "set-font": - _advTextStyle = _advTextStyle with { FontFace = ReadStr(a[0]) }; return pc + 1; case "comment": case "display-furigana": case "dev_ukn": - return pc + 1; + return StepAdvText(label, ins, pc); case "create-texture": case "set-texture": case "u00422E80": // pre-reference compatibility @@ -2459,32 +2369,9 @@ public sealed partial class VirtualMachine return StepSurface(label, a, pc); case "u0041F3A0": case "register-numeric-glyph-style": // 0x13a: (style)(surface)(atlas x/y)(digit w/h) - { - int styleIndex = unchecked((int)Read(a[0])); - if (!Gfx.RegisterNumericGlyphStyle(styleIndex, unchecked((int)Read(a[1])), - unchecked((int)Read(a[2])), unchecked((int)Read(a[3])), - unchecked((int)Read(a[4])), unchecked((int)Read(a[5])))) - { - HaltReason ??= $"numeric-glyph-style-index-out-of-range:{styleIndex}"; - return HALT; - } - return pc + 1; - } case "u00422460": case "draw-decimal-glyphs": // 0x23b: retained decimal glyph draw - { - int styleIndex = unchecked((int)Read(a[1])); - if (!Gfx.DrawDecimalGlyphs(Read(a[0]), styleIndex, unchecked((int)Read(a[2])), - unchecked((int)Read(a[3])), unchecked((int)Read(a[4])), - unchecked((int)Read(a[5])), unchecked((int)Read(a[6])))) - { - HaltReason ??= (uint)styleIndex >= 11 - ? $"numeric-glyph-style-index-out-of-range:{styleIndex}" - : $"numeric-glyph-style-unregistered:{styleIndex}"; - return HALT; - } - return pc + 1; - } + return StepAdvText(label, ins, pc); case "get-texture-size": case "fill-surface-rect": case "u00420D50": @@ -2569,22 +2456,9 @@ public sealed partial class VirtualMachine case "gfx-set-scale-current": // 0x1fd (handle)(sx%)(sy%)(sz%) -> current scale matrix 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]); - TextHistory.SetWaitIndicatorObjectHandle(requestedSlot, Read(a[1])); - AdvTextLayoutSnapshot layout = - TextHistory.GetLayoutSnapshot(requestedSlot); - _host.BindAdvWaitIndicator( - TextHistory.GetPresentationBinding(layout.Slot), - layout); - return pc + 1; - } - case "set-adv-text-object-range": // 0x213 (layout)(first handle)(count) - { - TextHistory.SetTextObjectRange((int)Read(a[0]), Read(a[1]), Read(a[2])); - return pc + 1; - } + case "set-adv-wait-indicator-handle": + case "set-adv-text-object-range": + return StepAdvText(label, ins, pc); case "gfx-elem-erase": return StepRetainedObject(label, a, pc); case "gfx-elem-release": // 0x1fa (surface slot)