feat(cli): --trace flag streams engine diagnostics (run/play/sweep)
Also gate Stub events under TracingSteps: stubbed ops are per-instruction frequency (0x258/0x259 stmt markers en masse), so --trace stays a clean high-level flow view; --trace-steps shows step+stub detail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,14 @@ subsystem oracles. Test scenes are **synthesized** via `Age.Engine/Sys4/ScriptAs
|
||||
| `play [--boot] [--state <f>] [--save-state <f>] <SCENE.BIN…> [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 <path>] [--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 [-- <userargs>]`.
|
||||
Plays the real bytecode with call-script execution on (subroutines run live). `--headless` can't render
|
||||
|
||||
@@ -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 <path>] [--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 <file> | trace <out.json>"); 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user