fix(phase-29): progress.linked_subsegs fails closed (R32) + the main-EXE bucket re-measured

- progress.linked_subsegs() was FAIL-OPEN: gated on the module global BINARY that set_binary()
  assigns, it returned an EMPTY SET when imported as a library without that call — i.e. "no
  linked library subsegs", which for main is confidently wrong (there are 49) and silently
  reclassifies ~960 already-byte-identical PsyQ-linked stubs as outstanding game-code work.
  Now raises when unconfigured; the CLI path is untouched (set_binary assigns before calling).
  Caught by hitting it myself while measuring bucket #2.
- ENDGAME-MAP CORRECTION (measured, zero-token): the map's "main EXE game code ~59,765 ins /
  ~1,048 stubs" conflates two populations. Correctly split: game code 1,042 stubs / 31,888
  measurable ins; LINKED PsyQ library 960 stubs / 27,877 ins (already byte-identical). Bucket #2
  is ~47% smaller than quoted. Caveat kept: 467 game-code stubs have NO sig row (the documented
  main second-oracle gap), so the true weight is above 31,888 and not currently measurable —
  re-price when the main second oracle lands, do not quote either number alone.
- .run/s21_zerocrack.json: the 60-family zero-crack pool enumerated (45 plain / 15 jr) and
  honestly discounted — its top entries (0x8013c414 -O0 wall, 0x80144090 LENGTH-DRIFT,
  0x80133ab0 pinned) are already-diagnosed refusals, so ~95k of the 208,499 is not available.
