- docs/commit-map.tsv: 4,032 rows (ordinal of the ORIGINAL main -> rewritten hash, author/committer dates, subject);
1 pruned row of zeros (ordinal 1712, "session archive update"); 0 old hashes asserted; ordinal 1 unchanged by the
rewrite (byte-identical)
- resolve_tokens: 1,238 commit:NNNN tokens -> shortest-unique new hashes in 98 files (docs, phase-ends, logs, tool
docstrings, 2 C comments, the A5 evidence logs); residue left as tokens: commit:1712 x4 (the pruned commit),
commit:orphan-24 x2, commit:orphan-26, commit:orphan-35 (cited commits that exist in no lineage)
- the rewrite (C4): filter-repo 2.47.0 on a bare clone of the C2 tip, 311 s, exactly 1 pruned, main 4,032 -> 4,031;
the pre-rewrite history is mirrored in the private archive repo and in the local bundle
- the proof (C5): verify_rewrite 4,031 pairs / 0 failures; absent_scan 0 offenders; gate_scan 0 offenders on the clone
- adoption (C6): 100 text files differ at the tip, 0 purge paths, 0 added/deleted; leftover refs dropped; no gc yet
- resolver skips tools/public_rewrite/ (its self-test fixtures are the token grammar, not citations); repo-local
identity is the GitHub noreply address from here on; CURRENT_PHASE: C4–C7 logged, checkpoint -> NEXT = C8
- hash_dict (4,420 commit objects -> commit:NNNN / twin / orphan; 150,280 prefixes; 0 ambiguous; 0 collisions with 1,480
cited content hashes; scratch mailmap), scrub (12/12 self-test; HEAD sample 731 distinct tokens == git's own lookup),
gate_scan (+ expected_offenders.txt fixture: the R39 negative control, PASS over 16.8 GB in 2 m 25 s; rom_blob_ids =
content/signature hits U purge-path blobs not shared with any other path), run_filter (module API; --force only for the
deleted tag), verify_rewrite (pairwise proof + no purge path survives + pruned set == derived purge-only set),
build_commit_map (0 old hashes asserted; unchanged commits exempted), resolve_tokens, absent_scan (positive control on the
current repo: FAIL 82,362), probe_github.sh (skips unchanged commits)
- trial #1 found two defects the plan had not foreseen: the EMPTY blob in the strip list (--strip-blobs-with-ids then
undid every "file emptied" change in history and pruned a restore commit) and the byte-identical Initial commit keeping
its hash; both fixed with controls
- trial #2: filter 269 s, exactly 1 pruned, verify 4,029 pairs / 0 failures, map 4,030 rows, absent_scan + gate PASS on
the clone, 1,231 tokens resolved at a trial tip (residue 7: the pruned commit x3, orphans x4), aggressive repack 500 -> 80 MB
- SETUP P33 C1 section + rows (R21); runbook §3/§5/§6/§10 measured; requirements-python.txt; CURRENT_PHASE -> NEXT = C2
- audit_public: purge paths from purge_set.txt (the C1 rewrite's own input) + a DERIVED ROM-hash set (1,801 manifest
rows + 218 check.*.sha + redump Track 1) + 50 MiB cap; controls: the current tree FAILS naming exactly the purge set
(255 rows), a clean subset OK, a renamed EXE copy caught by content; zero-length files exempt (the empty-file SHA1 is
also the zero-length SC04/SC05 FILE_029/1.6 payloads')
- compile_only: Makefile flags parsed at run time; TUs from <alias>_SRC_DIR with nested-binary pruning; skips derived
(70 LINKED tiles, 47 INCLUDE_ASM TUs), -O0 TUs compiled at -O0; PR scope 54/54 in 1.6 s; fleet 4,170/4,170 in 123 s
at -j32, failed 0; unknown alias refused (R43)
- no-rom.yml: audits (8 commands, each re-run under the system python without the venv -> rc 0) + compile-only (apt
binutils-mipsel + cpp-mipsel-linux-gnu, cc1 from the sha256-checked tarball, maspsx submodule; PR scope on push/PR,
--all weekly + dispatch); the audits job is RED until C3 by design
- SETUP P33 B7 section + 4 inventory rows (R21); CURRENT_PHASE log + checkpoint -> NEXT = B8
- ImportAnnotations.java: the S86 OSGi-bundle blocker was 3 javac errors (Long->int unboxing x2, a nonexistent
LocalVariableImpl ctor -> VariableStorage); "/undefined" resolves to DataType.DEFAULT (it lives in neither type
manager — main's first proof passed the cmp with failed=13 because the plate-comment rows had set the same function
comments); ghidra_rebuild.sh now dies unless the import printed failed=0 (R49), writes .proof markers
- ghidra_annotations_delta.py: analysis drift measured and encoded as three counted classes — Error/Analysis bookmarks;
auto-named DEFAULT functions the rebuild did not create (29 in main's LINKED regions); auto-named rows lagging the curated
symbol file (10 sep8 + 5 aug31, R15). Result: main 38 hand-authored rows (13 annotated fns incl. 3 the ELF does not
define, 22 comments, 3 labels); resident/overlays/protos container rows only; the DB holds no hand-authored types
- controls (R39): mutated block row -> PROOF FAIL; synthetic comment/bookmark/label/signature round-trip -> PROOF PASS
twice (idempotent); the filter keeps the synthetic rows and a hand-renamed name-only diff; fake failed=2 refused,
resident re-proven; roster --check controls both ways
- proofs, all PASS failed=0: resident 65s, ov_SC01_077 169s, ov_SC06_018 173s, SLUS_007.26 210s, sep8 202s, aug31 206s
- tools/ghidra_roster.py -> config/ghidra/ROSTER.md (--check in tools-health, ignores the per-machine proof column)
- .claude/settings.json hooks $CLAUDE_PROJECT_DIR-relative; ghidra_mcp_start.sh is a silent exit 0 without Ghidra or
the project (both controlled); SETUP P33 B5 section + 5 inventory rows + §2.8 (R21); CURRENT_PHASE log + checkpoint
- main:func_80032A74 (422 ins, closeness 1): the residual is ONE reload-time slot at sp+0x48 (u16 draft = 422/422 code, DIFF 22 frame
rows; s16 draft = DIFF 1 at idx 244 lh vs lhu). Every post-parameter slot producer enumerated from gcc-2.7.2 (reload1.c:658 ghost
alter_reg / caller-save.c:249 area / reload1.c:879 invalid-equiv / reload1.c:3499 spill_stack_slot) and refuted on the bytes: combine's
newi2pat ghosts re-derive a narrow load and the site is lhu (no lb, no double load); a save area without sw/lw needs sched.c:4962
staleness and no register-only insn shares a block with a call; $t0 holds no pseudo; LO mult results retry into GR_REGS. Verdict
PROVED at 1 (pin kept with the verdict; ledger WALL-PROVED).
- NEW mechanism measured: local-alloc.c optimize_reg_copy_2 (tmp = x; tmp op= c; x = tmp) mints a ghost with stale refs, but after
regclass -> GR_REGS, allocated, no slot (P13/P14). 18 isolated reproducers, 0 draft variants; cookbook §501-M; accelerators (12).
- tools/ghost_census.py (new): ghosts in a .lreg dump with their class (ST_REGS => slot). tools/cc1_dumps.sh: prints the .frame line,
ins count, spill lines and the census; the under-counting standalone-(use) grep is gone. SETUP rows (R21).
- tools/backlog.py: load_best kept the EARLIEST record at equal closeness (docstring said latest) — the S84 row never rendered; fixed.
- CURRENT_PHASE.md: S84 log + refreshed 🛑 checkpoint (rows (b)(c)(d) next, then T5). No src/config/carve change; fleet check-all
218/218 rc 0 at the S84 preflight.
- 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
- 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).
- 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
- 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.
- 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)
- 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
- 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
- _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.
- 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
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).
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.
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).
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 🛑.
- 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.
- 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.
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.
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.
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.
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.