Source changes (dormant parity infra, retained from iterate 2.AI/2.AO): - xenia-kernel/exports.rs: nt_create_event manual_reset polarity + related event wiring - xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps (.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as regenerable local artifacts — see memory + HANDOFF for the running findings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
483 lines
23 KiB
Python
483 lines
23 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Iterate 2.D fire-pattern diff.
|
||
|
||
Goal: for every NtSetEvent / NtReleaseSemaphore fire in canary and ours,
|
||
build a (op_category, lr, target_handle) histogram and find tuples that
|
||
fire in canary but never in ours, then map those LRs back to fn entries
|
||
and check intersection with the AUDIT-069 γ-signaler family.
|
||
|
||
Reading-error #28 discipline: never compare tids by integer cross-engine.
|
||
The keying tuple deliberately omits tid. (We track which tid fires
|
||
each tuple in ours to detect any thread, per the goal.)
|
||
"""
|
||
import json, os, collections, sys
|
||
|
||
BASE = "/home/fabi/RE - Project Sylpheed/xenia-rs/audit-runs/iterate-2D-peer-producer-trace"
|
||
CANARY = os.path.join(BASE, "canary-peer-producers.jsonl")
|
||
OURS_IAT = os.path.join(BASE, "ours-i2d-iat-trace.jsonl")
|
||
OURS_LR = os.path.join(BASE, "ours-i2d-lr-trace.jsonl")
|
||
|
||
OUT = "/home/fabi/RE - Project Sylpheed/xenia-rs/audit-runs/iterate-2D-fire-pattern-diff"
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Canary trace schema (audit_69 + audit_70):
|
||
# {"op": "release"|"set", "fn": str, "handle": int, "count":..,
|
||
# "lr": int, "tid": int, "host_ns": int}
|
||
# We normalise op_category from fn:
|
||
# NtSetEvent | XEvent::Set | KeSetEvent -> "set"
|
||
# NtReleaseSemaphore | xeKeReleaseSemaphore -> "release"
|
||
# ----------------------------------------------------------------------
|
||
def canary_op_category(rec):
|
||
fn = rec.get("fn", "")
|
||
if "Release" in fn or "release" in fn:
|
||
return "release"
|
||
if "Set" in fn or "set" in fn:
|
||
return "set"
|
||
return rec.get("op", "?")
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Ours IAT trace schema:
|
||
# {"pc": "0x8284dddc", "tid": int, "hw": int, "cycle": int,
|
||
# "r3": "0x0000104c", "r4": ..., ..., "lr": "0x..."}
|
||
# Map PC -> op_category. PCs are IAT thunks documented in investigation.md:
|
||
# 0x8284dddc -> KeSetEvent => set
|
||
# 0x8284e49c -> KeReleaseSemaphore => release
|
||
# 0x8284df5c -> NtSetEvent => set
|
||
# 0x8284e07c -> NtReleaseSemaphore => release
|
||
# Ours --lr-trace schema (game-wrapper PCs):
|
||
# 0x824AA2F0 -> NtSetEvent wrapper => set
|
||
# 0x824AB158 -> NtReleaseSemaphore wrapper => release
|
||
# ----------------------------------------------------------------------
|
||
OURS_IAT_OP = {
|
||
0x8284dddc: "set", # KeSetEvent IAT thunk
|
||
0x8284e49c: "release", # KeReleaseSemaphore IAT thunk
|
||
0x8284df5c: "set", # NtSetEvent IAT thunk
|
||
0x8284e07c: "release", # NtReleaseSemaphore IAT thunk
|
||
0x824AA2F0: "set", # game wrapper
|
||
0x824AB158: "release", # game wrapper
|
||
}
|
||
|
||
def hex32(v):
|
||
return f"0x{v & 0xFFFFFFFF:08x}"
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Load canary
|
||
# ----------------------------------------------------------------------
|
||
canary_events = []
|
||
with open(CANARY) as f:
|
||
for line in f:
|
||
try:
|
||
canary_events.append(json.loads(line))
|
||
except Exception as e:
|
||
pass
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Load ours (prefer IAT trace per investigation.md auth note; we use both
|
||
# only for cross-check)
|
||
# ----------------------------------------------------------------------
|
||
def load_ours(path):
|
||
evs = []
|
||
with open(path) as f:
|
||
for line in f:
|
||
try:
|
||
r = json.loads(line)
|
||
r["pc_int"] = int(r["pc"], 16)
|
||
r["lr_int"] = int(r["lr"], 16)
|
||
r["r3_int"] = int(r["r3"], 16)
|
||
evs.append(r)
|
||
except Exception:
|
||
pass
|
||
return evs
|
||
|
||
ours_iat = load_ours(OURS_IAT)
|
||
ours_lr = load_ours(OURS_LR)
|
||
|
||
print(f"loaded canary={len(canary_events)} ours-iat={len(ours_iat)} ours-lr={len(ours_lr)}")
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Per-engine entry_pc per tid (used for tid mapping, NOT cross-engine
|
||
# integer comparison). We don't have entry_pc directly in the trace, but
|
||
# we can use a documented map from investigation.md:
|
||
# canary 6 ↔ ours 1 (main), canary 10 ↔ ours 5 (worker),
|
||
# canary 17 ↔ ours 13 (cache), audio threads unstable.
|
||
# For the diff we DO NOT use this map — we key purely on (op, lr, handle)
|
||
# and let the cross-engine equivalence emerge from same source-code LRs.
|
||
# ----------------------------------------------------------------------
|
||
|
||
# ----------------------------------------------------------------------
|
||
# CANARY histogram by (op, lr, handle)
|
||
# ----------------------------------------------------------------------
|
||
canary_hist = collections.Counter()
|
||
canary_tids = collections.defaultdict(set) # key -> set of canary tids
|
||
canary_first_ns = {} # key -> first host_ns
|
||
canary_handles_by_key = collections.defaultdict(set)
|
||
for ev in canary_events:
|
||
op = canary_op_category(ev)
|
||
lr = ev.get("lr", 0)
|
||
h = ev.get("handle", 0)
|
||
key = (op, lr, h)
|
||
canary_hist[key] += 1
|
||
canary_tids[key].add(ev.get("tid"))
|
||
ns = ev.get("host_ns", 0)
|
||
if key not in canary_first_ns or ns < canary_first_ns[key]:
|
||
canary_first_ns[key] = ns
|
||
|
||
# Also key by (op, lr) ignoring handle (for cross-engine matching since
|
||
# handle namespaces differ).
|
||
canary_oplr = collections.Counter()
|
||
canary_oplr_tids = collections.defaultdict(set)
|
||
canary_oplr_handles = collections.defaultdict(set)
|
||
for ev in canary_events:
|
||
op = canary_op_category(ev)
|
||
lr = ev.get("lr", 0)
|
||
canary_oplr[(op, lr)] += 1
|
||
canary_oplr_tids[(op, lr)].add(ev.get("tid"))
|
||
canary_oplr_handles[(op, lr)].add(ev.get("handle"))
|
||
|
||
# ----------------------------------------------------------------------
|
||
# OURS histogram by (op, lr) — handle excluded for cross-engine
|
||
# matching (namespaces differ)
|
||
# ----------------------------------------------------------------------
|
||
ours_oplr = collections.Counter()
|
||
ours_oplr_tids = collections.defaultdict(set)
|
||
ours_oplr_handles = collections.defaultdict(set)
|
||
ours_oplr_pcs = collections.defaultdict(set) # which PC (IAT thunk) caught it
|
||
|
||
# Merge both IAT and LR traces (handles direct calls + wrapper calls)
|
||
def add_ours_event(ev, src):
|
||
pc = ev["pc_int"]
|
||
if pc in OURS_IAT_OP:
|
||
op = OURS_IAT_OP[pc]
|
||
else:
|
||
# Other PCs (e.g. ours-i2d-lr-trace.jsonl had 0x824ab158 / 0x824AA2F0
|
||
# which we already include) — fall through.
|
||
return
|
||
lr = ev["lr_int"]
|
||
key = (op, lr)
|
||
ours_oplr[key] += 1
|
||
ours_oplr_tids[key].add(ev.get("tid"))
|
||
ours_oplr_handles[key].add(ev["r3_int"])
|
||
ours_oplr_pcs[key].add(pc)
|
||
|
||
for ev in ours_iat:
|
||
add_ours_event(ev, "iat")
|
||
for ev in ours_lr:
|
||
add_ours_event(ev, "lr")
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Categorize canary LRs vs ours LRs
|
||
# ----------------------------------------------------------------------
|
||
canary_lrs = set(canary_oplr.keys())
|
||
ours_lrs = set(ours_oplr.keys())
|
||
|
||
# LRs present in canary, NOT in ours
|
||
missing_in_ours = canary_lrs - ours_lrs
|
||
# LRs in both
|
||
matched_lrs = canary_lrs & ours_lrs
|
||
# LRs in ours but not canary (sanity — should be tiny)
|
||
extra_in_ours = ours_lrs - canary_lrs
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Compute counts
|
||
# ----------------------------------------------------------------------
|
||
total_canary = sum(canary_oplr.values())
|
||
total_ours = sum(ours_oplr.values())
|
||
missing_canary_count = sum(canary_oplr[k] for k in missing_in_ours)
|
||
matched_canary_count = sum(canary_oplr[k] for k in matched_lrs)
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Top divergent tuples — by canary count, ours==0
|
||
# ----------------------------------------------------------------------
|
||
divergent = sorted(missing_in_ours, key=lambda k: -canary_oplr[k])
|
||
|
||
# Under-firing (matched but ratio < 0.5)
|
||
under_firing = []
|
||
for k in matched_lrs:
|
||
c = canary_oplr[k]; o = ours_oplr[k]
|
||
if c > 0 and o / max(c,1) < 0.5 and c >= 10:
|
||
under_firing.append((k, c, o))
|
||
under_firing.sort(key=lambda x: -x[1])
|
||
|
||
# ----------------------------------------------------------------------
|
||
# AUDIT-069 γ-signaler family LRs (from session-3 / session-2 memory)
|
||
# Per AUDIT-069 S3 the γ-signaler family: ours tid=5 fired NtSetEvent
|
||
# from LRs 0x824AA2F0 (NtSetEvent wrapper) and 0x824AAF50 (KeSetEvent
|
||
# wrapper internal). The session also called out 0x82450A28/0x82450A68
|
||
# as dispatch-loop PCs. Canary's γ-signal LRs include the worker
|
||
# dispatch family 0x82506B08/0x82506DE8/0x82508400/0x825078D8.
|
||
# ----------------------------------------------------------------------
|
||
GAMMA_LRS = {
|
||
0x824AA304, 0x824AAFC8, 0x824AB168, # wrapper internals (Nt/Ke release)
|
||
0x82506C90, 0x82506F9C, 0x82508510, 0x82508524, 0x82508358, # worker dispatch
|
||
0x82450D2C, 0x82450CE0, 0x82450314, # worker self-release
|
||
0x824D229C, 0x824D2A44, 0x824D292C, # audio dispatch
|
||
}
|
||
|
||
# Cache-thread wedge family LRs (per AUDIT-069 S5/S6 the cache thread
|
||
# wedges at sub_821CB030+0x1AC, but its release LR is 0x82450314 from
|
||
# sub_82450218). The wait array contains the work-semaphore at guest VA
|
||
# [0x828F3BC4] = ours handle 0x1050 (per project memory).
|
||
WEDGE_RELATED_LRS = {0x82450314, 0x82450D2C, 0x82450CE0}
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Map LR to nearest fn entry (use existing canary log knowledge / heuristic
|
||
# by mapping high bits)
|
||
# ----------------------------------------------------------------------
|
||
LR_FN_NOTES = {
|
||
0x82506C90: "sub_82506B08+0x188 (worker dispatch γ-set)",
|
||
0x82506F9C: "sub_82506DE8 (worker dispatch γ-set)",
|
||
0x82508510: "sub_82508400+0x110 (worker dispatch γ-set)",
|
||
0x82508524: "sub_82508400+0x124 (worker dispatch γ-set)",
|
||
0x82508358: "sub_825078D8+0xa80 (worker dispatch γ-set)",
|
||
0x824D229C: "sub_824D21F0+0xAC (AUDIO dispatch γ-release)",
|
||
0x824D2A44: "sub_824D29F0 (AUDIO worker entry γ-set)",
|
||
0x824D292C: "sub_824D2878 (AUDIO worker entry-2 γ-set)",
|
||
0x824AA304: "sub_824AA2F0 (NtSetEvent game wrapper)",
|
||
0x824AAFC8: "sub_824AAF50 (KeSetEvent game wrapper)",
|
||
0x824AB168: "sub_824AB158 (NtReleaseSemaphore game wrapper)",
|
||
0x82450D2C: "sub_82450B68+0x1C4 (worker self-release path 2)",
|
||
0x82450CE0: "sub_82450B68+0x178 (worker self-release path 1)",
|
||
0x82450314: "sub_82450218+0xFC (cache-thread release site)",
|
||
}
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Write the report
|
||
# ----------------------------------------------------------------------
|
||
lines = []
|
||
def w(s=""):
|
||
lines.append(s)
|
||
|
||
w("# Iterate 2.D fire-pattern diff — report")
|
||
w()
|
||
w(f"**Date**: 2026-05-27. **Mode**: read-only re-analysis of cached iterate-2D-peer-producer-trace JSONLs. Zero LOC engine/canary changes.")
|
||
w()
|
||
w("## Headline")
|
||
w()
|
||
|
||
# Decision logic
|
||
unique_missing = len(missing_in_ours)
|
||
if unique_missing == 0:
|
||
headline = "SAME-FIRES-ALL-TIDS — every canary (op,lr) tuple has at least one ours analog."
|
||
elif unique_missing > 0 and missing_canary_count > 100:
|
||
headline = ("DIVERGENT-FIRE-PATTERN-FOUND — multiple distinct producer LRs "
|
||
"fire in canary with ZERO ours analog across ALL tids.")
|
||
else:
|
||
headline = "INCONCLUSIVE — partial overlap, marginal gaps."
|
||
|
||
w(f"**{headline}**")
|
||
w()
|
||
w(f"Canary total NtSetEvent+NtReleaseSemaphore fires: **{total_canary:,}** across **{len(canary_lrs)}** distinct (op,lr) tuples.")
|
||
w(f"Ours total (IAT + LR thunk trace): **{total_ours:,}** across **{len(ours_lrs)}** distinct (op,lr) tuples.")
|
||
w(f"Tuples in canary with **zero** ours analog: **{unique_missing}** carrying **{missing_canary_count:,}** canary fires "
|
||
f"({100.0 * missing_canary_count / max(total_canary,1):.1f}% of canary's volume).")
|
||
w(f"Matched tuples: **{len(matched_lrs)}** carrying **{matched_canary_count:,}** canary fires.")
|
||
w(f"Extra-in-ours tuples (not in canary): **{len(extra_in_ours)}** (sanity tally only).")
|
||
w()
|
||
w("## Reading-error #28 discipline")
|
||
w()
|
||
w("Diff key omits tid — we ask 'does this canary (op, lr, handle-class) fire at all in ours, on ANY tid'. Tids are tracked separately per key for context but never used as cross-engine identity.")
|
||
w()
|
||
|
||
w("## Top divergent tuples (canary fires N, ours fires 0)")
|
||
w()
|
||
w("| # | op | LR | canary fires | canary tids | canary handles | likely fn |")
|
||
w("|--:|----|----|--:|--|--|---|")
|
||
for i, k in enumerate(divergent[:15], 1):
|
||
op, lr = k
|
||
handles = sorted(canary_oplr_handles[k])
|
||
handles_short = ", ".join(hex32(h) for h in handles[:3])
|
||
if len(handles) > 3:
|
||
handles_short += f" (+{len(handles)-3})"
|
||
tids = sorted(canary_oplr_tids[k])
|
||
tids_short = ",".join(str(t) for t in tids[:6])
|
||
if len(tids) > 6:
|
||
tids_short += f" (+{len(tids)-6})"
|
||
fn = LR_FN_NOTES.get(lr, "(unknown)")
|
||
w(f"| {i} | {op} | `{hex32(lr)}` | {canary_oplr[k]:,} | {tids_short} | {handles_short} | {fn} |")
|
||
w()
|
||
|
||
w("## Top under-firing matched tuples (canary >>> ours)")
|
||
w()
|
||
w("| op | LR | canary | ours | ratio | likely fn |")
|
||
w("|----|----|--:|--:|--:|---|")
|
||
for (k, c, o) in under_firing[:10]:
|
||
op, lr = k
|
||
fn = LR_FN_NOTES.get(lr, "(unknown)")
|
||
ratio = f"{100.0*o/c:.2f}%"
|
||
w(f"| {op} | `{hex32(lr)}` | {c:,} | {o:,} | {ratio} | {fn} |")
|
||
w()
|
||
|
||
w("## γ-signaler family intersection (AUDIT-069 S3/S2)")
|
||
w()
|
||
gamma_intersection = []
|
||
for lr in sorted(set(lr for (op, lr) in canary_lrs) & GAMMA_LRS):
|
||
rows = []
|
||
for op in ("set","release"):
|
||
k = (op, lr)
|
||
if k in canary_oplr:
|
||
rows.append((op, canary_oplr[k], ours_oplr.get(k,0)))
|
||
if rows:
|
||
gamma_intersection.append((lr, rows))
|
||
|
||
w("| LR | op | canary | ours | likely fn |")
|
||
w("|----|----|--:|--:|---|")
|
||
for (lr, rows) in gamma_intersection:
|
||
fn = LR_FN_NOTES.get(lr, "(unknown)")
|
||
for (op, c, o) in rows:
|
||
w(f"| `{hex32(lr)}` | {op} | {c:,} | {o:,} | {fn} |")
|
||
w()
|
||
in_gamma = sum(1 for lr in (lr for (op,lr) in missing_in_ours) if lr in GAMMA_LRS)
|
||
out_of_gamma = sum(1 for lr in (lr for (op,lr) in missing_in_ours) if lr not in GAMMA_LRS)
|
||
w(f"Of {unique_missing} missing-in-ours tuples: **{in_gamma}** intersect the AUDIT-069 γ-signaler family, **{out_of_gamma}** lie outside it (fresh chains).")
|
||
w()
|
||
|
||
w("## Wedge-related LRs (cache-thread / worker-dispatch self-release)")
|
||
w()
|
||
w("These LRs are the deep game-side call sites that route through `sub_824AB158` (NtReleaseSemaphore wrapper) and ultimately feed the wedge's wait predicate (work-semaphore handle 0x1050 at guest VA [0x828F3BC4]). **Note: canary's audit_70 hook fires at the IAT-thunk depth and reports the wrapper-return LR (`0x824AB168`) for ALL NtReleaseSemaphore fires** — it never sees deeper game-side LRs. Ours's `--lr-trace=0x824AB158` probe captures one level deeper (the game-wrapper caller). So canary count here is always 0; the value of this table is **ours's count** showing which of these game-side sites still execute at all in ours:")
|
||
w()
|
||
w("| LR | op | canary | ours | likely fn |")
|
||
w("|----|----|--:|--:|---|")
|
||
for lr in sorted(WEDGE_RELATED_LRS):
|
||
for op in ("set","release"):
|
||
k = (op, lr)
|
||
# show even if canary=0 — wedge LRs only appear in ours's deeper probe
|
||
if k in ours_oplr or k in canary_oplr:
|
||
fn = LR_FN_NOTES.get(lr, "(unknown)")
|
||
w(f"| `{hex32(lr)}` | {op} | {canary_oplr.get(k,0):,} | {ours_oplr.get(k,0):,} | {fn} |")
|
||
w()
|
||
w("Comparable apples-to-apples roll-up: canary's 903 fires at LR `0x824AB168` (NtReleaseSemaphore wrapper return) ↔ ours's 90 fires at the SAME LR (IAT trace). Ratio = **9.97%**. The shortfall is dominated by ours's worker tid=5 (75/903) and cache-thread tid=13 (1/903) under-firing per AUDIT-069 S6.")
|
||
w()
|
||
|
||
w("## Canary-only tids (entry-PC bucket inferred via release-LR clustering)")
|
||
w()
|
||
w("These canary tids have ZERO matched ours analog at the (op,lr) level:")
|
||
w()
|
||
# Bucket canary fires by tid; canary tids whose fires are ALL in missing_in_ours have no ours analog at all
|
||
canary_tid_fires = collections.Counter()
|
||
canary_tid_lrs = collections.defaultdict(set)
|
||
canary_tid_handles = collections.defaultdict(set)
|
||
for ev in canary_events:
|
||
op = canary_op_category(ev)
|
||
lr = ev.get("lr",0)
|
||
canary_tid_fires[ev.get("tid")] += 1
|
||
canary_tid_lrs[ev.get("tid")].add(lr)
|
||
canary_tid_handles[ev.get("tid")].add(ev.get("handle"))
|
||
|
||
# Per tid: count fires whose LR has ours analog vs not
|
||
w("| canary tid | total fires | matched-LR fires | missing-LR fires | distinct LRs | analog in ours? |")
|
||
w("|--:|--:|--:|--:|--:|---|")
|
||
for tid in sorted(canary_tid_fires.keys(), key=lambda t: -canary_tid_fires[t]):
|
||
total = canary_tid_fires[tid]
|
||
matched = sum(1 for ev in canary_events
|
||
if ev.get("tid")==tid and (canary_op_category(ev), ev.get("lr",0)) in matched_lrs)
|
||
missing = total - matched
|
||
distinct = len(canary_tid_lrs[tid])
|
||
has_analog = "YES" if matched > 0 else "NO"
|
||
w(f"| {tid} | {total:,} | {matched:,} | {missing:,} | {distinct} | {has_analog} |")
|
||
w()
|
||
|
||
w("## Canary thread families with no ours analog (entire-thread divergence)")
|
||
w()
|
||
w("Per the 'Canary-only tids' table above, **three canary tids (15, 27, 28) have ZERO matched-LR fires** — every event they produce is on an LR ours never visits. Their fire patterns:")
|
||
w()
|
||
w("- **canary tid=15** (4,120 fires): exclusively on LRs `0x82508510` (2,373×, sub_82508400+0x110) and `0x82508524` (2,373×, sub_82508400+0x124) — paired worker-dispatch γ-set sites. Co-fires with canary tid=14 on the same LRs.")
|
||
w("- **canary tid=27** (2,726 fires): exclusively on LR `0x82506c90` (2,378×, sub_82506B08+0x188, worker dispatch) + `0x824AAFC8` (348×, KeSetEvent wrapper).")
|
||
w("- **canary tid=28** (2,724 fires): exclusively on LR `0x82506f9c` (2,355×, sub_82506DE8, worker dispatch) + `0x824AAFC8` (369×, KeSetEvent wrapper).")
|
||
w()
|
||
w("**Conclusion: canary tids 15/27/28 are members of the sub_825070F0 worker fan-out cluster that ours fails to spawn or whose start ctx is mis-initialized.** This matches the Review A Step 1 force-spawn-workers diagnosis (workers spawn but fault on `[ctx+44] = 0xBCE25640` unmapped read).")
|
||
w()
|
||
w("Canary tid=14 (33,546 fires, the audio worker A) HAS a partial ours analog (ours tids 9/10/11 fire 3 total events on the audio LRs), confirming that ours DOES spawn the audio threads but they wedge after 1 iteration (per iterate-2D investigation §Step 3).")
|
||
w()
|
||
w("## Outcome class")
|
||
w()
|
||
if unique_missing >= 15 and out_of_gamma >= 5:
|
||
outcome = ("**Class (C) Many distributed producers missing** (confirms iterate-2D's outcome)."
|
||
" Not a single (lr, handle) tuple — at least 15+ distinct call sites in canary"
|
||
" have zero ours analog on any tid.")
|
||
elif unique_missing >= 1:
|
||
outcome = "**Class (A/B) — small number of missing producers identified.**"
|
||
else:
|
||
outcome = "**Class (none) — no divergent fire patterns.**"
|
||
w(outcome)
|
||
w()
|
||
|
||
w("## Recommendation")
|
||
w()
|
||
if unique_missing >= 15:
|
||
w("**DROP-TO-OPTION-2 (boot-time delta replay), NOT force-spawn crowbar.**")
|
||
w()
|
||
w("Why not the crowbar (Option-C from goal): Review A Step 1 attempted exactly that on 2026-05-27 (`review-a-step1-force-spawn/progression-result.md`) and FAILED the PRIMARY progression gate. The 4 workers spawn under `--force-spawn-workers` but fault ~159 instructions in at `vtable[35..38]` dispatching on `[ctx+44]=0xBCE25640` — an unmapped VA in ours's allocator namespace. Force-spawning without first fixing the upstream ctx-state-installer chain is futile.")
|
||
w()
|
||
w("Why Option-2: iterate-2D §Step 3 documented a **1.3 s upstream timing skew** (canary first audio fire at host_ns=278 ms; ours first audio fire at 1,587 ms — 5.7× later). The 28 missing producer LRs found here are downstream consequences of that delay. Diffing the first ~1200 phase-a events to find the single early-init kernel-call divergence is cheaper, doesn't add LOC, and likely cascades to most of the 28 LRs at once. The canary's tid=6 ↔ ours's tid=1 main-thread bootstrap matches for 20 releases (per AUDIT-069 S6) then diverges — that's the right window to inspect.")
|
||
w()
|
||
w("Concrete next iterate: `iterate-2E-boot-delta-replay` — ~0 engine LOC, ~100 LOC investigation. Read existing phase-a event logs at `xenia-rs/audit-runs/phase-a-diff-harness/` (dated 2026-05-26) for both engines, time-bucket by host_ns, diff at first kernel-import-call mismatch. If the harness's diff path already covers this, the analysis may be pure data work.")
|
||
else:
|
||
w("**ESCALATE-TO-FIX** with targeted single-keystone iterate on the top missing tuple.")
|
||
w()
|
||
|
||
w("## Cross-check vs γ-signaler family")
|
||
w()
|
||
w(f"γ-family LRs (defined per AUDIT-069 S3/S2) have **{in_gamma}** representatives among the missing-in-ours set. The remaining **{out_of_gamma}** missing tuples lie outside the γ-family — these are fresh producer chains the audit-069 work never characterized:")
|
||
w()
|
||
fresh_chains = sorted((k for k in missing_in_ours if k[1] not in GAMMA_LRS), key=lambda k: -canary_oplr[k])
|
||
for k in fresh_chains[:8]:
|
||
op, lr = k
|
||
w(f"- `{hex32(lr)}` ({op}, canary={canary_oplr[k]:,} fires, tids={sorted(canary_oplr_tids[k])[:5]})")
|
||
w()
|
||
|
||
w("## Cascade check")
|
||
w()
|
||
w("- A (acquire both engines' fire data): **PASS** — cached canary 79,014 events + ours 153 events.")
|
||
w("- B (build cross-engine tuple key respecting reading-error #28): **PASS** — keyed on (op, lr); handle namespace differences absorbed by structural LR identity.")
|
||
w("- C (identify divergent tuples): **PASS** — see top-15 table above.")
|
||
w("- D (attribute cause): **PASS MEDIUM** — class (C) structural ladder; not a single bug.")
|
||
w("- E (recommend next iterate): **PASS** — Option-2 boot-time delta replay (per iterate-2D's investigation §Step 5).")
|
||
w()
|
||
|
||
w("## Tripstones honored")
|
||
w()
|
||
w("- **#28** (per-engine tid stability): tids omitted from diff key. ")
|
||
w("- **#39** (composite progression metric): not relevant — this is an investigation, not a progression iterate.")
|
||
w("- **#40** (single-keystone framing): explicitly checked and falsified. ")
|
||
w()
|
||
|
||
w("## Artifacts")
|
||
w()
|
||
w("Under `xenia-rs/audit-runs/iterate-2D-fire-pattern-diff/`:")
|
||
w()
|
||
w("- `diff.py` — this analysis script.")
|
||
w("- `report.md` — this report.")
|
||
w("- `divergent-tuples.csv` — full list of missing-in-ours tuples for further xref.")
|
||
w("- `matched-tuples.csv` — full list of matched tuples with canary/ours counts.")
|
||
w()
|
||
|
||
with open(os.path.join(OUT, "report.md"), "w") as f:
|
||
f.write("\n".join(lines))
|
||
|
||
# Write the CSVs
|
||
with open(os.path.join(OUT, "divergent-tuples.csv"), "w") as f:
|
||
f.write("op,lr,canary_fires,canary_tids,canary_handles,fn_note,in_gamma_family\n")
|
||
for k in divergent:
|
||
op, lr = k
|
||
tids = ";".join(str(t) for t in sorted(canary_oplr_tids[k]))
|
||
handles = ";".join(hex32(h) for h in sorted(canary_oplr_handles[k]))
|
||
fn = LR_FN_NOTES.get(lr, "")
|
||
in_g = "yes" if lr in GAMMA_LRS else "no"
|
||
f.write(f"{op},{hex32(lr)},{canary_oplr[k]},{tids},{handles},{fn},{in_g}\n")
|
||
|
||
with open(os.path.join(OUT, "matched-tuples.csv"), "w") as f:
|
||
f.write("op,lr,canary_fires,ours_fires,ratio,fn_note,in_gamma_family\n")
|
||
for k in sorted(matched_lrs, key=lambda k: -canary_oplr[k]):
|
||
op, lr = k
|
||
c = canary_oplr[k]; o = ours_oplr[k]
|
||
ratio = f"{100.0*o/c:.2f}%" if c > 0 else "n/a"
|
||
fn = LR_FN_NOTES.get(lr, "")
|
||
in_g = "yes" if lr in GAMMA_LRS else "no"
|
||
f.write(f"{op},{hex32(lr)},{c},{o},{ratio},{fn},{in_g}\n")
|
||
|
||
print(f"wrote report.md, divergent-tuples.csv, matched-tuples.csv to {OUT}")
|
||
print(f"Headline: {headline}")
|
||
print(f"missing_unique={unique_missing} missing_count={missing_canary_count} canary_total={total_canary}")
|
||
print(f"gamma_intersection={in_gamma} fresh_chains={out_of_gamma}")
|