diff --git a/docs/PROJECT-STRUCTURE.md b/docs/PROJECT-STRUCTURE.md index 4347a8e..8dfe49d 100644 --- a/docs/PROJECT-STRUCTURE.md +++ b/docs/PROJECT-STRUCTURE.md @@ -192,6 +192,9 @@ text style and glyph-delay control, direct surface-string and retained numeric-g 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. +`engine/Age.Engine/Vm/VirtualMachine.AdvServices.cs` owns persistent/active message-skip control, read-skip +settings and queries, auto-message state/timing, and per-message voice/skip reset opcode dispatch; shared state +and refresh helpers remain in the VM coordinator because live text and input paths also consume them. 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 5e071e7..6dd4095 100644 --- a/docs/remake-architecture-and-roadmap.md +++ b/docs/remake-architecture-and-roadmap.md @@ -674,6 +674,13 @@ do not mix mechanical moves with semantic changes. 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. + The ninth bounded `VirtualMachine.Step` extraction moved persistent/active message-skip control, read-skip + settings and queries, auto-message state/timing, and per-message voice/skip reset dispatch into + `engine/Age.Engine/Vm/VirtualMachine.AdvServices.cs`. The top-level dispatcher retains all labels and aliases + at their existing positions and routes them through guarded `StepAdvService`; shared state and refresh helpers + remain in the VM coordinator because live-text and input paths also consume them. 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. @@ -1046,8 +1053,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, 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. +text-history `VirtualMachine.Step` families routed through domain handlers, with ADV skip/auto-message services +now isolated as well, extract the interactive input/hotspot/cursor callback 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.AdvServices.cs b/engine/Age.Engine/Vm/VirtualMachine.AdvServices.cs new file mode 100644 index 0000000..6f46d24 --- /dev/null +++ b/engine/Age.Engine/Vm/VirtualMachine.AdvServices.cs @@ -0,0 +1,72 @@ +using Age.Engine.Model; + +namespace Age.Engine.Vm; + +public sealed partial class VirtualMachine +{ + private int StepAdvService(string label, IReadOnlyList a, int pc) + { + switch (label) + { + case "u0041B290": + case "set-message-skip": // 0x88: persistent all-message fast-forward service state + _messageSkipEnabled = Read(a[0]) != 0; + _messageSkipServiceActive = _messageSkipEnabled; + _host.SetMessageSkipActive(_messageSkipServiceActive); + return pc + 1; + case "u00414E50": // 0x19a: persistent state used by the SO001 active overlay + Write(a[0], _messageSkipEnabled ? 1 : 0); return pc + 1; + case "u00414E80": + case "suspend-adv-skip-service": // 0x19b: preserve the toggle while leaving ADV presentation + _messageSkipServiceActive = false; + _host.SetMessageSkipActive(false); + return pc + 1; + case "u00414EC0": + case "resume-adv-skip-service": // 0x19c: recompute active fast-forward on ADV entry + _messageSkipServiceActive = + _messageSkipEnabled || _advReadSkipState || _host.IsAdvReadSkipActive; + _host.SetMessageSkipActive(_messageSkipServiceActive); + RefreshPhysicalMessageSkipState(); + return pc + 1; + case "get-message-skip": // 0x1c7: persistent Skip or host-supplied Ctrl fast-forward + // Native persistent state and the independently polled physical action-6 channel both + // re-arm the transient run-state bit consumed by this query. + Write(a[0], _messageSkipServiceActive || _host.IsMessageSkipActive ? 1 : 0); return pc + 1; + case "get-adv-read-skip-state": // 0x1cc: per-message read/click skip service state + case "get-adv-service-state": // compatibility with pre-recovery generated tables + Write(a[0], _advReadSkipState || _host.IsAdvReadSkipActive ? 1 : 0); return pc + 1; + case "u0041B9B0": + case "set-read-message-skip": // 0x1ca: engine setting message:ReadTextSkip + _sharedProfile.ReadMessageSkipEnabled = Read(a[0]) != 0; + RefreshAdvReadSkipState(); + return pc + 1; + case "u00414FD0": + case "get-read-message-skip": // 0x1cb + Write(a[0], _sharedProfile.ReadMessageSkipEnabled ? 1 : 0); + return pc + 1; + case "u00414F60": + case "get-auto-message": // 0x1b6: VM service state used by the ADV redraw callback + Write(a[0], _autoMessageEnabled ? 1 : 0); return pc + 1; + case "u0041B640": + case "set-auto-message": // 0x1b7 + _autoMessageEnabled = Read(a[0]) != 0; return pc + 1; + case "u0041B670": + case "get-auto-message-time": // 0x1b8 (selector 0=post-voice Time0, 1=unvoiced Time1, out) + Write(a[1], Read(a[0]) == 0 ? _autoMessageTime0Ms : _autoMessageTime1Ms); return pc + 1; + case "u0041B710": + case "set-auto-message-time": // 0x1b9 (selector, milliseconds) + if (Read(a[0]) == 0) _autoMessageTime0Ms = Read(a[1]); + else if (Read(a[0]) == 1) _autoMessageTime1Ms = Read(a[1]); + return pc + 1; + case "u00415670": + case "block-mark": + case "reset-message-voice-state": // 0x1bc resets native per-message voice/queued-voice state + _autoVoicePending = false; return pc + 1; + case "u00415BF0": + case "reset-message-skip-input": // 0x101 clears transient input/run bits, not op 0x88 state + return pc + 1; + default: + throw new InvalidOperationException($"Non-ADV-service opcode routed to ADV-service handler: {label}"); + } + } +} diff --git a/engine/Age.Engine/Vm/VirtualMachine.cs b/engine/Age.Engine/Vm/VirtualMachine.cs index a610a9c..4f05c21 100644 --- a/engine/Age.Engine/Vm/VirtualMachine.cs +++ b/engine/Age.Engine/Vm/VirtualMachine.cs @@ -2206,58 +2206,30 @@ public sealed partial class VirtualMachine } case "u0041B290": case "set-message-skip": // 0x88: persistent all-message fast-forward service state - _messageSkipEnabled = Read(a[0]) != 0; - _messageSkipServiceActive = _messageSkipEnabled; - _host.SetMessageSkipActive(_messageSkipServiceActive); - return pc + 1; case "u00414E50": // 0x19a: persistent state used by the SO001 active overlay - Write(a[0], _messageSkipEnabled ? 1 : 0); return pc + 1; case "u00414E80": case "suspend-adv-skip-service": // 0x19b: preserve the toggle while leaving ADV presentation - _messageSkipServiceActive = false; - _host.SetMessageSkipActive(false); - return pc + 1; case "u00414EC0": case "resume-adv-skip-service": // 0x19c: recompute active fast-forward on ADV entry - _messageSkipServiceActive = - _messageSkipEnabled || _advReadSkipState || _host.IsAdvReadSkipActive; - _host.SetMessageSkipActive(_messageSkipServiceActive); - RefreshPhysicalMessageSkipState(); - return pc + 1; case "get-message-skip": // 0x1c7: persistent Skip or host-supplied Ctrl fast-forward - // Native persistent state and the independently polled physical action-6 channel both - // re-arm the transient run-state bit consumed by this query. - Write(a[0], _messageSkipServiceActive || _host.IsMessageSkipActive ? 1 : 0); return pc + 1; case "get-adv-read-skip-state": // 0x1cc: per-message read/click skip service state case "get-adv-service-state": // compatibility with pre-recovery generated tables - Write(a[0], _advReadSkipState || _host.IsAdvReadSkipActive ? 1 : 0); return pc + 1; case "u0041B9B0": case "set-read-message-skip": // 0x1ca: engine setting message:ReadTextSkip - _sharedProfile.ReadMessageSkipEnabled = Read(a[0]) != 0; - RefreshAdvReadSkipState(); - return pc + 1; case "u00414FD0": case "get-read-message-skip": // 0x1cb - Write(a[0], _sharedProfile.ReadMessageSkipEnabled ? 1 : 0); - return pc + 1; case "u00414F60": case "get-auto-message": // 0x1b6: VM service state used by the ADV redraw callback - Write(a[0], _autoMessageEnabled ? 1 : 0); return pc + 1; case "u0041B640": case "set-auto-message": // 0x1b7 - _autoMessageEnabled = Read(a[0]) != 0; return pc + 1; case "u0041B670": case "get-auto-message-time": // 0x1b8 (selector 0=post-voice Time0, 1=unvoiced Time1, out) - Write(a[1], Read(a[0]) == 0 ? _autoMessageTime0Ms : _autoMessageTime1Ms); return pc + 1; case "u0041B710": case "set-auto-message-time": // 0x1b9 (selector, milliseconds) - if (Read(a[0]) == 0) _autoMessageTime0Ms = Read(a[1]); - else if (Read(a[0]) == 1) _autoMessageTime1Ms = Read(a[1]); - return pc + 1; case "u00415670": case "block-mark": case "reset-message-voice-state": // 0x1bc resets native per-message voice/queued-voice state - _autoVoicePending = false; return pc + 1; + return StepAdvService(label, a, pc); case "set-text-history-recording": case "append-text-history-metadata": case "step-text-history": @@ -2308,7 +2280,7 @@ public sealed partial class VirtualMachine 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; + return StepAdvService(label, a, pc); case "end-text-line": case "set-font": case "comment": case "display-furigana": case "dev_ukn":