diff --git a/docs/PROJECT-STRUCTURE.md b/docs/PROJECT-STRUCTURE.md index a21398f..5c611cc 100644 --- a/docs/PROJECT-STRUCTURE.md +++ b/docs/PROJECT-STRUCTURE.md @@ -211,6 +211,9 @@ ADV coroutine handler save/yield/resume, and bounded labeled-yield dispatch; pro lifecycle are routed separately. `engine/Age.Engine/Vm/VirtualMachine.ScriptLifecycle.cs` owns process/frame/root exit, ordinary cross-script calls, mounted append autoruns, and preloaded script-slot load/call dispatch; frame execution, script-provider access, and shared lifecycle state remain in `VirtualMachine.cs`. +`engine/Age.Engine/Vm/VirtualMachine.Values.cs` owns integer arithmetic, bitwise and comparison operations, +string comparison/concatenation/conversion/move, native byte-length and CP932 operations, and the host-backed +fullwidth string editor; shared operand storage, addressing, and native-string encoding remain in the coordinator. 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 5da7583..94ebea4 100644 --- a/docs/remake-architecture-and-roadmap.md +++ b/docs/remake-architecture-and-roadmap.md @@ -721,6 +721,13 @@ do not mix mechanical moves with semantic changes. provider access, call-depth enforcement, and shared lifecycle state remain in the coordinator. Runtime validation remains green. + The sixteenth bounded `VirtualMachine.Step` extraction moved integer arithmetic, bitwise and comparison + operations, string comparison/concatenation/conversion/move, native byte-length and CP932 operations, and + the host-backed fullwidth string editor into `engine/Age.Engine/Vm/VirtualMachine.Values.cs`. The top-level + dispatcher retains all labels at their existing positions and routes the two groups around the timing query + through guarded `StepValue`; shared operand storage, addressing, and native-string encoding remain in the + coordinator. 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. @@ -1094,8 +1101,8 @@ 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, animation, presentation, ADV-text, text-history, ADV-service, input, timing, persistence, and memory/collection `VirtualMachine.Step` families routed -through domain handlers, with control-flow/coroutine and process/root-exit/cross-script lifecycle dispatch now -isolated as well, extract scalar arithmetic and string-value dispatch next without replacing the proven -dispatcher or changing public types, commands, and generated output. +through domain handlers, with control-flow/coroutine, process/root-exit/cross-script lifecycle, and scalar and +string-value dispatch now isolated as well, extract diagnostic accumulation and prompt dispatch 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.Values.cs b/engine/Age.Engine/Vm/VirtualMachine.Values.cs new file mode 100644 index 0000000..ed2602f --- /dev/null +++ b/engine/Age.Engine/Vm/VirtualMachine.Values.cs @@ -0,0 +1,82 @@ +using Age.Engine.Hosting; +using Age.Engine.Model; +using Age.Engine.Sys4; + +namespace Age.Engine.Vm; + +public sealed partial class VirtualMachine +{ + private int StepValue(string label, IReadOnlyList a, int pc) + { + switch (label) + { + case "add": Write(a[0], Read(a[1]) + Read(a[2])); return pc + 1; + case "sub": Write(a[0], Read(a[1]) - Read(a[2])); return pc + 1; + case "mul": Write(a[0], Read(a[1]) * Read(a[2])); return pc + 1; + case "div": Write(a[0], PyDiv(Read(a[1]), Read(a[2]))); return pc + 1; + case "mod": Write(a[0], PyMod(Read(a[1]), Read(a[2]))); return pc + 1; + case "and": Write(a[0], Read(a[1]) & Read(a[2])); return pc + 1; + case "or": Write(a[0], Read(a[1]) | Read(a[2])); return pc + 1; + case "sar": Write(a[0], Read(a[1]) >> (int)(Read(a[2]) & 31)); return pc + 1; + case "shl": Write(a[0], Read(a[1]) << (int)(Read(a[2]) & 31)); return pc + 1; + case "eq": Write(a[0], Read(a[1]) == Read(a[2]) ? 1 : 0); return pc + 1; + case "ne": Write(a[0], Read(a[1]) != Read(a[2]) ? 1 : 0); return pc + 1; + case "string-equals": + Write(a[0], string.Equals(ReadStr(a[1]), ReadStr(a[2]), StringComparison.Ordinal) ? 1 : 0); + return pc + 1; + case "string-not-equals": + Write(a[0], string.Equals(ReadStr(a[1]), ReadStr(a[2]), StringComparison.Ordinal) ? 0 : 1); + return pc + 1; + case "concat": + { + string left = ReadStr(a[1]); + string right = ReadStr(a[2]); + WriteStr(a[0], left + right); + return pc + 1; + } + case "toString": + WriteStr(a[0], unchecked((int)Read(a[1])).ToString(System.Globalization.CultureInfo.InvariantCulture)); + return pc + 1; + case "absolute-value": + { + int value = unchecked((int)Read(a[1])); + int sign = value >> 31; + Write(a[0], unchecked((value ^ sign) - sign)); + return pc + 1; + } + case "lt": Write(a[0], Read(a[1]) < Read(a[2]) ? 1 : 0); return pc + 1; + case "lte": Write(a[0], Read(a[1]) <= Read(a[2]) ? 1 : 0); return pc + 1; + case "gr": Write(a[0], Read(a[1]) > Read(a[2]) ? 1 : 0); return pc + 1; + case "gre": Write(a[0], Read(a[1]) >= Read(a[2]) ? 1 : 0); return pc + 1; + case "mov": + case "set-string": + if (IsStr(a[0]) || IsStr(a[1])) WriteStr(a[0], ReadStr(a[1])); + else Write(a[0], Read(a[1])); + return pc + 1; + case "halve-strlen": // 0x1a6: strlen(native encoded bytes) >> 1 + Write(a[0], NativeStringByteLength(ReadStr(a[1])) >> 1); + return pc + 1; + case "edit-fullwidth-string-dialog": // 0x144: blocking AGERc command-10 editor + { + string current = ReadStr(a[0]); + string initial = ReadStr(a[1]); + FullwidthTextEditResult result = + _host.EditFullwidthString(new(current, initial)); + if (result.Accepted) WriteStr(a[0], result.Text); + return pc + 1; + } + case "cp932-character-length": // 0x2c6: Japanese-locale _mbstrlen + Write(a[0], Cp932Text.CharacterLength(ReadStr(a[1]), _nativeStringEncoding)); + return pc + 1; + case "cp932-substring": // 0x2c8: multibyte-character interval [start,start+count) + WriteStr(a[0], Cp932Text.Substring( + ReadStr(a[1]), + unchecked((int)Read(a[2])), + unchecked((int)Read(a[3])), + _nativeStringEncoding)); + return pc + 1; + default: + throw new InvalidOperationException($"Non-value opcode routed to value handler: {label}"); + } + } +} diff --git a/engine/Age.Engine/Vm/VirtualMachine.cs b/engine/Age.Engine/Vm/VirtualMachine.cs index eb66de2..6e98c11 100644 --- a/engine/Age.Engine/Vm/VirtualMachine.cs +++ b/engine/Age.Engine/Vm/VirtualMachine.cs @@ -1212,73 +1212,36 @@ public sealed partial class VirtualMachine case "set-surface-persistence-flags": // 0x258 (slot)(flags): bit 0 = numbered-load reload Gfx.SetSurfaceReloadOnRestore(unchecked((int)Read(a[0])), (Read(a[1]) & 1) != 0); return pc + 1; - case "add": Write(a[0], Read(a[1]) + Read(a[2])); return pc + 1; - case "sub": Write(a[0], Read(a[1]) - Read(a[2])); return pc + 1; - case "mul": Write(a[0], Read(a[1]) * Read(a[2])); return pc + 1; - case "div": Write(a[0], PyDiv(Read(a[1]), Read(a[2]))); return pc + 1; - case "mod": Write(a[0], PyMod(Read(a[1]), Read(a[2]))); return pc + 1; - case "and": Write(a[0], Read(a[1]) & Read(a[2])); return pc + 1; - case "or": Write(a[0], Read(a[1]) | Read(a[2])); return pc + 1; - case "sar": Write(a[0], Read(a[1]) >> (int)(Read(a[2]) & 31)); return pc + 1; - case "shl": Write(a[0], Read(a[1]) << (int)(Read(a[2]) & 31)); return pc + 1; - case "eq": Write(a[0], Read(a[1]) == Read(a[2]) ? 1 : 0); return pc + 1; - case "ne": Write(a[0], Read(a[1]) != Read(a[2]) ? 1 : 0); return pc + 1; + case "add": + case "sub": + case "mul": + case "div": + case "mod": + case "and": + case "or": + case "sar": + case "shl": + case "eq": + case "ne": case "string-equals": - Write(a[0], string.Equals(ReadStr(a[1]), ReadStr(a[2]), StringComparison.Ordinal) ? 1 : 0); - return pc + 1; case "string-not-equals": - Write(a[0], string.Equals(ReadStr(a[1]), ReadStr(a[2]), StringComparison.Ordinal) ? 0 : 1); - return pc + 1; case "concat": - { - string left = ReadStr(a[1]); - string right = ReadStr(a[2]); - WriteStr(a[0], left + right); - return pc + 1; - } case "toString": - WriteStr(a[0], unchecked((int)Read(a[1])).ToString(System.Globalization.CultureInfo.InvariantCulture)); - return pc + 1; case "absolute-value": - { - int value = unchecked((int)Read(a[1])); - int sign = value >> 31; - Write(a[0], unchecked((value ^ sign) - sign)); - return pc + 1; - } + return StepValue(label, a, pc); case "get-monotonic-time-ms": return StepTiming(label, a, pc); - case "lt": Write(a[0], Read(a[1]) < Read(a[2]) ? 1 : 0); return pc + 1; - case "lte": Write(a[0], Read(a[1]) <= Read(a[2]) ? 1 : 0); return pc + 1; - case "gr": Write(a[0], Read(a[1]) > Read(a[2]) ? 1 : 0); return pc + 1; - case "gre": Write(a[0], Read(a[1]) >= Read(a[2]) ? 1 : 0); return pc + 1; + case "lt": + case "lte": + case "gr": + case "gre": case "mov": case "set-string": - if (IsStr(a[0]) || IsStr(a[1])) WriteStr(a[0], ReadStr(a[1])); - else Write(a[0], Read(a[1])); - return pc + 1; case "halve-strlen": // 0x1a6: strlen(native encoded bytes) >> 1 - Write(a[0], NativeStringByteLength(ReadStr(a[1])) >> 1); - return pc + 1; case "edit-fullwidth-string-dialog": // 0x144: blocking AGERc command-10 editor - { - string current = ReadStr(a[0]); - string initial = ReadStr(a[1]); - FullwidthTextEditResult result = - _host.EditFullwidthString(new(current, initial)); - if (result.Accepted) WriteStr(a[0], result.Text); - return pc + 1; - } case "cp932-character-length": // 0x2c6: Japanese-locale _mbstrlen - Write(a[0], Cp932Text.CharacterLength(ReadStr(a[1]), _nativeStringEncoding)); - return pc + 1; case "cp932-substring": // 0x2c8: multibyte-character interval [start,start+count) - WriteStr(a[0], Cp932Text.Substring( - ReadStr(a[1]), - unchecked((int)Read(a[2])), - unchecked((int)Read(a[3])), - _nativeStringEncoding)); - return pc + 1; + return StepValue(label, a, pc); case "u00425790": // upstream ABI label case "append-diagnostic-value": // 0x1b2: generic operand text -> EngineCtx accumulator _diagnosticOutput.Append(FormatDiagnosticOperand(a[0]));