sots-re/verify/results/shim/aidesign/parse_aidesign.py
alex 254ce7c156 SD: the ship-design composer 0x006ad700 -- a word-cost model, verified where it could be
Lane PAR localised the AI's RNG variance to this function and stopped. This
reads it from the instruction stream (0x006ad700..0x006ae61a, swept to the next
function start; the padding confirms Ghidra's size is right on this one) and
measures it live with a sub-bracket on VM145.

THE MODEL. Nine live draw sites plus two provably dead. Only three of the nine
can cost more than one word, and only through NextInt's rejection loop; the four
cl_Chance probabilities are all strictly inside (0,1), so neither zero-word
early-out is reachable anywhere in the function. The loop-carried draw fires
once per SMALL STANDARD WEAPON BANK selected by

  f  = 1.00 / 0.75 / 0.50 by request flags, hull size and one 0.3 coin
  M  = (int)(N * f);  D' = max(1, (N+1)/M)
  L  = #{ bank j : PointDefence section, or j mod D' == 0 }

which is not monotone in N -- at f=0.75, N=4 costs 4 words and N=5 costs 3.

VERIFIED. Client 32's seven turn-1 words decompose as TWO composer calls: a
costOnly=1 price query (3 words) and a costOnly=0 build (4). That was predicted
from two push literals at 0x006cda9a/0x006cdb17 and committed before the probe
existed. Two pinned runs in fresh processes agree row for row. The probe's bank
counts 3/1/2 match the Tarkas section catalog's bank counts exactly -- a hooked
pointer walk and a parsed data file agreeing from opposite directions. The
detour is behaviour-neutral: unpinned it reproduces the published
d59bb9f2fd0eb535.

NOT VERIFIED, and this is the part worth reading. The loop-carried draw has
NEVER FIRED -- six composer calls across four runs, zero. On turn 1 the weapon
lookups gate it; on turn 15 every composer call is a price query that returns
nine steps earlier. Five of the nine sites have never fired and two of nine
exits have ever been taken. The formulas for those are read, not measured, and
section 6 says so.

CORRECTS roll-parity.md: its site table omits a live draw site (0x00691e9b,
which the same lane measured live at 0x00691ea0), so the AI turn has 22 live
sites and not 21; and 'six of seven turn-1 words come from the composer' is
five from its body plus two from a helper it calls -- all seven are in its
subtree.

NEW: on turn 15 the composer is 10 of 16 words, not 16 of 16. The task
system's coin at 0x0069086a fires four times and never fired on turn 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 20:50:03 -04:00

108 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""Lane SD: read `shim.airng.txt` and print the composer rows next to the turn bracket they sit in.
Usage: parse_aidesign.py <run-dir-or-file> [...]
Prints, per run:
* the arming lines, because an absent `aidesign` row and a hook that never armed look identical
(method rule 20, and lane L3's `hooks=off watch=on` that reported a confident zero);
* one line per AI turn bracket with its word total;
* one line per composer call with the cost model's prediction beside the measurement;
* a footer that checks the two arithmetic identities the run must satisfy:
sum(composer sub_words) <= the bracket's left_delta, with the split reported, and
words == sub_words on every composer row (the two independent measurements).
"""
import re
import sys
import os
ARM = re.compile(r"^(airng|aidesign): ")
BR = re.compile(r"^airng seq=(\d+) pid=(\d+) agent=(\S+) .*left_delta=(\d+) observed=(\d+) "
r"calls=(\d+) residual=(-?\d+) foreign_words=(\d+)")
DES = re.compile(r"^aidesign seq=(\d+) dseq=(\d+) rc=(-?\d+) dry=(\d+) size=(-?\d+) budget=(\S+) "
r"role=(-?\d+) flags=(\S+) words=(\d+) sub_words=(\d+) left=(-?\d+)->(-?\d+) "
r"N=(\d+) q=(\d+)/(\d+)/(\d+) pd=(\d)(\d)(\d) f=(\S+) M=(-?\d+) D=(-?\d+) "
r"H_pred=(\d+) H_obs=(\d+) model=(\S+)")
SITE = re.compile(r"^aidesignsite seq=(\d+) dseq=(\d+) (.*)$")
PART = re.compile(r"^aidesignpart seq=(\d+) dseq=(\d+) part=(\d+) sec=(\S+) mounts=(\d+) "
r"qual=(\d+) pd=(\d) name=(.*)$")
CALL = re.compile(r"^airngcall seq=(\d+) pid=(\d+) ret_rva=\S+ va=(\S+) facade=(\S+) calls=(\d+)"
r"(?: trues=(\d+))?")
def one(path):
txt = open(path, encoding="utf-8", errors="replace").read().splitlines()
print(f"===== {path}")
armed = False
for ln in txt:
if ARM.match(ln):
print(" " + ln)
if ln.startswith("aidesign: composer") and "create=MH_OK enable=MH_OK" in ln:
armed = True
print(f" ARMED: {'yes' if armed else 'NO -- every zero below is void'}")
bracket_words = {}
des_by_seq = {}
for ln in txt:
m = BR.match(ln)
if m:
seq, pid, agent, delta, obs, calls, resid, foreign = m.groups()
bracket_words[seq] = int(delta)
print(f" bracket seq={seq} pid={pid} agent={agent} words={delta} observed={obs} "
f"residual={resid} foreign={foreign}")
continue
m = DES.match(ln)
if m:
g = m.groups()
des_by_seq.setdefault(g[0], []).append(g)
print(f" composer dseq={g[1]} rc={g[2]} dry={g[3]} size={g[4]} flags={g[7]} "
f"words={g[8]}/{g[9]} N={g[12]} q={g[13]}/{g[14]}/{g[15]} "
f"pd={g[16]}{g[17]}{g[18]} f={g[19]} M={g[20]} D={g[21]} "
f"H_pred={g[22]} H_obs={g[23]} {g[24]}")
continue
m = SITE.match(ln)
if m:
print(f" sites dseq={m.group(2)} {m.group(3)}")
continue
m = PART.match(ln)
if m:
g = m.groups()
if g[3] != "0x00000000":
print(f" part {g[2]} sec={g[3]} mounts={g[4]} qual={g[5]} pd={g[6]} {g[7]}")
continue
m = CALL.match(ln)
if m:
print(f" facade seq={m.group(1)} {m.group(3)} {m.group(4)} calls={m.group(5)} "
f"trues={m.group(6)}")
# ---- the two identities ----------------------------------------------------------------
bad = 0
for seq, rows in des_by_seq.items():
total = sum(int(r[9]) for r in rows)
if seq in bracket_words:
out = bracket_words[seq] - total
if out < 0:
print(f" ! seq={seq}: composer sub_words {total} EXCEEDS bracket words "
f"{bracket_words[seq]} -- the sub-bracket is double counting")
bad += 1
else:
# Not an error: on a deeper board the AI also draws in the task system and the
# research pick. The split is the number worth reading.
print(f" seq={seq}: {total} of {bracket_words[seq]} words in the composer, "
f"{out} outside it")
for r in rows:
if r[8] != r[9]:
print(f" ! seq={seq} dseq={r[1]}: words={r[8]} != sub_words={r[9]} "
f"-- the two measurements disagree")
bad += 1
print(f" identities: {'OK' if bad == 0 else str(bad) + ' FAILED'}")
def main():
for a in sys.argv[1:]:
if os.path.isdir(a):
one(os.path.join(a, "shim.airng.txt"))
else:
one(a)
if __name__ == "__main__":
main()