fix(progress): the #else half of a NON_MATCHING block is LIVE — REAL was undercounting by 7

classify() consumed everything from '#ifdef NON_MATCHING' through '#endif', swallowing
the #else half. But banking replaces the #else INCLUDE_ASM with the real body and leaves
the old attempt in the dead half — so every function banked that way landed in NO bucket:
not real, not a stub, invisible in both numerator and denominator.

Measured: CdReadStateMachine, CdReadSectorReadyCB and StreamLoadStateMachine are
byte-identical in the shipped build and counted as zero. REAL 873 -> 880, matchable
1911 -> 1918 (seven functions fleet-wide, not the three I first checked).

Now consumes only the DEAD half, then decides from the LIVE half: an INCLUDE_ASM there
still buckets as NON_MATCHING (accounting unchanged), anything else rewinds and is
classified normally.

THIRD coverage defect of this exact shape in this one function — the K&R-definition case
(~190k instructions erased) and the '#if 0' case are both documented in its own comments,
which is what pointed me at it. A scanner that walks preprocessor structure needs a test
per branch, not per directive.

Found by the S73 documentation audit, which I had written off as producing only doc typos.
This commit is contained in:
Drew T
2026-09-02 17:14:32 -06:00
parent 5ae43099e0
commit 9f8242d63c
3 changed files with 38 additions and 15 deletions
+4 -4
View File
@@ -4,21 +4,21 @@
# cross-binary collapsible-byte leverage: docs/duplicates.cross.md.
# THREE progress metrics (all matter — see the labels):
FLEET fn-count byte-ident: 362828 / 362952 = 99.97% (REAL+LINKED+empties; FUNCTION-count, ×134-inflated — one crack counts per overlay)
FLEET fn-count byte-ident: 362835 / 362959 = 99.97% (REAL+LINKED+empties; FUNCTION-count, ×134-inflated — one crack counts per overlay)
FLEET instr-weighted : 13472251 / 13523865 = 99.6% (shipped .text across main + resident + 211 overlays; the decomp.dev-DISPLAY number)
FLEET distinct-code(uniq): 5801059 / 5851972 = 99.1% (90847/90929 unique fns; the DISTINCT-RE number)
MAIN game-code weighted : 44903 / 79510 = 56.5% (INCLUDED in the fleet numbers above since 2026-07-22 — roadmap §1 metrics contract; LINKED-excluding Ghidra sig dated 2026-08-05; caveat is R34: no independent second oracle for a PS-X EXE, NOT drift)
(fleet EXCLUDING main, for continuity with pre-2026-07-22 readings: 13427348 / 13444355 = 99.9%)
FLEET REAL substantive : 360658 (of which dedup-shared 255632 via 2220 groups / 255708 instances)
FLEET REAL substantive : 360665 (of which dedup-shared 255632 via 2220 groups / 255708 instances)
FLEET LINKED PsyQ objs : 959
FLEET NON_MATCHING : 0 (0 in any default build — G4)
FLEET INCLUDE_ASM stubs : 124
FLEET matchable : 362952
FLEET matchable : 362959
| binary | REAL | shared | LINKED | byte-ident | matchable | byte-ident % |
|---|---:|---:|---:|---:|---:|---:|
| main | 873 | 2 | 959 | 1875 | 1911 | 98.1% |
| main | 880 | 2 | 959 | 1882 | 1918 | 98.1% |
| resident | 138 | 0 | 0 | 140 | 145 | 96.6% |
| md_MAIN_001 | 11 | 0 | 0 | 11 | 11 | 100.0% |
| md_MAIN_003 | 43 | 0 | 0 | 43 | 47 | 91.5% |
+5 -5
View File
File diff suppressed because one or more lines are too long
+29 -6
View File
@@ -595,12 +595,35 @@ def classify():
while i < n:
s = lines[i].strip()
if s.startswith('#ifdef NON_MATCHING'):
blk = []
while i < n and not lines[i].strip().startswith('#endif'):
blk.append(lines[i]); i += 1
i += 1
m = re.search(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', '\n'.join(blk))
if m: nonmatching.append(m.group(1))
# THE `#else` HALF IS LIVE CODE, AND SWALLOWING IT UNDERCOUNTS REAL (P31 S73).
# The old form consumed from `#ifdef NON_MATCHING` all the way to `#endif`, so a
# function BANKED into the `#else` branch landed in NO bucket at all — not real,
# not a stub, invisible, exactly like the K&R and `#if 0` defects documented below
# and above. Measured: CdReadStateMachine, CdReadSectorReadyCB and
# StreamLoadStateMachine were all byte-identical in the shipped build and counted
# as zero, because banking replaces the `#else` INCLUDE_ASM with the real body and
# leaves the old attempt in the dead half.
# So: consume only the DEAD half, then decide from the LIVE half. If it still holds
# an INCLUDE_ASM the function is a NON_MATCHING stub (unchanged accounting); if it
# holds anything else, rewind and let the normal scan classify it.
dead = []
while i < n and not lines[i].strip().startswith(('#else', '#endif')):
dead.append(lines[i]); i += 1
had_else = i < n and lines[i].strip().startswith('#else')
i += 1 # step over the `#else` / `#endif`
live_start = i
live = []
if had_else:
while i < n and not lines[i].strip().startswith('#endif'):
live.append(lines[i]); i += 1
i += 1 # step over the `#endif`
m = re.search(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', '\n'.join(dead + live))
if m and (not had_else or re.search(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)',
'\n'.join(live))):
nonmatching.append(m.group(1)) # still a stub: accounting unchanged
continue
if had_else and live:
i = live_start # a banked body: classify it normally
continue
# `#if 0` — DEAD CODE, not a match (Phase-28 T5). The scanner knew `#ifdef NON_MATCHING`
# but not this, so a `#if 0`-preserved analysis body was read as a live definition AND its