Commit Graph

742 Commits

Author SHA1 Message Date
Drew T 2fdcc4f120 docs(phase-32): T3 S83 CHECKPOINT — 28 banks (census 44 -> 16 stubs / 3,827 ins), fleet R22 218/218 twice, the 17-row Haiku queue 17/17 MATCH; cookbook §500-F; 🛑 block refreshed
- CURRENT_PHASE.md: T3 row (47/47 drafted, 38 banked, 9 NEAR ledgered, 0 FAIL), the S83 log entry, the 🛑 SESSION
  CHECKPOINT rewritten to the verbatim-replay standard (supersedes the 10:01 block): state, what S83 did, the 16-row
  census with draft paths, NEXT (step 8 permuter_ils ×2 + one Opus look, then the close), files/tools/gotchas, T4/T5 carry
- cookbook §500-F (the queue's yield + the four integration classes a per-draft rtu_match cannot see: same-TU spellings,
  §304 ×3, verdict-relative-to-TU-at-verification, the md_* jtbl route is §303); docs/cookbook-index.md regenerated (--check OK)
- .run/P32/t3/BRIEF.md: the §304 + exact-TU-spelling contract lines; harvest_notes.md; verdicts.jsonl +17 (session 491895ad);
  pending_launch.txt emptied (all 17 launched 10:57); the 17 Haiku drafts + 3 agent reports (R20)
- tools/jtbl_carve.py: the leading-island refusal now says §303 supersedes the §260 split for md_* modules (P32 S83 witness)
- backlog: the 9 NEAR rows logged (class · closeness · best draft · cost "1 Opus agent, 30–70 min", R41); docs/backlog.md 15 open
- .run/P32/t3s3/ (NEW, allowlisted): splice.py, bank.sh (verbatim grep -> rtu xN -> splice xN -> one build -> sha -> commit
  on green), backlog_near.sh, prompts/ (17), gate/slate_main2.json + log, r22/r22b/tools-health/build/bank/twin_rescan logs
- census jsons .run/P32/frontier_t3s3_{mid,haiku}.json; progress digests regenerated by tools-health (R22 218/218 at 10:46 and 11:09)
2026-09-05 11:11:03 -06:00
Drew T 11f79c1b0a docs(phase-32): T3 checkpoint — the 31 agents' FULL final reports saved to .run/P32/t3/reports/ (tools/agent_reports.py, NEW) + tools/transcript_dump.py (NEW, the dead-session reader); SETUP rows, playbook addendum-2, 🛑 block §3/§6 point at them 2026-09-05 10:11:43 -06:00
Drew T 9480c979a0 docs(phase-32): T3 CHECKPOINT — the launching coordinator overflowed after bank 9; 22 agent verdicts recovered (20 MATCH / 9 NEAR / 2 FAIL of 31), 11 swept Opus deliverables restored + re-verified, cookbook §500 harvest, playbook §S80 addendum-2, agent_drafts_restore.py
- verdict ledger .run/P32/t3/verdicts.jsonl rebuilt from the 31 T3 transcripts (agent_verdicts.py); every unbanked draft
  re-verified with rtu_match in its real TU: 10 MATCH awaiting the gate (main func_80015B6C 120 + func_8002FDE8 73;
  md_SC03_054 func_801EF6D8 604 + six jtbls; md_SC03_053 func_801EF734 44 + func_801EF7E4 72; md_MAIN_007
  func_800CF148/2BC/EEFC/EF94/068) + func_800CF3B0 leaf-exact behind the TU's void/3-arg decl; 9 NEAR at exact length
  (2/6/15/17/27/35/46/49/137), each with its class and inert-lever list
