Commit Graph

31 Commits

Author SHA1 Message Date
Drew T c716eb15e1 feat(phase-15): T4 — fleet progress roll-up + fix cross-report double-count
- progress.py: refactor to report(binary) + set_binary() + a per-binary .s index
  (fleet run 6m38s -> 7s); add --fleet -> docs/progress.fleet.md (deterministic,
  source-derived per-binary table + fleet totals). Single-binary output byte-unchanged.
- fleet now: 947 REAL / 959 LINKED / 13132 byte-identical / 344010 matchable = 3.82%
  across 136 binaries (main + resident + 134 overlays)
- dup_report --cross: HONESTY FIX (R14) — an onboarded overlay is both a named BINARIES
  entry AND a .run/sig.ov_* glob hit; it was counted twice, inflating collapsible bytes
  to 58.7M once the fleet was onboarded. Dedupe by alias -> accurate 9366 h_exact groups
  / 28.6M collapsible (matches the Phase-11 figure)
- Makefile: wire progress.py --fleet into make report (main-only block)
- docs: progress.fleet.md (new), duplicates.cross.md regenerated, SETUP inventory
- make report green; dedup-check 8 validated / 0 failed
- ghidra/ churn NOT staged (R23)
2026-06-16 20:00:19 -06:00
Drew T 0c7619231d feat(phase-15): T2 — dedup_propagate.py (match-once -> propagate-many) proven on wave
- tools/dedup_propagate.py: lift a matched body -> DEFINE_func_<ADDR>() macro in
  src/shared/engine_core.h -> instantiate in place at every onboarded overlay sharing
  that h_exact -> snapshot + per-overlay byte-gate (fail-closed revert) -> register in
  config/dedup.us.yaml. Keyed by addr-int (sig lowercase vs splat func_%08X).
- proof: 4 medium funcs propagated across all 16 onboarded overlays; clean check-all
  18/18 byte-identical; dedup_integrate 8 validated/0 failed; idempotent; negative test
  (corrupt shared body) -> make check FAILS then restores
- Makefile: header-dependency tracking (cpp -MMD -MP + -include ) so editing a
  shared header recompiles dependents — closes a stale-build/false-pass gap the negative
  test exposed; output-neutral (R22 clean 18/18)
- structural self-check: a leftover INCLUDE_ASM stub is byte-identical, so the byte-gate
  can't catch under-application -> assert DEFINE present + stub gone
- FINDING: 577 of ov_SC01_077's 785 matches are h_exact across all 134 overlays
  (2.19 MB collapsible, already matched) -> T5 = bulk-propagate these + harvest the rest
- cookbook §14 (the propagation runbook + gotchas), SETUP tool inventory
- ghidra/ churn NOT staged (R23); build/asm/.run gitignored
2026-06-16 19:24:08 -06:00
Drew T e2200e17db feat(phase-13): T5 — make build-all / check-all (fleet PASS/FAIL)
Recursive $(MAKE) BINARY=<b> over $(BINARIES) (correct per-binary OBJS prune,
not foreach), aggregated to one PASS/FAIL + per-binary .run/check.<b>.log.
check-all: 6/6 [OK] exit 0; negative control (corrupt sha) -> [FAIL] + nonzero.
2026-06-16 17:07:57 -06:00
Drew T 144839f24b feat(phase-13): T1a/T1b — SC01/077 onboarded + all-asm byte-match (d19c9580)
- ov_SC01_077 scaffolded: config/overlays.mk (OVERLAY_BINARIES + var block),
  config/splat.ov_SC01_077.yaml (from template), check.sha, empty symbols, +
  ov_SC01_077 entry & sentinel anchor in the 4 Python BINARIES dicts
- GATE T1b: make check BINARY=ov_SC01_077 -> d19c9580 BYTE-IDENTICAL @ 100%
  INCLUDE_ASM; clean rebuild leaves main 143dbb89 + resident 8e17e02f unregressed
- reusable non-word-aligned-overlay handling (~75% of fleet) added to the
  template + Makefile: (1) [word_floor, bin, trailing] carve for the final 1-3
  bytes spimdisasm drops; (2) build/assets/%.o incbin rule (+ .data align=1) for
  splat bin assets (asset_path scoped per-alias); (3) objcopy end-align TRIM
  (shrink-only, <=3 B) removing the .ld's segment-end ALIGN(.,4) pad
- .gitignore /assets/ (regenerable splat output); clean removes assets/
2026-06-16 15:39:52 -06:00
Drew T 50c5c9ea33 feat(phase-13): T0 — overlay splat template + overlays.mk indirection
- config/splat.us.overlay.template.yaml: reusable flat-blob splat template for
  location overlays (@TOKEN@ placeholders), modeled on splat.resident.yaml; code
  at file 0x0 (no leading-rodata trick), stacks symbols.resident.txt (overlays
  call the engine), vram 0x80128158
- Makefile: -include config/overlays.mk + BINARIES := main resident
  $(OVERLAY_BINARIES); the generated overlays.mk keeps new_overlay.sh out of the
  hand-maintained Makefile body; silent when absent (fresh-clone-safe)
- GATE T0: clean rebuild main 143dbb89 + resident 8e17e02f BYTE-IDENTICAL
  (indirection proven a no-op)
