Capture natural Game Start diagnostics
This commit is contained in:
@@ -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 <id>` = raw SYS4INI file index.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 <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.
|
||||
- `--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 <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
|
||||
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/`).
|
||||
|
||||
|
||||
@@ -18,14 +18,30 @@ public sealed class GodotTraceSink : ITraceSink
|
||||
public readonly ConcurrentQueue<long> 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 });
|
||||
}
|
||||
|
||||
@@ -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]]
|
||||
|
||||
Reference in New Issue
Block a user