mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
fix(phase-32): T2c (1) — split_indicator's population is derived from the yamls (was a stale stored 213-name list; tools-health said "213 OK of 213" over 218 binaries); R32 denominator assertion
This commit is contained in:
@@ -782,6 +782,7 @@ Every script under `tools/` (plus the two report make-targets), grouped by purpo
|
||||
| | `tools/main_diff_locate.py` — `classify()` | **(P31 S75, cookbook §447)** The `TABLE REJECT` class was **unreachable for main** and demanded purity. (a) It summed bytes whose object string contains `(.rodata)`, but main's `section_order` is `[.rodata, .text, .data, .bss]` — its rodata sits BELOW `.text` and its jump tables live in `.data` objects, so the test could never fire on the one binary with the most jump-table functions left. Now matches `(.data)` OR `(.rodata)`. (b) It required `ro == outside`, so a few bytes of perturbed code dropped the verdict through to `PLUMBING REJECT` and its §376 declaration advice. Now **dominance-based** (≥60%), reporting the split and naming which part is the table problem and which the declaration problem. Measured on `SaveLoadRoutine` (1,165 ins, the §434 wall): body BYTE-IDENTICAL, 3,787 of 3,989 differing bytes (94.9%) in `.data` jump tables, 202 (5.1%) in `.text`, built image 4 bytes SHORT — verdict moved `PLUMBING REJECT` → `TABLE REJECT (MIXED)`. The §376 chain had been run on it twice and fixed nothing, because it addresses the 5%. NC over all five pre-existing verdict shapes: 5 of 6 unchanged. |
|
||||
| | **`tools/asm_in_c.py`** (NEW) | **(P31 S75, cookbook §448)** Finds every function that is **assembly posing as C** — a §265 file-scope `__asm__` body (class A) or a C function whose body is only asm statements (class B) — while correctly EXCLUDING the §3a cross-jump barrier, which is what `__asm__` means in 3,182 of the 4,224 sources. Measured: **199 functions, 154 of them GAME CODE, 171 in main**, largest `SaveLoadRoutine` (1,165 ins). These were in NO `progress.py` bucket, so main's REAL% was overstated (45.88% → **42.15%** once counted; `progress.py` gained a `VERBATIM __asm__ bodies` line that counts them byte-identical but NEVER as REAL). Design is the point: **three independent detectors that must agree**, disagreement reported as a defect (it caught `jtbl_*` being claimed as functions); **SDK-ness derived from the 14 shipped PsyQ archives via `nm`** (2,227 symbols), not a hand list (which had mis-classified `VectorNormalSS`/`SquareRoot12`/`OuterProduct12` as game code); coverage asserted; and **`--selftest`** with a known-true case of EVERY spelling — hand counts went 116→112→108→178→199 because the sources use both `".ent\tNAME\n"` and `".ent NAME\n"`, and a bare `".ent\t"` fragment yields a phantom function called `t`. Run `--selftest` before believing the number. |
|
||||
| | `tools/jtbl_carve.py` — `_merge_pre` carries `--pre` forward | **(P32 T1a, cookbook §498)** `set_overlays_var` rebuilt `<bin>_JTBL_INTERLEAVE` from the carve set alone and DROPPED the binary's `--pre <obj>` clause (the resident's §8f leading-rodata sandwich, `--pre hdr.rodata.o` — the one binary in the fleet with a `--pre`). `make extract` then refused (`ld_interleave: hdr.rodata.o … would be parked with .text`), the build linked the STALE script, and the gate booked the draft as DIFF (249,252 differing bytes of artefact). `_merge_pre(existing_line, args)` now carries an existing `--pre` into the regenerated value; idempotent; a line without `--pre` is unchanged (every overlay). Unit control on 4 shapes. |
|
||||
| | `tools/split_indicator.py` — population derived, not stored | **(P32 T2c)** When `.run/S70_bins_sorted.txt` existed it WAS the population: a stored 213-name list that went stale at the first onboarding (R51) — tools-health printed "213 OK of 213" over a 218-binary fleet. The population is now derived from `config/splat.*.yaml` (+ main); the S70 file is an ORDER hint only, new names are appended, and the denominator is asserted equal to the yaml-derived set (R32). Verified 218/218 after the five module onboardings. |
|
||||
| | `tools/payload_base_evidence.py` | **(P32 T2a, memory-map §S45 p7)** Static, controls-gated base evidence for a never-onboarded payload: module-id word, TEXT_LO estimate (first `addiu $sp,-N`), absolute-pointer set, `lui` hi-half histogram, and a **jal→function-start vote** (every `target − start` pair; ≥2 hits = a self-calling module — starts = prologues ∪ the word after each `jr $ra`+delay, because leaf functions have no prologue). Scores a BOUNDED candidate list (the 5 §S44 slots ∪ all 134 IDXTAB DESTPTRs ∪ the vote's top bases): STRONG (every internal jal lands on a start, ≥2) / CONSISTENT / INCONSISTENT / NO-EVIDENCE; prints an AMBIGUOUS tie set instead of picking; **OUTWARD-EXPLAINED** (T2b): a pure vote base whose internal targets are function starts of the fleet's overlays is downgraded (SC03/56's false STRONG); the requester cross-check (`req_fit`) is informational only — shared engine code makes every requester fit. **R39 `--controls`** (runs before ANY emission): the byte-proven bases of md_MAIN_008/011/013/042, md_SC03_073, md_SC02_009, md_SC07_004 must come out top-ranked from their payloads alone (7/7; every TEXT_LO estimate == the yaml's incl. the header-table modules 0x7C/0x14/0x158). **R43:** a payload with no self-reference at any candidate is REFUSED as base-independent. The byte gate (`new_binary.sh` first build) is the arbiter (P9). `--json` keeps the rows (`.run/P32/t2a/evidence.json`). |
|
||||
| | `tools/interleave_check.py` — `--pre`-tolerant anchor | **(P32 T1a, cookbook §498)** The `--order` anchor was `:= --order`, so a line carrying `--pre <obj>` first (the resident) parsed as n=0 and printed a false DRIFT (rc 0 — a soft alarm). Now `:= (?:--pre \S+ )?--order`; `--fix` already preserved the prefix (it replaces only the order token). Control: ov_SC02_017 ALIGNED n=44 unchanged; resident ALIGNED n=5. |
|
||||
| | `tools/harvest_verify.py` — post-carve extract rc checked | **(P32 T1a, cookbook §498)** `_jtbl_prep_one` ran `make extract` after a successful carve and IGNORED its exit code (R49/R61): a refused layout left the stale linker script in place and the whole-binary gate reported the draft as DIFF. Now a failed re-extract prints `!! extract-after-carve FAILED … CARVE refusal, NOT a draft verdict`, restores the carve snapshot, re-extracts, and returns refusal (CARVE-REFUSED class). |
|
||||
|
||||
@@ -297,12 +297,20 @@ if __name__ == '__main__':
|
||||
a = ap.parse_args()
|
||||
if a.self_test:
|
||||
sys.exit(self_test())
|
||||
bins = [a.binary] if a.binary else \
|
||||
(open('.run/S70_bins_sorted.txt').read().split()
|
||||
if os.path.exists('.run/S70_bins_sorted.txt')
|
||||
else ['main'] + sorted(os.path.basename(p)[len('splat.'):-len('.yaml')]
|
||||
# THE POPULATION IS DERIVED FROM THE YAMLS (R33/R36; P32 T2c). `.run/S70_bins_sorted.txt` used to BE the
|
||||
# population when present — a stored list that went stale the moment a binary was onboarded (R51): tools-health
|
||||
# printed "213 OK of 213" over a 218-binary fleet and nobody could tell. It is now an ORDER hint only; every
|
||||
# yaml-derived binary it does not name is appended, and the denominator is asserted (R32).
|
||||
derived = ['main'] + sorted(os.path.basename(p)[len('splat.'):-len('.yaml')]
|
||||
for p in glob.glob('config/splat.*.yaml')
|
||||
if 'us.exe' not in p and 'template' not in p))
|
||||
if 'us.exe' not in p and 'template' not in p)
|
||||
if a.binary:
|
||||
bins = [a.binary]
|
||||
else:
|
||||
order = open('.run/S70_bins_sorted.txt').read().split() if os.path.exists('.run/S70_bins_sorted.txt') else []
|
||||
known = set(derived)
|
||||
bins = [b for b in order if b in known] + [b for b in derived if b not in set(order)]
|
||||
assert sorted(bins) == sorted(derived), "split_indicator: population != yaml-derived set (R32)"
|
||||
bad = 0
|
||||
for b in bins:
|
||||
st, lines = check(b)
|
||||
|
||||
Reference in New Issue
Block a user