diff --git a/docs/tools-reference.md b/docs/tools-reference.md index 78ab32a..ffa030c 100644 --- a/docs/tools-reference.md +++ b/docs/tools-reference.md @@ -85,6 +85,14 @@ subsystem oracles. Test scenes are **synthesized** via `Age.Engine/Sys4/ScriptAs | `play [--boot] [--state ] [--save-state ] [0xADDR=VAL…]` | ★ Cross-scene **state runner**: run a scene sequence carrying persistent globals. `--boot` first runs the 9 `*INIT` data scripts (real skill/item/unit/map/stage state). `--state`/`--save-state` load/persist a JSON snapshot. | `GameSession`; **executes call-script**. | | `sweep [--boot] [0xADDR=VAL…]` | Corpus-scale run. **With call-script execution on: 284/297 exit, 13 STEP-LIMIT** (input/state-gated ADV scenes spin headless once subroutine global-writes drive their loops — state divergence, not a bug; 0 depth-cap/unresolved). **With seeds = a story-state explorer**: reports which scenes' dialogue changes ±seed (e.g. form flag `0xa57=1` → 34/297 scenes). | | +**`--trace [--trace-file ] [--trace-steps]`** (on `run`/`play`/`sweep`): stream the engine's own +diagnostic events over the `Age.Engine.Diagnostics.ITraceSink` seam — scene/subroutine frame enter+exit +(indented by call depth), call-script dispatch with resolved name, and the final halt+step count — to +console or a file. Add `--trace-steps` for per-instruction opcode/arg + stub-op detail (high volume; +gated). Absent ⇒ no tracing (`NullTraceSink`, byte-identical run). This is an **engine** fact stream: +frontends consume it instead of reimplementing a diagnostic `IHost`. Example: `play SC0000.BIN --trace` +shows `» SC0000.BIN (enter, TopScene)` → `call-script 0xee =INPUTNAME.BIN (resolved)` → `halt: …`. + **Godot frontend** (`S:/Godot/Godot_v4.7…`; project = `godot/`). Toolchain: `godot --headless --path godot --import` → `dotnet build godot/Himegari.csproj` → `godot [--headless] --path godot [-- ]`. Plays the real bytecode with call-script execution on (subroutines run live). `--headless` can't render diff --git a/engine/Age.Cli/Program.cs b/engine/Age.Cli/Program.cs index ffbf242..08efaee 100644 --- a/engine/Age.Cli/Program.cs +++ b/engine/Age.Cli/Program.cs @@ -1,6 +1,8 @@ using System.Text.Json; using System.Text.RegularExpressions; +using Age.Engine.Diagnostics; using Age.Engine.Hosting; +using Age.Engine.Model; using Age.Engine.Sys4; using Age.Engine.Vm; @@ -9,13 +11,25 @@ var table = OpcodeTableJson.Load(Paths.OpcodesJson); // `trace` stays provider-less on purpose (the base-ISA offset oracle). var provider = Sys4ScriptProvider.Load(table); +// --trace [--trace-file ] [--trace-steps] → a TextTraceSink to console or file; else inert. +static ITraceSink BuildSink(string[] a, OpcodeTable tbl) +{ + if (!a.Contains("--trace")) return NullTraceSink.Instance; + bool steps = a.Contains("--trace-steps"); + int fi = Array.IndexOf(a, "--trace-file"); + TextWriter w = (fi >= 0 && fi + 1 < a.Length) + ? new StreamWriter(a[fi + 1]) { AutoFlush = true } + : Console.Out; + return new TextTraceSink(w, tbl, steps); +} + if (args.Length == 0) { Console.WriteLine("usage: run | trace "); return 1; } if (args[0] == "run") { var script = Sys4Loader.Load(args[1], table); var runHost = new CaptureHost(); - var vm = new VirtualMachine(script, table, runHost, null, provider); + var vm = new VirtualMachine(script, table, runHost, null, provider, BuildSink(args, table)); vm.Run(); Console.WriteLine($"{Path.GetFileName(args[1])}: {vm.Steps} steps, {vm.Emitted.Count} show-text, {vm.CallScriptDispatches} call-scripts (halt: {vm.HaltReason})"); foreach (var (off, text, scr) in vm.Emitted.Take(30)) Console.WriteLine($" [{scr} 0x{off:x}] {text}"); @@ -100,7 +114,7 @@ if (args[0] == "play") foreach (var name in scenes) { var script = Sys4Loader.Load(scripts[name.ToUpperInvariant()], table); - var r = session.RunScene(script, table, new CaptureHost(), null, provider); + var r = session.RunScene(script, table, new CaptureHost(), null, provider, BuildSink(args, table)); totalLines += r.Emitted.Count; Console.WriteLine($" {name,-14} {r.Emitted.Count,4} lines, {r.Steps,7} steps (halt: {r.Halt})"); } @@ -165,7 +179,7 @@ if (args[0] == "sweep") foreach (var name in names) { var session = Fresh(); - var r = session.RunScene(Sys4Loader.Load(scripts[name], table), table, new CaptureHost(), null, provider); + var r = session.RunScene(Sys4Loader.Load(scripts[name], table), table, new CaptureHost(), null, provider, BuildSink(args, table)); var halt = r.Halt ?? "null"; haltDist[halt] = haltDist.GetValueOrDefault(halt) + 1; totalLines += r.Emitted.Count; diff --git a/engine/Age.Engine/Vm/VirtualMachine.cs b/engine/Age.Engine/Vm/VirtualMachine.cs index 87989af..6426db4 100644 --- a/engine/Age.Engine/Vm/VirtualMachine.cs +++ b/engine/Age.Engine/Vm/VirtualMachine.cs @@ -226,7 +226,9 @@ public sealed class VirtualMachine case "play-bgm": _host.PlayBgm(Read(a[0])); return pc + 1; case "play-voice": _host.PlayVoice(Read(a[0])); return pc + 1; default: - _sink.Emit(TraceEvent.Stub(op, pc)); return pc + 1; + // Stub is per-instruction frequency (the VM handles ~30 ops; the rest hit here, e.g. + // 0x258/0x259 stmt markers appear en masse), so gate it with Step — else --trace floods. + if (_sink.TracingSteps) _sink.Emit(TraceEvent.Stub(op, pc)); return pc + 1; } } }