mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
a0cf302e5bdf426c19c6ce2eba7c8a7303514879
93 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9c4d32d651 | tools(phase-33): E4 — xsig packaged: tools/xsig/ (xsig.py library + CLI sign-s | sign-objdump | cross | verify | selftest, from the Phase-21 .run/xdedup probe; README with the recorded worked example — BFM × Xenogears + Vagrant Story 103 hits all PsyQ library/BIOS, BFM × Tomba 126 hits 124 library: the decision log's clean negative; MIT LICENSE; tests/ from a game-free fixture compiled with the pinned triple and linked at two addresses with --emit-relocs, relocation records merged into the listings, 8/8) in tools-health + CI + doc_links; SETUP row + P33 E4 section; the standalone copy at .run/P33/xsig-repo/ (one commit, noreply identity, system-python tests OK) for Drew to push as Druthulu/xsig; log + checkpoint (NEXT = E5, a cut candidate for Drew) | ||
|
|
50c1b69e4d | docs(phase-33): E3 — docs/gcc-2.7.2-map/README.md (the five files by pass group, the condensed §31 triage table, the byte-proof method, the provenance legend: vanilla 2.7.2 subset from the GNU tarball sha256 7cd8bce5… vs pmret/gcc-papermario = gcc 2.8.1, the +611-line drift caveat, the S23 audit 119/40/7/4) + tools/gccmap_cites.py: every file.c:NNN cite in the map tagged [2.7.2]/[2.8.1 pm]/[repo] from the trees (quoted snippets → function extents → nearest occurrence → the author's cues → cite_overrides.tsv, 20 rows with reasons → a tie is a valid 2.7.2 line; a contradiction leaves [?]); 135 cites: 79/55/1, --verify 0 disagreements, --controls 6/6, idempotent; three tagger defects caught by its own controls before any tag landed (window span pairing, code fences, self-read cues); --check (textual) in tools-health + CI audits; doc_links default; SETUP row + P33 E3 section; log + checkpoint (NEXT = E4); 8 orphaned 49h permuter workers stopped | ||
|
|
0cf971d1f4 | docs(phase-33): F3 CLOSE — the wiring: wiki_render --selftest in make tools-health (run: OK, 12/12 inside it), SETUP rows + the P33 F3 section (25 pages / 264 links / 1,896 lines; sources named), runbook §11 gains the post-flip wiki step (first page in the UI, then tools/wiki_sync.sh --push — Drew); CURRENT_PHASE: F3 ticked, the S88 F3 log entry, the checkpoint rewritten for the successor (NEXT = E3; Drew's pending gc + ticket status; rule candidate (h): a probe never writes into the repository it guards) | ||
|
|
2cbedf5878 | docs(phase-33): D5 governing-docs consistency — CLAUDE.md fail-safe names every purged path (H1 in force), DIGEST §1 H1/R1/R20 + the Phase-14→P33 in-place flip, roadmap-to-100 'Status at Phase 33' block, gen2-roadmap Phase-14 SUPERSEDED banner; tools/doc_links.py (relative-link checker with a tracked pending list that gate 2 refuses; negative control rc 1) in tools-health; SETUP row; checkpoint -> NEXT = F1 (Block D done) | ||
|
|
ecf67e6ff3 | feat(phase-33): D3 progress publishing — per-binary instruction totals in docs/progress.json, docs/badges/*.json (shields endpoint), --check covers JSON + README block + badges (in make audit-digest), tools/objdiff_report.py (report.proto v2; validated with objdiff-cli 3.8.1 incl. a mutated-unit control), tools/frogress_upload.py (dry-run default), .github/workflows/progress.yml (artifact SLUS_007.26_report, no rebuild); SETUP rows + P33 D1–D3 section; checkpoint -> NEXT = D4 | ||
|
|
848f294fc8 | docs(phase-33): D1 the README rewrite — the claim and the contract, a GENERATED numbers block (tools/progress.py --json -> docs/progress.json, --readme rewrites the marked block, --check asserts freshness; wired into make report), what is not our C, build-from-your-own-disc, layout, how it was made (ProjectArchitect), about the history (tokens + commit-map), license split, no-ROM policy, special thanks; forward references to D2/D3/F1/F2 files recorded in the checkpoint | ||
|
|
936d7d741c |
feat(phase-33): B5 Ghidra regenerability PROVEN — the RE work as text (config/ghidra/*.jsonl + ROSTER.md), six programs rebuilt from disc + symbol files + that file with PROOF PASS
- 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 |
||
|
|
cabab00c0e | wip(phase-33): B5 Ghidra regenerability — ExportAnnotations.java + ghidra_export_annotations.sh PROVEN (all 129 programs exported to byte-stable JSONL in 18.5 s), ghidra_annotations_delta.py (live − baseline), ImportAnnotations.java + ghidra_rebuild.sh WRITTEN but unproven (the resident rebuild hit an OSGi script-bundle load error — a compile error in the new importer breaks every script in the dir; the known bad LocalVariableImpl ctor is named in CURRENT_PHASE); Makefile print-% + GHIDRA_PROJ repo-relative; the six ghidra_*.sh repo-relative (BFM_GHIDRA_PROJ); DefineFunctions arg path; ImportPsyqGdt install-dir default; ExportSymbols R15 fix; scratch project under build/ (Ghidra refuses '.'-prefixed path components); SETUP P33 B5 section; checkpoint refreshed for the next session (S86 paused at 87% context) | ||
|
|
89e087f52a | feat(phase-33): B3 tools/bootstrap.sh + make bootstrap (apt presence printed, venv from requirements, submodules, cc1 tarballs sha256-checked + extracted, check-env) + check-env gains submodule/preset-header/payload-census lines; PROVEN on a fresh clone: bootstrap from nothing -> check-env OK -> disc-extract OK -> extract-all 217/217 -> check-all 218/218 in 4m18s with no SDK objects (the public user's path); SETUP P33 B3 (R21) | ||
|
|
0265712916 | feat(phase-33): B1 make disc-extract — the rom->decoder step in the build: extract.py --expect-manifest (compare against the committed oracle, never write it; mismatch -> .run/extract/ + diffs, exit 1) + --allow-missing-audio (explicit PARTIAL for Track-1-only dumps); disc-extract = probe (0.7 s no-op) -> disc presence -> redump SHA1/CRC32 -> extract+compare -> verify (15.7 s full); extract/extract-all call it; check-env WARN-on-absent EXE + oracle self-consistency; help rewritten; the 4 splat preset headers TRACKED (clean keeps them); .gitignore re-tightened to H1 (dumps/*.bin, ghidra/, tools/psyq/, session archive/, ghidra-ext zips, brave.exe; EXE re-include dropped); controls: no-disc exit 2, truncated disc FAILs with the oracle untouched, regenerated tree byte-identical to the previous (1,801 files) | ||
|
|
4bb29544a2 | feat(phase-33): A3 the with/without-SDK dual — NO_SDK=1 knob (skips the eleven psyq_integrate rewrites AND the -T externals fragments), make sdk-dual (refuses without all 11 SDK object dirs; WITH → extract → rm build/psyq → NO_SDK=1 → extract → WITH restore; map assertions; both legs == config/check.us.sha), wired into tools-health with a [skip] when the SDK is absent; proven: main 143dbb89… byte-identical WITH (1,288 psyq objects) and WITHOUT (12 libcd1 stub tiles), 28 s wall; SETUP P33 A3 (R21) | ||
|
|
b23300fc21 | feat(phase-33): A2 reporting instruments — make sig-main rewritten (tools/main_seed_ends.py: main's game-code boundaries DERIVED from the link map + objects, tiling asserted; 809 fns / 45,150 ins), progress.py weighs main by the build-derived sig and EXITS with no sig (R32), the digest's oracle clause derived live (0 phantom / 0 truncated / 0 pad-tail), backlog.linked_closed retires the LINKED-range legacy row (0 open), dup_report on the derived sig; CORRECTED main denominator 45,150 (Ghidra's flow boundaries left 3,628 words of game code unowned) — fleet instr 13,492,113 / distinct 5,820,205, all 100%; make report BINARY=main EXIT=0, audit-digest OK; SETUP §6.8 + P33 A2 rows (R21) | ||
|
|
563a211448 |
build: exclude dotfiles from the C source find (-not -name '.*') — a tool's live probe in src/ broke gate_main's clean rebuild (P32 S83)
- `C_SRCS := $(shell find src -name '*.c' …)` admitted src/.masked_diff_probe.<pid>.c — masked_diff._common_typedefs()'s per-process probe, written and deleted within one process — when a concurrent agent's probe existed at make's parse time; the file was gone by compile time and the clean main rebuild failed with "No rule to make target build/src/.masked_diff_probe.3973390.o, needed by build/us/SLUS_007.26" (gate_main3 log: "batch FAILED (sha None); bisecting" -> one wasted rebuild, then BANKED) - byte-neutral: md_SC03_056 smoke build bc768a6b BYTE-IDENTICAL rc 0; with a throwaway src/.probe_test_s83.c present, `make -pn build BINARY=main` shows C_SRCS without it and with src/800.c; the fleet R22 at the T3 close re-verifies - the consumer is the right place for this guard (R54): every tool that probes in src/ is covered at once |
||
|
|
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).
|
||
|
|
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 🛑. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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.
|
||
|
|
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.
|
||
|
|
5172df5f0b |
fix(build): REORDER_TUS missed 800c2_2/800c2_3 — $(filter) is an exact stem match
`$(filter $*,$(REORDER_TUS))` matches the TU stem EXACTLY, so `800c2` never
covered `800c2_2` or `800c2_3`. Those two TUs went through maspsx while their
siblings went through reorder_passthrough | as -O2 (the §332b island, landed
2026-09-01).
That gap is why func_80062388's `lui at / jr ra / sw a0,lo(at)` was written up
as COMPILER-INEXPRESSIBLE in cookbook §452: a probe (`void f(int v){D=v;}` ->
cc1 -> reorder_passthrough | as -O2) emits exactly that sequence. It was a
build-config gap, not a gcc-2.7.2 define_delay limit. §452 corrected.
Byte-neutrality PROVEN the right way -- gate_main --assert-baseline builds the
committed tree with NO draft substituted:
BASELINE GREEN — 143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL
This unblocks the 24 SDK-C-REORDER units, four of which were banked as verbatim
assembly on 2026-09-02 off a wall list that predated the fix by one day, with
closeness-0 drafts already in hand.
|
||
|
|
cb948a6bbc |
feat(decomp): the ov_SC01 reloc-only cluster + its 5th latent victim — 5 fns, 1,301 ins
S74 handed this forward as "1,116 instructions behind one question": family_remap
on ov_SC01_004/005/006/008 gated DIFF 4/4 against the banked exemplar
ov_SC01_009:func_8017EB08, and the class had been carried as a codegen wall since
S70. The four bodies were byte-identical to the exemplar the entire time.
Word-level classification vs the exemplar, computed independently twice (a Fable
agent's script, then mine from scratch against the retail images), identical:
nins=279 EQ 213 · RELOC-HI16 23 · RELOC-LO16 24 · INTERNAL-J 19 · CODEGEN 0
Zero register-allocation, instruction-selection or scheduling differences.
ROOT CAUSE — tools/jtbl_carve.py reserved ONE WORD TOO MANY per table:
* spimdisasm runs an island's LAST `jtbl_` dlabel one word into the following
NON-ZERO data (string bytes 0x696F760A / 0x000013FF / 0x62647020), so the
zero-word trim cannot see it; and
* the over-span clamp that would have caught it was guarded by
`len(sltiu_bounds) == 1` -- but `sltiu` is ALSO how gcc emits an unsigned
range check ((u32)(x-lo) < n, I1). These four carry five distinct sltiu
immediates, so the guard silently disabled itself on precisely the functions
that needed it.
0x2C reserved for a 0x28 table => image 4 bytes short => ~850 %lo immediates
shift => whole-binary DIFF about a function whose own bytes are perfect.
Fixed with a PER-TABLE bound: gcc-2.7.2's dispatch is a fixed idiom, so the
`sltiu` nearest ABOVE that table's own %hi(jtbl_X) is unambiguous whatever else
the function tests. Second defect stacked behind it: a carve span whose
JTBL_PADS line lacks a `tables=` comment lost its existing table's start on
merge and refused "table starts do not fit the span" -- which harvest_verify
then "repaired" with a needless jr_isolate_all that walked back into the first.
THE NEGATIVE CONTROL IS THE STORY. Run over every other open table-bearing stub
fleet-wide, the fixed bound changed exactly one more table: ov_SC06_022/
func_80185B80 (185 ins), a FIFTH victim nobody had drafted against. A guard that
disables itself on a common idiom does not fail once -- it fails quietly across
the whole corpus.
Banked, each with its own byte-gate verdict (--no-propagate, clean re-gate):
func_8017EB30 ov_SC01_004 279
func_8017F2D4 ov_SC01_005 279
func_8017F2D4 ov_SC01_006 279
func_8017EC68 ov_SC01_008 279
func_80185B80 ov_SC06_022 185
Also here:
* dedup_propagate: memoize find_site's mask (lru_cache) -- 54 ms of masking
per call over the whole source, recomputed though it depends only on the
text. 2x on that loop (58.3 -> 33.0 ms/call), NC identical on 120 addrs.
Scoped honestly: that loop is ~2.4 min of a 30-min run; the profiler puts
43% in family_remap._alias_decl_for, which is NOT fixed here.
* Makefile: `clean` says out loud that BINARY= is ignored and it is fleet-wide
(cookbook §445) -- it silently deleted asm/ for all 213 binaries this session.
* Cookbook §446 (the carve law: when a standalone-MATCH jtbl draft gates DIFF,
diff the carve extent against 4 x sltiu before touching the body), §445, and
SETUP rows for both tools (R21).
* CURRENT_PHASE: the S75 log, incl. the measured fleet dedup-hygiene census
(~2,073 fns / ~12,116 items, all ALREADY MATCHED -- cleanup, not work) and
Drew's decision to leave it and gate --no-propagate from here.
|
||
|
|
95c7b7fe0f |
fix(tools): two tools read a source of truth describing a different world (+ hard-gate the third)
Three independent split agents hit both defects in one session, on the tools that CERTIFY and UNDO
the work they were doing. Each is fixed, negative-controlled against the exact failing case, wired
into its siblings, and documented in the same change (cookbook §436).
1. split_indicator attributed a jump table by the STUB'S DIRECTORY PATH. `make extract` does not
prune a re-homed subseg's `nonmatchings/<old>/` dir, so after a correct, byte-green §431 split
both the old and new dirs hold the moved stub — and the tool printed NEEDS SPLIT for a split that
was already correct. owners() now derives the owner from the CONFIG by address (R33), exactly as
jtbl_carve.func_subseg already does for the identical §8b hazard, and NAMES any leftover stub in
a `note:` line. Notes now print on an OK verdict too: hiding one behind `st != OK` is the same
defect in the other direction — a true verdict about a narrower world than the reader believes.
PROVEN by planting a stale stub for func_80182A00 under its old subseg: OK + the note, where the
old code would have seen one subseg owning two spans. --self-test still PASSes both directions.
2. jtbl_carve --revert did `git checkout --` on the WHOLE splat yaml. The carve owns only the
trailing data/.rodata region; the `c` pieces are source configuration it never writes. The blunt
form cannot tell "carve state I just added" from "the §431 split someone added to the same
uncommitted file", so --revert after a carve PROBE silently un-split the overlay — each agent
recovered only because they had backed the yaml up by hand. It now splices back only its own
region (parse_config gained an optional `lines=` so the SAME region derivation runs over the
committed text — one derivation, two callers), refuses loudly if the committed region carves onto
a subseg the current config no longer defines, and reports how many uncommitted `c` pieces it
preserved. PROVEN in the ov_SC01_084 worktree: carve → revert → the uncommitted split survived
("PRESERVED 30 uncommitted `c` piece(s)"), carve lines gone, diff back to the 6 split lines.
SIBLING: jtbl_family_bank.revert carried the same blunt checkout for the isolation's code pieces.
It now keeps whatever pre-dated the attempt (the `keep_regions` signal it already trusts for
src/) and NAMES anything it drops — an isolation region and a §431 split piece are both
`<ov>_jr_<addr>`, so no name test can tell them apart and only that signal can.
3. NOT A DEFECT, and recorded as such: a speculative carve fails the build with `jtbl_rodata_pads:
consumed 3 rodata jump table(s) but 9 pad spec(s) given`. That is R43 working — the pad spec is a
CONSEQUENCE of banking, not a prediction of it — and it reproduces identically on the pristine
unsplit config, so it is never evidence about a split.
make tools-health: split_indicator is a HARD GATE now, as its own comment promised it would become
once the last violation was split. 213 OK of 213; a new one fails the build instead of being echoed
past.
Cookbook §435 (an overlay TU split is near-free — 0/3,074, 1/2,679, 2/3,254 names crossed, because
the §8b carried decl layer re-emits externs per region so only typedefs can cross; and the gap test
between two rodata runs is "is this word a valid code address", not "is it zero") + §436 (the two
defects and the shape they share). Playbook + SETUP.md carry the emptied CARVE-BLOCKED class.
|
||
|
|
78ef96f606 |
docs: cookbook §426/§434, memory-map extent, Makefile overlay comment
* §426 listed three localizer verdicts; there are four, and the missing TABLE REJECT is the dominant residual on main's switch functions (§433). Its span-B table also still advertised SaveLoadRoutine as an unlockable owner — it is the §434 frame pair. * §434 quoted SaveLoadRoutine at 1139 instructions; the .s has 1165. * docs/memory-map.md:309 recorded saveHeaderTemplate @0x80072DF0 with 'handler code ptrs @+0x54' at the ledger's HIGHEST confidence. 0x80072DF0+0x54 = 0x80072E44, which is jtbl_80072E44 — func_8002B0B4's dispatch table and the first 12 bytes of the S72 span-B carve. The row's extent is wrong past +0x54 and now says so; a 'verified' row that overlaps a carve boundary is how a future resegmentation gets talked out of itself. * Makefile's overlay --front/--tail comment sat directly under main's --order call with nothing distinguishing them; now says which is which. |
||
|
|
fd5e700cfe |
chore(tools-health): note when to flip split_indicator from informational to a hard gate
Informational only while 4 known violations exist; a permanently-red gate trains people to ignore it (R54). When the last overlay is split, drop the '|| echo' so a regression fails the gate. |
||
|
|
b173d88676 |
feat(split_indicator): detect subsegs that MUST be split before their switch fns can bank
A code object contributes exactly ONE contiguous .rodata run, so a subseg owning raw jump tables in >=2 non-adjacent island spans makes every switch function outside the one carveable span unbankable at any effort. main sat in that state from Phase 7 to Phase 31 and eleven functions were written off as 'PROVEN gate-rejects' because of it. The evidence is derivable from the raw image on day one; nothing was comparing it. FIRST FLEET RUN: 209/213 OK, 4 overlays flagged — ov_SC01_084, ov_SC02_005, ov_SC02_011, ov_SC03_105 — holding 16 open functions / 3,613 instructions (18% of the non-main frontier). All 16 were already in the S71 exclude list, i.e. recorded as if unmatchable rather than as 'needs a subseg split'. 3.7s fleet-wide. Self-test covers all three directions: fires on main's pre-S72 island (fed synthetically, because the real tree no longer holds that state), stays silent on main today, and does not over-fire on a one-span subseg. Linked-library subsegs are excluded on principle — their code comes from a .a so cc1 emits no table for them; without that filter main reports NEEDS SPLIT on libgs6, which the self-test caught. Wired into make tools-health. accelerators #20 gains the when-to-split rule: split where the BUILD forces a boundary (decidable at 0% matched), at the span-owner boundaries and nowhere else, never on TU archaeology. |
||
|
|
7df4895e7b |
feat(main): split src/800.c at the jtbl-span TU boundaries — spans B and C now carve
BYTE-IDENTICAL with NO function banked (gate_main --assert-baseline, clean rebuild), which is the whole point: the structure lands first and proves neutral, then drafts bank against it. One code object contributes exactly ONE contiguous .rodata run, and 800.o's is span A, so spans B and C each needed their own object: 800 vram 0x800123F0-0x8002B0B4 -> .rodata span A (0x80072A38-0x80072C70) 800_b vram 0x8002B0B4-0x80035270 -> .rodata span B (0x80072E44-0x80073140) 800_c vram 0x80035270-0x8003A444 -> .rodata span C (0x800732A0-0x8007344C) The span owners' address ranges are disjoint and ordered — tables pack tight WITHIN a TU and are separated by other data ACROSS TUs — so these are (at least some of) the original translation-unit boundaries. Splitting here is both the fix and the minimum; any extra split would be speculation. main's island is now a 7-piece data->rodata sandwich, so ld_interleave moves from --front/--tail to --order. THE SPLIT WAS CHEAP, AND MY FIRST ESTIMATE WAS WRONG. I costed it at '2,318 scattered extern lines' — that is the TOTAL; what matters is how many CROSS a boundary, and that is 57 of 1,247 declared names (4.6%), of which 19 are typedefs with exactly one definition each and zero shape conflicts. Zero file-local statics. src/800_shared.h carries exactly those, derived from the COMPILER's own errors rather than a regex model of C (R33), and each typedef was MOVED, never copied. Unlocks 17 functions / 4,471 instructions = 39% of what is left in main, incl. SaveLoadRoutine (1139) and func_8003388C (663). |
||
|
|
cbf5bae043 |
feat(main): unblock main's switch functions — the rodata span carve + derived jtbl pads
main's gate could only ever say "got X want Y". S71 read 7 such verdicts as body rejects and recorded 11 functions as "PROVEN gate-rejects, §376 in its purest form". They are not: all 11 are switch functions, and the blocker is that main has had exactly ONE rodata carve since Phase 7 (LZSS's jtbl_80072A38). Every other main jump table stayed raw in the tail data, so a drafted switch DOUBLE-EMITTED its table, the image grew (+28/+52/+76/+84 measured), and all 238 symbols above 0x80072A4C shifted. * tools/main_diff_locate.py (NEW) — turns a red image into a named list of divergent symbols via the linker map; per-byte attribution, self-test flips a byte at a known address and asserts the containing symbol (plus the identical-pair direction). * gate_main.py — PRESERVES the red image + map before the R40 baseline control rebuilds over it, and auto-localizes: BODY REJECT vs PLUMBING REJECT vs MIXED. Also -j on the build (was single-threaded) and the §376 drop list written to .run/gate_main_dropped.json with the reconciliation chain. * splat.us.exe.yaml — the .rodata carve extends from the LZSS table alone to the whole contiguous game-jtbl span 0x80072A38-0x80072C70 (12 tables, one 800.o run). Byte-neutral with no drafts substituted (probed first). * jtbl_rodata_pads.py — --derive now works for main: one file-0-vram expression makes both address->bytes and yaml-piece->address correct for the EXE's 0x800 header and leaves flat overlays unchanged. Makefile arms it for BINARY=main. Banked byte-identical: func_8001A114, func_8001AAD0, func_8001AF34 — three of the eleven. 25 of main's 59 frontier functions (6,215 of 12,912 instructions) are in this class; the remaining spans need src/800.c split at the TU boundaries the spans reveal. |
||
|
|
067f25f682 |
feat(build): §332b — per-object REORDER path for the 800c2/800c3 PsyQ island
Those two objects were originally assembled in REORDER mode (the assembler filled
the delay slots). maspsx force-emits `.set noreorder`, making that unreachable, so
a whole class there read as a permanent compiler wall (§332) when the property
belongs to the OBJECT, not the toolchain.
For REORDER_TUS only, swap maspsx for tools/reorder_passthrough.py + `as -O2` --
the pipeline tools/oracle_reorder.py already proved byte-exact (0 diffs on
func_80061FA8 where the pinned path gives 57). Everything else is untouched.
Verified:
* branch selection BOTH ways: 800c3 -> reorder_passthrough, 800.c -> maspsx
* tools/reorder_passthrough.py --selftest, incl. a negative control (a line
merely CONTAINING "move", e.g. `jal remove_thing`, must not be rewritten)
* BYTE-INERT: main rebuilds BYTE-IDENTICAL via verify_binary (§384, re-extracts)
Note the first patch used `ifeq ($(filter $*,...))`, which make evaluates at PARSE
time when $* is empty -- it would have silently always taken the maspsx branch.
`$(if ...)` expands per-target, which is why the rule already uses that form for
JTBL_PADS.
|
||
|
|
91895afd3c |
fix(tools-health): audit-cdecl is a HEALTH check, not a regression suite — sample by default
Drew, correctly: "this is a tools health test, not a full regression test." audit-cdecl re-parsed every declaration in all 4,168 TUs and handed each to real gcc — ~787s of pure-Python collection before the first cc1 call. It made `make tools-health` unrunnable: >15 min, killed twice, never completed once. `--limit` already existed and its own help calls it "a fast smoke run"; nothing was using it. Sampled by default (CDECL_AUDIT_TUS ?= 60); the exhaustive form stays as `audit-cdecl-full` for when cdecl.py itself changes. audit-cdecl : >9 min -> 61s (4,777 declarations adjudicated, 0 rejected) tools-health: never completed -> 333s, rc=0, all green Known limit, recorded not hidden: --limit takes the FIRST N TUs, not a random sample, so the smoke run always exercises the same files. Randomising the sample (or rotating by seed) is the follow-up. |
||
|
|
26ba449684 |
perf(tools-health): parallelise the sig targets; fix a latent probe-file race; MEASURE the real cost
Drew asked why `make tools-health` runs 15+ min. Measured per step rather than
guessed (I guessed wrong twice first, and both are recorded in the comments):
sig-overlays ~52s serial -> 3.9s wall / 51.8s user (xargs -P$(JOBS), 32 cores)
sig-modules 0s sig-resident 0s audit-corpus 17s
audit-cdecl >9 MINUTES <-- the actual bottleneck, and NOT the gcc probes:
the `[gcc] N distinct declarations` line never printed inside a
10-minute run, so not one cc1 call had happened. `tu_statements`
over 4,168 TUs is ~787s single-core, all of it before the probes.
SHIPPED
* sig-overlays / sig-modules: xargs -P$(JOBS), same pattern extract-all and
check-all already use in this file. sig_image has exactly one write path
(its own per-alias .jsonl), verified before fanning out. NEGATIVE CONTROL:
141/141 sig files BYTE-IDENTICAL to the serial output. Also adds the failure
detection the serial loops never had -- a sig_image crash used to vanish (R32).
* cdecl._gcc_probe: `probe_{tag}.c` was ONE FIXED FILENAME PER TAG, correct only
while _sift is serial. Now unique per call, so concurrent probes cannot
overwrite each other's source between write and compile and return a verdict
about another chunk's declarations.
* cdecl._sift: threads over chunks + over the bisection probes (gcc is a
subprocess, so the GIL is released), results written back BY INDEX so the
output stays deterministic. A/B on --limit 6: IDENTICAL verdicts (829/829).
NOT SHIPPED, and the measurement is left in the code
A ProcessPoolExecutor over the collection phase was tried and REVERTED: 12 TUs
yield 32,352 statements, so the full pass ships ~11M strings through IPC and the
pickling costs more than the parse it saves. The fix is to dedupe/filter INSIDE
the worker or memoise per-TU by content hash -- left measured, not guessed.
|
||
|
|
d6e28fcb1a |
feat(tools): work_evidence — assert a tool ACTUALLY DID the work it reports
make tools-health audits DATA integrity (corpus/cdecl/binaries/digest/text) and
nothing audited TOOL BEHAVIOUR -- the gap all four S70 defects fell through. Each
reported success while doing nothing or doing harm, and none would have been found
by reading the source: a wrong instrument returns a plausible NUMBER, not an error.
tools/work_evidence.py, three assertions on OBSERVABLE CONSEQUENCE:
assert_inputs zero readable inputs is a DEFECT, not a zero-yield result. "0 of 0"
is a fact about the harness; "0 of 57" is a fact about the subject.
assert_floor work claiming a compile/gate cannot beat physics -- the ONLY tell on
the pgate defect was a 1-2s wall clock (§402).
assert_effect N claimed successes must show a persistent effect; verification is
not banking (§404).
Self-test is a negative control both directions (11/11): each assertion PASSES the
already-succeeded case and FAILS the known-bad one, and non-strict warns instead of
raising. Wired into `make tools-health` so it cannot rot (R54).
Wiring on the wave critical path:
* parallel_gate: per-worker wall-clock floor; a sub-floor worker is flagged
"BLIND SUSPECT" in the summary line instead of passing as a clean zero.
* gate_stage: the silent `if not draft_fns: return {...}` -- the exact point the
pgate defect flowed through -- is now loud and marks the result `refused`.
* harvest_verify: says at the point of confusion that "verified" is not "banked"
and names gate_stage as the entrypoint that persists.
Negative control: empty drafts dir -> loud + refused. Positive control: a real
2-draft dir still gates normally (drafts:2, no false refusal).
|
||
|
|
7a969d1c61 |
feat(o0): md_MAIN_003 carve — the module-binary -O0 route opens, func_800D0D6C banked (345 ins)
The single-object module binaries could not be carved at all: o0_subsplit planned correctly and then jr_isolate_all refused with 'unaddressable content'. That blocked 9 of the 12 remaining -O0-in-an--O2-TU functions fleet-wide, including a byte-correct 345-instruction draft with nowhere to go. THREE ROOT CAUSES behind the refusal, all fixed here: * overlay_src_split.load_ov_syms: an interior YAML comment terminated the symbol-file list. md_MAIN_003's yaml annotates the list body, so only symbols.us.txt loaded and D_800D3200 resolved to None -> refusal. * jr_isolate_all._partition: a trailing content chunk (the verbatim-asm pair after the last addressable anchor) now attaches to the LAST region when every symbol it defines resolves at/after the last cut, instead of hard-refusing. * _file_scope_decls: bare tag forward decls (struct S_D2394;) exempted from the dedupe refusal; plus addr_of's D_<hex8> fallback. THEN A LINK FAILURE THE CARVE CAUSED, worth knowing: spimdisasm migrates rodata referenced by exactly one function into that function's .s ONLY within the same subseg. The carve moved func_800D30D0 into the jr subseg while the .rodata island stayed on md_MAIN_003, so three dlabel string blocks were SILENTLY DROPPED -> undefined reference to D_800CEE58/D_800CEE80. Adding INCLUDE_RODATA does not resurrect them (splat marks them migrated segment-wide and emits nothing). The fix is to rename the .rodata subseg to the jr object, where every island emitter lives. The regenerated func_800D30D0.s came back byte-identical to the pre-carve .s. Makefile: the -O0 glob widened to src/md_*/md_*_o0?.c. Without it the region file compiles -O2 -- byte-neutral while stub-only, but every -O0 draft banked into it would mystery-fail the gate (§362's trap class). This is why the Makefile and tool hunks MUST land with the carve: a fresh clone would otherwise lose the -O0 flag. VERIFIED INDEPENDENTLY of the agent that did it: sha1 dd1b32ecf1103c6f7cf1943d25546a3046e17b14 == config/check.md_MAIN_003.sha, from a rebuild I ran myself; md_MAIN_003 13 -> 12 stubs; func_800D0D6C absent from corpus.stubs. interleave_check's DRIFT on this binary is PRE-EXISTING (identical on a clean tree, verified before any change) -- md_MAIN_003 has no _JTBL_INTERLEAVE block and must not get one; forcing ALIGNED moves the leading rodata island after .text and shifts every address by 0xD8. config/overlays.mk untouched (R59/R60). 8 of the 9 md_MAIN_003 -O0 stubs remain: they need drafts and follow-on carves. |
||
|
|
0b75da6a36 | feat(build+tools): module jtbl pads DERIVED at build time — jtbl_rodata_pads --derive <binary> --tu <tu> (walks the retail island with the emission stream: .s spans from their comments, C data anchors from D_ names, C tables lead/trailing zeros; refuses on any anchor miss); Makefile runs it for every md_* object (no stored spec, nothing to drift); harvest_verify hands module island walls to it instead of isolate/split; the modules.mk probe line removed; md_SC03_076 byte-identical from clean (P31 S62 T3a) | ||
|
|
05a4aeaf41 |
feat(o0): md_MAIN_011 compiles -O0 — 21 functions / 4,321 ins unlocked, byte-proven
Its entire code subseg is the -O0 run and the .c is stub-only, so this is the boot
precedent: a whole-object CC1FLAGS override, no splat change, no carve, and none of
the 18-P29 re-disassembly risk. Proven byte-neutral by a CLEAN per-binary rebuild —
build dirs deleted, re-extract, rebuild:
sha1 80731bac0ddd6b3e354f43b2c179582b12590752 == config/check.md_MAIN_011.sha
Landed with the coupling fix it requires, or the 21 would have stayed invisible.
Three tools decided -O0-ness from the subseg NAME ('_o0' in it, or 'boot'); this
object keeps its plain name, so match_one would have warned 'cannot bank' about
functions that now bank and the wave draw would have kept refusing to draw them. All
three now ask corpus.o0_subseg(), which derives the answer from the Makefile itself
(R33: a name is a convention, the Makefile is ground truth).
Verified end to end: match_one compiles md_MAIN_011 targets at -O0 with the
cannot-bank warning correctly gone, the wave draw emits cards for them, and
test_o0_detect still passes 167/167 coverage with 0 false positives.
|
||
|
|
e18738c48e |
feat(phase-31 T5): THE FRONTIER ATLAS — 5,139 lever-labeled crack groups over all 12,058 open fns
- tools/atlas.py: cousin units baseline + T1.5 h_seqn merges + CALIBRATED warm tier (measured: li-norm metric holds ~99% recall to 0.55; rule = smallest t with neg-accept<=0.2% AND recall>=95% -> THRESH_WARM=0.70 @ 99.1%/0.18% — false merges waste exemplar cracks, misses only route cheaper) + seed sweep (65% of open skeletons carry a >=0.55 matched seed) + kNN graph + tiny-direct + evidence joins (audit/backlog/ledgers/cards; unparsable=fatal) + lever labels with confidence measured>ledger>tell>default>UNKNOWN - partition ASSERTED: 12,058 = progress stubs 12,051 + NM 7 EXACTLY (chased the +1: data blobs now excluded, reconciled against classify() buckets; T1 banks confirmed absent); every instance in exactly one group; main joins at the atlas layer only (family maps stay non-main — 4 silent-skip hazards) - warm tier merged 1,019; top group unifies 268 drifted per-location skeletons - lever table: head-crack 186.9k ins / UNKNOWN 138.6k (honest) / extend-tell 76.7k / redraft 46.8k / jtbl-carve 45.7k / integration 23.4k / seeded 23.4k / len-vein 16.8k / swaprepeat 9.2k / plumbing 8.1k / o0 6.6k / cc1 6.4k - atlas_features: li_norm_toks exported (shared with atlas, R33; hash-stable); mid_jr verifier fixed (compared ZERO rows — R32 silent no-op; now 6,444/6,444) - make atlas = full regen chain (~10-15 min, zero tokens); --targets emits crack slates (12/12 .s resolved); survey 92 s - SETUP rows (R21); docs/frontier-atlas.md committed |
||
|
|
22eed7d78a |
feat(phase-31 T3): main enablement — sig-main at splat-true lengths + main streams
- sig_image: --seeds accepts '0xADDR NINS' (and jsonl nins); a seeded nins is
authoritative ([addr, addr+4*nins), bypasses func_end whose heuristic mis-sliced
3/40 main samples); R32 guard on seeded end > hi
- corpus: s_ins_count() factored from audit() (R33, one counter) + '--seed-ends'
CLI emitting per-stub splat-true lengths
- make sig-main: 2,002 main stubs signed -> .run/sig.main.jsonl; FULL word
cross-check 2,002/2,002 EXE slices == .s words (0 SLICE-SUSPECT; .s word field
is byte-order hex, not LE — first checker draft misread 1,999 false suspects).
Deliberately splat-SEEDED; main's independent second oracle stays deferred
(second-oracle.md; sig_is_independent(main) stays False)
- family_remap: vram_of/img_path special-case 'main' derived from splat.us.exe.yaml
(file0-vram = code-seg vram - start = 0x8000F800; target_path); stream_words
('main') verified 25/25 vs .s
- regression: sig-resident re-run byte-identical after the shared read_seeds change
- SETUP §6.3 rows (R21)
|
||
|
|
d6ade08f3a |
feat(phase-31 T0): pivot log + instrument freshness + hygiene
- decision-log: the P31 re-charter entry (organize-before-grind; R37/R38/R39 ratified at gate-1) per R31 - harvest_verify.py: import guard — a bare import now RAISES loud instead of running a full gate (CLI unchanged, verified both directions) - sig-resident: bootstrap boundary artifacts fixed (fused +0 data word with func_800CEDFC; func_800D33E0 dropped past a glued tail) -> ELF-seeded per the S45 pattern, exactly 145 fns; true denominator confirmed 145 (progress was right); audit-corpus 0 PHANTOM + 0 TRUNCATED; all three oracles agree - family maps regenerated at HEAD commit:2161: 11,025 open non-main members reconciles EXACTLY with 12,059 - main 1,034 (102 stale phantoms cleared); adapt cards 704, aprop cards 204 (full emission) - main fuel-gap finding: 2,001/2,002 main stubs already have cached Ghidra-C (only func_80049600 missing) — the roadmap '0/2,096' note was stale - tools-health OK (dedup 2,063/0; C1 254,521/254,521; audit-digest green) |
||
|
|
a0f07d629e |
chore(phase-30 S45 II.2): retirements (R33) + SETUP module recipe
- DELETED: disc_code_sweep.py (superseded by disc_audit/make audit-disc), reconcile_decls.py (superseded by reconcile_tu; incumbent row removed from cdecl audit_differential — the differential existed to prove this deletion safe), rollout_801457a4_o0/rollout_whale_o0/ rollout_o0_cluster one-shots (rollout_o0.py is the live generic), ImportOverlay.java + VerifyOverlay.java (ghidra_import_raw.sh is the live path) - reference check first (R14): the plan's 'zero build refs' was wrong for 3 — comment refs annotated, the one LIVE import (cdecl) reworked; audit-cdecl + tools-health re-proven green - SETUP §6.7: module-class recipe (TEXT_LO derivation, paired-.rodata hdr carve, A4 symbol- window law, ELF-seeded sig-modules) + new_binary.sh inventory row + 3 RETIRED rows (R21); disc-completeness Reproduce marked retired |
||
|
|
4cadac4e11 |
feat(phase-30 S45 II.1c): module batch dedup-banked + verified — 408 banks, R22 183/183, audit-disc 75->34 (parked-only)
- dedup measure (R37 probe): 69/1,113 module fns h_exact-match matched corpus (~6%, LOW as
planned — modules are novel frontier); dedup_extend inapplicable (same-vram group model) ->
family_sweep --hseq --band all over the 57 matched-exemplar families: 408 member-matches
banked (182 into modules, 226 into the big 3 — families Part I's --only scoping missed),
169 failed + 77 STRUCT = genuine per-member frontier
- R22 clean-fleet 183/183 BYTE-IDENTICAL; audit-disc UNCLAIMED 75->34 residue 0 (34 = 31
parked-for-L3 + SC03/53,54,56 — 3 rows Discovery-3 never tiered, now parked with evidence)
- three instrument fixes, each negative-control-proven:
- family_sweep --hseq stub map derives ov_*+md_*+resident (was sig.ov_* glob -> module
members silently 'not-stub', R32 class) [committed earlier as commit:1506]
- sig-modules seeds from the built ELF's func_* symbols (bootstrap GLUES adjacent fns
around jtbl dispatch -> 24 false TRUNCATED; perturbed-sig control still bites)
- corpus.audit counts CODE lines only (module .s carries its header jtbl as .word lines);
progress.py buckets INCLUDE_RODATA symbols as blobs (unbucketed R32 hole)
- NEW HONEST BASELINE (183 binaries): 94.0% instr / 95.96% fn-count / 87.6% distinct;
tools-health OK, audit-digest OK
|
||
|
|
ec1a805766 |
feat(phase-30 S44 I.1b): Makefile learns the module class — modules.mk + sig-modules
- -include config/modules.mk (silent when absent, same contract as overlays.mk) and BINARIES += $(MODULE_BINARIES). Everything downstream of $(BINARIES) — prune, check-all, build-all, expected — is untouched and picks modules up automatically. - NEW sig-modules target: signs every module at ITS OWN vram with ITS OWN --text-lo (the §154 module-id-word law — bootstrap from offset 0 yields 0 functions on 75/78 payloads). Derived MODULE_SIG_JOBS from modules.mk (R33, the sig-overlays pattern). Wired into tools-health after sig-resident. Empty registry = clean no-op (verified). - NEGATIVE CONTROLS: make -n sig-modules iterates an empty list; main rebuilds 143dbb89 byte-identical with no modules.mk present. |
||
|
|
03794d91cd |
feat(phase-30 S43): make audit-disc — the disc PARTITION holds at residue 0; 34 UNCLAIMED code payloads
L1 of Drew's definitive disc audit ("we really need a full audit that definitively lists ALL code
that we need to decomp"). THE INVARIANT (R32): every byte on the disc belongs to exactly ONE bucket,
the buckets SUM TO THE DISC, and residue is a DEFECT — a partition with an asserted residue of zero
is a completeness proof; a longer list is only a longer list.
- WALKS THE DISC IMAGE, NOT OUR CONFIGS, classifies WHOLE payloads (no window), and decodes BOTH the
raw and LZSS layers — the three shapes that produced the three "more code all along" surprises
(the 0.4.dec glob missing 4 SC07 overlays; disc_code_sweep blind to COMPRESSED code, its type-4
row vacuous for 138 known binaries; a 4,096-word window reading only payload heads).
- CLAIMED-BY IS DERIVED (R33): config/check.<bin>.sha IS the SHA1 of that binary's disc payload, so
payload->binary is a hash lookup against the build's own byte-identity gate. It cannot drift.
- RESULT, 416,021,760 bytes, 1,291 payloads, RESIDUE 0:
onboarded-code 32,564,876 (7.83%) · UNCLAIMED-CODE 1,700,049 (0.41%) ·
classified-data 150,631,480 · audio-video 184,338,000 · filesystem-metadata 46,787,355
34 UNCLAIMED code payloads — largest a 383,783 B type-1 in MAIN.CD, the rest small type-1 entries.
These are the "there was more code all along" surprises, now ENUMERATED instead of stumbled into.
- MY OWN FIRST RUN FAILED THE PARTITION by -49,709,520 B, and the fail-closed exit is what caught it:
.DA entries' LBAs point PAST track 1 into the CD-DA tracks (double-counted against the whole-track
audio total), and .STR/.XA are MODE2 FORM2 (2324 user bytes/sector, not 2048). Both fixed.
- NOT wired into tools-health: it needs disks/, which a fresh clone does not have (H1).
- KNOWN GAP, stated not hidden: LIST.CD fails the TOC walk (it IS the TOC cache, not a container)
and is booked as data — correct today, worth a real classifier when L2 lands.
|
||
|
|
a0e499d8f1 |
feat(phase-30 S39): make audit-frontier — the reconciliation gate (Drew's MASTER_REMAINING, derived form)
Drew asked whether we should build a master list of all funcs, a banked list, and a MASTER_REMAINING = total - banked that we hand-edit on every bank. Assessment in docs/decision-log.md (2026-08-04): ADOPT THE GOAL, REJECT THE MECHANISM. The triple already exists and is DERIVED, not maintained: total = .run/sig.*.jsonl (sig_image over the ORIGINAL bytes, independent of splat) banked = sig - stubs (INCLUDE_ASM pastes the original asm => not-wrapped == byte-exact) remaining = corpus.stubs() (filesystem-derived, coverage-asserted) and "remove it when we bank it" already happens -- banking IS deleting the INCLUDE_ASM line. A hand-maintained file would drift SILENTLY and flatteringly, which is the exact failure R33 exists for (fuel_manifest recorded 130 live stubs when the truth was 30, hiding 91.6% of remaining gain). What was genuinely missing is CROSS-ASSERTION. Six artifacts answer "what's left" -- corpus.stubs, worklist, backlog, family_hseq, fuel_manifest, progress.fleet -- each individually derived, none ever compared to the others. That is what cost P30 T0 a hand-reconciliation (family_hseq 29,961 vs progress.py 28,296). R34: not a better assertion inside one oracle, but a second one that can argue. tools/audit_frontier.py takes corpus.stubs as the reference and checks every other view against it: rows/targets naming an already-banked function, and any view whose PUBLISHED count disagrees with a recount. On first run it immediately caught a real one: family_hseq publishes 11,456 unmatched instances; only 11,297 of its members are still open per the corpus (delta +159) -- the map predates tonight's 159 banks. Ranking work off it would have mis-scoped by that much. It also PRINTS ITS OWN SCOPE LIMIT, deliberately: agreement here does NOT mean the denominator is complete. Every view, and the byte-gate itself, is blind to never-onboarded code -- the 39 type-1 modules and main's missing independent boundary oracle stay open (R34/R36). DELIBERATELY NOT wired into tools-health (Drew said "dont do this now" about the master list; this is the additive half). --strict exits 1 for when he wants it binding; wiring is one line. |
||
|
|
1576570271 |
fix(phase-30 S1e): the distinct-code "regression" was a STALE DIGEST — alias lever ungated
The S38 checkpoint gated the phase's best lever ("do NOT scale the alias lever") on
distinct-code falling 89.3 -> 89.2. It never fell.
PROOF (each commit's metric recomputed from its OWN committed tree, 0 unresolved):
commit:1426 TRUE : instr 12394533 distinct 5022306 (77895 uniq)
commit:1426 COMMITTED: instr 12402412 distinct 5029324 (78025 uniq) <- stale
HEAD TRUE == COMMITTED: instr 12405402 distinct 5025082 (77952 uniq)
=> true delta 843->HEAD: instr +10869, distinct +2776 ins / +57 uniq. ALL ROSE.
The 843 digest was generated from a working tree still holding work REVERTED before the
commit landed (+7,879 ins / +130 uniq overstated) and never regenerated, so the next
HONEST digest read as a fall. => THE ALIAS LEVER IS UNGATED (scale it, §61 small batches).
Both recorded leads were wrong (R14): progress.py:423's SIG regex feeds fn-count ONLY
(neither weighted metric sees a C identifier — both derive matched = sig - corpus.stubs),
and "the harvest reverted functions to INCLUDE_ASM" died on one grep (483 removed, 0 added).
The 3-grep proof: identical sigs + unchanged tools/ + zero +INCLUDE_ASM => HEAD's stub set
is a strict subset => both numerators are FORBIDDEN to fall.
THREE INSTRUMENT DEFECTS, all one class (a bare except around a fail-CLOSED oracle):
- progress.py stub_addrs wrapped corpus.stubs in `except Exception: return set()`. An empty
stub set means "could not answer", not "no stubs", so matched = sig - stubs credited EVERY
function. Byte-witnessed: instr 100.00% / distinct 100.00% in a tree with no asm/. Now
propagates.
- cast_call_sites.tu_for + reconcile_tu.tu_for had the identical swallow, falling back to the
default <ov>.c instead of the jr/-O0 split TU — silently reinstating the exact bug
cast_call_sites' own docstring says it exists to fix. A wrong-TU reconcile fails the gate,
and this phase's base rate is ~24k PLUMBING vs 4,917 DIFF, so it presents as a codegen wall.
Now propagate CorpusError; ValueError fallback for curated names preserved; derived-TU path
re-verified (a _jr_ split stub resolves correctly, both tools agree).
NEW GATE (R34 — the byte-gate is a null oracle for DOCUMENTS; check-all stays 140/140 over a
stale digest forever): tools/audit_digest.py + `make audit-digest`, wired into tools-health
after report. Recomputes the three headline metrics from the current tree and fails if the
committed digest disagrees. Compares INTEGERS, not percentages — the +7,879-instruction
staleness printed as "94.4%" on both sides. Negative-control-proven against the stale 843
digest (fails, exit 1) and green on HEAD.
Verified: make report exit 0 (dedup-check 1910 validated / 0 failed, C1 coverage
241216/241216); audit-digest OK; cookbook-index OK (398 sections); metrics unchanged by the
fix (94.40% / 89.18%). No src/ or config/ edits — no bytes touched, nothing banked.
cookbook §140 · decision-log 2026-08-04 · SETUP.md inventory (R21) · R14/R32/R34/R35.
|
||
|
|
b9efe66f91 |
fix(phase-30): the JR-PAIR "wall" was TWO instrument defects — pair banked, class retired
S28 ledgered `JR-PAIR-IN-ONE-O0-OBJECT` (two jr fns matched in one -O0 object => a clean build that cannot link: `undefined reference to $L105` + `func_8013C938`) with §81 step 1 (isolate one into its own code subseg) as the untested escape. BOTH the class and the escape are REFUTED — no isolation, no compiler wall, both fns banked from a genuinely clean fleet. The 4th consecutive "structural wall" to resolve to our own tooling (§124/§125/§126/§131). - DEFECT 1 (tools/jtbl_carve.py): ov_SC01_077_o0's carve at 0xb01a4 predates the §8e `tables=` persistence and is a MERGED DOUBLE (func_8013C0F8 $L75 + func_8013C414 $L105); the 2nd owner is MATCHED so extract pruned the stub .s naming its table. The single-table- predecessor inference derived 3 starts where the object emits 4 tables -> JTBL_PADS 0,4,4 -> jtbl_rodata_pads refused mid-stream, correctly. FIX: R32 coverage assertion + payload recovery at the single choke point (spec_from_starts) — every zero word inside a span is an original `.align 3` pad (the tool's own axiom), so the word after it STARTS a table; recovered starts are logged. No-op where structure is known (the 134 sibling _o0c spans carry tables=+0x0,+0x70). Honest limit: tight (0-pad) boundaries stay unrecoverable but fail LOUD via the filter's count guard — never silent. - DEFECT 2 (Makefile): no .DELETE_ON_ERROR, so `as` (a pipeline consumer) left a TRUNCATED .o on disk — 12 of 16 T func_, undefined $L57/$L59/$L63/$L75/$L76 — newer than its .c, and the NEXT build linked the corpse. That IS the S28 link error, one build downstream of a loud, correct compile error. Negative-control-proven on a scratch invocation. - BANKED: func_8013B83C (272 ins) + func_8013BD74 (198 ins) in ov_SC01_077 (d19c9580). Byte proof: 4 tables 0x801D8254/828C/82FC/836C (13/27/27/27 entries, each zero-pad separated); span 0xb00fc..0xb0280 = 388 B = 52+4+108+4+108+4+108 exactly; spec 0,4,4,4. - R22 clean-fleet (make clean + extract-all + check-all): 140 passed, 0 failed of 140. The incremental result was NOT trusted (§130). Fleet 93.25% fn-count / 89.2% instr / 80.5% distinct; dedup 1905/0; 0 NON_MATCHING (G4). - cookbook §132 + index (356 sections): the mechanism, the fingerprint (an undefined $L<n> in a LINK error is a truncated object, never codegen), the 30-second standalone-TU ladder that named the 4th table owner before any build, and the transferable rule — a fail-loud guard is only as trustworthy as the artifact hygiene around it. |
||
|
|
05f6293067 |
fix(phase-30): 137 tracked C sources held a raw NUL that made grep SILENTLY SKIP them
FOUND BY ACCIDENT, WHICH IS THE POINT. `grep -rn func_8013C08C src/` returned NOTHING for a function that is defined right there. The file held a RAW NUL byte inside a character literal — the source read `== '<NUL>'` where it should read `== '\0'`. It COMPILES (the fleet was byte-identical), so no byte-gate ever objected. But file(1) classifies such a file as `data`, and **grep treats a file containing NUL as BINARY and reports nothing, silently**. The whole file therefore vanished from every grep-based audit and every hand search. I burned real time chasing a phantom missing function before `file` gave it away. SCOPE, measured: 137 files — every `_o0c`/`_o0e` region created in THIS session. The templated bodies carried the NUL fleet-wide, so I propagated the defect today. All fixed (`'<NUL>'` -> `'\0'`); R22 CLEAN-FLEET 140 passed, 0 failed of 140 => byte-neutral. NEW ORACLE: tools/audit_text_sources.py + `make audit-text-sources`, wired into tools-health, coverage-asserting over all 3,887 tracked .c/.h files (R32). This is the SAME silent-skip family as SS124 (a scanner that cannot see something reports it is not there) and SS126a (a bare except swallowing a coverage assertion) — but one layer LOWER, in the tool everyone reaches for first. The byte-gate is structurally blind to it (R34): the bytes are correct, so it has nothing to say. It needs its own oracle. MY OWN ERROR, RECORDED: proving the new guard fires, I injected a NUL into the REAL tracked file and restored it through nested shell escaping. The restore left `'\\0'` — an escaped backslash, i.e. a multi-character constant, NOT a NUL — a genuine semantic change. R22 caught it (139/140) in one cycle, before any commit; repaired to `'\0'` (10465 -> 10466 bytes) and re-verified 140/140. The lesson is not "be careful": a negative control must corrupt a SCRATCH COPY under .run/, never the tracked file it is testing. Testing a guard must not risk introducing the defect the guard exists to catch. |
||
|
|
45d26cc413 |
feat(phase-30): T2 carve-within-a-carve PROVEN end-to-end; Arm-A does not bite; 3 -O0 fns banked
T2's central unknown is resolved, and it is NOT what the phase plan predicted. The plan
named the Arm-A splat `%lo +0x20` defect as T2's real substance. Four probes, each
isolating ONE variable, SHA vs config/check from a clean tree (SS125 rules):
1. sub-split the jr object at ARBITRARY addresses, pure -O2 -> BYTE-IDENTICAL
** the re-carve is NEUTRAL; Arm-A does not bite here **
2. same split, middle region routed to -O0 -> DIVERGED (2 vars at once)
3. same NAME as probe 1, only the -O0 flag added -> DIVERGED
** therefore the FLAG, not the subseg name **
4. -O0 regions cut to EXCLUDE the matched bodies -> BYTE-IDENTICAL
** route PROVEN end-to-end **
THE REAL OBSTACLE: an address range is not an optimization region. Interleaved among the
15 -O0 stubs at 0x80183CF0..0x80184920 are TWO already-MATCHED functions (func_80184440,
func_801848E4) that expand from engine_core.h and compile at -O2. Flipping the FILE to -O0
recompiles them too. Cut around them and it is byte-clean.
MY EARLIER "15 contiguous -O0 fns, clean cut" WAS AN UNDER-COUNT (R14 on myself): I derived
it by scanning asm/**/*.s for the frame-pointer prologue, and a MATCHED function emits no
.s -- so the scan was structurally blind to exactly the bodies that break the flip. Same
shape as SS124. Any T2 driver must derive -O0 bounds as (address range MINUS already-matched
bodies), never from an asm scan.
BANKED (3, whole-binary gate the sole arbiter): func_801846E4 + siblings func_8018473C /
func_80184794. Each global DERIVED FROM THE ASM (%hi/%lo operands), not taken from the
draft's comment; 3/3 match_one --o0 MATCH (22 ins) with a -O2 control showing the mismatch;
3/3 verified through harvest_verify; bank truth read from the SOURCE (SS55b trap 4).
MAKEFILE: the -O0 glob widened `ov_*_o0b.c` -> `ov_*_o0?.c` so ANY lettered -O0 sub-split is
covered by one rule. A MISSED rule is silent -- the region would compile -O2 and every
residual it produced would be a pure artifact (SS116). corpus.o0_sources() re-verified: 137
sources, resolves `?` via glob, both pre-existing rules intact.
CONFIG: ov_SC03_014_jr_8017EB7C sub-split into 5 regions (pre / _o0c / matched-O2 /
_o0d / post), reusing jr_isolate_all's plan+build_new_config+validation verbatim so the
carve-repoint and source-repartition semantics are the proven ones.
R22 CLEAN-FLEET: extract-all 139/139 (+main) ; check-all 140 passed, 0 failed of 140.
|
||
|
|
4570a5854b |
feat(phase-30): cookbook-index — a SYMPTOM-keyed index (226 sections, derived + coverage-asserted, in tools-health)
Wave-1 measured the tax: three agents each reported a 'NEW idiom' that was ALREADY documented — the asm-label alias (line ~2516, same 'address-of perturbs regalloc' mechanism) and the void->s32 non-neutrality (§41d, Phase 26; the agents cited the very entry §41d corrects). They consulted the cookbook as instructed and could not FIND them. 716 KB / 226 sections with no index = a discoverability failure, and every wave re-paying for prior waves' findings is the inverse of R16. docs/cookbook-index.md maps SYMPTOM (what you see in the diff) -> sections, 14 buckets, a section listed under every symptom it addresses. Derived by tools/cookbook_index.py (R33 — cannot drift), --check wired into tools-health. R32 on my own tool: the first regex required an em-dash separator and silently dropped 50 sections — including §1 (idiom catalog), §2, §5a (cross-jump, cited by an agent today). An index missing its most-cited entries turns 'I could not find it' into 'it is not there'. Now asserts extracted == candidate '§' headers and hard-exits on a gap. |
||
|
|
ef85803b1f |
feat(phase-29): backlog prune — compact the append-only near-miss log + wire into make report
The near-miss ledger (.run/backlog.jsonl) is append-only, so it filled with already-banked noise: 6,867 rows, ~98% banked. load_best()/render() already filtered on READ (docs/backlog.md was correct), but the raw log drifted stale and every render re-scanned all 6,867 rows against the stub oracle. - backlog.py: new `prune` subcommand — atomic rewrite (temp + os.replace) to load_best()'s output (drop-now-matched P9 + best-per-addr collapse). Idempotent. 6,867 -> 1,704 open near-misses. - Makefile: `backlog.py prune` wired into `make report` (BINARY=main block) so the ledger tracks reality every cycle instead of drifting. - Finding (Drew's question): crack waves DO log every non-byte-match to the backlog durably (gate_stage copies best_draft -> .run/backlog_drafts/). BUT the `closeness` field is UNRELIABLE — byte-correct drafts (match_one MATCH) are logged with closeness>0 (e.g. func_8012F49C logged 29, actually MATCH). And a reach-N function's draft is overlay-SPECIFIC (per-location symbols), so the backlog is a messy recovery source vs the fresh per-wave stranded drafts. Integration-recovery should consume the fresh wave-dir strandeds, not re-derive from the backlog. |
||
|
|
5e78dccf07 |
feat(phase-29 §8e): jtbl 8-align pad-spec filter — the 4-giant unblock tooling (fleet-neutral)
- ROOT CAUSE PINNED (the session-2 half-pin was INVERTED; both probes were vacuous, R35): cc1 emits .align 3 before EVERY jump table; maspsx passes it VERBATIM (the :435 'drop' is an inventory-only pass); as bakes the pad SECTION-RELATIVE; link placement was never guilty (SUBALIGN(2) + ALIGN(.,4) place 4-mod-8 carve starts tight). Merging originally-separate TUs fires an intra-TU align where the original packed tight -> +4 at rodata 0xCC -> image-wide %lo shift. Honest probes persisted: .run/probe_jtbl/ (verdict.md + objdumps). - NEW tools/jtbl_rodata_pads.py: post-maspsx filter replaces each rodata .align 3 with the ORIGINAL's exact pad bytes per a JTBL_PADS spec; fail-loud on table-count drift / non-align-3 / non-jtbl rodata content. Byte-proven: verbatim 0xE4 pad-at-0xCC -> filtered 0xE0 tight (= the merged carve span). - jtbl_carve.py: spec-aware same-subseg merge (gap 0 or 4-with-zero-payload-word; else NON-CONTIGUOUS -> isolate), interval-arithmetic pad specs (committed values CARRIED, never re-derived), JTBL_PADS target-var emission into overlays.mk + revert() restore + stale-.o invalidation; the false 'maspsx drops .align' docstring corrected (H5). - Makefile: $(if $(JTBL_PADS),| jtbl_rodata_pads.py ...) stage in build/src/%.o + file-scope empty default (env-shield). jtbl_family_bank.stub_file: duplicate-stub fail-loud (the earlier 'ladder failure' was a wrong-TU splice into a stale _a.c stub, byte-witnessed). - R22 clean-fleet WITH the fix wired: 140/140 byte-identical, tools-health green (dedup 1846/0, C1 234205/234205), ZERO new banks -- fleet-neutral by construction. - cookbook §8e (the jtbl alignment law) + §8a/§8a-pad corrections; decision-log R31 entry; SETUP.md tool row; .gitignore allowlist for the probe verdict artifacts. |
||
|
|
f6f89781ff |
feat(phase-29 T2 Arm A): swing verdict = BANKED FACT (9/9 -O0 members on ov_SC07_010); fleet -O0 rollout deferred at the splat wall
- tools/rollout_o0_cluster.py (new) + Makefile O0_CLUSTER_OBJS -O0 wildcard: the -O0-cluster carve (0x13410..0x14834), adapting rollout_whale_o0.py to a 3-way <ov>/<ov>_o0/<ov>_o2b split - ov_SC07_010: carve byte-neutral -> family_sweep --hseq banked 9/9 -O0 exemplar-family members whole-binary (R22 clean-fleet 140/140). The Task-1 masked-MATCH swing verdict is now a BANKED FACT: -O0 cluster members DO bank at -O0 (§52b). Phase-20 'func_8013B7AC overlay-local' refuted. - THE WALL (byte-proven, TOOLING not compiler): the same carve on 006/007/011 byte-shifts the whole image (+0x20 %lo data-symbol shift, 34% diff) from a CLEAN build; boundaries verified as real fn-starts. Root cause = splat re-disassembly of a 3-way-split subseg that still holds INCLUDE_ASM stubs (the whale's stub-free _o0b shape avoids it). The Phase-20 '-O0 split infra' wall, root-caused. - DEFERRED (ROI): full -O0 fleet rollout (~1,233 / ~0.6pp) — 3/4 sampled walled + 134 jr-embedded + bigger levers (Task 3 core-cracks, Task 6 tiny-IMM ~5,566). decision-log R31 + cookbook §18-P29. - 140/140 byte-identical; dedup 1840/0; 0 NON_MATCHING (G4); main 143dbb89. Task 2 substantively done. |