- phase-ends/CURRENT_PHASE.md: Phase-13 task log (plan approved gate 1)
2026-06-16 15:19:49 -06:00
Drew T f672c709c1 feat(phase-11): T6 — sign the 134 overlays; cross-report spans 136 binaries (milestone report half)
- sig_image overlay discovery: linear partition (split contiguous code at jr+delay boundaries) +
  detect_code_end (first run of invalid instrs = the code->data transition; overlay code decodes
  ~100% valid, data drops to 43-95%). BFS dead-ended — overlays dispatch via function-pointer
  tables, not jal — so call-graph BFS found ~2 fns; linear partition recovers the whole code prefix
- Makefile: sig-overlays: signed 134 overlays -> .run/sig.ov_*.jsonl signs all 134 SCxx 0.4.dec @ 0x80128158 -> .run/sig.ov_*.jsonl (27s)
- dup_report --cross: ingest the overlay sigs (sig.ov_* convention) + main/resident; condensed
  source header (overlay set-sha for reproducibility); cap top-200 per subsection + state totals;
  compact member sample + #bin column
- RESULT (docs/duplicates.cross.md): h_exact 9366 cross-binary groups / 28.5 MB collapsible; h_norm
  8957 / 38.3 MB. Top group = a 770-instruction function BYTE-IDENTICAL in all 134 overlays (one
  match credits the whole fleet) — 'one match unlocks many' quantified; the Phase-12/13 work queue
- dedup-check green; committed per-binary digests byte-stable; report deterministic (sorted glob,
  no Date/random); the EXE<->resident pair still shares nothing (overlay<->overlay dominates)
2026-06-16 01:26:49 -06:00
Drew T b06d74c1be feat(phase-11): T2 — dedup.us.yaml registry + dedup_integrate byte-honesty validator
- config/dedup.us.yaml: cross-binary code-share registry (group -> {id,tier,hash,
  source,func,members[{binary,vram,name}]}); empty for now (T3 adds the first group)
- tools/dedup_integrate.py: --check validator. Fail-closed if a shared function's
  current sig hash drifted from the recorded hash (a stale share can never silently
  mislead, P9/G3); unsigned binary -> WARN (unvalidated), not a hard fail
- Makefile: dedup-check is the fail-closed last line of `make report` (gated BINARY=main)
- DEVIATION D1: game-code dedup is SOURCE-LEVEL (shared body in src/shared/<fn>.h,
  instantiated per site), NOT a psyq_integrate object-swap — game funcs are interior to
  one object per binary so the linker can't swap them; the byte-gate is the existing
  per-binary make check. .ld interpose stays the library mechanism (Phase 8)
- verified: --check passes on empty registry; make report runs it; clean rebuild main
  -> 143dbb89 BYTE-IDENTICAL (T2 is a proven build no-op, R22)
2026-06-16 00:47:20 -06:00
Drew T 7cc2911456 feat(phase-11): T1 — cross-binary dup_report --cross → duplicates.cross.md
- dup_report.py: additive --cross mode ingests all BINARIES sigs, tags each row
  with its binary, buckets by h_exact then h_norm across binaries, splits
  cross-binary (binaries>1, the Phase-12/13 work queue) vs intra-binary, ranks by
  collapsible bytes (count-1)*nins*4, one row per member -> docs/duplicates.cross.md
- Makefile: make report emits the cross report once (gated BINARY=main)
- per-binary duplicates.md/.resident.md stay byte-stable (per-binary path unchanged)
- EXE<->resident cross-binary groups = 0 (4.0 vs 4.7 + different roles, as the
  byte-finding predicted); real cross groups arrive with overlays (T6)
- phase-ends/CURRENT_PHASE.md: Phase 11 plan + per-task log
2026-06-16 00:37:18 -06:00
Drew T 03978ec4ba feat(phase-10): T4 — resident Ghidra program (2nd binary) + symbol seed + sig
- tools/ghidra_import_raw.sh (NEW): raw-blob importer (BinaryLoader + --loader-baseAddr +
  PSX:LE:32:default) — the Gen2 counterpart to ghidra_import.sh (PS-X-EXE only); reusable for
  Phase-13 location overlays. Imports the resident blob as program 'resident' @0x800CEDF8.
- tools/ghidra_scripts/DefineFunctions.java (NEW): seed splat's validated entry points
  (.run/<prog>_funcs.txt) — raw-binary auto-analysis finds only the reachable subset (23/143);
  this defines all 143 (created=120/existed=23/failed=0). R9-verified 143 funcs persisted.
- FINDING: DetectPsyQ reports the resident is PsyQ 4.7.0 (the EXE is 4.0.0); the lone in-range
  PsyQ-signature hit is DsMix (libsnd -> resident holds the sound driver). 4.7 .LIBs DEFERRED to
  Phase 11 start (Drew); carried to PhaseEnd Notes. Phase 10 needs nothing from 4.7.
- config/symbols.resident.txt: seed DsMix (R13 candidate, 4.0-sig vs 4.7 blob — confirm in Phase 11);
  stacked under symbols.us.txt, applied on re-extract -> resident still 8e17e02f BYTE-IDENTICAL (R22),
  main still 143dbb89 (no regression)
