diff --git a/engine/Age.Cli/Program.cs b/engine/Age.Cli/Program.cs index 92fc970..34c6a16 100644 --- a/engine/Age.Cli/Program.cs +++ b/engine/Age.Cli/Program.cs @@ -14,10 +14,13 @@ if (args.Length == 0) { Console.WriteLine("usage: run | trace " if (args[0] == "run") { var script = Sys4Loader.Load(args[1], table); - var vm = new VirtualMachine(script, table, new CaptureHost(), null, provider); + var runHost = new CaptureHost(); + var vm = new VirtualMachine(script, table, runHost, null, provider); vm.Run(); - Console.WriteLine($"{Path.GetFileName(args[1])}: {vm.Steps} steps, {vm.Emitted.Count} show-text (halt: {vm.HaltReason})"); - foreach (var (off, text, _) in vm.Emitted.Take(20)) Console.WriteLine($" [{off:x}] {text}"); + Console.WriteLine($"{Path.GetFileName(args[1])}: {vm.Steps} steps, {vm.Emitted.Count} show-text, {runHost.CallScriptCount} call-scripts (halt: {vm.HaltReason})"); + foreach (var (off, text, scr) in vm.Emitted.Take(30)) Console.WriteLine($" [{scr} 0x{off:x}] {text}"); + var sources = vm.Emitted.Select(e => e.Script).Distinct().ToList(); + Console.WriteLine($"source scripts ({sources.Count}): {string.Join(", ", sources)}"); return 0; } diff --git a/godot/GodotAdvHost.cs b/godot/GodotAdvHost.cs index ed6e708..f86cea8 100644 --- a/godot/GodotAdvHost.cs +++ b/godot/GodotAdvHost.cs @@ -42,7 +42,10 @@ public sealed class GodotAdvHost : IHost // called from the main thread (click) or the selftest auto-clicker public void SignalInput() { if (_gate.CurrentCount == 0) _gate.Release(); } - public void CallScript(long id) { } + // Records each call-script the VM dispatches (runs on the VM thread, so collect thread-safely and + // let the main thread report it — Godot drops GD.Print from background threads). + public readonly System.Collections.Concurrent.ConcurrentQueue Dispatched = new(); + public void CallScript(long id) => Dispatched.Enqueue(id); public void OnStub(int opcode) { } // ---- texture ops (run on the VM thread; marshal Godot node work to the main thread) ---- diff --git a/godot/Main.cs b/godot/Main.cs index f01d3eb..138a43e 100644 --- a/godot/Main.cs +++ b/godot/Main.cs @@ -75,9 +75,11 @@ public partial class Main : Godot.Control var userArgs = OS.GetCmdlineUserArgs(); _selftest = System.Array.IndexOf(userArgs, "--selftest") >= 0; + string scene = "SC0000"; // --scene : which scene to play (default SC0000) var seeds = new List<(int Addr, long Val)>(); // --seed 0xADDR=VAL (repeatable) — initial global state for (int i = 0; i < userArgs.Length; i++) { + if (userArgs[i] == "--scene" && i + 1 < userArgs.Length) scene = userArgs[i + 1]; if (userArgs[i] == "--shot" && i + 1 < userArgs.Length) _shotPath = userArgs[i + 1]; if (userArgs[i] == "--shot-page" && i + 1 < userArgs.Length) int.TryParse(userArgs[i + 1], out _shotPage); if (userArgs[i] == "--seed" && i + 1 < userArgs.Length) @@ -98,8 +100,8 @@ public partial class Main : Godot.Control Script script; IScriptProvider provider; if (_selftest) (script, provider) = BuildSelfTestScene(table); - else { script = Sys4Loader.Load(Paths.Scripts()["SC0000.BIN"], table); provider = Sys4ScriptProvider.Load(table); } - _host = new GodotAdvHost(this, ResourceMap.Load(), "SC0000"); + else { script = Sys4Loader.Load(Paths.Scripts()[scene.ToUpperInvariant() + ".BIN"], table); provider = Sys4ScriptProvider.Load(table); } + _host = new GodotAdvHost(this, ResourceMap.Load(), scene); _vm = new VirtualMachine(script, table, _host, null, provider); foreach (var (addr, val) in seeds) _vm.Globals[addr] = val; // seed initial state before running _ = Task.Run(() => { _vm.Run(); _done = true; }); @@ -122,6 +124,7 @@ public partial class Main : Godot.Control var img = GetViewport().GetTexture().GetImage(); img.SavePng(_shotPath); GD.Print($"SHOT saved page {_pageCount} -> {_shotPath}"); + ReportSubroutines(); GetTree().Quit(0); } return; @@ -129,6 +132,7 @@ public partial class Main : Godot.Control if (_done && !_ended) { _ended = true; + ReportSubroutines(); ShowEnd(); if (_selftest) RunSelfTest(); } @@ -195,6 +199,17 @@ public partial class Main : Godot.Control // + CallDeferred marshalling) drives the VM faithfully — i.e. produces the SAME output as a plain // in-process run of the identical scene. Full op handling is on (the synthetic scene includes a real // nested call-script); the expected value is computed live from a headless run, not a frozen golden. + // Reports (from the main thread) the call-scripts the VM executed as nested subroutines this run. + private void ReportSubroutines() + { + var ids = new List(); + while (_host.Dispatched.TryDequeue(out var id)) ids.Add(id); + if (ids.Count == 0) { GD.Print("[subroutines] none dispatched on this path"); return; } + var distinct = new List(); + foreach (var id in ids) { var h = "0x" + id.ToString("x"); if (!distinct.Contains(h)) distinct.Add(h); } + GD.Print($"[subroutines] {ids.Count} call-scripts executed as nested frames ({distinct.Count} distinct: {string.Join(", ", distinct)})"); + } + private void RunSelfTest() { var table = OpcodeTableJson.Load(Paths.OpcodesJson);