This commit is contained in:
Drew T
2026-07-26 23:41:40 -06:00
parent 2b38c68333
commit ccbc65e9c3
2 changed files with 97 additions and 0 deletions
+86
View File
@@ -4952,3 +4952,89 @@ one session."* The address was never actually missing — `func_80174CB0` states
logged once each way produced **two** "best" records. Now keyed on the derived address.
- **Measured:** rows keyable **128/1,701 (7.5%) → 1,701/1,701 (100%)**; coverage assertion PASSES;
`load_best` → 1,689 open records.
## ⚠️ R14 CORRECTION TO MY OWN T2 ROUTE TABLE (caught within the hour, recorded not silently fixed)
The T2 table above ordered its if-chain with `jr` FIRST, so any family carrying a mid-jr was
bucketed as "jr → §81 carve chain" **regardless of whether its exemplar needed a crack at all**.
That conflated two orthogonal axes and under-reported the zero-crack pool by 15 families.
**The corrected split — the primary axis is "does the exemplar need a CRACK?", and the sweep path
(plain vs §81 carve) is ORTHOGONAL to it:**
| primary | fams | templ ins | share |
|---|---|---|---|
| NEEDS A CRACK | 1,280 | 1,039,542 | 80.1% |
| **ZERO-CRACK (exemplar already matched)** | **60** | **208,499** | **16.1%** |
| permanent walls | 2 | 50,094 | 3.9% |
| cross-tab (the real work map) | fams | templ ins |
|---|---|---|
| draft + cached Ghidra-C, plain sweep | 91 | 371,053 |
| draft + NO cache, plain sweep | 1,023 | 356,425 |
| draft + cached Ghidra-C, **jr sweep** | 32 | 251,685 |
| **matched exemplar, jr sweep** | **15** | **123,482** |
| **matched exemplar, plain sweep** | **45** | **85,017** |
| draft + NO cache, jr sweep | 134 | 60,379 |
**⇒ The zero-crack pool is 60 families / 208,499 ins (8.9% of ALL remaining weighted ins), not the
45 / 85,017 my first table implied.** It needs NO drafting — remap + preamble + gate — and 15 of the
60 simply need the §81 carve chain that SESSION-20 proved 5×. Queued as the next zero-token lever
(blocked only because it builds, and the wave holds `asm/`).
*Why this is written down rather than quietly corrected: this is the same shape as SESSION-20's
"FREE pool" framing error — a classification presented as a route. The lesson that stuck there was
that the label must name the ACTUAL blocker; here an if-chain's ORDER silently became the label.*
## ▶ THE ZERO-CRACK POOL, ENUMERATED AND HONESTLY DISCOUNTED (`.run/s21_zerocrack.json`)
60 families / 208,499 templ ins, split 45 plain (85,017) + 15 jr (123,482). Value is concentrated —
the plain top-4 hold 72% of the plain pool, the jr top-3 hold 74% of the jr pool — **and those top
entries are precisely the ones already known to refuse.** Discounting BEFORE the sweep, not after:
| exemplar | templ ins | status from prior sessions |
|---|---|---|
| `0x8013c414` (jr) | 45,073 | **-O0 family** — blocked by the fleet-scale carve's splat `%lo` re-disassembly sensitivity (Arm A proved the members DO bank 9/9 on one overlay; the blocker is build-infra, characterized, deferred on ROI) |
| `0x80144090` | 20,944 | **LENGTH-DRIFT +13 B / ~3 ins** — SESSION-20 diagnosed this as genuine codegen, explicitly "not mechanical" |
| `0x80133ab0` | 18,084 | **pinned exemplar** — refused outright by the §42e guard in T0.2; per §86 retry under `--allow-pins` with a ONE-member probe first |
| `0x8014cf04` | 11,234 | prior attempt close=27 (WAVE) |
| `0x8014032c` (jr) | 25,071 | untried this phase — the best clean jr candidate |
| `0x8013c0f8` (jr) | 21,098 | untried this phase |
| `0x80143d28` | 10,880 | untried this phase — the best clean plain candidate |
**⇒ Do NOT quote 208,499 as the available win.** ~95k of it is already-diagnosed blocked or
length-drifted. The genuinely untried head is roughly `0x8014032c` + `0x8013c0f8` + `0x80143d28`
≈ 57k ins, plus a long ×2–×20 tail. Per §86 the procedure is **one-member probe per family, then
sweep or skip** — never a blanket run (SESSION-20's blanket run spent ~412 futile cycles, 60%, on
dead families).
## ✅ T3d — ENDGAME-MAP CORRECTION: bucket #2 (main EXE) over-states its remaining work by ~47%
Measuring main to prepare bucket #2 surfaced a conflation in the SESSION-20 endgame map, which
lists *"main EXE game code — ~59,765 ins / ~1,048 stubs"*. Those two numbers are not the same
population:
| main | stubs | weighted ins |
|---|---|---|
| **game code** (the actual work) | **1,042** | **31,888** measurable + 467 stubs with no sig row |
| **LINKED PsyQ library** (already byte-identical via the object link, Phase 8) | 960 | 27,877 |
| total INCLUDE_ASM lines in main | 2,002 | 59,765 |
The stub count (1,048) was game-code-only; the instruction count (59,765) was game code **plus the
linked libraries**. A LINKED stub is not outstanding work — it builds byte-identically from the real
PsyQ object (and from the committed asm fallback on a fresh clone). **So bucket #2's real size is
~31,888 measurable ins + the 467 unmeasured, not 59,765.**
Caveat kept explicit: 467 of the 1,042 game-code stubs have **no sig row** — that is the documented
main second-oracle gap (`sig_is_independent('main')` is False; `sig_image` cannot yet sign the EXE —
0x800 header offset, interleaved data islands). So main's true game-code weight is *above* 31,888 and
is not currently measurable. Bucket #2 should be re-priced when the main second oracle lands, not
quoted from either number alone.
**A fail-open tool was the reason this was easy to get wrong — now fixed (R32):**
`progress.linked_subsegs()` is gated on the module global `BINARY` that `set_binary()` assigns.
Imported as a *library* without that call it returned an **empty set** — "this binary has no linked
library subsegs", which for `main` is confidently wrong (there are 49), and silently reclassifies
~960 already-byte-identical library stubs as outstanding game-code work. It now **raises** when
unconfigured. The CLI path is unaffected (`set_binary` assigns `BINARY` before calling it).
I hit this defect myself, in the first five minutes of using it — which is the argument for the fix.
+11
View File
@@ -333,6 +333,17 @@ def linked_subsegs():
`ifeq ($(BINARY),main)` block (PsyQ library linking is the EXE's layout — Phase 8), so only
`main` has LINKED subsegs. A second binary (e.g. resident) has none. When a future binary gains
its own gated integration, parse that gate here instead of the main-only shortcut."""
# FAIL-CLOSED on an unconfigured module (R32). This function is gated on the module global
# BINARY, which set_binary() assigns; imported as a LIBRARY without that call it used to
# return an empty set — i.e. "this binary has no linked library subsegs", which for `main` is a
# confidently WRONG answer (there are 49). An importer then classifies ~1,300 already-
# byte-identical LINKED library stubs as outstanding game-code work. Silent, and exactly the
# shape R32 exists for; caught 2026-07-27 by an importer (mine) that hit it.
# set_binary() assigns BINARY before calling this, so the CLI path is unaffected.
if BINARY is None:
raise RuntimeError(
"progress.linked_subsegs(): module not configured — call progress.set_binary(<binary>) "
"first. Returning an empty set here would silently claim 'no linked subsegs'.")
if BINARY != "main":
return set()
if not MAKEFILE.exists():