- R48 incident: one agent's `find .run/P32/t3/opus -maxdepth 1 -type f ! -name <mine> -exec mv {} _scratch/` swept 11
  sibling deliverables (two MATCHes among them); found in _scratch/, restored to the contract paths, byte-verified;
  tools/agent_drafts_restore.py (NEW: transcript replay) as the fallback; .gitignore allowlist for .run/P32/** so the
  drafts, ledger and census files are committed (R20)
- harvest (R16/R30): cookbook §500 (10 banked closers, 10 MATCH closers, 9 NEAR classes, two NEW mechanisms — the
  pinned-base-vs-pseudo-address alias basin and #line-equalised ASM_OPERANDS for cross_jump — and the wave-process
  defects); wave-playbook §S80 addendum-2 (per-function work dirs, JSON-only final message, the 20-agent cap, the
  recovery tools); accelerators P32 T3; decision-log P32 S82 (R31); SETUP tooling row (R21); cookbook-index
  regenerated; .run/P32/t3/BRIEF.md output contract amended for the 17 queued launches
- CURRENT_PHASE: T3 row IN PROGRESS, Log entry, 🛑 SESSION CHECKPOINT (census 44 stubs / 5,313 ins with every row's
  state and draft path, the 9-step resume order, the dead session's read-only T4 pre-read); harness task list rebuilt
- no src/ or config/ change in this commit; no fleet R22 has run since the 10 T3 banks — the resume order starts with one
2026-09-05 09:36:43 -06:00
Drew T 8b2bbff831 fix(phase-32): T2c (1) — split_indicator's population is derived from the yamls (was a stale stored 213-name list; tools-health said "213 OK of 213" over 218 binaries); R32 denominator assertion 2026-09-05 00:08:44 -06:00
Drew T 1e843c607a feat(phase-32): T2b (4) — SC03/56 ONBOARDED as md_SC03_056 @0x801CBB50 (ov_SC03_002's DESTPTR), byte-identical bc768a6b; ALL FIVE parked payloads are now binaries (fleet 213 -> 218); evidence tool v2
- md_SC03_056 (TEXT_LO 0x4, 4 stubs / 61 ins): 15/17 pointers cluster inside at 0x801CBB50; one outward call
  (0x8018151C) hits a function only 3 overlays have, ov_SC03_002 among them; req_fit 9/9 for ov_SC03_002
- payload_base_evidence.py v2 (controls 7/7 throughout): (a) STRONG = internal jals + fn-ptr-table entries on the
  module's own starts >= 2 (SC03/53 STRONG); (b) OUTWARD-EXPLAINED — a pure jal-vote base whose "internal" targets
  are function starts of the fleet's overlays is downgraded: SC03/56's 0x80178C8C was two SHARED-engine functions
  spaced like two of its five starts (and nobody's DESTPTR), a false STRONG; (c) the requester cross-check is
  informational only — shared engine code makes every requester fit (an R39 control caught it scoring: 6/7)
- memory-map §S45 p7 amended: all five rows ONBOARDED + the two instrument findings (the first build is a NULL
  oracle for FINE base errors — +8 builds byte-identical, +0x1000 fails the link; outward-explained vote bases);
  SETUP row amended. The parked-for-L3 ledger is EMPTY pending `make audit-disc` (T2c).
2026-09-04 23:52:58 -06:00
Drew T b52d67be0b feat(phase-32): T2b (3) — SC03/53 + SC03/54 ONBOARDED as md_SC03_053 / md_SC03_054 @0x801EF468 (the script slot), byte-identical c0848f30 / 06bd73df
- md_SC03_053 (TEXT_LO 0x4, 15 stubs / 372 ins) and md_SC03_054 (TEXT_LO 0xF0 — a 19-entry fn-ptr header, 7 stubs
  / 764 ins) share ov_SC03_001's DESTPTR slot 0x801EF468, the slot the S45 tracer watched other SC03 scripts load into
- BASE EVIDENCE (memory-map §S45 p7, static-derived STRONG at 0x801EF468 and nowhere else): SC03/53 — 52/75 absolute
  pointers inside, 3 of them + its one internal jal exactly on its own function starts (0 at every rival); SC03/54 —
  106/115 pointers inside, 5 header-table entries exactly on starts (0 at every rival); lui 0x801F ×18 / ×46
- first builds byte-identical (base-lenient, R34 — the base rests on the alignment; the first internal-call C bank
  byte-proves it); the §S45 p6 "onboard at 0x801EF468, let the first build decide" step, finally run
2026-09-04 23:51:45 -06:00
Drew T 10aaf5c296 feat(phase-32): T2b (2) — MAIN/9 ONBOARDED as md_MAIN_009 @0x800CD348 (TEXT_LO 0x3C), byte-identical d270f695; the OPDEMO1 module leaves the parked ledger
- tools/new_binary.sh md_MAIN_009 extracted/retail/MAIN.CD.dir/FILE_009.dir/0.1 0x800CD348 0x3C -> first build
  BYTE-IDENTICAL sha d270f695b793b5c03db159b7aabcc066daa87eda; 11 stubs (609 ins); window 0x800CD348..0x800CDD38
  lies below the resident's symbol region, so the default symbol stack stands (no A4 edit)
- BASE EVIDENCE (memory-map §S45 p7, static-derived STRONG): 6/6 internal jals and 9/9 absolute pointers land
  on the module's own function starts at exactly ONE base, 0x800CD348 — inside slot B's region (+0x82C from
  0x800CCB1C), not a previously known slot; lui 0x800C/0x800D ×51. Same caveat as md_MAIN_007: the first build
  is base-lenient (R34), the base rests on the alignment and will be byte-proven by the first internal-call C bank.
2026-09-04 23:51:08 -06:00
Drew T 1a696a851a feat(phase-32): T2b (1) — MAIN/7 ONBOARDED as md_MAIN_007 @0x800CEDF8 (TEXT_LO 0x34), byte-identical 2ff702b6; the OPDEMO0 module leaves the parked ledger
- tools/new_binary.sh md_MAIN_007 extracted/retail/MAIN.CD.dir/FILE_007 0x800CEDF8 0x34 -> first build
  BYTE-IDENTICAL sha 2ff702b605ab5cfc18474c464c4c07e5f8ffd48c; A4 applied (symbols.resident.txt not stacked —
  the window lies inside the resident's symbol region), re-extract + rebuild byte-identical; 19 stubs (802 ins)
- BASE EVIDENCE (memory-map §S45 p7, static-derived): STRONG — 9/9 internal jals and 14/16 absolute pointers
  land on the module's own function starts at 0x800CEDF8 (the boot slot of md_MAIN_001/008/011); lui 0x800C/0x800D
- HONEST CAVEAT (R34, measured 2026-09-05): the all-INCLUDE_ASM first build is a NULL oracle for FINE base
  errors — the same payload builds byte-identical at 0x800CEE00 (+8) — and catches only GROSS ones (at +0x1000
  two internal jal targets leave the window: `undefined reference to func_800CEEA4/func_800CF3F4`, link fails).
  The base therefore rests on the static alignment, and will be byte-proven by the first C bank that calls an
  internal sibling. Controls: .run/P32/t2b/{control_full,control_fine}.log
- registered in modules.mk + the report/diff dicts (R36 citizenship asserted by tools-health at T2c)
2026-09-04 23:49:05 -06:00
Drew T 848c7c50ab feat(phase-32): T2a — tools/payload_base_evidence.py (controls-gated static base evidence) + memory-map §S45 p7: the parked five get candidate bases
- the instrument: module-id word, TEXT_LO estimate, absolute-pointer set, lui hi-half histogram, and a
  jal->function-start VOTE (starts = prologues ∪ the word after every `jr $ra`+delay — leaf functions have no
  prologue, the recall killer of S45's vote_base 4/12); scores a BOUNDED candidate list (5 §S44 slots ∪ 134
  IDXTAB DESTPTRs ∪ vote bases): STRONG / CONSISTENT / INCONSISTENT / NO-EVIDENCE; AMBIGUOUS tie sets are
  printed, never picked; a payload with no self-reference is REFUSED as base-independent (R43)
- R39 controls run before any emission: md_MAIN_008/011/013/042, md_SC03_073, md_SC02_009, md_SC07_004
  re-derive their byte-proven bases top-ranked from their payloads alone (7/7); TEXT_LO estimates == yaml
  (incl. the header-table modules 0x7C/0x14/0x158). The first draft of the scorer FAILED 5/7 (prologue-only
  starts; a top-rank assertion on modules the bytes cannot discriminate) — fixed by the controls, not shipped
- the five (G5 static-derived, US): MAIN/7 STRONG 0x800CEDF8 (9/9 jals, 14/16 ptrs on starts); MAIN/9 STRONG
  0x800CD348 (6/6, 9/9); SC03/53 + SC03/54 CONSISTENT with 0x801EF468 top of a 12-way tie; SC03/56 SPLIT
  (jal vote 0x80178C8C vs pointers/lui ~0x801CBB50). T2b probes each with new_binary.sh — the byte gate decides
- SETUP row (R21); evidence rows .run/P32/t2a/evidence.json
2026-09-04 23:42:53 -06:00
Drew T c513e1fbbd feat(phase-32): T1a (2) — resident: func_800D128C (243 ins) BANKED byte-identical 8e17e02f via the raw splice + a 5-piece carve; three instrument fixes (§498)
- BANK: the stored S71 closeness-0 draft spliced into src/resident/resident_jr_800D128C.c; jtbl_carve --func
  carved jtbl_80113FB8 (119 entries, 1 pad word trimmed) + jtbl_80114198 into [0x451c0, .rodata,
  resident_jr_800D128C] + [0x453c4, data, tail3]; JTBL_PADS 0,4; make extract + make build BINARY=resident -j8
  rc 0, sha 8e17e02ff8954d07c979449198f7e1645046b353 == check (R53). pads_audit ok/ok; interleave_check
  ALIGNED n=5; verbatim_check --strict 5==5. Resident stubs 2 -> 1 (func_800D06E8 remains).
- WHY THE GATE SAID DIFF (parallel_gate banked 0/DIFF on an rtu_match MATCH): jtbl_carve.set_overlays_var
  regenerated resident_JTBL_INTERLEAVE from the carve set and DROPPED the resident's `--pre hdr.rodata.o`
  (§8f leading-rodata sandwich); make extract refused (ld_interleave: hdr.rodata.o would be parked with
  .text), the build linked the STALE script (249,252 differing bytes from file offset 0x4), and
  harvest_verify._jtbl_prep_one never read the post-carve extract's exit code (R49/R61).
- FIXES (R35/R40/R57): jtbl_carve._merge_pre carries an existing --pre forward (idempotent; overlays
  unchanged, 4-shape unit control); harvest_verify refuses loudly on a failed post-carve extract and
  restores the snapshot (CARVE refusal, NOT a draft verdict); interleave_check's anchor accepts a leading
  --pre (was a false DRIFT n=0 on the resident; control ov_SC02_017 ALIGNED n=44 unchanged).
- cookbook §498 (+ the stale-asm-after-a-failed-extract sequencing law); SETUP rows for all three
2026-09-04 23:28:59 -06:00
Drew T c52190ca86 fix(phase-32): T1b (1) — jr_isolate_all keys a bodiless typedef struct Tag Alias; by the ALIAS (§497); ov_SC02_017 dry-run REFUSED -> CLEAN, no source rename
- _type_names returned the TAG for `typedef struct Rec801806C8_s Rec801806C8;`, so the typedef block and the
  tag's own packed struct definition collided under one key with different bodies and the R43 "CONFLICTING
  bodies — a rename is needed" refusal fired on legal C. Now keyed by the alias (_TYPEDEF_TAG_ALIAS); the
  `carried` set learns the alias; `typedef struct X X;` (alias == tag) keeps the old key so a second one
  still dedupes/refuses. Unit control on 7 block shapes PASS; ov_SC02_017 --only func_80186C64 --dry-run:
  2 region files, no carve repoints. cookbook §497; SETUP row.
2026-09-04 23:24:02 -06:00
Drew T 380ccdc843 feat(phase-32): T1a (1) — resident code subseg split (3 regions, byte-identical 8e17e02f) + jr_isolate_all include-derived provided types (§496)
- jr_isolate_all resident --only func_800D128C: [0x4 c resident] [0x12ec c resident_jr_800D00E4]
  [0x2494 c resident_jr_800D128C]; the banked jr func_800D00E4's .rodata carve + JTBL_PADS + --order
  repointed to resident_jr_800D00E4.o (config/overlays.mk resident block only, R60); make extract +
  make build BINARY=resident -j8 rc 0, sha 8e17e02ff8954d07c979449198f7e1645046b353 == check (R53)
- TOOL FIX (R43/R33): the carried-type test consulted _engine_types() (engine_types.h + common.h) for
  every TU, assuming each region includes engine_core.h; the resident includes only common.h, so its
  file-local `typedef struct {...} CdFileLoc;` (a name engine_types.h also defines) was silently NOT
  carried -> `parse error before cdFileLocTable` in both region TUs, build rc 2 while the stale binary
  on disk read green. Now _provided_types(header) derives the set from the TU's own #include lines
  (engine_core.h => engine_types.h + common.h, never engine_core's macro-internal typedefs; common.h
  => common.h) and _file_scope_decls(items, provided) uses it at both decision points. R39 controls:
  overlay header == legacy set (1,197 names); resident set lacks CdFileLoc. cookbook §496; SETUP row
- rtu_match func_800D128C --split resident_jr_800D128C: MATCH (243 ins) on the stored S71 draft;
  the gate is the next commit
2026-09-04 23:20:04 -06:00
Drew T 452975e852 docs(phase-31): S80 #10 CLOSE — the verbatim end-state: manifest 6 → 5 rows (the five PERMANENT rows RATIFIED in _README; the GAME-C row decompiled), cookbook §495 (two def-side declaration walls, the S79 assembly "bank" P9 correction, the gate that dropped a bank on exit 0), decision-log S80 addendum (R31), SETUP rows; tools/parallel_gate.py: banked-but-not-merged now exits 2 with the worker's raw git status kept + per-run .run/pgate_runs/<ts>.json; CURRENT_PHASE #10 bullet + the S80 #10 CLOSE checkpoint (R22 213/213, tools-health OK, census 21 stubs / 4,554 ins, NEXT #11 = PhaseEnd, gate 2); regenerated digests 2026-09-04 22:07:35 -06:00
Drew T 7fbdb8fd63 fix(phase-31): S80 #9c — the permuter could not permute a PINNED seed, and it was our instrument: hide_asm carried only the __asm__ spelling (3 S79 seeds use asm("$7")), permuter_ils warm-restarted from the DECODED waypoint (raw pins back in base.c → cycles 2..N were silent parser refusals reported "(unchanged)"), and defines_fn refused K&R-style definitions (436 stored backlog drafts kept out of the lane for four phases). Fixed + R39-controlled over 5,311 drafts (the bare word asm in INCLUDE_ASM path strings was a caught false positive): re-hide every waypoint, assert the definition survived, abort exit-2 on a refusal (R61a), flushed logs (R55). Every S79 pinned seed now iterates; ov_SC06_022:func_8017DF28 (pinned WALL, closeness 2) reached 1 in its first cycle. cookbook §493 S80 correction + §494 v1 (S79 idioms); SETUP rows (p16_permute/permuter_ils, agent_verdicts.py) 2026-09-04 20:59:35 -06:00
Drew T 2709321082 docs(phase-31): S79 HANDOFF checkpoint mid-task #9 — 7 banks this task (open stubs 51 -> 25 this session), plateaus with residuals named, 11 drafting agents still running; tools/agent_verdicts.py extracts their final JSON verdicts from the subagent transcripts for the fresh session to aggregate (procedure + paths in the 🛑 block) 2026-09-04 19:50:01 -06:00
Drew T a0139c31c8 docs(phase-31): S79 #8 close — cookbook §493 (the permuter route end-to-end; the D-NEAR ledger), p16_permute surfaces the permuter's parser refusals, SETUP row, census 31, report, checkpoint (task #9 brief)
Stubs 32 -> 31 after the func_80015760 bank (commit:3877); R22 fleet 213/213 (.run/S79_check_all_8.log);
main game-code 93.5% (38,854 / 41,534). Permuter ILS plateaus recorded with their residual named:
func_80015608 best 1, func_80039B20 best 7, func_80038698 pinned seed refused (11). The ILS runner
had reported "no waypoint" for 8 cycles in 20 s on a seed the permuter's C parser rejects; it now
prints [permuter] REFUSED and leaves PERMUTER_REFUSED.txt (positive-controlled on func_80038698).
2026-09-04 18:50:20 -06:00
Drew T 595fc9fa49 docs(phase-31): S79 #6 close — cookbook §491 (the mechanical class: a phantom stub, two jtbl twins, one clone; three tool gaps), jtbl_pads_fix regex fix (+positive control), SETUP rows, census 35, checkpoint refreshed (task #7 brief)
Stubs 38 -> 35 after the #6 banks (commit:3868 commit:3869 commit:3870 commit:3871); R22 fleet 213/213
(.run/S79_check_all_6.log); frontier_classify 35 rows (main 16, md_MAIN_003 5, resident 2, ov 12).
jtbl_pads_fix's PAD_ERR_MORE regex carried jtbl_rodata_pads' old wording and reported "no
pad-count drift" over a red build; it now accepts both spellings and, positive-controlled with a
deliberately short spec, reports "emits >4 table(s), spec declares 4". The deferred carves and
their blockers are itemised in §491 and in the checkpoint's task #7 brief.
2026-09-04 18:19:57 -06:00
Drew T 02f060f607 feat(phase-31): S79 #5 — the libpad 4.2.1 + libapi 4.2 band and the apicard region LINKED from real objects: 13 stubs + 4 TUs + the reorder island gone; main 16 stubs, fleet 38
800c3 (0x8005CE18-0x8005FC68, one contiguous run of 33 interleaved Sony objects) is now four
stub rows — libapi1 (21 BIOS trampolines + COUNTER), libpad1 (PADENTRY + PADMAIN 760), libapi2
(L02/L03), libpad2 (PADCMD PADIF PADPORTD PADSEQD WAITRC2) — fed by two WINDOWED psyq_integrate
calls from the raw .run/obj42/{libapi42,libpad421} dirs (integrate tiles each stub with one
library; every boundary checked against .text SECTION sizes). The apicard region's three
"game code" rows were libapi 4.2's C objects to the byte: 800c2 = FIRST.o (firstfile + the
"no jump table wall" stub func_80062144), 800c2_2 = PAD.o, 800c2_3 = PATCH.o + CHCLRPAD.o ->
apicard5/6/7; make_apicard_used.py sources libapi from 4.2 (the EXE's real libapi; libcard
stays 4.0) into .run/obj42/apicard_used, 26 objects / 7 blocks, no game code left in
0x80061F38-0x80062888. src/800c3.c (129 hand-matched "C", 62 verbatim bodies, 19 stubs incl.
the four §332 %lo-in-a-delay-slot "walls"), src/800c2.c, src/800c2_2.c, src/800c2_3.c removed;
REORDER_TUS is empty (mechanism kept). Cookbook §490.

Two stale instruments fixed: exclude_audit let a pinned WALL outrank LINKED (PopMatrix/
PushMatrix had sat as walls since S68 while living in libgte3, linked since Phase 8) — LINKED
dominates now, config/wave_exclude.txt 13 -> 3; frontier_classify carried a hard-coded 49-name
LINKED set (R51) and reported 337 "stubs" — derived from the Makefile now.

Verified: main 143dbb89f34491258bbc27810d0a12ec8b43a8dd WITH all SDK dirs and WITHOUT them from
a fresh extract; make tools-health OK; R22 fleet extract-all 212/212 + check-all 213/213.
Metrics: main REAL 839->773, LINKED 1,150->1,256, VERBATIM 29->3, stubs 29->16, byte-identical
2,075/2,091 = 99.2%; game-code weighted 93.3% (38,748/41,534), remainder 2,786 = the open-stub
sum; fleet stubs 51->38 (frontier_classify: 39 rows incl. the data word). Verbatim manifest
33 -> 6. Docs: worklist rows + "S79 task #5", SETUP (fresh-clone obj42 commands, Makefile
blocks, exclude_audit), decision-log "S79 addendum 2", accelerators "S79 (2)", CURRENT_PHASE
S79 FINAL refreshed (census, metrics, the task #6 brief).
2026-09-04 17:56:31 -06:00
Drew T 757bd82a0f feat(phase-31): S79 #4 — scattered-.bss split at link-prepare (psyq_bss_split): SYS.o→libgpu2, VM_F.o→snd12, GS_001.o→libgs8 LINKED; libgpu_used retired
The §9.1 "scattered .bss commons" exclusion class (Phase 8 → P31) is closed 3/3. New
tools/psyq_bss_split.py (own ELF32 REL reader/writer) cuts an object's packed .bss into
per-base NOBITS pieces: bases derived from the game bytes per HI16/LO16 pair, references
walked in offset order into single-base runs, cuts snapped to symbol starts (the linker
scattered SYMBOLS), symbols moved, a LOCAL section symbol per piece inserted, relocs
retargeted with the addend rewritten in the immediates, self-diffed. It runs inside the one
prepare step shared by psyq_link.link_object / psyq_link_region.build_region /
psyq_integrate.integrate (prepare_object before classify), re-derived every build.

GS_001.o was certified "5 interleaved bases, NOT splittable" by the S77 probe, which grouped
by BASE; by RUN it is six symbol-aligned pieces. All seven cuts across the three objects are
confirmed by the other objects' by-name recoveries (_que 0x800C5510, _svm_sreg_buf
0x800B9B58, PSDBASEX/CLIP2/PSDBASEY/POSITION/GsDRAWENV). R39 negative control: 235 placed
objects across 9 curated dirs, 0 refusals, exactly 3 splits (a libcd .bss+size end pointer
refused the first build → reference problems are fatal only when a split is needed).

Wiring: yaml 800c→libgpu2, sgap_6→sgap_6+snd12, gsgap3→libgs8 (comments rewritten);
LIBGPU_ELF := .run/obj40/libgpu (curated libgpu_used retired); libgs 34 objs/8 blocks
(make_libgs.sh +GS_001); snd 63/12 (make_snd_used.py exclusions 4→3). src/800c.c and
src/gsgap3.c removed (Sony code hand-matched as REAL/verbatim), sgap_6.c keeps only
func_8003FA54; splat-emitted libgpu2.c/libgs8.c/snd12.c stubs for the no-SDK fallback.

Verified: main 143dbb89f34491258bbc27810d0a12ec8b43a8dd WITH the SDK objects and WITHOUT
them from a fresh extract; make tools-health OK; R22 fleet clean extract-all 212/212 +
check-all 213/213. Metrics: main REAL 886→839, LINKED 1,040→1,150, VERBATIM 85→29, stubs 29
(unchanged); game-code weighted 91.1% (40,895/44,870) — both terms lost the 3,667 SDK ins;
the remainder is still exactly the 3,975-ins open-stub sum. Verbatim manifest --update
200→33 rows (subtractive). Docs: cookbook §489 (+index), psyq-worklist rows + "S78 task #4",
SETUP S79 R21 table, decision-log S79 addendum, accelerators S79, CURRENT_PHASE S79 FINAL 🛑.
2026-09-04 17:19:29 -06:00
Drew T a85733a487 feat(phase-31): S78 #3 — 13 "game code" subsegs were PsyQ objects: wired LINKED (libgte 70/30, libgs 33/7, snd 62/11); main's game-code metric corrected to 91.8%
- exact tiles, 0 tokens: libgte23-26 (MSC01/02/05/09, SMP_00, FGO_01-06, PATCHGTE), libgte9 re-derived
  as SMP_05 NormalClip (SMP_06 NormalClipS = nested sub-pattern; psyq_integrate now drops nested
  placements), libgte27-30 (the libgs-gap MTX_05/07/11, REG03+REG11), libgs7 (2D_BG0+2D_BG1), snd10
  (VM_NO1), snd11 (VM_NOWON carved off sgap_8). LINKED 959->1040, REAL 912->886 (SDK inline-asm wrappers
  re-provenanced), VERBATIM 146->85, 13 TUs deleted; splat re-emits the stub records.
- main 143dbb89 WITH and WITHOUT the SDK objects. The no-SDK fallback had been red since S7x
  (CdReadyCallback called by its SDK name while the libcd stub carried func_800435B4) — curated
  CdReadyCallback = 0x800435B4, refs unified. R22 clean fleet 213/213; tools-health OK.
- METRIC CORRECTION (R35): progress.py's "MAIN game-code weighted" sig never excluded the LINKED
  objects (its comment said it did) — ~31k linked-SDK ins sat in the denominator as unmatched game
  code. Exclusion now derived LIVE from the Makefile stub lists + yaml ranges: 91.8% (44,562/48,537),
  not 59.8%; the 3,975-ins remainder equals the open-stub sum exactly.
- VM_F.o probed SPLITTABLE at .bss 0x50c (SYS.o's class -> task #4). cookbook §488; worklist S78 #3;
  decision-log + accelerators; SETUP rows.
2026-09-04 16:26:12 -06:00
Drew T a7394f44dc feat(phase-31): S78 #12 — the 800c3 "wall" band is LIBPAD 4.2.1 + LIBAPI 4.2: 46 names applied; integrate wired by subseg range; renames via ApplySymbols
- provenance: the psx loader's per-version PsyQ signature sets place PADENTRY/PADCMD/PADPORTD/
  PADSEQD (4.2), WAITRC2 (4.3), COUNTER/C114/FIRST/PAD/PATCH/CHCLRPAD (libapi 4.2) byte-exact in
  0x8005CE48-0x8005FC68 / 800c2 -> 12 of main's 29 stubs incl. all four §332 walls are Sony's
  DualShock library in reorder mode. 46 names -> symbols.us.txt (count 1081), band TUs, verbatim
  manifest, wave_exclude; firstfile/firstfile2 (4.2 naming); CdGetToc @0x800430B8 (was the Phase-21
  xdedup mislabel DecDCToutCallback). SETUP §5.1 corrected; psyq-worklist S78; cookbook §487;
  decision-log + accelerators S78; CHECKSUMS +Psy-Q_46.zip +PSYQ_SDevTC_v4.5.zip.
- psyq_integrate: --yaml maps stub<->objects by SUBSEG RANGE with an exact-tiling check and PRINTS
  the located-but-unwired residue (libgte: 13 objs / 1,264 ins) — main's LINKED build had been RED
  at HEAD since the S77 psyq_identify fix (22 libgte blocks merged to 3; gate worktrees take the
  stub fallback so it never showed); a library object's exported symbol whose recovered address the
  curated file names differently is --redefine-sym'd (R15; A66 firstfile->firstfile2).
- Ghidra: 47 MCP renames did NOT persist through the sentinel stop (R9 caught it) -> NEW
  tools/ghidra_scripts/ApplySymbols.java + tools/ghidra_apply_symbols.sh mirror the curated file
  headless with a real save: 73 renamed, R9-verified x4. SETUP inventory rows (R21).
- lint_symbol_refs: scans verbatim __asm__ bodies (`.ent\tfunc_X` is invisible to \b and to the
  string-masked scan); negative-controlled (red on the pre-fix TUs, green on the passing tree).
- R22: clean extract-all 212/212 + check-all green on the final config; main rebuilt byte-identical
  143dbb89 after the last src-only fix -> 213/213; tools-health OK.
2026-09-04 15:57:06 -06:00
Drew T 375507834c docs(progress): main's R34 caveat is retired — its boundaries are independently verified now
The fleet report still printed 'caveat is R34: no independent second oracle for
a PS-X EXE'. That was true until this session; make sig-main-oracle +
audit-corpus now cover main at 0 phantom / 0 truncated / 1 explained pad-tail.
A stale caveat is the same class of false statement as a stale wall verdict.
2026-09-03 22:50:43 -06:00
Drew T f030992c67 fix(psyq_identify): read .text bytes, not objdump's rendering — 10 more objects located
obj_text_pattern parsed ONE WORD PER DISASSEMBLY LINE, and objdump collapses a
run of identical words into a single `...` line. Every collapsed word was
silently missing from the pattern, so from the first run onward the pattern was
MISALIGNED against the image and find() returned None -- printed as the
confident, wrong sentence "not linked by EXE".

Measured on 2D_BG0.o (libgs): 3 `...` lines, 520 words parsed for a 526-word
object. It was listed as absent while 507 of its 507 non-relocated words match
the EXE exactly at 0x8005080C. Any object whose .text holds a run of >=3
identical words was invisible -- to the map the entire library-linking pipeline
consumes for placement.

That is why 2D_BG0.o was never linked: not excluded by a reason, just invisible.
It sits in config/splat.us.exe.yaml under a scattered-.bss exclusion that cannot
apply to it, since the object has no .bss section at all.

Reading the section bytes and taking relocation offsets from `objdump -r`
removes the pretty-printer from the loop (R33).

MEASURED: libgs goes from 36/201 to 46/201 objects located.
2026-09-03 22:35:12 -06:00
Drew T a13b2a5c38 carve(main): 3-way -O0 island split of 800_b for func_8002C410
func_8002C410 MATCHES 299/299 at -O0 and DIFFs 228-vs-299 at -O2 (verified
independently with match_one --o0 vs --no-auto-o0). gcc-2.7.2 has no
per-function optimize pragma, so opt level is per FILE, and the function needs
its own object. Main had no path to one: the Makefile's -O0 wildcard covered
src/ov_*/ and src/md_*/ but NOT top-level src/*.c, and o0_subsplit.py is
overlay-shaped -- it died on config/splat.main.yaml, which does not exist.

Measured the scope first (R37): the -O0 detector flags exactly TWO open main
stubs -- this one, and func_80011380, which already lives in -O0 boot.c and is
the proved floor. So this unblocks one function, not a class.

FIVE COUPLED PIECES, which is why the carve is worth recording:
  1. splat code rows: 800_b cut 3 ways -- 800_b / 800_b_o0a / 800_b_2
  2. splat .rodata: span B SPLIT, because the 3-way cut put its two jtbl owners
     in different objects -- func_8002B0B4 into 800_b, func_800335B8 into
     800_b_2 -- and one code object may contribute exactly ONE contiguous
     .rodata run. The boundary is DERIVED, not guessed: 800_b.o's compiled
     .rodata is 0xf8 bytes, so the front run ends at 0x80072E44+0xf8. The
     build's own jtbl_rodata_pads caught the missing piece.
  3. src/800_b.c split 3 ways -- 86-line prologue duplicated, 3 defs before the
     island, 97 after
  4. Makefile -O0 glob widened to top-level src/*_o0?.c
  5. ld_interleave --order: 800_b_2.o inserted after 800_b.o. Missing this
     floated the tail rodata and shifted every data symbol by exactly its size,
     +0x204, across 704 two-byte runs -- which is how it was found.

o0_subsplit.py now REFUSES main loudly instead of dying on a missing file
(R43/R61a) and names the manual procedure.

VERIFIED BYTE-NEUTRAL BEFORE ANY BANKING: main builds
143dbb89f34491258bbc27810d0a12ec8b43a8dd with the split in place and
func_8002C410 still an INCLUDE_ASM stub.
2026-09-03 22:20:57 -06:00
Drew T 5399845172 feat(psyq_bss_probe): a Phase-8 link exclusion re-derived from the bytes — 3 of 4 objects are not blocked as recorded
The yaml has excluded SYS.o/GS_001.o/2D_BG0.o/VM_NO1.o from the LINKED build
since Phase 8 for 'scattered-.bss commons ... no single NOLOAD base reproduces
it'. Every word of that is true, and it does not imply unlinkable.

psyq_bss_probe derives each object's .bss bases FROM THE BYTES (for each
HI16/LO16 pair against the bare .bss section, the object's immediates give the
addend and the game's give the resolved address, so base = resolved - addend)
and then asks the unasked question: are the offset ranges DISJOINT?

  SYS.o     3,109 ins  2 bases  0x0000-0x0044 @ 0x80078830
                                0x0148-0x0150 @ 0x800c53cc  -> SPLITTABLE at 0x148
  GS_001.o    384 ins  5 bases  interleaved                 -> the genuine wall
  2D_BG0.o    526 ins  NO .bss                              -> reason cannot apply
  VM_NO1.o    305 ins  NO .bss                              -> reason cannot apply

§9.2's escape (weaken the .bss symbol, --defsym it) really cannot reach these —
a relocation against the bare SECTION has no name to defsym — and that is what
made 'unlinkable' look like the conclusion. But a section reference only needs
the section PLACED, and a section can be split.

Completeness checked before believing it (R32): the probe counts .bss refs from
EVERY section; SYS.o's .data has zero, so the two-way split covers every
reference. Placement is derived, not configured — the object is located by
masking relocated fields and requiring a UNIQUE match, which independently
reproduced SYS.o @ 0x80059234 / 3,109 ins, agreeing with both the yaml subseg
bounds and the manifest's psyq_identify count.

Incidental: src/800c.c is 100% SYS.o (its span is exactly the object's .text
size), despite the subseg comment calling it '-O2 game code'.

Cookbook §484; yaml comment corrected in the same change.
2026-09-03 22:07:31 -06:00
Drew T 867f09221c feat(oracle): main gets its independent second oracle — contract §1.3 closed
The roadmap's completion contract requires both audit oracles green before any
100% claim on main, and main had none: audit-corpus covered overlays and
resident only, and R34 is explicit that the byte gate is a perfect CORRECTNESS
oracle and a NULL COVERAGE oracle — green whether a function was sliced right
or invented, because the .s pieces paste back either way.

sig_image gains multi-range signing, closing all three blockers
docs/second-oracle.md scoped:
  * the 0x800 PS-X EXE header -> --vram-base 0x8000F800 puts file offset 0 at
    vram, so the header falls below the first range
  * interleaved data + linked islands -> --segments derives 28 game-code ranges
    from the splat yaml's SEGMENT rows
  * one text range -> the signer loops ranges, bootstrapping INSIDE each, which
    is what stops the linear partition running through a data island and minting
    functions out of it (the detector manufacturing the class it detects)

INDEPENDENCE IS PRESERVED, NOT WORKED AROUND. Ranges come from segment TYPES,
never from splat's function boundaries; entries are still found by byte-derived
jal-closure. Seeding from splat's symbols would make every phantom look real —
the trap the design doc names. .run/sig.main.jsonl (the splat-SEEDED atlas sig)
is a different file and corpus.ORACLE_SIG keeps the audit off it.

RESULT: 986 functions signed. main audit = 0 PHANTOM, 0 TRUNCATED, 1 PAD-TAIL.
Fleet audit-corpus = 0 + 0, unchanged for resident and overlays.

NEW AUDIT CLASS, from the first real finding. func_80062144: splat .s 65 ins,
oracle 64 — the extra line is a nop one line BELOW endlabel. That is an
alignment pad the matching side already emits from C (§295; two S77 wave agents
did it on func_8005E13C and func_8005D538), not a mis-slice. Lumping it with
TRUNCATED would make the oracle's first finding look like a defect and bury the
class that is one.

COVERAGE ASSERTED both ways before trusting it (R32): all 30 game-code stubs
fall inside a range, and 0 of 199 addr-parseable LINKED stubs do.
2026-09-03 21:58:17 -06:00
Drew T 46097c2339 feat(permuter_sweep): hand a wave's NEARs to the permuter, and correct §479 a second time
THE GAP: a drafting agent is briefed to STOP at a plateaued permuter-class
residual — right, since an agent grinding a register permutation burns tokens
for nothing — so every SCHEDULE-REORDER/DELAY-SLOT/REGALLOC-PERM residual lands
unattempted while the local permuter costs no tokens. In S77 the hand-off
happened only when I remembered.

THE CORRECTION THIS TOOL FORCED. §479 v2 claimed the predictor of a permuter win
was 'prior-attempt history: all 3 winners were drafts nobody had worked'.
Building the selector on that claim refuted it immediately: journal_notes
reports prior attempts for ALL EIGHT known runs, winners included (2, 3, 3).
What I had eyeballed was the DRAFT HEADER narrative, a different corpus — the
winners came from a recovery pile whose files carry no header journal. That is
provenance, not evidence.

So the tool selects on the two NECESSARY conditions only (small residual, a
match_one class the permuter can search), prints prior-attempt counts as
information, and puts the unvalidated filter behind --skip-ground, off by
default so it cannot silently discard good work (R39).

AND A BUG IN THE NEW TOOL, caught by cross-checking against known-true numbers:
wave_results globbed journals across EVERY session and did last-write-wins on a
bare function name, so an older wave's row won and carried its stale
draft_path — the sweep reported func_8002AC98 at closeness 73 and func_80015608
at 65 while both drafts measure 1 and 3. R48 inside a brand-new tool. Journals
are now read newest-last and rows are kept only when the draft lives under this
wave's directory. After the fix all seven cross-checkable residuals agree with
what the agents independently reported (9, 8, 7, 3, 3, 1, 1).

§479 now states the honest position: ~3 in 8 at <=4, no validated predictor, and
a note that a yield table is evidence while a story about why is a hypothesis
needing its own negative control before it goes in the cookbook.
2026-09-03 21:09:59 -06:00
Drew T ec258ff75a feat(recover_route): route a gate DROP to the tool that applies, and wire it into gate_main
gate_main printed ONE recovery chain for every dropped draft, and it was the
SELF chain (fix_arity_callers --any-proto + cast_self_callers) regardless of
what the clashing symbol actually was. Two of the three classes are not that
chain:

  CALLEE — §378 does not transfer; cast_self_callers reads the return type off
           the draft and cannot cast a callee, so --any-proto runs unprotected
           over every call site. S69 measured 60 decls no-protoed, binary RED.
  DATA   — neither tool in the printed chain touches a data extern at all.

Measured cost of the wrong route THIS session: func_8006252C was dropped on a
clash with itself; following the shape of the printed chain I reached for
scope_demote_drafts first, which aliased D_80078D08 through __asm__ and BROKE
the build. The real blocker was one --sync-decls away. Three tools, wrong
order, one destructive — because the report named a chain instead of a route.

A route is an ORDERED LADDER, not a prediction: for a DATA clash the choice
between adopting the TU's spelling and demoting to block scope depends on
whether the draft can live with the TU's type, which no classifier can know.
The byte gate remains the sole arbiter (G3/P9). Refusals come first (R43/R61a):
a verbatim draft and a NEAR are not declaration problems.

NEGATIVE CONTROL (R39): all 7 S77 drops whose winning tool was already known
route correctly — 2 SELF (cast_self_callers), 1 CALLEE (sync_tu_decls via a
definition header), 4 DATA — and the DATA ladder's order matches which rung
actually won in each case (sync for D_80072978, demote for D_80072960 and
D_80074818). Verbatim draft refused; real-C draft not refused.

Playbook §4b and SETUP updated in the same change.
2026-09-03 21:05:50 -06:00
Drew T 3005fc1239 fix(sync_tu_decls): a no-op sync is not progress, and a repeated symbol is not a blocker
replace_decl returned True whenever the PATTERN matched, even when the
substitution produced identical text. So a draft that already carries the TU's
exact spelling looped until --rounds ran out, spending ONE CLEAN REBUILD PER
ROUND, and then printed 'gave up after 6 rounds (6 synced)' — which reads as
six useful syncs.

Measured on func_8005FA94: 6 rounds, every one
'D_80072960 -> extern void (*D_80072960)(void *);', zero change to the draft,
five wasted rebuilds and a misleading report. R61(a): a no-op must not be
reported as work.

Two guards: no text change ends the loop naming the already-correct spelling
and saying the residual is elsewhere; and a symbol the gate names twice in one
run ends it too, since re-syncing it cannot help.

The comparison is LINE-NORMALISED because the pattern ends in \s*$ and the
substitution eats the matched line's newline — a byte compare called that a
change. Caught by a known-true check (identical/different/absent), not by
reading the code.
2026-09-03 20:38:59 -06:00
Drew T 39ac37a808 fix(gate_main): the in-TU clash pre-check must only compare FILE-SCOPE declarations
DECL is `^\s*extern`/MULTILINE, so it matched an INDENTED extern inside a
function body, and the pre-check then compared that block-scope declaration
against the TU's file-scope spelling — making the checker STRICTER THAN CC1.

Per cookbook §481, gcc-2.7.2 raises `conflicting types' as an ERROR only in the
SAME scope; across scopes it degrades to `type mismatch with previous external
decl', a WARNING the build already emits elsewhere. So a block-scope extern
cannot clash, and dropping on one refuses correct work.

Measured in a single gate: func_8001FC08 (400 ins — a deliberately renamed
MTX_8001FC08 at block scope, which is the ONLY legal fix there because two
anonymous struct typedefs in one TU are never compatible in C89) and
func_8002FF0C (166 ins — a deliberate block-scope scalar shadow of
D_800A46D2). 566 instructions of byte-correct body refused by a rule the
compiler does not apply.

_depth0() blanks brace-nested regions, string literals and comments before
DECL runs, on both the TU side and the draft side.

NEGATIVE CONTROL (R39): on a synthetic TU it keeps both file-scope decls and
excludes the block-scope, in-string and in-comment ones; on the two real drafts
it removes EXACTLY the three disputed symbols (D_80074818, D_80075018,
D_800A46D2) and leaves all 23 other declarations in each untouched.

R39 governs the direction: a check that discards good work is worse than one
that lets a failure through, and a real conflict still surfaces via the
COMPILE-conflict path plus a byte gate that cannot be fooled. R61(b).
2026-09-03 20:20:56 -06:00
Drew T f9f446449e feat(claude_wave_packs): wire neighbor_ref into every pack, and resolve its names to the source spelling
playbook §2b has called neighbor_ref the biggest measured cost lever in the
wave since S68 (~20x token swing) and documented it as a MANUAL per-card
command wired into nothing — so it ran for approximately zero cards. Packs now
carry an ALREADY-MATCHED NEIGHBOURS block, same additive never-fail contract as
the past-attempt notes. First run: 30/30 targets had a matched neighbour.

It also shipped with a defect that would have silently un-done it:
neighbor_ref reports the SYMBOL-TABLE name, and for an unnamed function that is
Ghidra's FUN_8003a0e4 — which appears nowhere in src/*.c, where the function is
func_8003A0E4. An agent sent to read FUN_8003a0e4 finds nothing and concludes
there is no neighbour. _src_name resolves against the destination TU's own text,
falls back to the address, and shows the symbol-table spelling in parentheses.
Measured: 150 of 150 neighbour names needed resolving; 0 primary names remain
Ghidra-style. Checked against known-true cases first (resolves FUN_8003a0e4,
leaves func_8003A0E4 alone, leaves an unknown name untouched).

R61(b): the pack was asserting a name true of the symbol table and false of the
world the agent works in.
2026-09-03 20:08:27 -06:00
Drew T 408f826f29 fix(blocker_probe): a verbatim draft is not a decompile
A §265 verbatim draft — the target's own asm in a file-scope __asm__ —
assembles to the bytes it was copied from, so BOTH of this tool's oracles emit
the strongest possible signal: static `none`, real cc1 `MATCH`. The routing
then reads "byte-correct body, nothing blocking it", the byte gate refuses it
for free, and progress.py moves by exactly zero.

Measured: of the 13 MATCH rows in the S77 overlay pool, SIX were verbatim
(md_MAIN_003 x3, md_MAIN_020, ov_SC05_005, ov_SC06_010). The whole 13-draft
cohort gated 0, and the probe had scoped it as the highest-value work
available.

S76 closed exactly this hole in gate_main, harvest_verify and
api_agent.prior_draft. This is the fourth consumer — and the one that SCOPES
the work, so it is the one whose blindness costs a session's plan. Uses the
gate's own detector (DP.is_verbatim_asm_draft) so the two cannot drift (R33),
and a VERBATIM row is excluded from the agreement arithmetic rather than
counted as a match.

NEGATIVE CONTROL (R39): md_MAIN_020's verbatim draft now reports
`verbatim_asm / VERBATIM (not a decompile)`; ov_SC04_018's two real-C drafts
still report `none / MATCH` exactly as before.
2026-09-03 19:16:51 -06:00
Drew T 454d3878bc fix(sync_tu_decls): a definition is a declaration; a gate refusal is not a verdict
Two defects, both found by driving the last two self_decl_tu drafts to a bank.

1. tu_decl looked only for an `extern … sym …;` line, so when the clashing
   symbol is a function the TU DEFINES it stopped with

       stopping: func_8005E480 clashes with the TU itself but src/800c3.c has
       no `extern` line to copy.

   though the authoritative spelling was in the definition's own header at
   src/800c3.c:916. This was the terminal blocker of BOTH remaining drafts
   (func_8005E3AC on func_8005E480, func_8005E79C on func_8005E804). The
   definition is now preferred over an extern when both exist — it is the one
   cc1 checks every other declaration against. Banked func_8005E3AC in one
   round. Checked against known-true cases before being trusted: definition
   path on func_8005E480/func_8005E804, extern path still verbatim on
   func_8005D734, absent symbol still None.

2. gate_main refuses outright on a dirty src/ or a red baseline and never
   reaches a per-draft opinion. The round loop matched neither DROP_RE nor
   COMPILE_RE in that output and fell through to "no declaration conflict
   named; stopping after 0 sync(s)" — reporting a HARNESS refusal as a property
   of the DRAFT (R40). Measured on func_8005E79C, whose gate was refused
   because the bank one command earlier had left src/ uncommitted. The refusal
   is now surfaced and exits 3.
2026-09-03 18:56:21 -06:00
Drew T c04d5e0093 fix(cast_self_callers): --sync-decls must emit a declaration the TU can parse
`--sync-decls` copied the draft's parameter list verbatim into the TU. A draft
names types that are not in scope where the declaration sits, and both forms
of that broke the COMMITTED baseline build in one apply:

    src/800.c:2631   extern void func_80015760(Obj_80015760 *obj, s32 *ot);
                     -> the type is draft-local; the TU has never heard of it
    src/800c3.c:866  s32 func_8005E3AC(Ctx *s, s32 size);
                     -> `Ctx' is typedef'd at line 941, 75 lines BELOW the decl

    src/800c3.c:866: parse error before `*'
    src/800.c:2631: parse error before `*'

Caught by gate_main's BASELINE RED check with no draft substituted, so the
failure was attributed to the plumbing and not to seven innocent drafts.

THE FALLBACK FOLLOWS THE TOOL'S OWN DOCTRINE. Once the call sites are cast, the
declaration emits no code; it only has to be COMPATIBLE with the definition and
PARSE. `<ret> fn();` satisfies both without naming a type, and C89 6.5.4.3
makes it compatible with a prototyped definition exactly when no parameter is
affected by the default argument promotions. So the draft's own spelling is
still preferred — it is the byte-proven behaviour and it keeps the declaration
informative — and the no-proto form is used ONLY where that spelling cannot
parse at that line. Where it cannot parse AND a narrow parameter forbids
no-proto, the tool refuses loudly and names the type (R43).

NEGATIVE CONTROL (R39) over all 40 main recovery drafts: 68 edits before and
after, 65 byte-identical. The three that changed are exactly the declarations
naming an out-of-scope type — Obj_80015760, Ctx, and Slot54/Rec14 — and no
already-correct declaration is churned.

BASELINE PROOF: with all 14 plumbing edits applied and NO draft substituted,
main builds 143dbb89f34491258bbc27810d0a12ec8b43a8dd — byte-identical. The
casts move zero bytes, as the §20 fold predicts.
2026-09-03 18:49:56 -06:00
Drew T 2312c1f557 fix(cast_self_callers): a statement keyword is not a return type
`return func_X(a0, a1);` has the exact shape of a forward declaration —
leading identifier, name, parenthesised argument list, `;` — so every
permissive "<type> <fn>(...);" regex in this tool read that CALL as a
DECLARATION. One misclassification, three consumers, two opposite failures:

  * `is_declaration`  -> `cast_sites` SKIPPED the call site, leaving the
    caller's bytes exposed to the synced (narrowed) prototype.
  * `sync_decls`      -> REWROTE the whole statement into a declaration,
    silently deleting the function's `return`.
  * `DEF_RE`          -> read the same line as "the definition itself", and
    in `draft_signature` could have handed back ret="return".

Witnessed on a dry run before anything touched src/:

    src/800.c:713
      - return func_80013154(a0, a1, a2);
      + s32 func_80013154(s16 x, s16 y, s16 step);

One shared `_kw_prefixed()` guard, called from all three sites (R33 — the
guard lives in one place, never duplicated into three regexes).

BLAST RADIUS: 524 `return func_X(...);` lines across 482 files fleet-wide.
AUDIT: no past journal records a keyword-prefixed `before`, so no committed
source was corrupted by this.

NEGATIVE CONTROL (R39), old vs new over all 40 main recovery drafts:
68 edits each, 67 byte-identical, zero false positives. The single
difference is the defect itself — the corrupting declaration-rewrite
replaced by the correct cast:

    - return func_80013154(a0, a1, a2);
    + return ((s32 (*)())func_80013154)(a0, a1, a2);
2026-09-03 18:45:20 -06:00
Drew T 9bdd2e27f7 fix(sync_tu_decls): read the post-build conflict too, and refuse a NEAR up front
Two gaps found by running it over all 16 candidates.

It only parsed the slate-load 'DROP … clashes with …' path, so five drafts
whose conflict surfaced AFTER the build as 'COMPILE conflict on `SYM'' looked
unrecoverable when they were the same class one symbol deeper. Both forms are
read now.

And a CC1-FAIL classification says the declaration blocked COMPILATION, never
that the body underneath is right: five candidates compiled once synced and
then failed the byte gate because they were NEARs (closeness 12-89) all along.
It now scores the body first and refuses a NEAR, so a gate is not spent
learning what match_one already knows (R37).

That check had the §238 bug it exists to prevent — I called match_one without
--asm-subdir, so it defaulted to asm/resident/nonmatchings/resident, judged a
DIFFERENT function, returned no verdict, and let the NEAR through. The subdir
now comes from the stub oracle. Caught only by controlling the guard against
a case whose answer I already knew.

Controls: closeness-12 draft REFUSED as a NEAR; func_80013154 still refused as
self_decl_tu with the correct redirect.
2026-09-03 17:31:35 -06:00
Drew T 14f0f91438 feat(tools): sync_tu_decls — bank a draft by copying the TU's own declarations
The dominant reason a byte-correct draft does not bank is not codegen: the
draft and its destination TU spell a shared symbol differently and gcc-2.7.2
rejects the redeclaration. gate_main's pre-check already NAMES the symbol and
which side it kept, and the TU holds the authoritative spelling — so the fix
needs no judgement. Copy the TU's extern line verbatim into the draft,
re-gate, repeat.

Done by hand this session it banked func_8005EB28 in one round and
func_8005EC00 in two, both stuck across multiple slates, both byte-identical
after. The conflicts are typically a CASCADE: banking one function gives the
TU a real definition that then contradicts the stale extern every later draft
in that TU still carries.

Refuses the self_decl_tu class loudly (the TU declares the function being
banked, so the call SITES must change too — that is cast_self_callers
--sync-decls), and refuses any binary but main, whose gate is the one that
names the symbol (R43).

Controls: on an already-banked function it reports no conflict rather than
claiming a bank; on func_80013154 it refuses with the right reason. The byte
gate remains the sole arbiter — every round ends in a real gate run.
2026-09-03 17:20:36 -06:00
Drew T dc4412b1de fix(verbatim_to_stub): refuse a §179-C epilogue-less fragment
A function with no `jr $ra` of its own falls into a sibling's shared
epilogue. gcc-2.7.2 has no sibcall/tail-merge pass and appends an epilogue to
every C function it compiles, so no C spelling can ever match — converting one
to an INCLUDE_ASM stub just puts an unbankable target into the drawable
frontier.

I did exactly that to six functions in src/800c.c, on a `rows == 1` filter
that meant "the manifest listed one row", not "this is an independent
function" — ignoring the DECOMPILE-AS-PARENT disposition whose entire meaning
is "this row is a FRAGMENT". Three drafting agents then rediscovered §179-C
from scratch, one citing the cookbook line that names its own target.

The symptom is one grep, so nobody should pay an agent to find it again.

TWO THINGS THIS COST, both caught only by testing a known-true case:
  * the first version read the function's .s — but splat stops emitting <fn>.s
    for a verbatim body, so it had nothing to read and returned False: inert
    for precisely the case it guards. It now reads the verbatim block itself.
  * my first negative control was CloseEvent, a libapi trampoline that
    genuinely has no `jr $ra` — a "false positive" that was the correct
    answer. Re-controlled on VectorNormal (verbatim, has jr $ra, guard stays
    silent) vs func_80047E58 (verbatim, no jr $ra, guard fires).

Census of main's verbatim blocks: 37 have jr $ra, 100 do not.
2026-09-03 15:02:50 -06:00
Drew T 505a50a9b6 fix(draw_waves): --main was a no-op; every mixed draw saw ZERO main functions
bins is built from src/* DIRECTORIES, and main has no src/main/ — its TUs are
top-level src/*.c. So "main" was never in the list, and the filter that keeps
it could only ever preserve a "main" already present. --only-main worked
solely because it overwrote the list; --main contributed nothing, in every
mixed draw this project has ever run.

The tool meanwhile printed "main: refusing 49 LINKED subseg(s)" whenever
--main was passed, so it announced it was handling main while main was never
iterated. A flag that changes nothing is worse than a missing flag: it
answers the question you asked.

Measured: 0 -> 55 main stubs reach the pool. This is why S76y's 47 main
targets had to be assembled by hand from corpus.stubs — the draw could not
see the actual frontier. Coverage is now ASSERTED (R32): --main with zero
main stubs exits 4 and names itself a defect rather than reporting an empty
population as a fact.
2026-09-03 14:47:46 -06:00
Drew T 1778556b6d fix(draw_waves): a ledgered stub that is still OPEN is still work
After two S76 draws the tool reported 'population: 0 open stubs' with 51
open stubs on disk. True, and about a scope far narrower than the reader
believes — the session's dominant defect class. The draw ledger records what
was ATTEMPTED, not a property of the function, so a stub still open after
being drawn (the draft was never gated, or the blocker has since been fixed)
was filtered forever while the work remained.

This is the S72 exclude-list lesson in a second place, and the fix is the
same shape: --redraw-open includes them, and the population line now always
names how many were filtered for that reason alone, saying explicitly when
an empty pool means 'the ledger has seen them all', not 'the frontier is
empty' (R41 — a number ships with its denominator).
2026-09-03 14:26:57 -06:00
Drew T 63d9a36f8d fix(rtu_match): route the reorder-island TUs through as -O2, like match_one
The fourth copy of one defect. The Makefile pipes REORDER_TUS through
reorder_passthrough.py into as -O2; rtu_match hardcoded maspsx + as -O1, so
for those TUs it reported a phantom +1 epilogue instruction and
recover_integration --probe-only booked it as a real DIFF.

Found by a drafting agent on func_8005D4B8: the already-fixed match_one said
MATCH 14/14 while rtu_match said 15/14, and the agent correctly identified
its own oracle as the liar rather than the draft. Derived from the Makefile,
never copied (R51).
2026-09-03 14:25:26 -06:00
Drew T 9df4ae32f6 fix(api_agent): never warm-start a pack from the target's own assembly
Third door of one defect, and the one that mattered. A §265 verbatim body is
stored as <fn>.c like any draft, so prior_draft offered it under 'a previous
attempt left this body behind, keep what matches' — an invitation to
resubmit it. match_one then says MATCH, the gate goes green, nothing is
decompiled.

Measured today: gate_main banked 9 such bodies with progress.py moving by
exactly zero; harvest_verify had no guard at all; and with BOTH gates fixed,
two relaunched agents (func_8005E79C, func_8005EAC8) STILL returned verbatim,
because the pack handed it to them and they reasonably reported 'the prior
draft is already MATCH closeness 0'. It is — that is the problem. Fixing the
consumers is not the same as fixing the supply.

Verified on func_8005EAC8: 2 verbatim candidates now rejected with a named
reason (R32, never a silent drop) and the warm start falls back to a real C
body from wave_m05/shard31. Shared by claude_wave_packs, so every future
Claude wave gets it too.
2026-09-03 14:20:44 -06:00
Drew T 186a8b1548 fix(match_one): model the reorder island, not maspsx, for its four TUs
REORDER_TUS := 800c2 800c2_2 800c2_3 800c3 are piped through
reorder_passthrough.py into as -O2 by the Makefile — the mode that fills
delay slots and emits the jr/addiu epilogue. That island landed 2026-09-01
and banked 20 functions. match_one, the oracle every drafting agent scores
against, still compiled those TUs through maspsx + as -O1, so it reported a
phantom LENGTH-DRIFT in the epilogue and an extra instruction.

Measured on one plain-C draft of func_8005ECC0:
  maspsx + as -O1   closeness 5, 36 ins vs 35   'the §188 wall'
  reorder + as -O2  closeness 2, 35 ins vs 35   epilogue identical

Cost, in the S76w wave alone: seven of eleven main agents produced correct C,
saw the phantom tail, correctly identified the §182/§188 shape, consulted
oracle_reorder.py — which told them 'file IMMOVABLE, no C-level work can ever
close it' — and each submitted a §265 verbatim-asm body instead. They all
reasoned correctly from a false premise the knowledge base gave them.

The TU list is DERIVED from the Makefile, never a second copy (R51 — a
derived property stored as config goes stale, which is this defect exactly).
oracle_reorder.py's docstring is corrected and the cookbook carries the
§182/§188 correction with the byte evidence.
2026-09-03 14:17:53 -06:00
Drew T dcbcb04bf1 fix(harvest_verify): refuse a verbatim-asm draft, same as gate_main
One defect, two doors. gate_main gained this refusal earlier today after 9
main functions round-tripped verbatim -> stub -> verbatim and 'banked' with
progress.py moving by exactly zero. The S76w wave then produced verbatim
submissions for md_MAIN_003 and ov_SC06_010 — which reach the tree through
harvest_verify, not gate_main, so the guard I added would never have fired
on them.

This is the §442/S74 sibling-provisioner lesson again: a fix made in one of
two paths is a fix in neither. Both gates now call the same
draft_prechecks.is_verbatim_asm_draft, and harvest_verify SKIPs with a named
reason rather than silently dropping (R32/R43).
2026-09-03 14:14:11 -06:00
Drew T 9ab0d9eb67 fix(gate_main): refuse a verbatim-asm draft at slate load
I converted 9 main SDK functions from §265 verbatim bodies to INCLUDE_ASM
stubs so they could be decompiled, then 'banked' all 9 from stored drafts
that were those same verbatim asm blocks. match_one printed closeness 0 nine
times and the whole-binary gate went BYTE-IDENTICAL — both truthfully, since
a raw asm blob assembles to the bytes it was copied from. Nothing was
decompiled. progress.py caught it by not moving: REAL 882, VERBATIM 164,
INCLUDE_ASM 37, identical before and after. The banks are reverted.

The cookbook's closing paragraph, written last session, describes this exact
trap. I read it and hit it anyway ~4 hours later, because the rule was
addressed to 'any burst over this class' and I was hand-picking stored
drafts, and because 'no byte gate can catch it' reads as unpreventable. The
byte CHECK cannot; a slate-load refusal can.

draft_prechecks.is_verbatim_asm_draft: a file-scope __asm__ naming the fn via
.ent/.globl/label AND no C definition of it. Both spellings of .ent handled
(inside a C string it is a backslash-t, not a tab — five censuses of this
class disagreed until that was fixed). gate_main refuses such a slate beside
its existing INCLUDE_ASM no-op refusal (R43).

Census of the draft store: 1,099 of 704,375 .c files are verbatim-asm drafts
under ordinary <fn>.c names. Negative control: 0 false positives across
45,898 drafts carrying both a C definition and an inline __asm__ (R39).
2026-09-03 13:20:19 -06:00
Drew T 9992cab319 refactor(main): convert the last 9 DECOMPILE-NOW verbatim bodies to stubs
The 9 SDK functions the verbatim manifest marks DECOMPILE-NOW in src/800c3.c
and src/800c2_2.c were §265 verbatim __asm__ blocks: byte-identical by
construction, undecompiled, and unreachable by every gate in the project,
which splices a draft in place of an INCLUDE_ASM line these did not have.
splat also stops emitting <fn>.s for them, so they had no target asm to
match against either. Byte-neutral: main still builds 143dbb89.

verbatim_to_stub refused three of them — src/800c2_2.c has no sibling
INCLUDE_ASM to copy the subdir spelling from, and all three of its remaining
functions are verbatim, so the file can never grow the sibling the rule
wants. The tool that exists to reach unreachable functions could not reach
them. It now DERIVES the spelling and proves it: the prefix from this
binary's other TUs, the last component from the file stem, which must appear
as a "c" segment in the binary's own splat config — the same file that
decides where splat writes the .s. Still refuses when either half is
unproven; --asm-subdir is the explicit override.

The S75 checkpoint recorded this group as "20 of 21 banked, one bisection";
counted from src/, it is 9 outstanding, corroborated by an independent count
from config/verbatim_manifest.json.
2026-09-03 13:05:11 -06:00
Drew T 08d49c1715 feat(tools): gate main's slates in parallel worktrees, arbiter unchanged
gate_main is the only trustworthy EXE verifier and is strictly serial: one
flock, one tree, a full clean rebuild per bisect step. The serialization is
an artifact of the SHARED TREE, not of the verification, so this runs the
REAL gate_main inside N git worktrees and hands the union of what they prove
to ONE authoritative gate_main in the real tree. Workers discover; only the
final serial pass banks. Two chunks that each pass alone can still fail
together, which is exactly why that pass exists (G3 — the arbiter never moved).

The non-obvious hazard is asm/: parallel_gate symlinks all 442 MB because an
overlay gate only reads it, but main's verification RUNS make extract, which
writes it. main owns 6.2 MB of that, so this copies main's subtree per worker
and symlinks the other 214 binaries read-only.

Two defects the negative control caught, both mine:
  * .run/obj40 (11 MB of SDK objects) was never provisioned. The Makefile says
    a tree without them 'builds byte-identically via the stubs'; that is no
    longer true for main, whose decompiled src/800_c.c CALLS CdReadyCallback —
    the link failed outright with an empty build/psyq/.
  * make_worktree reads parallel_gate's module-level WT_ROOT, so the first run
    put its worktree in .run/pgate/wt0 — the slots parallel_gate force-removes.

Measured: one gate cycle is 16s in both trees, so MAX_STEPS=24 is ~6.4 min
serial and ~90s across four workers. The docstring's original '1-2 min per
step' was my assertion, not a measurement, and is corrected in the file.
2026-09-03 13:00:16 -06:00
Drew T d564b4b4e7 feat(gate_main): persist every proven verdict the moment it exists
try_batch is stateless and the bisect loop held `good` only in memory,
writing .run/gate_main_banked.json once at the very end. A 34-minute
bisection killed by a timeout, a Ctrl-C or a supervisor therefore lost
every match it had already PROVEN — and each of those proofs cost a full
clean EXE rebuild. The S75 checkpoint named this the single highest-value
gate improvement available.

Adds an atomic .run/gate_main_progress.json written after every verdict,
and a resume that reuses it. Three guards, each a way it could silently
lie: the journal must belong to this slate; entries are re-keyed against
`kept` so a draft dropped by resolve_conflicts cannot sneak back; and the
draft's content hash must still match (R56 — a verdict measures those
bytes). Resumed sets are re-verified as one batch anyway, so a wrong reuse
costs one rebuild and can never bank anything unproven. --no-resume opts out.

Negative-controlled on six cases incl. a changed draft, a foreign slate and
a half-written journal.
2026-09-03 12:52:09 -06:00
Drew T fc7caf599b refactor(tools): retire asm_in_c.py — the taxonomy is DATA now, not a regex census
R33, "the best outcome is a DELETED SCANNER, not a fixed regex". asm_in_c.py
existed to DISCOVER the §265 verbatim class by parsing __asm__ blocks. That job
is done, and regex was the wrong instrument: five successive censuses returned
116 -> 112 -> 108 -> 178 -> 199, and the classification was worse than the count
-- it called 154 rows "game code" where the authoritative answer is 24.

The real answers came from evidence a regex cannot see:
  * the <OBJ>_OBJ_<hex> naming key -- every one is placed_object.text_start +
    hex, so those symbols are OFFSETS INTO LIBRARY OBJECTS, not functions;
  * the PsyQ archive symbol tables in .run/obj40/, which keep statics as W
    symbols, so for a byte-identical object the archive IS the function map
    (checkRECT = SYS.o+0x52C = func_80059760, and NONE of the 44 SYS_OBJ_*
    symbols in SYS.o is a function).

So:

config/verbatim_manifest.json (NEW, committed) -- the authoritative census.
200 rows, derived once from the ROM image + archives + naming key, each with a
class and a DISPOSITION:
    PERMANENT-VERBATIM   69 rows / 57 units   hand asm; never decompilable
    DECOMPILE-AS-PARENT  57 rows / 23 units   a FRAGMENT; decompile unit_entry,
                                              never the fragment itself
    DECOMPILE-NOW        41 rows / 41 units
    DECOMPILE-LOW-VALUE  20 rows /  4 units
    UNCERTAIN             5 / NOT-VERBATIM 7 / NOT-CODE 1

tools/verbatim_check.py (NEW) -- a GUARD, not a census. Detects verbatim bodies
(the cheap part, and the only part regex is good at), diffs the NAMES against the
manifest, and reports NEW / GONE / MOVED. A NEW row means someone banked assembly
and it is about to become invisible work; it is never allowed to inherit a
disposition by default. It deliberately does not classify or count units.
Compares case-insensitively on the hex, because an address is a NUMBER (R48).

tools/verbatim_target_s.py -- put on the MANIFEST LEASH. It used to enumerate
every verbatim SYMBOL, and 62 of those are not functions (fragments, bare
epilogue tails, padding, trampolines). Emitting per-symbol targets for them is
what sent two drafting bursts at things no C function can express. It now takes
only DRAFTABLE dispositions: 66 targets emitted, 134 skipped and SAID SO.

tools/verbatim_to_stub.py -- repointed to verbatim_check for detection, so there
is ONE detector in the tree rather than three copies.

tools/asm_in_c.py -- REMOVED.
2026-09-03 12:05:37 -06:00
Drew T 254feb8ee4 fix(gate_main): the uncommitted-work guard belonged OUTSIDE the bisection loop
I added the guard to try_batch() an hour ago. try_batch runs REPEATEDLY during
bisection, and its own first substitution makes main's TUs dirty -- so on
iteration two the guard could not tell the operator's unsaved work from the
gate's own in-flight edit, and aborted the run:

    M src/800c3.c
    gate_main: aborting with an UNVERIFIED substitution in main's TUs — reverting

It failed safely (reverted, no bank lost, and said so), but it made the gate
unusable for any batch larger than one.

Hoisted to assert_main_tus_clean(), called ONCE from main() before any
substitution. The lesson is worth the line it costs: A GUARD MUST BE ABLE TO
DISTINGUISH THE STATE IT PROTECTS FROM THE STATE IT CREATES. Placed inside the
loop it was checking its own footprints.

Negative-controlled both directions: a genuinely dirty src/800c3.c is refused by
name before anything is substituted, and a clean tree now proceeds into the
bisection (currently running 21 drafts).
2026-09-03 11:37:07 -06:00