Capture natural Game Start diagnostics

This commit is contained in:
gamer147
2026-07-21 01:20:07 -04:00
parent f6479209ea
commit e86abfbf65
6 changed files with 105 additions and 31 deletions

View File

@@ -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) ## Native walls backlog (targets for this loop)
- ~~**call-script dispatch**~~ — **SOLVED** (above): `call-script <id>` = raw SYS4INI file index. - ~~**call-script dispatch**~~ — **SOLVED** (above): `call-script <id>` = raw SYS4INI file index.

View File

@@ -206,6 +206,16 @@ Operand 2 names the base cell itself: a global-bank operand produces a global re
- **grounding:** source=investigation, confidence=high - **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. - **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 ## control
### 0x1 `throw-exit-request` (throw-exit-request, argc 0) ### 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:** — - **summary:** —
- **grounding:** source=kelebek, confidence=med - **grounding:** source=kelebek, confidence=med
### 0x194 `u00425480` (u00425480, argc 3)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x195 `u00425580` (u00425580, argc 3) ### 0x195 `u00425580` (u00425580, argc 3)
- **summary:** — - **summary:** —
- **grounding:** source=kelebek, confidence=low - **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:** — - **summary:** —
- **grounding:** source=kelebek, confidence=low - **grounding:** source=kelebek, confidence=low
### 0x1b0 `u0041A510` (u0041A510, argc 3)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x1b2 `u00425790` (u00425790, argc 1) ### 0x1b2 `u00425790` (u00425790, argc 1)
- **summary:** — - **summary:** —
- **grounding:** source=kelebek, confidence=low - **grounding:** source=kelebek, confidence=low

View File

@@ -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 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. 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 ## Stage B2 — Faithful full boot
Replace `--boot`'s diagnostic seeding and separately injected inherited surfaces with normal boot execution. Replace `--boot`'s diagnostic seeding and separately injected inherited surfaces with normal boot execution.

View File

@@ -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 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 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 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 <NAME>` — override the default `SYSTEM4` root with a direct diagnostic scene, e.g. `--scene SC0240` (executes 29 nested subroutines). - `--scene <NAME>` — 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. - `--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. - `--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. created automatically.
- `--transition-click-ms <n>` — 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. - `--transition-click-ms <n>` — 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 <jsonl>` — diagnostic-only synchronized event stream for a real Godot run. Records every - `--timeline-log <jsonl>` — 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`, executed script byte offset/opcode, script-frame entry/exit with depth/cause/outcome, resolved call-script
`halted`), BGM events, and changed visible-object compositor outcomes in one ordered JSONL file. Combine with 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 `--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/`). failures at an exact bytecode boundary. Relative output paths are project-relative (`godot/`).

View File

@@ -18,14 +18,30 @@ public sealed class GodotTraceSink : ITraceSink
public readonly ConcurrentQueue<long> CallScripts = new(); public readonly ConcurrentQueue<long> CallScripts = new();
public void Emit(in TraceEvent e) 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) if (e.Kind == TraceEventKind.FrameEnter && e.Name != null)
{ {
_scripts.Push(e.Name); _scripts.Push(e.Name);
PublishCallStack(); 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) 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(); _scripts.Pop();
PublishCallStack(); PublishCallStack();
} }
@@ -35,6 +51,11 @@ public sealed class GodotTraceSink : ITraceSink
_locator.Step(script, e.Ins.Offset); _locator.Step(script, e.Ins.Offset);
_timeline?.Step(script, e.Ins.Offset, e.Opcode, e.Depth); _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) else if (e.Kind == TraceEventKind.Halt)
_timeline?.State("halted", new() { ["reason"] = e.Text, ["steps"] = e.Steps }); _timeline?.State("halted", new() { ["reason"] = e.Text, ["steps"] = e.Steps });
} }

View File

@@ -3109,33 +3109,33 @@ observed_types = ["string", "g-str", "l-str", "l-ptr", "l-str-ptr"]
[[opcode]] [[opcode]]
op = 0x194 op = 0x194
label = "u00425480" label = "string-equals"
argc = 3 argc = 3
abi_source = "kelebek+decode-validated" abi_source = "kelebek+decode-validated"
[opcode.semantics] [opcode.semantics]
name = "u00425480" name = "string-equals"
category = "unknown" category = "compute"
summary = "" summary = "(out)(left)(right) - compare two complete SYS4 strings and write 1 when equal, otherwise 0."
noop_headless = false noop_headless = false
source = "kelebek" source = "investigation"
confidence = "low" confidence = "high"
depends_on = [] 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]] [[opcode.semantics.args]]
i = 1 i = 1
role = "" role = "out"
observed_types = ["l-int"] observed_types = ["l-int"]
[[opcode.semantics.args]] [[opcode.semantics.args]]
i = 2 i = 2
role = "" role = "left"
observed_types = ["g-str", "l-str", "l-str-ptr"] observed_types = ["g-str", "l-str", "l-str-ptr"]
[[opcode.semantics.args]] [[opcode.semantics.args]]
i = 3 i = 3
role = "" role = "right"
observed_types = ["string", "l-str", "l-str-ptr"] observed_types = ["string", "l-str", "l-str-ptr"]
[[opcode]] [[opcode]]
@@ -3793,33 +3793,33 @@ observed_types = ["l-int"]
[[opcode]] [[opcode]]
op = 0x1b0 op = 0x1b0
label = "u0041A510" label = "copy-dwords"
argc = 3 argc = 3
abi_source = "kelebek+decode-validated" abi_source = "kelebek+decode-validated"
[opcode.semantics] [opcode.semantics]
name = "u0041A510" name = "copy-dwords"
category = "unknown" category = "compute"
summary = "" summary = "(source)(destination)(count) - copy count consecutive 32-bit cells from source to destination."
noop_headless = false noop_headless = false
source = "kelebek" source = "investigation"
confidence = "low" confidence = "high"
depends_on = [] 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]] [[opcode.semantics.args]]
i = 1 i = 1
role = "" role = "source"
observed_types = ["g-int", "l-int", "l-ptr"] observed_types = ["g-int", "l-int", "l-ptr"]
[[opcode.semantics.args]] [[opcode.semantics.args]]
i = 2 i = 2
role = "" role = "destination"
observed_types = ["g-int", "l-int", "l-ptr"] observed_types = ["g-int", "l-int", "l-ptr"]
[[opcode.semantics.args]] [[opcode.semantics.args]]
i = 3 i = 3
role = "" role = "count"
observed_types = ["imm", "l-int"] observed_types = ["imm", "l-int"]
[[opcode]] [[opcode]]