plan: differential VM-vs-engine oracle (lever #3, spec + plan)
Control-flow offset-path diff: Frida engine op-tracer (recon-gated tick/0x41b940 hook) + VM ITraceSink offsets + diff_optrace.py first- divergence report. Deterministic opening (SC0000 --boot). Ready to execute in a fresh context; prereq = game running at the opening. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
186
docs/superpowers/plans/2026-07-09-differential-oracle.md
Normal file
186
docs/superpowers/plans/2026-07-09-differential-oracle.md
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
# Differential Oracle (control-flow) — Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Pinpoint mis-modeled opcodes by diffing the real engine's offset-execution path against our VM's, on the deterministic opening — output "agreed N steps, diverge at offset X = op 0xYY".
|
||||||
|
|
||||||
|
**Architecture:** A Frida engine op-tracer emits per-op `(codebase, offset)` to `build/engine-optrace.jsonl` (recon-gated capture). Our VM emits the SC0000 offset sequence to `build/vm-optrace.json`. `diff_optrace.py` identifies SC0000's codebase in the engine trace, aligns the two offset sequences, and reports the first divergence. The diff/parse logic is pure (unit-tested); Frida + VM emitter are exercised live.
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3.11 (Frida 17.x for the tracer, `tomllib`/json for the diff), `Age.Cli` (.NET 8) for the VM trace, ghidra-mcp only if we need to re-confirm a handler addr.
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-07-09-differential-oracle-design.md`.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Frida scripts: `py -3.11 -u -X utf8 tools/frida/<name>.py`; **read-only** (Interceptor + memory reads; no patching). Model on `dump_engine.py`/`probe_frame_cadence.py` (attach + `send()`/`on(message)`; frida-subdir tools compute `REPO = parents[2]`, no `paths.py`).
|
||||||
|
- **Known-safe vs risky hooks:** `vm_operand_fetch@0x41b940` is proven-safe (plain-JS, `probe_frame_cadence.py`). `adv_interpreter_tick@0x410fb0` crashed with a CModule hook — only ever plain-JS here, and Task 1 gates on it.
|
||||||
|
- Engine ctx offsets (now typed): `cur_ctx_index@0x53d14`, `frame_pc@0x53d2c`, `frame_codebase@0x53d28`; per-context stride `0x78`. `offset = (pc − codebase)/4`.
|
||||||
|
- The opening is `SC0000 --boot` (deterministic). `build/` is disposable/gitignored.
|
||||||
|
- Control-flow only (offset path); effects-diff is out of scope (phase 2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Engine op-tracer + recon gate
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tools/frida/trace_engine_ops.py`
|
||||||
|
- Reference: `tools/frida/probe_frame_cadence.py` (the proven 0x41b940 hook + attach boilerplate)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the tracer with a selectable hook + batched flush**
|
||||||
|
|
||||||
|
`trace_engine_ops.py`: attach to the running game; a `--hook tick|operand` flag (default `tick`). JS `Interceptor.attach`:
|
||||||
|
- `tick` mode → `adv_interpreter_tick` `0x410fb0`; `operand` mode → `vm_operand_fetch` `0x41b940`.
|
||||||
|
- onEnter: `ctx` = `this.context.ecx` (thiscall `ecx=ctx`); `curCtx = ctx.add(0x53d14).readU32()`; `pc = ctx.add(0x53d2c + curCtx*0x78).readU32()` (read via a computed pointer — recompute the address each call); `codebase = ctx.add(0x53d28 + curCtx*0x78).readU32()`; push `{cb: codebase, off: (pc-codebase)>>2}` to a JS buffer. In `operand` mode, skip if `pc === lastPc` (dedupe consecutive same-pc).
|
||||||
|
- Flush the buffer to Python via `send(batch)` every ~2000 entries; Python appends each `{codebase, offset}` to `build/engine-optrace.jsonl`.
|
||||||
|
Model attach/`on(message)` exactly on `probe_frame_cadence.py`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: RECON — capture the opening safely (THE GATE)**
|
||||||
|
|
||||||
|
Launch the game to the opening (or attach at the title and let the opening replay). Run:
|
||||||
|
`py -3.11 -u -X utf8 tools/frida/trace_engine_ops.py --hook tick`
|
||||||
|
Let the opening auto-advance to the first wait-for-input, then stop. Inspect `build/engine-optrace.jsonl`.
|
||||||
|
- **GATE — tick hook stable + trace captured:** proceed. Record entry count + number of distinct codebases.
|
||||||
|
- **GATE — tick hook destabilizes the game** (crash/hang): switch to `--hook operand` (proven-safe) and re-capture. Record which hook worked.
|
||||||
|
- **GATE — neither yields a usable trace:** STOP, document in `engine-re.md`, end the slice.
|
||||||
|
Expected: a jsonl with thousands of `(codebase, offset)` entries across a handful of codebases (boot scripts + SC0000).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Sanity-identify the SC0000 codebase**
|
||||||
|
|
||||||
|
Quick check (inline Python): group entries by `codebase`; for each, does its offset-0/low-offset region's static opcode (from `build/disasm/SC0000.asm` via `sys4load`) match SC0000's opening? Report the codebase whose executed offsets are consistent with SC0000 (largest plausible run). This de-risks Task 3's codebase identification.
|
||||||
|
Expected: one codebase identifiable as SC0000.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit the tracer**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add tools/frida/trace_engine_ops.py
|
||||||
|
git commit -m "re(frida): engine op-path tracer (tick/operand hook, per-script offsets)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: VM offset-trace emitter
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify (if needed): `Age.Cli` trace command (a `--trace-json <path>` that dumps executed SC0000 offsets), or reuse an existing offset-trace artifact.
|
||||||
|
- Test: `engine/Age.Engine.Tests/` (a small test for the JSON emitter if one is added).
|
||||||
|
|
||||||
|
- [ ] **Step 1: Check for an existing full offset trace**
|
||||||
|
|
||||||
|
Inspect how `Age.Cli trace --trace` / the `vm0-trace.json` selftest artifact are produced (`grep` `Age.Cli`, `ITraceSink`, `vm0-trace`). If a full ordered-offset JSON for a booted scene already exists or is trivially emittable, use it and skip to Step 3.
|
||||||
|
Expected: a decision — reuse vs add a small emitter.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add a minimal `--trace-json` emitter (if needed)**
|
||||||
|
|
||||||
|
Add a `JsonOffsetTraceSink : ITraceSink` (or a post-run collector on the existing sink) that records each Step's script-relative offset in order, and a `--trace-json <path>` option on the trace command writing `{"scene":"SC0000","offsets":[...]}`. Keep it observe-only (parity preserved — non-JSON runs unaffected). Add one xUnit test: running a tiny synthetic scene yields the expected offset list. `dotnet test` green.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Produce the VM trace for SC0000 (booted, to match the engine)**
|
||||||
|
|
||||||
|
Run: `dotnet run --project engine/Age.Cli -- trace SC0000.BIN --boot --trace-json build/vm-optrace.json` (exact invocation per the CLI). Confirm the offset count is on the order of the engine trace's SC0000 run.
|
||||||
|
Expected: `build/vm-optrace.json` with the ordered SC0000 offsets.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit (if a C# change was made)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add engine/
|
||||||
|
git commit -m "feat(engine): --trace-json emitter for the differential oracle"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: `diff_optrace.py` — align + first-divergence
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tools/diff_optrace.py`
|
||||||
|
- Create: `tools/test_diff_optrace.py`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- `first_divergence(a: list[int], b: list[int]) -> dict` → `{"agreed": n, "index": i, "a": a[i]|None, "b": b[i]|None}` or `{"agreed": len, "index": None}` if one is a prefix of the other / equal.
|
||||||
|
- `pick_scene_codebase(entries: list[dict], vm_offsets: list[int]) -> int|None` → the codebase whose offset sequence shares the longest common prefix with `vm_offsets` (identifies SC0000's instance).
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing tests**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# tools/test_diff_optrace.py (plain runner)
|
||||||
|
import sys
|
||||||
|
from diff_optrace import first_divergence, pick_scene_codebase
|
||||||
|
FAILS=[]
|
||||||
|
def check(c,m): (FAILS.append(m) or print("FAIL:",m)) if not c else print("ok:",m)
|
||||||
|
|
||||||
|
def test_equal_no_divergence():
|
||||||
|
r = first_divergence([0,1,2,3],[0,1,2,3])
|
||||||
|
check(r["index"] is None and r["agreed"]==4, "equal traces -> no divergence")
|
||||||
|
|
||||||
|
def test_first_divergence_point():
|
||||||
|
r = first_divergence([0,1,2,9],[0,1,2,3])
|
||||||
|
check(r["index"]==3 and r["a"]==9 and r["b"]==3, "divergence at first differing offset")
|
||||||
|
|
||||||
|
def test_prefix_shorter_vm():
|
||||||
|
r = first_divergence([0,1,2,3],[0,1]) # vm ends early
|
||||||
|
check(r["index"]==2 and r["b"] is None and r["agreed"]==2, "shorter VM trace flagged at end")
|
||||||
|
|
||||||
|
def test_pick_codebase_by_longest_common_prefix():
|
||||||
|
entries=[{"codebase":100,"offset":0},{"codebase":100,"offset":5}, # cb100: [0,5,...]
|
||||||
|
{"codebase":200,"offset":0},{"codebase":200,"offset":1},{"codebase":200,"offset":2}]
|
||||||
|
check(pick_scene_codebase(entries,[0,1,2])==200, "codebase matching VM prefix chosen")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
test_equal_no_divergence(); test_first_divergence_point(); test_prefix_shorter_vm()
|
||||||
|
test_pick_codebase_by_longest_common_prefix()
|
||||||
|
print("FAILURES:",len(FAILS)); return 1 if FAILS else 0
|
||||||
|
if __name__=="__main__": sys.exit(main())
|
||||||
|
```
|
||||||
|
Run → FAIL (`ModuleNotFoundError`).
|
||||||
|
|
||||||
|
- [ ] **Step 2: Implement `diff_optrace.py`**
|
||||||
|
|
||||||
|
Pure functions `first_divergence` + `pick_scene_codebase` (per the tests), plus a CLI: load `build/engine-optrace.jsonl` (list of `{codebase, offset}` in order) and `build/vm-optrace.json` (`{"offsets":[...]}`); `cb = pick_scene_codebase(entries, vm_offsets)`; extract the engine offset sequence for `cb`; `d = first_divergence(engine_seq, vm_offsets)`; if diverged, print the offset, the **opcode at that offset** (decode `build/disasm/SC0000.asm` or call `sys4load`), and ±3 ops of context on each side. Run tests → PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run the oracle end-to-end**
|
||||||
|
|
||||||
|
Run: `py -3.11 -X utf8 tools/diff_optrace.py SC0000`
|
||||||
|
Expected: either "no divergence over N steps" or "agreed N, diverge at offset X = op 0xYY" with context. **Interpret the result:** if it lands on the known coroutine yield (`0x140`/`0x7b`, `~0x450–0x50f`), that confirms the tool pinpoints a real known gap (the self-test). Record the finding.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add tools/diff_optrace.py tools/test_diff_optrace.py
|
||||||
|
git commit -m "re: diff_optrace.py — engine-vs-VM offset-path divergence oracle"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: Docs + memory + close
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/engine-re.md` (the oracle: mechanism, capture method that worked, the first divergence found)
|
||||||
|
- Modify: `docs/tools-reference.md` (`trace_engine_ops.py`, `diff_optrace.py`, `--trace-json`)
|
||||||
|
- Modify: `~/.claude/…/memory/himegari-port-status.md` (milestone; lever #3 done; phase-2 effects-diff next)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update engine-re.md**
|
||||||
|
|
||||||
|
Record: the differential-oracle mechanism (offset-path diff), which capture hook worked (tick vs 0x41b940), the SC0000 codebase identification, and the first divergence the oracle reported (offset + op + interpretation). Note it as the repeatable way to localize a mis-modeled op.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update tools-reference.md**
|
||||||
|
|
||||||
|
Rows for `trace_engine_ops.py` (Runtime capture / Frida) and `diff_optrace.py` (Native engine RE or a new "Validation" group); note the `--trace-json` VM emitter.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update the status memory**
|
||||||
|
|
||||||
|
Record: differential oracle DONE — capture method, first divergence found, tool names; lever #3 of 3 complete; phase-2 (effects-diff: global/gfx writes) is the next extension.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit docs**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add docs/engine-re.md docs/tools-reference.md
|
||||||
|
git commit -m "re: record differential oracle outcome + first divergence"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Review
|
||||||
|
|
||||||
|
**Spec coverage:** engine tracer + recon gate → Task 1; per-script codebase tagging → Task 1 Step 3 + Task 3 `pick_scene_codebase`; VM offset trace → Task 2; diff + first-divergence + opcode context → Task 3; coroutine-gap self-test → Task 3 Step 3; docs/memory → Task 4. Covered. (The spec's loader-hook codebase-tagging is simplified to post-hoc `pick_scene_codebase` matching — noted; a loader hook can be added later if identification is ambiguous.)
|
||||||
|
|
||||||
|
**Placeholder scan:** Task 1/Task 2 describe the Frida/C# bodies to the key calls (hook target, ctx reads, `--trace-json`) rather than full source, and flag "model on `probe_frame_cadence.py`" / "reuse if exists" — intentional (live-API bring-up like the earlier Frida/Ghidra tasks). Task 3 (the pure, deterministic core) is complete code + tests. No hidden TODOs.
|
||||||
|
|
||||||
|
**Type consistency:** engine trace = list of `{codebase:int, offset:int}`; VM trace = `{"offsets":[int]}`; `pick_scene_codebase(entries, vm_offsets)->int`; `first_divergence(list[int],list[int])->{agreed,index,a,b}` — consumed consistently by the Task 3 CLI. Matches the tests.
|
||||||
102
docs/superpowers/specs/2026-07-09-differential-oracle-design.md
Normal file
102
docs/superpowers/specs/2026-07-09-differential-oracle-design.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Differential VM-vs-engine oracle (control-flow) — design
|
||||||
|
|
||||||
|
**Date:** 2026-07-09
|
||||||
|
**Status:** approved (design), plan pending
|
||||||
|
**Lever:** #3 of 3 in the RE-front-loading program (after handler-labeling, import-map, ctx-struct,
|
||||||
|
hot-helper naming). The high-value anti-walk-back tool. See the status memory.
|
||||||
|
**Home in the canonical map:** results in `docs/engine-re.md`; new tools in `docs/tools-reference.md`.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
The expensive RE churn this project keeps hitting is **wrong opcode *semantics*, believed then
|
||||||
|
retracted** — the `0x215` state-vs-command-buffer flip, "immediate-mode slot-0," "the opening is
|
||||||
|
sleep-paced." Every one was a claim about runtime behavior assumed from docs and built on before being
|
||||||
|
verified against the running engine. The first three levers made the *static* image far more legible;
|
||||||
|
this lever closes the loop by making *runtime truth* cheap to diff, so a semantics claim can be checked
|
||||||
|
against the real engine in minutes instead of hypothesized.
|
||||||
|
|
||||||
|
**Method:** run the same scene in the real engine (Frida trace) and our C# VM (`ITraceSink` trace) and
|
||||||
|
diff the **offset-execution path**. The first divergence is exactly the opcode/branch we modeled wrong.
|
||||||
|
|
||||||
|
**Key simplification:** both run the *same bytecode*, so the opcode at each script offset is *static*
|
||||||
|
(known from disassembly). We therefore diff the **sequence of script offsets executed** (control flow) —
|
||||||
|
not opcodes or effects. A path divergence = a branch (`jcc`) or opcode-length/semantics we got wrong.
|
||||||
|
Cheaper and higher-signal than capturing per-op effects, and it is precisely where the render-drift
|
||||||
|
walk-backs lived.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Engine trace (Frida) + VM trace (exists) → offset-path diff.
|
||||||
|
|
||||||
|
### 1. `tools/frida/trace_engine_ops.py` — engine op-tracer (read-only)
|
||||||
|
|
||||||
|
- Attach to the running game (like `dump_engine.py`/`probe_frame_cadence.py`).
|
||||||
|
- **Per executed op**, read from the engine `ctx`: `curCtx = ctx->cur_ctx_index` (`+0x53d14`),
|
||||||
|
`pc = *(ctx->frame_pc + curCtx*0x78)` (`+0x53d2c`), `codebase = *(ctx->frame_codebase + curCtx*0x78)`
|
||||||
|
(`+0x53d28`); emit `(codebase, offset = (pc − codebase)/4)`.
|
||||||
|
- **Loader hook** on the script loader (`call-script` handler `op_0x3_handler`@`0x41bc90` → loader
|
||||||
|
`FUN_0040e980`) records `codebase → script-id` as scripts load (id→name via `parse_sys4ini`'s
|
||||||
|
`callscript-names.json`), so the trace yields clean **per-script** offset streams (isolating SC0000
|
||||||
|
from boot/system scripts and coroutine interleavings).
|
||||||
|
- Capture the opening window: attach, let the deterministic opening auto-advance to the first
|
||||||
|
`wait-for-input`. Buffer entries in JS (array/ring), flush to `build/engine-optrace.jsonl`
|
||||||
|
(`{codebase, script, offset}` per op, in execution order).
|
||||||
|
- **Capture mechanism (recon-gated, Task 1):** primary = plain-JS `Interceptor.attach` on
|
||||||
|
`adv_interpreter_tick@0x410fb0` (clean one-op-per-tick signal). CModule on this path crashed before
|
||||||
|
(status memory); **plain-JS is untested here** → Task 1 proves it or falls back. Fallback = the
|
||||||
|
proven-safe `vm_operand_fetch@0x41b940` hook (`probe_frame_cadence.py` already uses it), reading the
|
||||||
|
same ctx fields and **deduping consecutive same-pc** (fires per-operand; misses zero-operand no-ops —
|
||||||
|
acceptable for control-flow since markers/no-ops don't branch).
|
||||||
|
|
||||||
|
### 2. VM op-trace (mostly exists — small emitter)
|
||||||
|
|
||||||
|
Our VM already emits the Step stream via `ITraceSink` (`Age.Cli trace`; byte-identical to `vm0.py`,
|
||||||
|
SC0000 ≈ 27,994 steps). Add a machine-readable emitter if not present: `Age.Cli trace SC0000.BIN
|
||||||
|
--boot --trace-json build/vm-optrace.json` → the ordered list of executed script-relative offsets for
|
||||||
|
SC0000. (If a suitable JSON trace already exists — e.g. the `vm0-trace.json` selftest artifact — reuse
|
||||||
|
its format.)
|
||||||
|
|
||||||
|
### 3. `tools/diff_optrace.py` — the diff
|
||||||
|
|
||||||
|
- Inputs: `build/engine-optrace.jsonl` + `build/vm-optrace.json` + the scene name (SC0000).
|
||||||
|
- Filter the engine trace to SC0000's script → engine offset sequence `E`. VM offset sequence `V`.
|
||||||
|
- Walk `E` and `V` in lockstep; report the **first index where they differ** (or where one ends early):
|
||||||
|
the diverging offset, the **opcode at that offset** (from `build/disasm/SC0000.asm` / `sys4load`), and
|
||||||
|
a few ops of surrounding context on each side. Also report how many steps agreed before divergence.
|
||||||
|
- Output: a concise divergence report to stdout (+ optional JSON).
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
running engine ─(Frida tick/operand hook + loader hook)─▶ `build/engine-optrace.jsonl`
|
||||||
|
`Age.Cli trace SC0000 --boot --trace-json` ─▶ `build/vm-optrace.json`
|
||||||
|
both + disasm ─(`diff_optrace.py`)─▶ "agreed N steps, diverge at offset X = op 0xYY (context …)".
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- **Self-test (expected):** our VM does not model the scene-coroutine framework (known gap), so the
|
||||||
|
oracle should pinpoint divergence at/near the coroutine yield (`0x140`/`0x7b`, SC0000 `~0x450–0x50f`)
|
||||||
|
— confirming that gap surgically. A divergence that lands on a *known* gap validates the tool.
|
||||||
|
- The engine trace length/shape for SC0000 is plausible (starts near offset 0, comparable magnitude to
|
||||||
|
the VM's opening run); the loader hook correctly tags the SC0000 codebase.
|
||||||
|
- `diff_optrace.py` on identical inputs reports "no divergence" (a trivial equal-traces unit test).
|
||||||
|
|
||||||
|
## Scope & boundaries
|
||||||
|
|
||||||
|
- **In:** control-flow (offset-path) diff on the deterministic opening (`SC0000 --boot`); read-only Frida
|
||||||
|
capture; the diff tool + report.
|
||||||
|
- **Out (phase 2+):** effects-diff (global-bank / gfx-registry writes — needs hooking engine write
|
||||||
|
paths); branchy/input-driven scenes (need matched input + state); auto-classifying the divergence
|
||||||
|
(we report it; the human/next-slice reverses it). No engine patching.
|
||||||
|
- **Regenerable:** both traces + any report live under `build/` (disposable).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- Task 1 recon: a clean SC0000 engine op-path captured safely (tick-hook or 0x41b940 fallback); the
|
||||||
|
capture method + trace length recorded; if neither is safe, an explicit documented stop.
|
||||||
|
- `build/engine-optrace.jsonl` (per-script tagged) + `build/vm-optrace.json` produced for SC0000.
|
||||||
|
- `diff_optrace.py SC0000` runs and reports either agreement or a first-divergence (offset + opcode +
|
||||||
|
context); the equal-traces unit test passes.
|
||||||
|
- The first real divergence is explainable (ideally the known coroutine gap), demonstrating the tool
|
||||||
|
pinpoints a mis-modeled op.
|
||||||
|
- `docs/engine-re.md` + `docs/tools-reference.md` updated; status memory records the milestone (lever #3
|
||||||
|
done; note phase-2 effects-diff as the next extension).
|
||||||
Reference in New Issue
Block a user