Files
OpenMaidEngine/tools/diff_optrace.py
gamer147 453f9b1861 re: diff_optrace resync-tolerant alignment (blind spots vs real forks)
Strict lockstep over-reported: the operand hook misses any op whose operands
aren't fetched via vm_operand_fetch (comment 0x1a7, set-string 0x192, the
0x1c7/0x1cc/0x131/... string-op cluster) — the engine executes them (they sit
after non-branching ops) but they're absent from its trace. New align() resyncs
over such one-sided insertions and reports only NON-realignable forks; it
separates VM-only blind spots (artifacts) from engine-only detours (real,
reconverging branch/state gaps, surfaced honestly). +2 tests (7/7).

Result: with G[0x6c1] seeded the SC0000 opening has NO non-realignable fork
across all 539 VM ops (was: false 'diverge at 0x8d'); residual = a 2-op
engine-only color detour (0x202/0x203 @ 0x122d0). Cold's first real fork is a
later G[0x6c1] gate. Validates the pre-scene-state theory end-to-end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 12:55:21 -04:00

249 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Differential offset-path oracle (docs/engine-re.md): diff the real engine's executed script-offset
path against our C# VM's, on the deterministic opening, and report the FIRST divergence — the exact
branch/opcode we modeled wrong.
Both run the same bytecode, so the opcode at each script offset is static (from disassembly); we diff the
*sequence of executed offsets* (control flow), not opcodes or effects. A path divergence is a jcc that
went the other way, an opcode length we got wrong, or a native context (coroutine) the VM doesn't model.
Inputs (both under build/, disposable):
build/engine-optrace.jsonl — {"codebase":int,"offset":int} per executed op, in order (trace_engine_ops.py)
build/vm-optrace.json — {"scene":"SC0000","offsets":[int,...]} (Age.Cli trace --trace-json)
py -3.11 -X utf8 tools/diff_optrace.py SC0000 [--engine PATH] [--vm PATH]
The pure core (first_divergence / pick_scene_codebase) is unit-tested in tools/test_diff_optrace.py.
"""
import json
import re
import sys
from pathlib import Path
import paths # noqa: E402 (sibling import; tools dir is on sys.path when run directly)
ENGINE_TRACE = paths.BUILD / "engine-optrace.jsonl"
VM_TRACE = paths.BUILD / "vm-optrace.json"
# ---- pure core (unit-tested) -------------------------------------------------
def first_divergence(a: list, b: list) -> dict:
"""First index where offset sequences `a` (engine) and `b` (VM) differ.
Returns {"agreed": n, "index": i, "a": a[i]|None, "b": b[i]|None}, or
{"agreed": len, "index": None} when one is a prefix of the other / they are equal.
When they agree over the whole common prefix but differ in length, the shorter ended early: the
divergence index is that common length, with the missing side reported as None."""
n = min(len(a), len(b))
for i in range(n):
if a[i] != b[i]:
return {"agreed": i, "index": i, "a": a[i], "b": b[i]}
if len(a) == len(b):
return {"agreed": n, "index": None, "a": None, "b": None}
return {"agreed": n, "index": n,
"a": a[n] if n < len(a) else None,
"b": b[n] if n < len(b) else None}
def operand_filter(offsets: list, argc_by_off: dict) -> list:
"""Keep only offsets whose instruction has >=1 operand — the subsequence an OPERAND-mode engine
capture (vm_operand_fetch hook, deduped per-pc) can see. Zero-operand ops (stmt-begin/end and other
markers) never trigger an operand fetch, so they are absent from the engine trace; filtering the VM
trace the same way makes the two directly comparable. (Markers don't branch, so control flow is
preserved.) Offsets not in the map (shouldn't happen for a valid scene) are dropped."""
return [o for o in offsets if argc_by_off.get(o, 0) >= 1]
def align(a: list, b: list, window: int = 6) -> dict:
"""Resync-tolerant alignment of engine offsets `a` vs VM offsets `b`. Strict lockstep over-reports:
the operand-hook engine capture misses not just zero-operand ops but any op whose operands aren't
fetched via `vm_operand_fetch` (comment, set-string, the string-op cluster) — the engine *executes*
them (they sit after non-branching ops) but they're absent from `a`, so they appear as one-sided
insertions in `b`. On a mismatch we skip up to `window` such insertions on either side to realign;
only a mismatch that CANNOT be resynced within the window is a genuine control-flow fork.
Returns {hard, i, j, a, b, resyncs, skipped_b, skipped_a}: `hard=True` with the diverging engine/VM
offsets at indices i/j when a real fork is found; `hard=False` (one side exhausted) when `b` is a
clean resync-subsequence of `a` (the VM merely halted early). `skipped_b` = the blind-spot ops."""
i = j = 0
resyncs = 0
skipped_b, skipped_a = [], []
while i < len(a) and j < len(b):
if a[i] == b[j]:
i += 1
j += 1
continue
found = False
for s in range(1, window + 1):
if j + s < len(b) and a[i] == b[j + s]: # b had s extra (blind-spot) ops
skipped_b.extend(b[j:j + s]); j += s; resyncs += 1; found = True; break
if i + s < len(a) and a[i + s] == b[j]: # a had s extra ops
skipped_a.extend(a[i:i + s]); i += s; resyncs += 1; found = True; break
if not found:
return {"hard": True, "i": i, "j": j, "a": a[i], "b": b[j],
"resyncs": resyncs, "skipped_b": skipped_b, "skipped_a": skipped_a}
return {"hard": False, "i": i, "j": j,
"a": a[i] if i < len(a) else None, "b": b[j] if j < len(b) else None,
"resyncs": resyncs, "skipped_b": skipped_b, "skipped_a": skipped_a}
def pick_scene_codebase(entries: list, vm_offsets: list):
"""The codebase whose in-order offset sequence shares the longest common prefix with `vm_offsets`
— identifies which loaded-script instance in the engine trace is the scene we ran in the VM. Returns
the codebase id, or None if no codebase shares even one leading offset (nothing matches)."""
seqs, order = {}, []
for e in entries:
cb = e["codebase"]
if cb not in seqs:
seqs[cb] = []
order.append(cb)
seqs[cb].append(e["offset"])
def lcp(seq):
n = 0
for x, y in zip(seq, vm_offsets):
if x != y:
break
n += 1
return n
best, best_len = None, 0
for cb in order:
length = lcp(seqs[cb])
if length > best_len:
best, best_len = cb, length
return best
# ---- CLI: load traces, decode opcode context, report -------------------------
def _load_engine(path: Path) -> list:
entries = []
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
entries.append(json.loads(line))
return entries
def _load_scene_disasm(scene: str):
"""offset -> (opcode:int, rendered_line:str) for the scene, straight from sys4load (authoritative)."""
import sys4load
key = scene.upper()
if not key.endswith(".BIN"):
key += ".BIN"
scr = sys4load.load(paths.scripts()[key])
sys4load.decode_code(scr)
op_by_off = {ins.offset: ins.opcode for ins in scr.instructions}
argc_by_off = {ins.offset: len(ins.args) for ins in scr.instructions}
line_by_off = {}
for line in sys4load.render_listing(scr).splitlines():
m = re.match(r"\s*0x([0-9a-fA-F]+):\s*(.*)", line)
if m:
line_by_off[int(m.group(1), 16)] = m.group(2).rstrip()
return op_by_off, argc_by_off, line_by_off
def _fmt(off, op_by_off, line_by_off) -> str:
if off is None:
return "(none — trace ended)"
op = op_by_off.get(off)
line = line_by_off.get(off, "?")
return f"0x{off:05x}: {line}" + (f" [op 0x{op:x}]" if op is not None else " [op ?]")
def report(scene, entries, vm_offsets, operand_mode=True):
op_by_off, argc_by_off, line_by_off = _load_scene_disasm(scene)
# An operand-hook engine capture only sees ops with >=1 operand; filter the VM trace to match so the
# two are the same subsequence. --full turns this off (for a tick-mode engine capture, which sees all).
if operand_mode:
vm_offsets = operand_filter(vm_offsets, argc_by_off)
cb = pick_scene_codebase(entries, vm_offsets)
if cb is None:
print(f"[!] could not identify {scene}'s codebase in the engine trace "
f"({len(entries)} entries, {len({e['codebase'] for e in entries})} codebases) — "
f"no shared leading offset with the VM trace"
f"{' (operand-filtered)' if operand_mode else ''}.")
return 2
engine_seq = [e["offset"] for e in entries if e["codebase"] == cb]
r = align(engine_seq, vm_offsets)
print(f"=== differential offset-path oracle: {scene} ===")
print(f"engine codebase 0x{cb:x}: {len(engine_seq)} executed offsets "
f"(of {len(entries)} total across {len({e['codebase'] for e in entries})} codebases)")
print(f"VM: {len(vm_offsets)} executed offsets")
# Blind-spot summary: ops present in the VM trace that the operand hook can't see in the engine
# (the engine executes them, but their operands aren't fetched via vm_operand_fetch).
if r["skipped_b"]:
from collections import Counter
c = Counter(op_by_off.get(o) for o in r["skipped_b"])
blind = ", ".join(f"0x{o:x}×{n}" for o, n in c.most_common())
print(f"operand-hook blind spots (VM-only, artifacts — engine ran them but the hook can't see "
f"non-fetched operands): {len(r['skipped_b'])} ops, {r['resyncs']} resyncs — {blind}")
if r["skipped_a"]:
# engine ran these; the VM's path skipped them and then reconverged. NOT artifacts — small
# reconverging branch/state differences worth a look (e.g. a still-missing pre-scene flag).
print(f"engine-only detours (VM skipped, reconverged — potential residual state/branch gaps): "
f"{len(r['skipped_a'])}")
for o in r["skipped_a"]:
print(f" {_fmt(o, op_by_off, line_by_off)}")
if not r["hard"]:
tail = (" — but see the engine-only detours above" if r["skipped_a"] else
" ✓ every difference was an operand-hook blind spot")
print(f"\nNo NON-REALIGNABLE fork: the VM path matches the engine for all {r['j']} VM ops "
f"(then the VM halts early at wait-for-input){tail}.")
return 0
i, j = r["i"], r["j"]
print(f"\nAGREED to engine op {i} / VM op {j}, then DIVERGE:")
if i > 0:
print(f" last agreed op {_fmt(engine_seq[i - 1], op_by_off, line_by_off)}")
print(f" ^ this instruction's successor differs — the mis-modeled branch/op/state")
print(f" engine went -> {_fmt(r['a'], op_by_off, line_by_off)}")
print(f" VM went -> {_fmt(r['b'], op_by_off, line_by_off)}")
def ctx(seq, idx, label):
lo, hi = max(0, idx - 3), min(len(seq), idx + 4)
print(f"\n {label} context [{lo}..{hi}):")
for k in range(lo, hi):
mark = " <-- diverge" if k == idx else (" (last agreed)" if k == idx - 1 else "")
print(f" [{k}] {_fmt(seq[k], op_by_off, line_by_off)}{mark}")
ctx(engine_seq, i, "engine")
ctx(vm_offsets, j, "VM")
return 0
def main(argv=None):
argv = list(sys.argv[1:] if argv is None else argv)
scene = next((a for a in argv if not a.startswith("-")), "SC0000")
def opt(flag, default):
return argv[argv.index(flag) + 1] if flag in argv and argv.index(flag) + 1 < len(argv) else default
engine_path = Path(opt("--engine", ENGINE_TRACE))
vm_path = Path(opt("--vm", VM_TRACE))
if not engine_path.exists():
print(f"[!] engine trace not found: {engine_path}\n Capture it first: "
f"py -3.11 -u -X utf8 tools/frida/trace_engine_ops.py --hook tick")
return 1
if not vm_path.exists():
print(f"[!] VM trace not found: {vm_path}\n Produce it first: "
f"dotnet run --project engine/Age.Cli -- trace {scene}.BIN --boot --trace-json {vm_path}")
return 1
entries = _load_engine(engine_path)
vm_offsets = json.loads(vm_path.read_text(encoding="utf-8"))["offsets"]
return report(scene, entries, vm_offsets, operand_mode="--full" not in argv)
if __name__ == "__main__":
paths.add_self_to_syspath()
sys.exit(main())