From 122c127c583e72b8ff5904a51c39a269d6a0fc9d Mon Sep 17 00:00:00 2001 From: gamer147 Date: Mon, 3 Aug 2026 00:08:24 -0400 Subject: [PATCH] Extract VM text history opcode handler --- docs/PROJECT-STRUCTURE.md | 2 + docs/remake-architecture-and-roadmap.md | 14 ++-- .../Vm/VirtualMachine.TextHistory.cs | 72 +++++++++++++++++++ engine/Age.Engine/Vm/VirtualMachine.cs | 56 ++------------- 4 files changed, 89 insertions(+), 55 deletions(-) create mode 100644 engine/Age.Engine/Vm/VirtualMachine.TextHistory.cs diff --git a/docs/PROJECT-STRUCTURE.md b/docs/PROJECT-STRUCTURE.md index 6e9d784..4347a8e 100644 --- a/docs/PROJECT-STRUCTURE.md +++ b/docs/PROJECT-STRUCTURE.md @@ -190,6 +190,8 @@ publication, skip-aware blocking fades/crossfades, foreground-transition waits, `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. +`engine/Age.Engine/Vm/VirtualMachine.TextHistory.cs` owns history recording control, metadata append/navigation, +retained history rendering, metadata/voice lookup, and history backlog clearing. 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 118038a..5e071e7 100644 --- a/docs/remake-architecture-and-roadmap.md +++ b/docs/remake-architecture-and-roadmap.md @@ -668,6 +668,12 @@ do not mix mechanical moves with semantic changes. `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. + The eighth bounded `VirtualMachine.Step` extraction moved text-history recording control, metadata append and + navigation, retained history rendering, metadata/voice lookup, and backlog clearing into + `engine/Age.Engine/Vm/VirtualMachine.TextHistory.cs`. The top-level dispatcher retains all labels and aliases + at their existing positions and routes them through guarded `StepTextHistory`; live ADV text remains with + `StepAdvText`, while skip/auto-message services remain outside both handlers. 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. @@ -1039,9 +1045,9 @@ layer's rendering diverges from ADV; save layout. ## 8. Immediate next step 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, surface/texture, retained-object, and animation `VirtualMachine.Step` -families routed through domain handlers, with retained presentation/transition dispatch now isolated as well, -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. +domains isolated and the audio, movie, surface/texture, retained-object, animation, presentation, ADV-text, and +text-history `VirtualMachine.Step` families routed through domain handlers, extract the ADV skip/auto-message +service 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.TextHistory.cs b/engine/Age.Engine/Vm/VirtualMachine.TextHistory.cs new file mode 100644 index 0000000..b9e12a6 --- /dev/null +++ b/engine/Age.Engine/Vm/VirtualMachine.TextHistory.cs @@ -0,0 +1,72 @@ +using Age.Engine.Model; + +namespace Age.Engine.Vm; + +public sealed partial class VirtualMachine +{ + private int StepTextHistory(string label, IReadOnlyList a, int pc) + { + switch (label) + { + case "set-text-history-recording": // 0x1bb: HISTORY.BIN suppresses recording its own UI + if (Read(a[0]) is 0 or 1) + { + bool enabled = Read(a[0]) == 1; + TextHistory.SetRecordingEnabled(enabled); + if (enabled) _host.EndTextHistoryPresentation(Gfx); + } + return pc + 1; + case "append-text-history-metadata": // 0x1d2: (metadata type, value) + TextHistory.AppendMetadata(Read(a[1]), Read(a[0]), _advTextStyle); return pc + 1; + case "step-text-history": // 0x1d0: cumulative delta from the latest retained boundary + if (TextHistory.TryStepGroup((int)Read(a[2]), out var historyEntry)) + { + Write(a[0], historyEntry.LayoutSlot); + Write(a[1], historyEntry.FirstRecordIndex); + } + else + { + Write(a[0], -1); + Write(a[1], -1); + } + return pc + 1; + case "render-text-history": // 0x1d1: rasterize/bind one retained group to a target layout + case "u0041BAE0": + { + int flags = (int)Read(a[2]); + if ((flags & 4) == 0 && TextHistory.TryBuildRenderBatch( + (int)Read(a[0]), (int)Read(a[1]), flags, Read(a[3]), Read(a[4]), out var batch)) + { + batch = batch with + { + // Native History uses the text manager's current leading, not a retained-record field. + Style = batch.Style with { LineSpacing = _advTextStyle.LineSpacing } + }; + _host.RenderTextHistory( + Gfx, + TextHistory.GetPresentationBinding(batch.LayoutSlot), + batch); + } + return pc + 1; + } + case "u0041BB90": + case "find-text-history-value": // 0x1d3: operand 3 is accepted but ignored natively + { + bool found = TextHistory.TryFindMetadata((int)Read(a[3]), Read(a[4]), out long value); + Write(a[0], found ? 1 : 0); + Write(a[1], value); + return pc + 1; + } + case "u0041BC00": + case "find-text-history-pair": // 0x1d4: operand 3 is accepted but ignored natively + TextHistory.TryFindVoicePair((int)Read(a[3]), out long voiceId, out long voiceArgument); + Write(a[0], voiceId); + Write(a[1], voiceArgument); + return pc + 1; + case "clear-text-history": // 0x85: bound the backlog to the current ordinary ADV block + TextHistory.Clear(); return pc + 1; + default: + throw new InvalidOperationException($"Non-text-history opcode routed to text-history handler: {label}"); + } + } +} diff --git a/engine/Age.Engine/Vm/VirtualMachine.cs b/engine/Age.Engine/Vm/VirtualMachine.cs index 6c82b98..a610a9c 100644 --- a/engine/Age.Engine/Vm/VirtualMachine.cs +++ b/engine/Age.Engine/Vm/VirtualMachine.cs @@ -2258,63 +2258,17 @@ public sealed partial class VirtualMachine case "block-mark": case "reset-message-voice-state": // 0x1bc resets native per-message voice/queued-voice state _autoVoicePending = false; return pc + 1; - case "set-text-history-recording": // 0x1bb: HISTORY.BIN suppresses recording its own UI - if (Read(a[0]) is 0 or 1) - { - bool enabled = Read(a[0]) == 1; - TextHistory.SetRecordingEnabled(enabled); - if (enabled) _host.EndTextHistoryPresentation(Gfx); - } - return pc + 1; - case "append-text-history-metadata": // 0x1d2: (metadata type, value) - TextHistory.AppendMetadata(Read(a[1]), Read(a[0]), _advTextStyle); return pc + 1; - case "step-text-history": // 0x1d0: cumulative delta from the latest retained boundary - if (TextHistory.TryStepGroup((int)Read(a[2]), out var historyEntry)) - { - Write(a[0], historyEntry.LayoutSlot); - Write(a[1], historyEntry.FirstRecordIndex); - } - else - { - Write(a[0], -1); - Write(a[1], -1); - } - return pc + 1; + case "set-text-history-recording": + case "append-text-history-metadata": + case "step-text-history": case "render-text-history": // 0x1d1: rasterize/bind one retained group to a target layout case "u0041BAE0": - { - int flags = (int)Read(a[2]); - if ((flags & 4) == 0 && TextHistory.TryBuildRenderBatch( - (int)Read(a[0]), (int)Read(a[1]), flags, Read(a[3]), Read(a[4]), out var batch)) - { - batch = batch with - { - // Native History uses the text manager's current leading, not a retained-record field. - Style = batch.Style with { LineSpacing = _advTextStyle.LineSpacing } - }; - _host.RenderTextHistory( - Gfx, - TextHistory.GetPresentationBinding(batch.LayoutSlot), - batch); - } - return pc + 1; - } case "u0041BB90": case "find-text-history-value": // 0x1d3: operand 3 is accepted but ignored natively - { - bool found = TextHistory.TryFindMetadata((int)Read(a[3]), Read(a[4]), out long value); - Write(a[0], found ? 1 : 0); - Write(a[1], value); - return pc + 1; - } case "u0041BC00": case "find-text-history-pair": // 0x1d4: operand 3 is accepted but ignored natively - TextHistory.TryFindVoicePair((int)Read(a[3]), out long voiceId, out long voiceArgument); - Write(a[0], voiceId); - Write(a[1], voiceArgument); - return pc + 1; - case "clear-text-history": // 0x85: bound the backlog to the current ordinary ADV block - TextHistory.Clear(); return pc + 1; + case "clear-text-history": + return StepTextHistory(label, a, pc); case "set-font-size": case "set-ruby-font-size": case "u0041B3D0":