- Makefile: per-binary GHIDRA_PROG -> 'make sig-refresh BINARY=resident' (.run/sig.resident.jsonl)
- dup_report.resident now real (6 byte-identical intra-resident groups — Phase-11 dedup fodder)
- ghidra DB committed (R23, MCP stopped): new resident program 00000003.* (+ main db.15->16 no-op)
2026-06-15 23:25:00 -06:00
Drew T cc73951128 feat(phase-10): T3 — per-binary reports/diff/expected for the resident binary
- diff_settings.py + progress.py/difficulty.py/dup_report.py: add the `resident` BINARIES entry
  (build/resident/, config/check.resident.sha, src/resident, asm/resident/nonmatchings, per-binary docs)
- dup_report.py: degrade gracefully when the Ghidra sig (.run/sig.resident.jsonl) is absent —
  write a placeholder + exit 0 instead of crashing the whole `make report` (sig is a T4 export)
- progress.py: scope linked_subsegs() to BINARY==main — PsyQ library linking is the EXE's layout
  (Phase 8, gated ifeq BINARY,main), so a second binary has 0 LINKED (was: listed main's libs)
- Makefile expected: per-binary-safe — drop `rm -rf expected/build` (clobbered every sibling
  baseline); refresh only the active binary's image dir + merge-copy (cp never deletes siblings)
- reports: resident = 0 REAL / 143 INCLUDE_ASM stubs / 100% stub (correct); main UNCHANGED
  (52 REAL / 959 LINKED / 50.24%); both expected/ baselines coexist (verified)
2026-06-15 23:06:25 -06:00
Drew T 95ade84768 feat(phase-10): T1 — scaffold the resident second binary (Makefile no-op on main)
- Makefile: BINARIES += resident; resident_* var block (vram 0x800CEDF8, build/resident/,
  config/*.resident.*); per-binary ASM_DIR/SRC_DIR + $(BINARIES)-derived OBJS prune-list so
  main (asm/+src/) and resident (asm/resident/+src/resident/) object sets stay disjoint;
  per-binary UNDEF_SYMS/UNDEF_FUNCS (resident writes under build/resident/)
- config/splat.resident.yaml: flat blob config (NO header, NO gp_value, single c seg @
  vram 0x800CEDF8, stacked symbols [symbols.us.txt, symbols.resident.txt], per-binary
  asm/src/build/undefined paths, EOF 0x5935C -> end vram 0x80128154)
- config/check.resident.sha (8e17e02f… = extracted MAIN.CD/FILE_010/1.1, 365,404 B, type-1)
- config/symbols.resident.txt (R13/R15 provenance banner; seeded empty, populated in T4)
- gates: (a) main clean rebuild 143dbb89… (refactor is a no-op on the byte-locked EXE);
  (b) make extract BINARY=resident -> 144 INCLUDE_ASM stubs + .ld @0x800CEDF8;
  (c) main OBJS=81 excludes resident even with resident on disk + main stays 143dbb89…
- src/resident/*.c left WIP-untracked (reshaped by T2 boundary carving)
2026-06-15 22:50:55 -06:00
Drew T 5e0d80a887 refactor(phase-9): T7 — ld_interleave.py --front/--tail; gate extract call to BINARY=main
- ld_interleave.py: argparse [ld] + --front/--tail (repeatable; default to the EXE's
  sandwich objects, transitional); the .data->.rodata->.data island is EXE-specific
- Makefile extract: ld_interleave call wrapped in ifeq(BINARY,main) (overlays have no
  rodata island) and passes --front 53198.data.o --tail 6324C.data.o explicitly
- GATES: positive extract+build -> 143dbb89 (tail_data=1 from the threaded flag);
  negative --tail BOGUS.data.o -> 'tail data object not found' exit=1 (threaded);
  restore -> 143dbb89
2026-06-15 20:49:45 -06:00
Drew T 22b20e8f31 refactor(phase-9): T6 — --binary selector for dup_report + difficulty; report threads BINARY
- dup_report.py + difficulty.py: argparse --binary (default main) + BINARIES table
  (main = current sig/src/asm/md/csv paths); overlay src/asm subtree deferred to P10
- Makefile report target threads --binary $(BINARY) to progress/difficulty/dup_report
- no-op proof: docs/progress.md + docs/duplicates.md UNCHANGED; difficulty.py old-code
  vs new-code output IDENTICAL (pure path-table refactor)
- docs/difficulty.md regenerated (the Phase-8-deferred regen: 9 harvested funcs left
  the unmatched queue — 12/12 row shift); build path untouched (still 143dbb89)
2026-06-15 20:46:48 -06:00
Drew T 06676e6ab1 refactor(phase-9): T5 — psyq_integrate flags + progress regex + 9 Makefile call sites
The build-path linchpin (one commit — integrate signature and the progress.py
Makefile regex must move in lockstep):
- psyq_integrate.py: argparse (--vram-base/--exe/--symbols flags BEFORE the 4
  positionals + nargs=* window); threads vram_base/exe into placement/classify
  and symbols_path into the stub-name->addr resolution (was hardcoded symbols.us.txt)
- Makefile: all 9 psyq_integrate calls pass --vram-base $(main_VRAM_BASE) --exe
  $(main_EXE) --symbols $(main_SYMBOLS) (flags before positionals)
- progress.py: stub-list regex gains (?:\s+--\S+\s+\S+)* to consume the leading
  flags; + a --binary selector (BINARIES table, default main = current paths)
- GATES: positive build -> 143dbb89; report 52/959/7/50.24% (regex still finds the
  9 stub lists); negative main_VRAM_BASE=0x8000F804 -> cae22f7e (NOT target);
  without-SDK fresh-clone fallback -> 143dbb89
2026-06-15 20:42:32 -06:00
Drew T 170586d6bf refactor(phase-9): T1 — data-driven BINARIES Makefile (no-op on EXE)
- BINARIES/BINARY guard + main_* var set + selected-binary aliases; the EXE's
  artifact paths (build/us/, config/splat.us.exe.yaml, check.us.sha) preserved
  verbatim so its rebuild stays a byte-exact no-op
- 9 PsyQ SDK-integration blocks wrapped in ifeq(BINARY,main); SYMS/LD/OBJCOPY
  stay outside (run for every binary); a 2nd binary skips the SDK block
- gate: make clean && extract && build -> 143dbb89 BYTE-IDENTICAL; make report
  52/959/7/50.24%; BINARY=bogus errors via the $(filter) guard
2026-06-15 20:20:42 -06:00
Drew T 11a9bdc032 feat(phase-8): link libapi+libcard combined 800c2 region (T7+T8) — 22 objs / +24 fns
- libapi (BIOS syscall trampolines) + libcard interleave in 0x61F38..0x62888; linked as
  one combined region (4 blocks apicard1..4) via tools/make_apicard_used.py (C112.o shared,
  byte-match dedup, 0 exclusions)
- subsegs via gen_lib_subsegs.py; integrate window 0x61F38..0x62888; region verified 22/22
- rm'd src/800c2.c (region starts with a lib block) -> splat regenerates apicard1..4 + frags
- dual byte-gate PASS: 143dbb89 with and without the apicard objects
- LINKED 935 -> 959 (+24); REAL still 43; byte-identical 49.81%
- libapi's ~22 objects in the 800c3 region DEFERRED (lowest value); SSGM + the scattered-.bss
  exclusions documented in docs/psyq-worklist.md

LIBRARY CEILING: 8 PsyQ libraries linked byte-identical (959 LINKED fns); byte-identical
coverage 20.31% (libcd+libgs only) -> 49.81%. Remaining = documented low-value deferrals.
2026-06-15 18:59:14 -06:00
Drew T 8bbf2e8e4d feat(phase-8): link libspu+libsnd combined sound region (T9+T10) — 60 objs / +225 fns
- libspu and libsnd interleave in 0x3A444..0x4239C (the 800-subseg tail), so they link
  as ONE combined region rather than two tangled passes
- NEW tools/make_snd_used.py: build the curated combined dir — merge both libs by vram,
  pick the byte-matching object per aliased address (link_object), exclude 4 addresses
  that don't reconcile in-region (kept as byte-identical stubs):
    0x3C438 S_R/S_W, 0x3D424 S_GRMDT/FB/T  — scattered-.bss commons, cross-object (§9.1)
    0x3D94C S_IH/UT_RON                      — false placement (inside libsnd SSSTART.o)
    0x3FA64 VM_F (237 ins)                   — scattered-.bss commons (the one real loss)
- subsegs via gen_lib_subsegs.py (9 snd blocks + 8 sgap game/excluded-stub gaps);
  integrate window 0x3A444..0x4239C; region byte-verified 60/60
- src/800.c trimmed at [0x3A444,0x4239C): kept 732 items < 0x3A444 (ALL matched C +
  game + the deferred SSGM stub preserved), 0 real-C moved
- dual byte-gate PASS: 143dbb89 with and without the sound objects
- LINKED 710 -> 935 (+225); REAL still 43; byte-identical 48.66% (nearly half the EXE)
- deferred: SSGM.o @0x1BD80 (isolated, matched-C region, 8 ins)
2026-06-15 18:54:21 -06:00
Drew T c4a627b22b feat(phase-8): link libgte byte-identical (T11, value-first) — 53/58 objs / +120 fns
- NEW tools/gen_lib_subsegs.py: generate splat subseg lines + integrate stub list for a
  multi-block library (section-size-correct block ends — bakes in the libc2/T6 boundary
  gotcha so it can't recur). Reused for libspu/libsnd next.
- libgte = GTE math, 53 objects in 22 blocks across the old 800b region (game code
  interleaved as 800b/800b_2..800b_7). Resegmented from the generator; integrate window
  0x4787C..0x51804 so it sees exactly the 22 in-region blocks
- 5 libgs-gap libgte objects (MTX_05/07/11/REG03/REG11) DEFERRED — gsgap1/2/4/5 stay
  stubs (gsgap2 != MTX_07 exactly, needs a sub-split); documented in worklist
- progress.py linked_subsegs() now resolves `$(VAR)` stub lists (LIBGTE_STUBS) — the long
  multi-block lists are passed via a make var
- region byte-verified 58/58 (clean, no scattered .bss); rm'd src/800b.c (region starts
  with a lib block) so splat regenerates the fragments; 22 src/libgteN.c + 6 game frags
- dual byte-gate PASS: 143dbb89 with and without the libgte objects
- LINKED 590 -> 710 (+120); REAL still 43; byte-identical 37.93%
- reordered remaining libs value-first (libspu/libsnd next; dense libcard/libapi last)
2026-06-15 18:43:27 -06:00
Drew T 9dbae2681e feat(phase-8): link libc2 byte-identical (T6) — 17 objs / +62 fns
- splat: libc2 C stdlib in 2 blocks — main 16-obj run libc2_1 (BZERO/MEMCPY/STRCMP/
  PRINTF/PRNT[jtbl ok]/.../SETJMP) split from 800c, + STRCAT.o libc2_2 split from
  800c2; new game-code fragments 800c3 + regenerated 800c2
- region byte-verified 17/17; PRNT.o printf-format jtbl resolves via NOLOAD .rodata
- BOUNDARY GOTCHA found + fixed: SETJMP.o .text is 0x80 B (8-align pad), not the
  0x78 psyq_identify reports (30 ins) -> the libc2_1/800c3 boundary was 8 B too low,
  overlapping SETJMP's padded tail; the relink inserted +8 padding and shifted the
  whole downstream image (56k diff bytes, +8 file len). Fix: boundary = last obj's
  readelf .text size (0x5CE18), not ins-count. Lesson recorded in docs/psyq-worklist.md
- when a library block sits at a subseg start, rm the old .c so splat regenerates it
- dual byte-gate PASS: 143dbb89 with and without the libc2 objects
- LINKED 528 -> 590; REAL still 43; byte-identical 32.25%
2026-06-15 18:32:48 -06:00
Drew T 0d6380c845 feat(phase-8): link libmcrd byte-identical (T5) — 2 objs / +104 fns
- splat: split 800c into [800c][libmcrd1][800c2][libmcrd2]; LIBMCRD.o (2186 ins,
  the 55 LIBMCRD_OBJ_* + _card_* memcard I/O) and USERFUNC.o (68 ins) are 2
  non-adjacent blocks split by game/libc2/libapi/libcard code (800c2)
- Makefile LIBMCRD_* + gated psyq_integrate libmcrd1,libmcrd2 + -T
- region byte-verified: 2/2 objects, 27 .bss commons all recovered (not scattered)
- src/800c.c trimmed (0 real-C moved); src/{libmcrd1,800c2,libmcrd2}.c committed
- dual byte-gate PASS: 143dbb89 with and without the libmcrd objects
- LINKED 424 -> 528; REAL still 43; byte-identical 29.29%
- NB: these are the libmcrd SDK objects; the game SaveLoadRoutine/Q#5 is Phase 12
2026-06-15 18:23:59 -06:00
Drew T ade73cce62 feat(phase-8): link libgpu EXT+PRIM byte-identical (T4); exclude SYS.o (scattered .bss)
- splat: split 800b2 into [800b2][libgpu][800c]; libgpu block = EXT.o+PRIM.o
  (vram 0x80058890-0x80059234, holds LoadClut/LoadClut2 + primitives)
- SYS.o (3109 ins) EXCLUDED — scattered-.bss commons (cookbook §9.1, the GS_001
  class): SYS references .bss by section+offset but the original linker scattered
  the commons across 0x80078xxx/0x800c5xxx, so no single NOLOAD base reproduces it.
  Stays a byte-identical INCLUDE_ASM stub in 800c; documented in docs/psyq-worklist.md
- curated dir .run/obj40/libgpu_used = {EXT,PRIM} (validates the _used mechanism for
  the later alias libraries); Makefile LIBGPU_* + gated psyq_integrate + -T
- src/800b2.c trimmed; src/libgpu.c + src/800c.c committed (stub records); the trim's
  9 "moved" items were all splat-auto empties (regenerated identically, 0 real lost)
- dual byte-gate PASS: 143dbb89 with libgpu objects AND without (stub fallback)
- LINKED 375 -> 424; REAL still 43; byte-identical 24.32%
2026-06-15 18:21:25 -06:00
Drew T 675872616d feat(phase-8): link libetc byte-identical (T3) — 5 objs / 35 fns
- splat: carve libetc subseg (vram 0x8004239C-0x80043088, VSYNC/INTR/INTR_VB/
  INTR_DMA/VMODE) out of the 800-subseg tail (ends exactly at libcd1); 5 objs,
  1 contiguous block, no placement window needed, no matched-C disturbed
- Makefile: LIBETC_ELF/OBJDIR/SYMS + gated psyq_integrate call + -T on the ld line
  (same conditional/stub-fallback model as libcd/libgs)
- src/800.c trimmed (split_src_region: dropped 35 libetc stubs, 0 real-C moved);
  src/libetc.c committed (35 INCLUDE_ASM stub records)
- pre-wire gate: psyq_link_lib 5/5 objects byte-identical (.text)
- dual byte-gate PASS: make check 143dbb89 WITH libetc objects AND without them
  (stub fallback) — both byte-identical
- progress.py LINKED 340 -> 375 (+35), stubs -> 1626, byte-identical 21.98%
  (metric auto-updated from the new Makefile psyq_integrate call)
2026-06-15 18:08:57 -06:00
Drew T 49db81d8ae feat(phase-7): Gen1 close-out — matching loop industrialized, PsyQ libs linked, LZSS matched (v1.7.0)
- reports (Task 3): tools/{progress,difficulty,dup_report}.py + make report/sig-refresh
  (deterministic digests); harvest to 43 REAL matches incl. LzssDecodeSector
- per-module -O0 split (Task 5.5): src/boot.c + Makefile per-file CC1FLAGS (cookbook §6)
- PsyQ SDK linking (sessions C-G): split/build/identify/link/integrate pipeline
  (tools/psyq_*.py, make_libgs.sh, ld_interleave.py, split_src_region.py) — libcd (18 obj)
  + FULL libgs (31 obj / 6 blocks) linked BYTE-IDENTICAL from real PsyQ 4.0 libs; byte-
  identical with or without the SDK objects (stub fallback); cookbook §7/§8/§9.1-9.5
- LZSS MATCHED byte-for-byte (session F): §5a cross-jump barrier + §10 regalloc/schedule
  idioms, ground-truthed against pinned gcc-2.7.2 source (the Gen1 LZSS gate)
- file-loader cluster: 6 matched + 5 NON_MATCHING-drafted (CdReadStateMachine,
  CdReadSectorReadyCB, StreamLoadStateMachine new this session, valid under -DNON_MATCHING);
  SaveLoadRoutine DEFERRED to Q#5 (save/memcard, documented stub)
- README Gen1-complete; worklog preserved -> phase-ends/logs/Phase7.md
- rules R17 (web-research compiler quirks), R18 (plain-English session summaries),
  R19 (preserve phase worklog out of load order)
- MILESTONE: Gen1 EXIT confirmed — byte-identical build, 43 matches incl LZSS, 7 zero-
  regression sessions, loader cluster matched/drafted, overlay map emulator-proven
- bumps project version 1.6.0 -> 1.7.0
2026-06-15 13:44:32 -06:00
Drew T f91a67a6fe feat(phase-7): libgs block 6 linked byte-identical (16 objs / 26 SDK funcs) — session F
- resegment 800b -> [800b pre][libgs6][800b2 post]; psyq_integrate the 16 contiguous
  block-6 libgs objects (GS_110/119/120/121/122/123/127/131/133/134/135/137, PRESET,
  PRESET3, OBJT, OBJT3) at vram 0x80053AF8-0x80057928; make check BYTE-IDENTICAL
- fix the resegment-triggered spimdisasm boundary instability: declare GsMulCoord2/3
  (real MATRIX.o funcs, verified via the PsyQ object, R15 Ghidra-mirror pending) +
  carve the s8 data descriptor table [0x53198,data,53198] (D_80062998 was shadowed by
  a mis-detected func_80062998); ld_interleave FRONT_DATA -> 53198.data.o
- psyq_integrate multi-library support: per-lib NOLOAD section namespacing (.nl_<tag>_*)
  so the 2nd integration's NOLOAD isn't skipped (was discarding OBJT3 .rdata/.bss);
  trial-link includes sibling *_externals.ld (kills the '31 UNRESOLVED' false alarm);
  global NOLOAD re-sort by vram (kills 'dot moved backwards')
- tools/make_libgs_block6.sh: reproducible curated-dir regen (disambiguated 16 objects)
- gated on [ -d .run/obj40/libgs_block6 ]: fresh-clone stub fallback verified byte-identical
- cookbook s9.4 (multi-library integration gotchas); CURRENT_PHASE updated
- next: full libgs integration (other 5 blocks), then Task 6 close-out + Task 7 PhaseEnd
2026-06-15 10:48:08 -06:00
Drew T 038e7de532 feat(phase-7): LZSS cross-jump barrier breakthrough + libgs byte-verified — session E checkpoint
- LZSS: defeated gcc 2.7.2 -O2 cross-jump-merge of the twin state-save tails with a
  zero-byte __asm__ __volatile__("" ::: "memory") barrier (find_cross_jump bails on
  ASM_INPUT; ground-truthed vs gcc-2.7.2.3 jump.c by a web-research subagent).
  LzssDecodeSector now 122 instructions (correct structure); ~3 regalloc/scheduling
  swaps remain -> C kept under #ifdef NON_MATCHING, default build = stub = BYTE-IDENTICAL
- surgical rodata carve (lean LZSS path, no libgs needed): splat carves ONLY
  jtbl_80072A38 ([0x63238,.rodata,800] bounded by [0x6324C,data,6324C]); ld_interleave
  wired into `make extract` as the .data->.rodata->.data sandwich (TAIL_DATA=6324C.data.o)
- libgs: 31/32 used objects byte-verified IDENTICAL to real PsyQ libgs 4.0 (0 conflicts,
  69 externals); GS_001 deferred (psyq-obj-parser .bss-common scattering); 6-block
  resegmentation wiring pending. psyq_identify.py now skips data-only objects (GLOBAL.o)
- cookbook: §3a (web-research compiler internals — escalation tier above the permuter)
  + §5a (the cross-jump barrier idiom) — both reusable
- build BYTE-IDENTICAL (143dbb89f34491258bbc27810d0a12ec8b43a8dd) throughout
2026-06-15 01:47:18 -06:00
Drew T 3788a89b3c feat(phase-7): libcd linked into byte-identical build (58 SDK funcs) — session D checkpoint
The first real PsyQ library is sourced from real SDK objects in the byte-identical
build, replacing the libcd-region asm stubs. Tasks 2'.1/2'.2/2'.3 — the whole
PsyQ-library-into-build mechanism, proven on the simplest library (libcd).

- psyq_link.py: per-object byte-link engine — recovers externals from the EXE's
  resolved relocations (R_MIPS_26 / HI16+LO16); weakens psyq-obj-parser's
  mislabelled common-style .bss globals so a strong --defsym wins
- psyq_link_lib.py: whole-library verify — 18/18 libcd objects byte-identical
- psyq_link_region.py: region link via NOLOAD data placement (no carving) +
  weaken-all-.bss; gap-aware per-object placement
- psyq_integrate.py: build wiring — splat resegment swap + per-object .text at
  vram + NOLOAD data; external resolution (func_<addr> aliases, symbols.us.txt
  jump-table names, recovered St*/CD_* globals, all weakened .bss commons);
  idempotent; conditional on tools/psyq/ (fresh clone builds via stubs)
- split_src_region.py: H5-safe src/*.c split at vram boundaries (preserves
  matched C + NON_MATCHING blocks)
- splat.us.exe.yaml: 5-way text split [pre][libcd1][gap][libcd2][post]
- src/800.c trimmed to <0x80043088; src/{800b,gap,libcd1,libcd2}.c splat stubs
- Makefile: libcd integration in the $(OUT) recipe (conditional + idempotent)
- cookbook §9.1/9.2/9.3 (the reusable mechanism for libgs/Gen2); CURRENT_PHASE
  session-D log; ≥3-session green bar A/B/C/+D
- make clean && extract && build && check -> 143dbb89... BYTE-IDENTICAL
2026-06-14 23:13:39 -06:00
Drew T 8ffb7f0607 feat(phase-7): -O0 split + 4 matches (42 real) + libcd.h — session B checkpoint
- per-file -O0 mechanism: split text c-subseg into boot@-O0 (src/boot.c) + 800@-O2
  via splat resegmentation + Makefile target-specific CC1FLAGS; the boot/main/
  game-mode-dispatch module (0x80010000-0x800123F0) is -O0, not the pinned -O2
  (per-module compiler mixing, SETUP §5.5). Regression-gated byte-identical.
- 4 byte-matches (38->42 real, make check BYTE-IDENTICAL throughout): GameModeDispatch
  (-O0 register-ptr far member), DebugMenuHandler (-O0 reserved-slot local), CdQueueBusy
  (-O2 if/else order + branch polarity), CdReadRequest (-O2 early-return fall-through)
- PsyQ infra: include/psyq/libcd.h (CdlLOC/CdlFILE from the .gdt) + 4 named symbols
  (CdSearchFile/CdPosToInt/CdIntToPos/VSync) in symbols.us.txt; func_* stubs renamed
- loader cluster non-jtbl COMPLETE (6 matched + 2 drafted): NON_MATCHING drafts of
  LoaderInitFileTable + ResourceLoadStateMachine (logically faithful, residuals in-source)
- tooling: progress.py/difficulty.py fixed for the multi-file split (glob src/*.c, all
  asm dirs, skip forward-decls); deterministic; 42 real / 4 NM
- flywheel: matching-cookbook §6 (-O0 detection + idioms), §7 (PsyQ types/symbols), T4
- build byte-identical 143dbb89f34491258bbc27810d0a12ec8b43a8dd from a full clean cycle
2026-06-14 19:37:05 -06:00
Drew T 379185a799 feat(phase-7): matching reports + 24 matches (38 real) — session A checkpoint
Mid-phase checkpoint (Drew-directed; phase NOT closed — R8 exception).

- reports (Task 3): tools/{progress,difficulty,dup_report}.py + `make report`
  (deterministic) + `make sig-refresh`; authoritative baseline 38 real / 2050
  matchable; empties audit 42/42 genuine jr;nop; docs/{progress,difficulty,
  duplicates}.md digests
- matches (Tasks 4-5): +24 byte-identical (22 accessor leaves via the harvest
  pattern + ResourceGetCdLoc + LoaderResetReadState); make check BYTE-IDENTICAL
  throughout (143dbb89...)
- config/symbols.us.txt: declare func_80047CAC (R15) — fixes a latent
  non-reproducibility (spimdisasm 1.41.0 auto-detect of an 8B inter-fn blob was
  unstable across clean extracts; the committed Phase-6 baseline did not
  deterministically rebuild)
- Task 1 rodata-island foundation: investigated (migration co-locates jump
  tables + resolves refs, but byte-identity blocked by splat global-migration +
  contiguous linker placement vs BFM's monolithic segment) -> DEFERRED to Task 2'
  with a candidate fix; full findings in phase-ends/CURRENT_PHASE.md
- phase-ends/CURRENT_PHASE.md: reordered plan, loader-cluster triage, per-session
  green-check log (1 of >=3 needed for the Gen1-exit milestone)
2026-06-14 17:36:12 -06:00
Drew T 5f937bdda5 feat(phase-6): compiler pinned + matching loop proven (14 matches) — LZSS to Phase 7
- splat.us.exe.yaml: text subseg asm->c (INCLUDE_ASM scaffold); include/common.h
  (committed prelude); Makefile c-rule cpp->cc1->maspsx->as + -Map
- PIN (G8): gcc-2.7.2-psx -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker
  + maspsx --aspsx-version=2.56 --expand-div; byte-exact on func_80018F20 +
  instruction-identical across 3 idiom classes; --expand-div REQUIRED for div/rem
  (SETUP.md §5.4 PINNED, Makefile MASPSX_FLAGS)
- harness: diff_settings.py (asm-differ mipsel object mode), tools/decompile.py
  (m2c wrapper), tools/permuter/ (decomp-permuter harness + objdump shim), expected/
- 14 real functions matched (asm-differ score 0): func_80018F20 + 13 accessors;
  +14 splat-auto empty no-ops; make check BYTE-IDENTICAL throughout
- 2 instruction-identical near-misses guarded NON_MATCHING (func_80015A74 scheduling,
  func_80016714 phantom frame) -> decomp-permuter candidates
- flywheel: docs/matching-cookbook.md, memory + CLAUDE.md step 5 (consult+evolve), R16
- requirements-python.txt: venv freeze (pycparser<3.0 pinned — permuter needs plyparser)
- LZSS spike: needs jump-table-in-rodata workflow (102 tables, mixed blob) -> Phase 7
- venv: asm-differ + permuter deps installed
- bumps project version 1.5.0 -> 1.6.0
2026-06-14 14:47:01 -06:00
Drew T d55882b4b8 feat(phase-5): splat config + all-asm byte-identical build of SLUS_007.26
- config/splat.us.exe.yaml: splat PSX config (platform psx, compiler PSYQ,
  subalign 2, gp_value 0x80074750, base_path .., main segment align 4); one
  asm text seg [0x800] + data seg [0x531DC], header via splat's header segtype
- Makefile: implement extract (splat split) / build (as -> ld(splat .ld +
  undefined_*_auto) -> objcopy) / check (SHA1 vs config/check.us.sha, auto-run)
  / expected (asm-differ baseline) / clean; check-env untouched
- config/check.us.sha: committed build-check hash (validation stage 2)
- config/symbols.us.txt: now hand-curated — 15 2D_* -> gfx2D_* (illegal leading
  digit broke as), +D_800629D4 label; header flags re-merge + Ghidra-mirror (G6)
- .gitignore: ignore regenerable splat outputs (asm/, include macros,
  undefined_*_auto.txt); refine the earlier commit-asm/ note
- docs: SETUP.md §4.5 binutils 2.42 = byte-clean (no 2.35 downgrade), §5.3 +
  ledger #8 RESOLVED -G0, §6.3 as-built; memory-map.md gp0 -> -G0 + text/data split
- MILESTONE: make extract && make build && make check -> build/us/SLUS_007.26
  SHA1 143dbb89... == original (BYTE-IDENTICAL), 100% asm, check fails on corruption
- rule R15 (symbol file = curated, valid-identifier build input, Ghidra-mirrored)
- bumps project version 1.4.0 -> 1.5.0
2026-06-14 12:05:38 -06:00
Drew T ffe2352db2 feat(phase-4): matching-build toolchain (WSL) — make check-env green
- apt: build-essential, binutils-mipsel-linux-gnu 2.42 (+gcc-mipsel 12.x), ninja
  1.11.1, python3-pip/venv, libelf-dev, clang-format, p7zip, bchunk, bsdmainutils
- .venv + splat64 0.41.0 ([mips]: spimdisasm 1.41.0, rabbitizer 1.16.2, PyYAML 6.0.3)
- 4 pinned submodules under tools/: maspsx@874855c5, decomp-permuter@b44b0622,
  asm-differ@2ad4a4a4, m2c@4266cc28 (.gitmodules)
- old-gcc 0.17 cc1 candidates -> tools/bin/gcc-2.7.2-{psx,cdk}/ (32-bit i386 static;
  tarballs are flat + same-named -> per-subdir extraction); CHECKSUMS.sha256 committed
  (gitignore exception !/tools/bin/*.sha256); psx cc1 self-id "GNU C 2.7.2 [AL 1.1,
  MM 40] Sony Playstation"
- Makefile: check-env (.ONESHELL bash) asserts python>=3.12, venv import splat, both
  cc1 (stdin smoke), maspsx, mipsel as/ld/objcopy, sha1(EXE)==EXPECTED_EXE_SHA1;
  binutils>=2.38 -> WARN (verdict deferred to Phase 5, §4.5); extract/build/check/
  expected/clean are loud-failing Phase-5 stubs (names per §6.3)
- docs/SETUP.md: as-built versions; §4.7 corrected (i386, flat-tarball fix); §4.8
  deferred to Phase 6; ledger #5 confirmed (Ubuntu 24.04.4), #6 (2.42), #7 (sha256)
- MILESTONE: make check-env exits 0; zero ROM-derived bytes staged
- bumps project version 1.3.1 -> 1.4.0
2026-06-14 03:56:07 -06:00