From e86abfbf650943b9f5e542bc0c23ef7569725f2c Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 21 Jul 2026 01:20:07 -0400 Subject: [PATCH] Capture natural Game Start diagnostics --- docs/engine-re.md | 19 +++++++++++++++++++ docs/opcode-reference.md | 18 ++++++++++-------- docs/phase-b-framework.md | 18 ++++++++++++++++++ docs/tools-reference.md | 18 ++++++++++++++++-- godot/GodotTraceSink.cs | 23 +++++++++++++++++++++- vm-map/opcodes.toml | 40 +++++++++++++++++++-------------------- 6 files changed, 105 insertions(+), 31 deletions(-) diff --git a/docs/engine-re.md b/docs/engine-re.md index 0063fc7..309d3b3 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -2186,6 +2186,25 @@ unchanged. --- +### Startup string predicate and unit-data block copy — `0x194` / `0x1b0` (2026-07-21) + +The natural Game Start capture reached two previously unnamed effectful handlers. They are independent +contracts even though both appeared in the same startup fallback inventory. + +`op_0x194_string_equals@0x426e20` resolves operands 2 and 3 as SYS4 strings, passes their data pointers and +explicit byte lengths to the native comparison helper, and writes `comparison == 0` to integer operand 1. +It is therefore an equality predicate, not a string assignment. The release corpus uses the result in +conditional branches; `GAMESTART@0x134c` compares `INPUTNAME` with `"?"`, while INIT2 also compares +`INPUTNAME` with an empty string during default-name initialization. + +`op_0x1b0_copy_dwords@0x427060` fetches operand 3 as a cell count, resolves operands 1 and 2 as source and +destination pointers, and calls `memcpy(destination, source, count * 4)`. The capture reached it three times +inside `UNITECH`/`CALCCC`, immediately after opcode `0x63`. The copy itself is proven; the pointer-producing +semantics of `0x63` remain unresolved, so the pair should be implemented only after that companion handler +is understood. + +--- + ## Native walls backlog (targets for this loop) - ~~**call-script dispatch**~~ — **SOLVED** (above): `call-script ` = raw SYS4INI file index. diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index 1e37ed4..458f93c 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -206,6 +206,16 @@ Operand 2 names the base cell itself: a global-bank operand produces a global re - **grounding:** source=investigation, confidence=high - **evidence:** Ghidra /v2: op_0x136_handler@0x429730 fetches operand 2 as an unsigned bit index, rejects values >=32 through the native script-error path, fetches operand 1, and writes value & ~(1 << index). HIDEWIN.BIN clears index 1 at 0x154 after testing mask 0x2. +### 0x194 `string-equals` (string-equals, argc 3) +- **summary:** (out)(left)(right) - compare two complete SYS4 strings and write 1 when equal, otherwise 0. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2 op_0x194_string_equals@0x426e20 fetches operands 2 and 3 through the string resolver, compares their byte ranges through FUN_004017a0, and writes compare_result==0 to integer operand 1. INIT2 and GAMESTART use it as a branch predicate for INPUTNAME/default-name handling; the natural Game Start diagnostic reached one GAMESTART call at 0x134c. + +### 0x1b0 `copy-dwords` (copy-dwords, argc 3) +- **summary:** (source)(destination)(count) - copy count consecutive 32-bit cells from source to destination. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2 op_0x1b0_copy_dwords@0x427060 fetches operand 3, resolves pointer operands 1 and 2, and calls memcpy(destination, source, count*4). The natural Game Start diagnostic reached it three times in UNITECH/CALCCC initialization, paired with unresolved pointer-preparation opcode 0x63. + ## control ### 0x1 `throw-exit-request` (throw-exit-request, argc 0) @@ -1056,10 +1066,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it " - **summary:** — - **grounding:** source=kelebek, confidence=med -### 0x194 `u00425480` (u00425480, argc 3) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x195 `u00425580` (u00425580, argc 3) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -1124,10 +1130,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it " - **summary:** — - **grounding:** source=kelebek, confidence=low -### 0x1b0 `u0041A510` (u0041A510, argc 3) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x1b2 `u00425790` (u00425790, argc 1) - **summary:** — - **grounding:** source=kelebek, confidence=low diff --git a/docs/phase-b-framework.md b/docs/phase-b-framework.md index d1d2678..4bc1822 100644 --- a/docs/phase-b-framework.md +++ b/docs/phase-b-framework.md @@ -259,6 +259,24 @@ means step 16, not step 1), making argument 10 about 160 ms and argument 30 abou voice ids were reaching `play-voice` but failing the port's SC-section-only lookup because ROOM owns no SC section. Voice resolution now uses the same scene-first, type-checked raw fallback as frontend textures. +**Natural Game Start diagnostic gate (2026-07-21; captured).** The opt-in `-StartupDiagnostics` route kept +the persistent `SYSTEM4` root and native exit semantics and added no seeds, boot prefix, timing changes, or +input automation. A user-driven cold boot traversed the complete initialization family, `LOGO`/`LOGO.AGF`, +`OP`/`OP.AGF`, `INIT`, `TITLE`, `GAMESTART`, its nested unit-data setup, and finally raw script `0x22` / +`SC0000`. It stopped at the first stable wait, `SC0000@0x83c`, whose preceding text instruction is +`SC0000@0x834` (`――かつて、戦いがあった。`). The 97,730-event timeline contains 96,908 opcode steps and +129 distinct opcodes; the Godot log has no warnings/errors, both movies start and stop normally, no audio +resource is unresolved, and no VM halt occurs. + +The 137 fallback events are not a single boot blocker. Most are declaration/statement/line markers already +proven safe, or deliberately deferred profile/read-text operations (`0x1a2`, `0x1a3`, `0x1cb`). The reached +effectful unknowns divide into SYSTEM4 layout setup and unit-data initialization. Native follow-up identifies +`0x194` as a string-equality predicate reached in `INIT2` and `GAMESTART`, and `0x1b0` as a dword-block copy +paired with still-unresolved pointer-preparation opcode `0x63` in `UNITECH`/`CALCCC`. This makes `0x194` the +smallest directly boot-relevant implementation slice; the `0x63`/`0x1b0` pair is the larger subsequent +backend-data slice. Closing Godot currently releases a parked ADV wait before process teardown, so the page +map may contain one trailing shutdown-only page; the final timeline `input-wait` is the authoritative stop. + ## Stage B2 — Faithful full boot Replace `--boot`'s diagnostic seeding and separately injected inherited surfaces with normal boot execution. diff --git a/docs/tools-reference.md b/docs/tools-reference.md index 5617b09..3c364de 100644 --- a/docs/tools-reference.md +++ b/docs/tools-reference.md @@ -141,6 +141,19 @@ the persistent `SYSTEM4.BIN` root and reaches TITLE naturally. The local `run-go make that route explicit with `--scene SYSTEM4` and pass neither `--boot` nor SC0000 seeds. `--headless` can't render texture ops (no GPU context) — run windowed for real scenes. On VM termination the console prints the exact halt reason and step count before the generic on-screen `-end-` marker. User args (after `--`): + +The local launchers also accept **`-StartupDiagnostics`** for a native-faithful cold boot through TITLE, +Game Start, and SC0000. The switch overrides `run-godot.cmd`'s usual `-NativeDebugMenu` convenience for +that run and adds no `--boot`, seeds, timing changes, screenshots, or input injection. It writes four +disposable files under `build/validation/title-newgame/`: `godot.log` (Godot warnings/errors), +`timeline.jsonl` (ordered VM/host/compositor evidence), `histogram.txt` (executed opcode and hot-call-site +counts), and `page-map.jsonl` (authoritative wait/text locations and call stack). After Godot closes, the +PowerShell launcher reports each artifact's byte size and warns if any was not flushed. Run +`run-godot.cmd -StartupDiagnostics`, choose Game Start normally, stop at the first stable SC0000 input +wait, optionally press F3, and close the window normally. Until shutdown cancellation is separated from +ADV input release, normal window close can append one trailing shutdown-only record to `page-map.jsonl`; +use the last `input-wait` event in `timeline.jsonl` as the authoritative manual stop coordinate. + - `--scene ` — override the default `SYSTEM4` root with a direct diagnostic scene, e.g. `--scene SC0240` (executes 29 nested subroutines). - `--selftest` — headless; runs a **synthesized** scene through the thread/suspend/`CallDeferred` plumbing and asserts it matches a live headless run (full handling; no vm0/frozen golden). Exits. - `--seed 0xADDR=VAL` (repeatable) — seed initial global state, e.g. `--seed 0xa57=1` unlocks Lily's form-A voiced dialogue. @@ -163,8 +176,9 @@ the exact halt reason and step count before the generic on-screen `-end-` marker created automatically. - `--transition-click-ms ` — diagnostic-only input injector: after a foreground transition has been active for `n` virtual milliseconds, send one click through the real input lifecycle. The click completes/consumes the transition and does not advance a stable page. Use with `--timeline-log`, `--gfx-log`, and windowed `--shot-sequence`; omit for normal play. - `--timeline-log ` — diagnostic-only synchronized event stream for a real Godot run. Records every - executed script byte offset/opcode, virtual time/frame, VM state changes (`running`, `sleep`, `input-wait`, - `halted`), BGM events, and changed visible-object compositor outcomes in one ordered JSONL file. Combine with + executed script byte offset/opcode, script-frame entry/exit with depth/cause/outcome, resolved call-script + ids/names, unknown-fallback `stub` events, virtual time/frame, VM state changes (`running`, `sleep`, `input-wait`, + `halted`), audio events, and changed visible-object compositor outcomes in one ordered JSONL file. Combine with `--scene SC0000 --boot --shot-sequence ... --gfx-log ...` to distinguish control-flow stalls from retained-object/compositor failures at an exact bytecode boundary. Relative output paths are project-relative (`godot/`). diff --git a/godot/GodotTraceSink.cs b/godot/GodotTraceSink.cs index e674eaf..36a98a2 100644 --- a/godot/GodotTraceSink.cs +++ b/godot/GodotTraceSink.cs @@ -18,14 +18,30 @@ public sealed class GodotTraceSink : ITraceSink public readonly ConcurrentQueue CallScripts = new(); public void Emit(in TraceEvent e) { - if (e.Kind == TraceEventKind.CallScript) CallScripts.Enqueue(e.Id); + if (e.Kind == TraceEventKind.CallScript) + { + CallScripts.Enqueue(e.Id); + _timeline?.Event("call-script", new() + { + ["id"] = $"0x{e.Id:x}", ["resolved_name"] = e.Name, + }); + } if (e.Kind == TraceEventKind.FrameEnter && e.Name != null) { _scripts.Push(e.Name); PublishCallStack(); + _timeline?.Event("frame-enter", new() + { + ["name"] = e.Name, ["depth"] = e.Depth, + ["cause"] = e.Cause.ToString(), ["call_id"] = $"0x{e.Id:x}", + }); } else if (e.Kind == TraceEventKind.FrameExit && _scripts.Count > 0) { + _timeline?.Event("frame-exit", new() + { + ["name"] = e.Name, ["depth"] = e.Depth, ["outcome"] = e.Text, + }); _scripts.Pop(); PublishCallStack(); } @@ -35,6 +51,11 @@ public sealed class GodotTraceSink : ITraceSink _locator.Step(script, e.Ins.Offset); _timeline?.Step(script, e.Ins.Offset, e.Opcode, e.Depth); } + else if (e.Kind == TraceEventKind.Stub) + _timeline?.Event("stub", new() + { + ["stub_opcode"] = $"0x{e.Opcode:x}", ["pc_index"] = e.Pc, + }); else if (e.Kind == TraceEventKind.Halt) _timeline?.State("halted", new() { ["reason"] = e.Text, ["steps"] = e.Steps }); } diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index 08e6253..7a22c5f 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -3109,33 +3109,33 @@ observed_types = ["string", "g-str", "l-str", "l-ptr", "l-str-ptr"] [[opcode]] op = 0x194 -label = "u00425480" +label = "string-equals" argc = 3 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00425480" -category = "unknown" -summary = "" +name = "string-equals" +category = "compute" +summary = "(out)(left)(right) - compare two complete SYS4 strings and write 1 when equal, otherwise 0." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2 op_0x194_string_equals@0x426e20 fetches operands 2 and 3 through the string resolver, compares their byte ranges through FUN_004017a0, and writes compare_result==0 to integer operand 1. INIT2 and GAMESTART use it as a branch predicate for INPUTNAME/default-name handling; the natural Game Start diagnostic reached one GAMESTART call at 0x134c." [[opcode.semantics.args]] i = 1 -role = "" +role = "out" observed_types = ["l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "left" observed_types = ["g-str", "l-str", "l-str-ptr"] [[opcode.semantics.args]] i = 3 -role = "" +role = "right" observed_types = ["string", "l-str", "l-str-ptr"] [[opcode]] @@ -3793,33 +3793,33 @@ observed_types = ["l-int"] [[opcode]] op = 0x1b0 -label = "u0041A510" +label = "copy-dwords" argc = 3 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041A510" -category = "unknown" -summary = "" +name = "copy-dwords" +category = "compute" +summary = "(source)(destination)(count) - copy count consecutive 32-bit cells from source to destination." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2 op_0x1b0_copy_dwords@0x427060 fetches operand 3, resolves pointer operands 1 and 2, and calls memcpy(destination, source, count*4). The natural Game Start diagnostic reached it three times in UNITECH/CALCCC initialization, paired with unresolved pointer-preparation opcode 0x63." [[opcode.semantics.args]] i = 1 -role = "" +role = "source" observed_types = ["g-int", "l-int", "l-ptr"] [[opcode.semantics.args]] i = 2 -role = "" +role = "destination" observed_types = ["g-int", "l-int", "l-ptr"] [[opcode.semantics.args]] i = 3 -role = "" +role = "count" observed_types = ["imm", "l-int"] [[opcode]]