Address-keyed join of the two symbol records, with the divergence surface banked - #2038
Conversation
✅ PR validation — Passednoverify: no source/build-data changes in this PR Each changed |
Gatekeeper review — MERGE AFTER ONE FIXThe tooling is careful and its central claim survives adversarial probing. Nothing here is load-bearing, nothing writes without That is the thing a reviewer otherwise has to take on trust, and it holds. Arithmetic checks out at every level ( The refusal machinery is the part I probed hardest, because a mangled-name parser that truncates instead of refusing would poison everything downstream. It refuses: Operators, templates in the path, local-linkage and garbage all REFUSE and downgrade to Write behaviour is genuinely opt-in: Merge-result gating: exports asserted complete (12753/12753 and 12750/12750), and no gate differs between Blocking — the 21 new tests never run in CI
312 is exactly the count on unmodified main. The path filter fires ( This matters more than a normal wiring omission because the workflow's own header says the enumerated list exists "so adding a module is a reviewed decision", and because the module is genuinely populated — 9 Fix: add While you are in there: the header says "runs 312 of those assertions" and that number is about to be wrong. It explicitly calls itself "the number to compare against if this job's total ever moves" — so move it. N1 — I checked the cross-overlay concern and it does not hold. Do not widen the guard.The review that fed me this recommended widening I read the actual records at all three:
In every one, the mirror's own old name is a real, class-bearing name from the previous naming wave ( Widening the guard as proposed would refuse three provably right restamps, and it would do so for a reason the data contradicts. The existing design already has the correct floor: a proposal requires the old name to map through The mechanism the review found is nonetheless real and worth one comment plus a regression test pinning these three rows and the reasoning, so the next person doesn't "fix" it. N2 — move
|
… map
The 21 tests this PR added never ran. .github/workflows/tool-tests.yml
enumerates its modules on purpose -- a glob would silently absorb a
self-skipping toolchain suite -- and test_symrecords was not on the list,
so run 33329528573 printed "Ran 312 tests", exactly the count on
unmodified main. The path filter fired; there was just nothing new to run.
Adds tools.test_symrecords to the run: list and to the trailing table, and
moves the header's own count. That count is documented as "the number to
compare against if this job's total ever moves", so it is measured, not
computed: the enumerated invocation now reports "Ran 336 tests ... OK".
The module contributes no skips (it reads only tracked text and imports
nothing outside the stdlib), so the expected-skip paragraph goes 306/312
-> 330/336. The module census in the same header goes 57 -> 58, stale
since this PR added the file.
class_rename_map() iterated every config entry at an address with no
module filter, while compare() refuses to explain a row where two modules
both assert a real name ("ambiguous-module"). An address the report
refuses to draw a conclusion FOR should not quietly supply evidence to
conclusions ABOUT other addresses -- that evidence is invisible to a
reviewer reading the report, because the row it came from is refused.
Same floor now applies in both places. On the live tree this changes no
conclusion: the class map is 109 pairs before and after, byte-identical,
and symbols/record_divergences.tsv regenerates with zero lines of diff.
Adds SharedOverlaySlotIsNotAutomaticallyAmbiguous, which pins the guard
in BOTH directions so the next reader does not "fix" it. The tempting
change is to widen ambiguous-module to "more than one module has an
entry" -- that would refuse three provably right restamps (0x020ada40,
0x020aed98, 0x020ee55c), where the colliding overlay's slot is still a
func_/data_ placeholder and the mirror's own old name identifies the
class. Those three are recorded in the docstring rather than asserted,
because #2039 restamps them and the rows then stop existing; a pin on the
addresses would be a test that deletes itself.
Two doc corrections from review: notes/symbol-name-provenance.md now
carries the caveat symrecords.py:14-17 states properly, that a mirror row
keeps its import-time spelling when no correction touches it -- the table
row read as though every row tracks config. And the allowlist test's
docstring narrows "nothing new reads verified.tsv at all" to "nothing new
in tools/", since its glob is tools/*.py non-recursively and cannot see
tools/archive/*.py or any reader outside tools/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdCK1xgrJdiJzPCh3bAJfQ
Gatekeeper note: three PRs contend on the
|
| PR | own module | header total |
|---|---|---|
#2035 premerge_check |
new, 87 | 312 → 399 |
#2038 test_symrecords |
new, 24 | 312 → 336 |
#2040 test_nearmiss_db |
12 → 21 | 312 → 321 |
All land ⇒ the true total is 432, and 306 of the 312 moves accordingly.
Two things worth stating plainly:
- This number is prose, not an assertion. The workflow comment calls it "the number
to compare against if this job's total ever moves" — nothing checks it. A wrong value
here is a silent documentation lie that survives indefinitely, which is the exact
failure class this repo keeps getting bitten by. - It will conflict, and that is the good outcome. All three PRs touch the same two
lines, so whichever lands second and third goes CONFLICTING rather than
auto-merging a stale integer.
When you resolve it: do not take --ours/--theirs on this file — that silently
reverts the other side's unrelated paths: and enumeration edits. Hand-edit the two
numbers, and measure the new total by running the enumerated list exactly as the
workflow does rather than doing the arithmetic. (#2040 already did it that way; that is
the standard.)
I am serializing the merges and will hold each one to a measured number.
The only conflict was .github/workflows/tool-tests.yml: #2035 wired tools.test_premerge_check into the same enumerated list and rewrote the same two prose passages this branch rewrites. Resolved by hand -- never with --ours/--theirs, which would have taken one whole side of the file and silently reverted the other's unrelated edits. Both sides survive: test_premerge_check (87) and test_symrecords (24) are both enumerated in the runner and both keep their legend block, and the "Costs nothing" paragraph names both. Every count in the header is now MEASURED on this merge result rather than carried from either side, because both sides' numbers were stale: 59 test_*.py modules (git ls-tree; main said 57, this branch said 58) 53 that ran only by hand (59 - the 6 that already had a workflow) 13 pytest-style modules (no unittest.TestCase; the header said eleven) 186 test functions in them (the header said 158) 423 assertions, 23 modules (python -m unittest -v <the enumerated list>: "Ran 423 tests ... OK". Main measures 399, so the delta is exactly test_symrecords' 24 and no module dropped out.) 417 of the 423 assert (423 - the 6 documented skips) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WdCK1xgrJdiJzPCh3bAJfQ
Audited the conflict resolution independently. MERGE.A The resolution is correct
Both legend blocks survived, which is the thing a whole-file The header counts are now measured, not addedMain's header was stale in a way that was easy to "fix" wrongly. It claimed 57 The resolution says 59 modules, six previously wired, "the other 53" — which is The one thing I checked that nobody asked forThis branch is 53 commits behind main and still carries the stray Gating the merge tree instead of the branch: Byte-identical. The three-way merge keeps main's deletion because this branch never Independent verification that this cannot turn main red
Green standalone and inside the merged 25-module set. Wiring it is safe. Note that Stated gapsNo ROM build, |
Why
config/**/symbols.txt (what the build links against) and symbols/verified.tsv (the import mirror) disagree on hundreds of addresses with no shared spelling, e.g. 0x0203562c is
_ZNK10dBgCh_Actr8IsOnWallEvin config and_ZNK12WithMeshClsn8IsOnWallEvin the mirror, and 0x0200651c diverges in class and dtor variant at once (_ZN11dCapEnemy_cD1Evvs_ZN8CapEnemyD2Ev). That is structural: corrections land in config, mirror rows only get restamped when a correction happens to touch them.On 2026-08-30 a name-keyed "is this body sourced?" sweep joined the records by name and manufactured phantom crack targets out of matched, enrolled functions (
func_ov062_0211bd10,_ZN13TreasureChest6State0Ev). The earlier session counted 553 divergent addresses; that number does not reproduce under any single definition against today's tree, so this PR makes the enumeration itself canonical instead of the number.What
--where 0x<addr>, and can plan (--restamp) or apply (--restamp --apply) a restamp of the mechanically explained mirror rows. Nothing applies by default.Review pointers
identityandplain; they include the _ZThn80 Animation thunks (Fix six misnamed _ZThn80 thunks: rename symbols to ROM truth + match all six #178 territory), BootScene/Scene and FaderColor/Color dtor identities, and the Spawn-vs-ctor naming debates.No src/ files move and contributions.json is untouched, so nothing here can shift credit.