From ccbc65e9c322217e1587abbe42dee78d755d346f Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Sun, 26 Jul 2026 23:41:40 -0600 Subject: [PATCH] fix(phase-29): progress.linked_subsegs fails closed (R32) + the main-EXE bucket re-measured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- phase-ends/CURRENT_PHASE.md | 86 +++++++++++++++++++++++++++++++++++++ tools/progress.py | 11 +++++ 2 files changed, 97 insertions(+) diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index 8462227ac3..317092033e 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -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. diff --git a/tools/progress.py b/tools/progress.py index 7b1891beb9..dbbc6ca0c9 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -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() " + "first. Returning an empty set here would silently claim 'no linked subsegs'.") if BINARY != "main": return set() if not MAKEFILE.exists():