Extract VM text history opcode handler

This commit is contained in:
gamer147
2026-08-03 00:08:24 -04:00
parent 4712343c79
commit 122c127c58
4 changed files with 89 additions and 55 deletions

View File

@@ -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, `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 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. 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-<SCENE>.jsonl` files are produced by editor/development Godot runs and map The disposable `build/page-map-<SCENE>.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 runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports

View File

@@ -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. `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. 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 **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 deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after
each domain move. each domain move.
@@ -1039,9 +1045,9 @@ layer's rendering diverges from ADV; save layout.
## 8. Immediate next step ## 8. Immediate next step
Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed 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` 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` domains isolated and the audio, movie, surface/texture, retained-object, animation, presentation, ADV-text, and
families routed through domain handlers, with retained presentation/transition dispatch now isolated as well, text-history `VirtualMachine.Step` families routed through domain handlers, extract the ADV skip/auto-message
and ADV text-layout/rendering dispatch isolated, extract the ADV text-history opcode family next without service opcode family next without replacing the proven dispatcher or changing public types, commands, and
replacing the proven dispatcher or changing public types, commands, and generated output. generated output.
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does 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. not replace Phase B gameplay validation or the open cross-platform gates.

View File

@@ -0,0 +1,72 @@
using Age.Engine.Model;
namespace Age.Engine.Vm;
public sealed partial class VirtualMachine
{
private int StepTextHistory(string label, IReadOnlyList<Operand> 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}");
}
}
}

View File

@@ -2258,63 +2258,17 @@ public sealed partial class VirtualMachine
case "block-mark": case "block-mark":
case "reset-message-voice-state": // 0x1bc resets native per-message voice/queued-voice state case "reset-message-voice-state": // 0x1bc resets native per-message voice/queued-voice state
_autoVoicePending = false; return pc + 1; _autoVoicePending = false; return pc + 1;
case "set-text-history-recording": // 0x1bb: HISTORY.BIN suppresses recording its own UI case "set-text-history-recording":
if (Read(a[0]) is 0 or 1) case "append-text-history-metadata":
{ case "step-text-history":
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 "render-text-history": // 0x1d1: rasterize/bind one retained group to a target layout
case "u0041BAE0": 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 "u0041BB90":
case "find-text-history-value": // 0x1d3: operand 3 is accepted but ignored natively 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 "u0041BC00":
case "find-text-history-pair": // 0x1d4: operand 3 is accepted but ignored natively 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); case "clear-text-history":
Write(a[0], voiceId); return StepTextHistory(label, a, pc);
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 "set-font-size": case "set-font-size":
case "set-ruby-font-size": case "set-ruby-font-size":
case "u0041B3D0": case "u0041B3D0":