Use prose examples the cartridge agrees with, and make the gate test prove work - #2074
Conversation
…prove work Re-cut of #2070. Its diagnosis was right -- a class-specific path hard-coded in tool prose is a live reference that check_dead_references resolves, so it goes red the moment the class is renamed. Its substitute had the same defect. `tools/rtti_name_audit.py --class`, walking _ZTV-4 -> _ZTI -> _ZTS in the cartridge: dActor_c ROM 8dActor_c AGREES Camera ROM 9dCamera_c DISAGREES -- #2070's replacement Amp ROM 7daBrq_c DISAGREES -- what #2070 replaced Both sides of that swap are in the 259-of-541 coined set and both are scheduled for rename, so #2070 bought nothing. dActor_c and dScMgBase_c agree with the cartridge and nothing above them can re-key their names. marker_evidence.py keeps its concrete struct sketch and loses only the per-symbol path. Rewriting a specific docstring into abstract prose costs explanatory value for nothing; the path was the only part that could die. A per-symbol file dies twice over -- on rename, and on TU promotion folding it into src/actors/<Class>.cpp. test_a_real_tree_header_still_parses asserted only rc == 0, which a header the parser SKIPPED satisfies just as well: include/dActor_c.h rc=0 33 commented fields, 0 mismatched include/fBase_c.h rc=0 skipped -- polymorphic, implicit vptr not modelled The second is the exact "reported a pass and checked nothing" shape this file exists to catch, and the old assertion passed on it. Now the test reads the gate's own output and requires a positive checked-field count, so a future substitution into a skipped header fails loudly instead of quietly asserting nothing. check_dead_references clean, check_python_names PASS, 20/20 tests pass.
✅ PR validation — Passednoverify: no source/build-data changes in this PR Each changed |
Both are pre-existing on main and both resolve today, so no gate is red --
but they are the per-symbol shape that dies twice over, on rename AND on TU
promotion, and one of them is live ammunition right now:
src/_ZN8PoleLiftD1Ev.cpp src_tu/actors/PoleLift.cpp already exists
src/_ZN5Stage11RenderModelEv.cpp
A PoleLift promotion deletes the first one out from under this docstring and
takes main red on dead references, which is exactly how main broke earlier
today at 6d8f8e5 (fixed in #2073).
Named by mangled symbol instead. check_dead_references reads only a/b-shaped
path tokens, so `_ZN8PoleLiftD1Ev` cannot dangle, and the symbol survives both
rename and promotion. The concrete evidence in both comments -- the local
ModelComponents stand-in, and the 0x80/0x158/0x50 padding numbers -- is
unchanged; only the citation spelling moved.
Amended: two more doomed paths in
|
#2075) Two assertions in tools/test_tubuild.py went stale at #2066 (467bde0), which moved ~PoleLift() into the class body. One of them was not merely stale -- it asserted the anomaly #2066 fixed: assert "1 ordinal pair(s) NOT in ROM order: [(0, 1)]" in out An out-of-line destructor makes mwccarm emit D2/D0/D1 with D0 before D1, opposite the ROM; the pilot report called that non-fixable. Defining the destructor inline emits exactly the ROM's two variants in ROM order. So the only way to make this assertion pass by "fixing the code" would be to revert #2066. Measured across 467bde0^ -> 467bde0, swapping the shadow source and the class header together (the pre-#2066 source does not compile against the post-#2066 header, which is why this surfaced as a failing assert rather than a quietly wrong number): before 37 sections, 8 .text, D2 present, "1 ordinal pair(s) NOT in ROM order: [(0, 1)]", 12 unlicensed after 35 sections, 7 .text, no D2, "ALL MATCH, ROM order", 11 unlicensed Section arithmetic: each vague-linkage symbol that stops being emitted costs two sections, itself plus its own .rela. That is 43->37 at #1555 (3 symbols) and 37->35 here (1). Rather than bump the numbers, the stale expectations become regression guards stated as absences, so reintroducing an out-of-line definition fails loudly here: assert "NOT in ROM order" not in out assert "_ZN8PoleLiftD2Ev" not in out assert "UNLICENSED function symbols" not in out The last is an absence because tubuild prints no function block at all when the count is zero -- asserting "(0)" would never match. Negative control: reverting both files to 467bde0^ fails the new assertion at tools/test_tubuild.py:166. The guards have teeth. No path naming PoleLift appears in the new prose. PoleLift is a coined name the cartridge contradicts (ROM RTTI: 18daObjKm2_Ami_Bou_c) and the rename is a prerequisite for promoting this very TU, so such a path is a dead reference with a scheduled fuse -- see #2074. Claude-Session: https://claude.ai/code/session_01WdCK1xgrJdiJzPCh3bAJfQ Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Re-cut of #2070. Right diagnosis, wrong substitute — and today's red
mainproves the diagnosis.#2070 has been sitting at the same head for nine hours with the ask unanswered, so I have re-cut it rather than let a correct idea rot. It should be closed in favour of this. Full credit for the finding is its; I only changed which header it points at, and added the assertion that makes the choice self-guarding.
The premise is correct, and it went off today
check_dead_referencesresolves anya/b-shaped token in a comment or docstring as a live repo-rooted path. So a class-specific path hard-coded in tool prose is a reference, and it dies when the class is renamed. That is exactly right.It is also not hypothetical.
mainwent red ondead referencestwo hours ago (6d8f8e529, fixed in #2073) because a docstring intools/test_tubuild.pynamedsrc/func_ov006_020f8224.cand #2071 promoted that file into a TU. Same mechanism, different trigger.But the substitute has the same defect
#2070 replaces
include/Amp.hwithinclude/Camera.h. Both are coined names the cartridge contradicts. Fromtools/rtti_name_audit.py --class, which walks_ZTV-4 → _ZTI → _ZTSin the ROM images:Both sides of #2070's swap are in the 259-of-541 coined set, and both are scheduled for rename. The PR's own reason for existing applies to its own fix.
This one uses
include/dActor_c.handinclude/dScMgBase_c.h. Both agree with the cartridge, anddActor_cisTI[2]of most actor classes — nothing above it can re-key its name.The part #2070 did not ask for, which matters more
test_a_real_tree_header_still_parsesasserted only that the gate returns 0:A header the parser skipped satisfies that just as well as one it checked:
fBase_calso agrees with the cartridge, so it passes the naming criterion and is still useless as a test subject — an agreeing name is necessary, not sufficient. And "reported a pass and checked nothing" is the precise failure mode this test file's own module docstring says it exists to catch:The test had that hole itself. It now reads the gate's stdout and requires a positive checked-field count, so a future substitution into a skipped header fails loudly. I verified both directions:
dActor_c.hpasses,fBase_c.hwould fail.marker_evidence.py
#2070 rewrote the concrete struct sketch into abstract prose. Only the path could ever go dead, so this keeps the sketch and drops the path, plus a line saying why so it does not get restored:
Ampstays as the class name in the sketch because that is the file it was actually read out of and a bare class name is invisible to the gate. Only the path token was ammunition.Per-symbol files are worth calling out separately:
src/_ZN3AmpD1Ev.cppdies twice over — on rename, and on TU promotion. Promotion deletes those by the dozen (#2071 removed 23 in one go), so it is by far the more frequent killer.Verified
Measured in a toolchain-wired worktree at
1090f44c5. Pre-push ran port-refcheck 405/405, duplicate-sources 11110 stems,src_tu95/95 compiled.