Skip to content

TU: promote seven policy-free translation units (#1993 slice 1) - #2023

Merged
andrewboudreau merged 3 commits into
mainfrom
tu/promote-slice-1
Aug 30, 2026
Merged

TU: promote seven policy-free translation units (#1993 slice 1)#2023
andrewboudreau merged 3 commits into
mainfrom
tu/promote-slice-1

Conversation

@andrewboudreau

Copy link
Copy Markdown
Collaborator

Slice 1 of the #1993 re-cut.

#1993 tried to promote 26 translation units in one shot and stalled on 32
compiler_only_output rows across 16 coined-name classes — rows banked as plain
deadstrip that assert "the cartridge never carried a separate copy" when the cartridge
does carry the record under the class's real name. The verdict on those rows is
here; the
audit that found all 32 is
here. Per
that assessment the PR is being re-cut into slices rather than conflict-resolved.

This is the subset with no such rows at all. All seven manifest entries have no
compiler_only_output key, absent as well as empty — verified against origin/main's blob
for each entry before any change was made. That is the entire reason this slice can land
ahead of the sixteen renames: there is no policy row here to get wrong.

module TU functions status before
ov004 dScMgBase_c 8 link-verified
ov006 MgBingoBallSlotsShot 2 text-verified
ov006 MgMushroomRoulette 2 text-verified
ov006 MgPairAGoneAndOn 2 text-verified
ov006 MgPicturePoker 3 text-verified
ov006 MgTrampolineTerror 2 text-verified
ov006 MgWhichWiggler 2 text-verified

tu_promote --dry-run, and the #2016 precondition

#2016 repointed 38 stale legacy_source extensions. Without it, tu_promote --dry-run
refused nine entries with has 0 entries in delinks.txt, expected exactly 1. On current
main that refusal is gone: the dry run over all seven ids reports zero refusals,
nothing suppressed.

  promote  ov004/dScMgBase_c                      8 function(s), 8 legacy source(s) -> src/actors/dScMgBase_c.cpp
  promote  ov006/MgBingoBallSlotsShot             2 function(s), 2 legacy source(s) -> src/actors/MgBingoBallSlotsShot.cpp
  promote  ov006/MgMushroomRoulette               2 function(s), 2 legacy source(s) -> src/actors/MgMushroomRoulette.cpp
  promote  ov006/MgPairAGoneAndOn                 2 function(s), 2 legacy source(s) -> src/actors/MgPairAGoneAndOn.cpp
  promote  ov006/MgPicturePoker                   3 function(s), 3 legacy source(s) -> src/actors/MgPicturePoker.cpp
  promote  ov006/MgTrampolineTerror               2 function(s), 2 legacy source(s) -> src/actors/MgTrampolineTerror.cpp
  promote  ov006/MgWhichWiggler                   2 function(s), 2 legacy source(s) -> src/actors/MgWhichWiggler.cpp
tu_promote: 7 entry(ies) would be promoted (dry run).

The real run: 7 promoted, 21 functions consolidated, 21 attribution overrides added.

Byte-neutrality

A promotion that quietly narrows a range produces a byte-perfect ROM, so no other gate
can see it. This one was checked directly rather than inferred.

Every entry in every config/**/delinks.txt was expanded from its start/end into a set
of individual byte addresses per section, at the merge base and at this tree, and the sets
compared for equality — not the totals, the sets. Done twice: once over complete entries
only (the bytes the build actually compiles) and once over every entry regardless.

module entries before entries after complete bytes verdict
ov004 280 273 48,004 coverage identical
ov006 1,851 1,844 401,340 coverage identical
other 112 files untouched

Also asserted, and all true:

  • No entry lost its complete marker. Every path that carried complete at the base
    and still exists as an entry still carries it; the rest were absorbed and are gone as
    entries entirely.
  • Section headers byte-identical in both files — no span was re-declared.
  • No overlapping ranges introduced (0 before, 0 after).
  • Every rewritten range is a contiguous merge of adjacent ranges with the same outer
    endpoints
    : 0x020b04e8..0x020b0a38 replaces eight abutting entries covering exactly
    that, and so on for the six ov006 TUs. This is visible in the diff.

tools/source_coverage.py from #2013 agrees independently:

source-built bytes   base 2,066,772 B   ->   now 2,066,772 B   (net +0 B)
  handed back to the cartridge: 0 B   |   newly built from source: 0 B
  by section: .init 125,712 B, .text 1,941,060 B
source-coverage: clean (no bytes handed back to the cartridge)

Other gates, run locally

gate result
prepush_attribution --base origin/main 11,294 tracked; 21 consolidated with credit intact, 0 changed, 0 lost
check_src_tu_compiles 89/89, including all seven from their new src/ home
check_src_tu 76 TUs, 1,300 mangled refs — every reference resolves
port_refcheck 405 checked, 0 stale
check_duplicate_sources 11,237 stems, none doubled
tiers_ratchet --check PASS, 2,546 / 2,546
test_tiers 29 tests OK
check_dead_references no new dead references
langmode_audit --check PASS (against both the root baseline and chaos-data's)
check_header_offsets N/A — no header changed
pre-push hook passed unaided, no --no-verify

rombuild.py was deliberately not run. build/ is shared across worktrees and three
other agents held live file locks throughout (tu-pushblock-genuine,
tu-ctmecha05-genuine, tu-kurumajiku-prod-cppown, two of them on other modules'
delinks.txt). A concurrent build manufactures fake failures for everyone sharing the
directory, and the lock service has no build-directory resource to serialise on. The 106/106
module-fidelity check is left to the validator, which runs it in isolation.

The two commits, and one disclosure

Commit 1 is tu_promote.py's output, unedited: 7 whole-TU complete entries replacing
21 per-function ones, 7 git mv at R100 (all seven show | 0 in --stat), 21 absorbed
one-function sources git rm'd, 7 manifest entries set to status: promoted, 21
attribution.json overrides.

attribution.json shows a 1,609-line diff for 21 added keys. That is not 1,609 changes:
rewrite_attribution ends with dict(sorted(ov.items())), and main's overrides block was
not in sorted order, so the whole block is re-emitted. Compared key-by-key as JSON:
903 → 924 overrides, 21 added, 0 removed, 0 changed, and aliases and note are
untouched. Worth flagging as a tooling nuisance — a shared ledger that a tool re-sorts
conflicts with every concurrent PR that touches it.

Commit 2 banks the four CONVERTED paths ov004/dScMgBase_c absorbed. The criteria are
file-wide, and the promoted TU carries all eight functions of the linker run — the four that
were never CONVERTED still use raw offset arithmetic, unk_<off> fields and mangled _Z
declarations, so the four that were banked stop passing the moment they share a file with
them. Same shape as #1996's ov002/daObjAbuku_c rows; byte-match outranks readability, no
readable code was reverted, no byte changed.

One disclosure on that commit: --update regenerates the whole baseline, so it also folded
in five lines of pre-existing staleness this change did not cause.
src/_ZN7dBase_c{18AfterInitResourcesEj,5SpawnEjP7fBase_cii,D0Ev,D1Ev}.cpp were absorbed
into src/actors/ActorDerived.cpp by a promotion that already landed and were never
re-banked — none of the four exists on origin/main either — and ActorDerived.cpp itself
qualifies but was unbanked. The tool classifies those four as moves, not removals, so they
carry no --reason and no exceptions row. Net 2553 → 2546 (+1 / −8): four mine, banked
with a reason; four pre-existing moves; one pre-existing addition. There is no way to bank
fewer.

What this PR does not do

🤖 Generated with Claude Code

andrewboudreau and others added 2 commits August 30, 2026 12:15
Slice 1 of the #1993 re-cut: the subset of that PR's promotions that carries
zero `compiler_only_output` rows, so none of the sixteen coined-name
`deadstrip` claims the cartridge contradicts is anywhere near it.

Promoted:

  ov004/dScMgBase_c              8 functions
  ov006/MgBingoBallSlotsShot     2
  ov006/MgMushroomRoulette       2
  ov006/MgPairAGoneAndOn         2
  ov006/MgPicturePoker           3
  ov006/MgTrampolineTerror       2
  ov006/MgWhichWiggler           2

`tools/tu_promote.py` replaced 21 per-function `delinks.txt` entries with 7
whole-TU `complete` entries, `git mv`'d the 7 sources out of `src_tu/` at R100,
`git rm`'d the 21 absorbed one-function sources, set each manifest entry to
`status: promoted`, and added 21 `attribution.json` overrides.

Byte-neutrality, proven by expanding every entry's start/end into a per-section
byte set across all 114 `delinks.txt` at the merge base and here:

  ov004  280 -> 273 entries   48,004 bytes   coverage identical
  ov006 1851 -> 1844 entries  401,340 bytes  coverage identical

Byte sets are identical for the `complete`-only view and the all-entries view,
section headers are unchanged, no entry lost its `complete` marker, and the
other 112 files are untouched. Every rewritten range is a contiguous merge of
adjacent ranges with the same outer endpoints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdCK1xgrJdiJzPCh3bAJfQ
The CONVERTED criteria are file-wide. `src/actors/dScMgBase_c.cpp` carries
all eight functions of the ov004 linker run, and the four that were never
CONVERTED still use raw offset arithmetic, `unk_<off>` fields and mangled
`_Z` free-function declarations -- so the four that WERE banked stop passing
the moment they share a file with them. A reconstructed TU must additionally
spell vague-linkage symbols directly or its range will not link, so
`no_mangled_refs` can never pass for one. Same shape as #1996's
ov002/daObjAbuku_c rows. No readable code was reverted and no byte changed.

`--update` also folded in five lines of pre-existing baseline staleness this
change did not cause: `src/_ZN7dBase_c{18AfterInitResourcesEj,5SpawnEjP7fBase_cii,D0Ev,D1Ev}.cpp`
were absorbed into `src/actors/ActorDerived.cpp` by a promotion that already
landed and were never re-banked (none of the four exists on `origin/main`
either), and `src/actors/ActorDerived.cpp` itself qualifies but was unbanked.
The tool classifies those four as moves, not removals, so they carry no
`--reason` and no exceptions row. The baseline is a whole-tree regeneration;
there is no way to bank fewer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdCK1xgrJdiJzPCh3bAJfQ
@tangos-validator

tangos-validator Bot commented Aug 30, 2026

Copy link
Copy Markdown

✅ PR validation — Passed

Committed merge passes; 21 contributor credit reassignment(s) noted, not a blocker.

Full merge validation

Check Result
Committed test merge yes
Byte-verified functions 10,980 / 11,347 (96.77%, -14)
Byte-verified code bytes 2,043,520 / 2,211,124 (92.42%, -1,596)
Claimed, not byte-verified 236 functions, 61,784 bytes (+14)
Perfect source moves 0 R100
Enrolled ranges (delinks complete) 11,058 functions, 2,052,772 bytes (92.84%, +0) -- differs from byte-verified by +78
Contributor credit 0 added, 21 changed, 0 lost
Relocation check 21 checked; 21 VERIFIED
Port reference check 405 checked; 0 stale
Module fidelity 106/106 exact; 100.000000% compared bytes
Code linked from verified source 11,087 functions, 2,066,772 bytes (93.47%)
Module bytes from source 2,066,772 / 3,049,600 (67.8%); 811,492 (26.6%) are data no delink entry reaches
ROM data reproduced from source 457 symbol(s) exact, 242 partial, 9 differ

Contributor credit moved (21)

Function Source Before After
ov004:0x020b04e8 src/_ZN11dScMgBase_c16OnPendingDestroyEv.cpp -> src/actors/dScMgBase_c.cpp andrewboudreau github-actions[bot]
ov004:0x020b04ec src/_ZN11dScMgBase_c6RenderEv.cpp -> src/actors/dScMgBase_c.cpp andrewboudreau github-actions[bot]
ov004:0x020b04f4 src/_ZN11dScMgBase_c12BeforeRenderEv.cpp -> src/actors/dScMgBase_c.cpp andrewboudreau github-actions[bot]
ov004:0x020b0618 src/_ZN11dScMgBase_c8BehaviorEv.cpp -> src/actors/dScMgBase_c.cpp andrewboudreau github-actions[bot]
ov004:0x020b0620 src/_ZN11dScMgBase_c14BeforeBehaviorEv.cpp -> src/actors/dScMgBase_c.cpp tangosdev github-actions[bot]
ov004:0x020b0840 src/_ZN11dScMgBase_c21AfterCleanupResourcesEj.cpp -> src/actors/dScMgBase_c.cpp andrewboudreau github-actions[bot]
ov004:0x020b08f0 src/_ZN11dScMgBase_c18AfterInitResourcesEj.cpp -> src/actors/dScMgBase_c.cpp andrewboudreau github-actions[bot]
ov004:0x020b0930 src/_ZN11dScMgBase_c19BeforeInitResourcesEv.cpp -> src/actors/dScMgBase_c.cpp tangosdev github-actions[bot]
ov006:0x020dbd54 src/MgPicturePoker_Spawn.cpp -> src/actors/MgPicturePoker.cpp tangosdev github-actions[bot]
ov006:0x020dbe14 src/func_ov006_020dbe14.c -> src/actors/MgPicturePoker.cpp tangosdev github-actions[bot]
ov006:0x020dbe30 src/func_ov006_020dbe30.c -> src/actors/MgPicturePoker.cpp tangosdev github-actions[bot]
ov006:0x020ede18 src/MgWhichWiggler_Spawn.cpp -> src/actors/MgWhichWiggler.cpp tangosdev github-actions[bot]
ov006:0x020ede80 src/func_ov006_020ede80.c -> src/actors/MgWhichWiggler.cpp tangosdev github-actions[bot]
ov006:0x020fa6ac src/MgPairAGoneAndOn_Spawn.cpp -> src/actors/MgPairAGoneAndOn.cpp tangosdev github-actions[bot]
ov006:0x020fa740 src/func_ov006_020fa740.c -> src/actors/MgPairAGoneAndOn.cpp tangosdev github-actions[bot]
ov006:0x0210a400 src/MgMushroomRoulette_Spawn.cpp -> src/actors/MgMushroomRoulette.cpp tangosdev github-actions[bot]
ov006:0x0210a4ac src/func_ov006_0210a4ac.c -> src/actors/MgMushroomRoulette.cpp tangosdev github-actions[bot]
ov006:0x02119824 src/MgBingoBallSlotsShot_Spawn.cpp -> src/actors/MgBingoBallSlotsShot.cpp tangosdev github-actions[bot]
ov006:0x02119900 src/func_ov006_02119900.c -> src/actors/MgBingoBallSlotsShot.cpp tangosdev github-actions[bot]
ov006:0x0212471c src/MgTrampolineTerror_Spawn.cpp -> src/actors/MgTrampolineTerror.cpp lunavyqo github-actions[bot]
ov006:0x021248a8 src/func_ov006_021248a8.cpp -> src/actors/MgTrampolineTerror.cpp tangosdev github-actions[bot]

Byte-verified means the range carries complete in a delinks.txt, so the ROM build compiled it and compared it to the cartridge. The 236 claimed functions have a src/ file named after the symbol with no NONMATCHING banner, and nothing compiles them -- dsd fills their addresses with the ROM's own bytes. Both together are the 11,216 this project calls matched.

Warnings: contributor attribution changed, not a blocker (21 changed, 0 lost -- src/actors/dScMgBase_c.cpp: andrewboudreau -> github-actions[bot]; src/actors/dScMgBase_c.cpp: andrewboudreau -> github-actions[bot]; src/actors/dScMgBase_c.cpp: andrewboudreau -> github-actions[bot]; +18 more); 21 address range(s) left the byte-verified set while enrolled totals held steady: ov004:0x020b04e8-0x020b04ec, ov004:0x020b04ec-0x020b04f4, ov004:0x020b04f4-0x020b0618, ov004:0x020b0618-0x020b0620, ov004:0x020b0620-0x020b0840, +16 more; 14 more function(s) now claim a match that nothing compiles; enroll them in a delinks.txt to have the ROM build check them.

Per-file link-check detail

All 7 changed file(s) compile to the ROM byte-for-byte with correct relocation targets.

File Symbol Result Slots checked
src/actors/MgBingoBallSlotsShot.cpp MgBingoBallSlotsShot_Spawn + func_ov006_02119900 ✅ verified 2
src/actors/MgMushroomRoulette.cpp MgMushroomRoulette_Spawn + func_ov006_0210a4ac ✅ verified 2
src/actors/MgPairAGoneAndOn.cpp MgPairAGoneAndOn_Spawn + func_ov006_020fa740 ✅ verified 2
src/actors/MgPicturePoker.cpp MgPicturePoker_Spawn + func_ov006_020dbe14 + func_ov006_020dbe30 ✅ verified 3
src/actors/MgTrampolineTerror.cpp MgTrampolineTerror_Spawn + func_ov006_021248a8 ✅ verified 2
src/actors/MgWhichWiggler.cpp MgWhichWiggler_Spawn + func_ov006_020ede80 ✅ verified 2
src/actors/dScMgBase_c.cpp _ZN11dScMgBase_c16OnPendingDestroyEv + _ZN11dScMgBase_c6RenderEv + _ZN11dScMgBase_c12BeforeRenderEv + _ZN11dScMgBase_c8BehaviorEv + _ZN11dScMgBase_c14BeforeBehaviorEv + _ZN11dScMgBase_c21AfterCleanupResourcesEj + _ZN11dScMgBase_c18AfterInitResourcesEj + _ZN11dScMgBase_c19BeforeInitResourcesEv ✅ verified 8

The private worker commits a test merge, builds the stock ROM profile, compares every executable module, measures matched and source-built code, checks contributor lineage, and verifies affected relocations. The mod profile is opt-in and is not part of this merge gate.

@andrewboudreau

Copy link
Copy Markdown
Collaborator Author

Promotion loses no vtable/RTTI record — checked in both directions

A known hazard for TU promotion is that an extern "C" or otherwise vtable-less
promoted TU emits no _ZTV/_ZTI/_ZTS, so consolidating files that did own such
records silently drops ROM-data coverage. romdata_check is the tree's only vtable gate
and a lost record does not move a single ROM byte, so nothing else would catch it.

This slice was checked symmetrically rather than assumed safe. Each side was compiled with
its own pinned 2004/b56 and the resulting ELF symbol table read directly for STT_OBJECT
symbols:

After — the 7 promoted TUs:

ov004/dScMgBase_c            OBJECT syms: NONE
ov006/MgBingoBallSlotsShot   OBJECT syms: NONE
ov006/MgMushroomRoulette     OBJECT syms: NONE
ov006/MgPairAGoneAndOn       OBJECT syms: NONE
ov006/MgPicturePoker         OBJECT syms: NONE
ov006/MgTrampolineTerror     OBJECT syms: NONE
ov006/MgWhichWiggler         OBJECT syms: NONE

Before — all 21 absorbed one-function sources at the merge base: every one reports
OBJECT: 0, and the total count of _ZTV/_ZTI/_ZTS definitions across all 21 is 0.

So the count is 0 before and 0 after: there is no record for the promotion to lose. The
_ZTV spellings that appear textually in the Mg* sources are extern vptr stores
references to a vtable defined elsewhere, not definitions of one.

Two notes on the file counts, since they can look off at a glance:

  • Diffed against the merge base the deletion set is exactly 21. Diffing against
    today's origin/main shows 24, but the three extras
    (WaterfallMist_Spawn.cpp, _ZN16daObjWaterfall_cD0Ev.cpp, _ZN16daObjWaterfall_cD1Ev.cpp)
    did not exist at the merge base at all — they arrived on main with Use ROM-proven daObjWaterfall_c C++ identity #2012 after this
    branch was cut. They are not touched by this PR.
  • main has advanced onto config/converted-baseline.json since this branch was cut, which
    commit 2 also rewrites. GitHub still reports this PR MERGEABLE, but that file is the one
    to re-check if the branch sits unmerged for long.

@andrewboudreau

Copy link
Copy Markdown
Collaborator Author

Reconciling the validator's -14 functions / -1,596 bytes

PR validation passes, but its headline rows move down and that deserves an explanation
rather than a shrug, because "coverage identical" and "-1,596 bytes" cannot both be loose
talk.

Byte-verified functions   10,980 / 11,347  (96.77%,    -14)
Byte-verified code bytes  2,043,520 / 2,211,124  (92.42%, -1,596)

No byte lost verification. The deltas are an artifact of per-function granularity, and
the arithmetic pins it exactly. The validator attributes a delinks entry to the single
function whose address equals the entry's start; the remaining functions inside a
whole-TU range are no longer matched to an entry of their own, so they drop out of the
per-function set:

TU fns range bytes first fn remaining fns
dScMgBase_c.cpp 8 1,360 4 7 → 1,356
MgBingoBallSlotsShot.cpp 2 224 220 1 → 4
MgMushroomRoulette.cpp 2 176 172 1 → 4
MgPairAGoneAndOn.cpp 2 176 148 1 → 28
MgPicturePoker.cpp 3 236 192 2 → 44
MgTrampolineTerror.cpp 2 492 396 1 → 96
MgWhichWiggler.cpp 2 168 104 1 → 64
total 14 → 1,596

14 functions and 1,596 bytes — the reported deltas to the unit. These are precisely the
non-first functions of each promoted TU, nothing else.

Those bytes are still compiled and still compared to the cartridge. The whole-TU entry
carries complete and spans the identical address range, so the ROM build builds all 1,360
bytes of dScMgBase_c and diffs them. Three independent facts agree:

  • the delinks byte sets are identical before and after, compared as sets per section;
  • source_coverage.py reports net +0 B, nothing handed back to the cartridge;
  • the validator's own note says the enrolled totals held steady — only the
    per-function attribution moved.

The validator's own wording, 14 more function(s) now claim a match that nothing compiles,
is the part that is misleading here: something does compile them — the TU does. Its
range→function resolution is exact-start matching, not containment.

This is inherent to TU promotion, not to this slice. Any promotion of N functions into
one TU will show -(N − 1) functions and the corresponding bytes on these two rows while
losing no verification whatsoever. Worth knowing before the later, larger #1993 slices land,
since those will show much bigger negative numbers for the same benign reason. The row that
would actually catch bytes being handed back to the cartridge is source-built coverage, and
it is flat.

@andrewboudreau

Copy link
Copy Markdown
Collaborator Author

Gatekeeper review — one change requested, then this merges

The promotion itself is sound and I re-verified it independently rather than
inheriting the authoring agent's numbers. One thing rode in that this PR did not
cause, and it breaks a different open PR.
Everything else is fine.

Requested change: drop the ActorDerived.cpp swap from converted-baseline.json

config/converted-baseline.json in this diff does two unrelated things. The first is
this PR's own work. The second is not:

-  "src/_ZN7dBase_c18AfterInitResourcesEj.cpp"
-  "src/_ZN7dBase_c5SpawnEjP7fBase_cii.cpp"
-  "src/_ZN7dBase_cD0Ev.cpp"
-  "src/_ZN7dBase_cD1Ev.cpp"
+  "src/actors/ActorDerived.cpp"

None of those five paths belong to any of the seven TUs this PR promotes. They are
arm9/ActorDerived, promoted back in #1995. They are here because
tiers_ratchet.py --update is a whole-tree regeneration, so it banks whatever else
happens to have become bankable since the last update. The PR body discloses the sweep
honestly — this is about what it costs, not about it being hidden.

It is not needed. I reverted just those five lines in the merge tree and re-ran the
gate under main's own tiers_ratchet.py:

merge result as-is              EXIT 0   PASS   baseline 2546  current 2546
merge result, swap reverted     EXIT 0   PASS   baseline 2549  current 2546  (+1 gained, not yet banked)  (4 moved into a promoted TU)

Both green. The reverted form reports exactly what origin/main reports today — the
four paths rescue through the normal MOVED path and nothing is lost.

Leaving it in breaks #2001. #2001 replaces the file-wide scorer with a per-member
one. Composed against this PR's tree it fails differently depending on whether the
swap is present:

tree #2001's ratchet fix needed
main + #2001 RED — 4 MOVED paths lose the rescue the legacy_source-keyed rescue I posted on #2001
main + #2023 as-is + #2001 RED — src/actors/ActorDerived.cpp IDENTITY UPGRADE INCOMPLETE the posted fix does not cover this
main + #2023 swap reverted + #2001 RED — 4 MOVED paths the posted fix — and I verified the composition is EXIT 0 with it applied

The reason is that banking src/actors/ActorDerived.cpp as a whole-file row is an
over-credit under the new metric: that file's members include func_02013edc, which is
not a real function name. Main's file-wide scorer judges the path (ActorDerived.cpp
— a real name) and passes it; the per-member scorer judges the symbol and does not.
So this PR banks a row #2001 immediately has to unwind. Cheaper not to bank it.

Concretely: restore those four src/_ZN7dBase_c*.cpp entries, delete
src/actors/ActorDerived.cpp, set "count": 2549. --check stays green.

What I verified is clean

  • No erosion. Merge-tree coverage .text 1,941,060 + .init 125,712 = 2,066,772 B,
    byte-identical to origin/main. entries 11,025 -> 11,011 is the documented
    consolidation artifact — 21 per-symbol entries collapsing into 7 TU entries.
  • Converted ratchet PASS on the merge result, not just on the branch. Worth saying
    plainly: nothing in CI gates a merge resultpr-validate.yml is
    pull_request_target-only and there is no ROM build on a main push.
  • The seven promoted files are identical blobs to their src_tu originals with no
    copy left behind, so the promotion cannot have changed a byte of source.
  • The four new converted-backslide-exceptions.jsonl rows are the honest instrument
    here, not a fudge.
    src/actors/dScMgBase_c.cpp really does carry four
    never-converted siblings from the same linker run, so the file-wide criteria really
    cannot pass. I checked the claim rather than taking the reason text on trust: 384
    lines, 32 mangled _Z declarations, 15 raw pointer casts, 4 unk_<off> fields. The
    reasons are specific and true, and no readable code was reverted.

One thing to be clear-eyed about, not a blocker

These are not readable C++ yet. Three of the seven, measured:

file lines extern "C" func_/Unk_ defs raw casts mangled _Z
MgBingoBallSlotsShot.cpp 57 4 2 1 4
MgPicturePoker.cpp 72 13 1 2 6
dScMgBase_c.cpp 384 1 2 15 32

I am not asking for that to change here, because the trade goes the right way under
the project's own ordering — byte-match first, readability last. src_tu/ is not in the
build, so today these 513 lines are gated by nothing at all; promoting them puts them
under the ROM link. That is a real verification gain bought with four rows of
readability credit, and it is the documented cost of TU promotion.

But it does set the follow-up: the four never-converted dScMgBase_c siblings are now
the thing standing between this class and its four CONVERTED rows coming back. Worth an
issue so later slices do not each quietly spend a few more rows without anyone tracking
the total.

Verdict

Make the five-line converted-baseline.json revert and I will merge on the next green.
Main moved to 0cf57c268 while this sat, so a revalidation is happening regardless —
the revert costs nothing extra. Nothing else on this PR needs to change.

@andrewboudreau

Copy link
Copy Markdown
Collaborator Author

Addendum — a second fix, found on adversarial re-review

I had a second pass run against this branch independently (fresh git archive exports, no figures inherited from the PR body or from my earlier comment). It reproduced my converted-baseline.json finding and confirmed the promotion itself is sound — coverage set-identical at the byte level (SHA-1 of the expanded per-(module, section) union matches on both trees), all 21 deleted ranges re-owned contiguously by their TUs, attribution.json 903 → 924 with 0 removed and 0 changed, and the compiler_only_output census byte-identical across the merge with zero _ZTV/_ZTI/_ZTS on a plain deadstrip, so #2019's refusal has nothing to fire on.

It also found something I missed.

All seven promoted files still carry the generator's SHADOW banner

Every one of the seven says, at the top:

SHADOW translation unit ... NOT ENROLLED, NOT CANONICAL. This file contributes nothing to the ROM build. It is a STARTING POINT

After this PR all seven are enrolled complete and are canonical. Each one contributes its range to the ROM build — that is the entire point of the promotion.

I checked the tree's convention rather than assuming one. Of the six TUs already promoted into src/actors/ on origin/main:

file banner
ActorDerived.cpp stripped
ActorBase_SceneNode.cpp stripped
daKpFr_c.cpp stripped
daObjAbuku_c.cpp stripped
daKrpa_c.cpp stripped
daObjPathLift_c.cpp kept

So stripping on promotion is the convention, 5-1, and daObjPathLift_c is the standing oversight rather than the precedent.

Comment-only, zero byte risk. But leaving it means seven canonical, ROM-linked sources each open by telling the next reader they are scratch files that contribute nothing — and this PR would take the tree from one such file to eight, which is the point where it stops being an oversight and starts being the norm.

So the ask on this PR is now two things, both text-only

  1. The five-line converted-baseline.json revert from my earlier comment — restore the four src/_ZN7dBase_c*.cpp entries, drop src/actors/ActorDerived.cpp, set "count": 2549. (Unrelated work --update's whole-tree regeneration swept in; both trees are green either way, so this is hygiene, not correctness.)
  2. Strip the SHADOW banner from all seven promoted files.

Neither needs a rebuild. Same commitment as before: make these and I will merge on the next green.

One thing to keep an eye on afterwards

dScMgBase_c.cpp carries a TUBUILD WARNING that #pragma opt_strength_reduction off was function-scoped in its legacy file and is now file-global across all eight members. That entry is link-verified, so it is proven as it stands — but a file-global codegen pragma is exactly the thing that silently changes meaning when someone later reorders or adds to that file. Worth a comment in the file next to the pragma saying which member actually needed it.

I also filed #2029 for the CONVERTED-credit question raised by the four exception rows here — that is a trend to track, not a blocker on this PR.

… banner

Two text-only fixes requested in review on #2023. No byte of compiled output
moves: the seven .cpp diffs are comment-only (proved by comparing the
comment-stripped token streams -- all seven hash identically before and after),
and config/converted-baseline.json is not an input to any compiler.

config/converted-baseline.json -- `tiers_ratchet.py --update` regenerates the
whole tree and swept three unrelated things into this PR's delta:

  * it DROPPED src/_ZN7dBase_c{18AfterInitResourcesEj,5SpawnEjP7fBase_cii,D0Ev,
    D1Ev}.cpp, four paths an already-landed promotion absorbed into
    src/actors/ActorDerived.cpp and never re-banked;
  * it ADDED src/actors/ActorDerived.cpp, which qualifies but was unbanked;
  * it re-sorted src/_ZN11daDsnBase_c16CleanupResourcesEv.cpp, which sits out
    of `sorted()` order on main because `--update` is the only writer and the
    row predates it.

None of the three is caused by this PR, and each is a pre-existing baseline
staleness that belongs in its own change. The file is now rebuilt from the
merge base (eabf1a0) by hand -- there is no targeted regeneration entry
point; `--update` writes the whole tree from `sorted(current)` or nothing --
so this PR's own delta on it is exactly five lines: the four
src/_ZN11dScMgBase_c*.cpp paths that ov004/dScMgBase_c really did absorb, and
the count. 2553 - 4 = 2549, and `count` equals len(converted) as it does at
every other revision.

The seven promoted files -- the tubuild `create` banner claimed "NOT ENROLLED,
NOT CANONICAL", "contributes nothing to the ROM build" and "a STARTING POINT".
After this PR each of the seven is enrolled and canonical and contributes its
range to the link, so all three statements are false. Replaced with the shape
the already-promoted TUs use (src/actors/daObjAbuku_c.cpp from #1996, and
daKpFr_c.cpp / daKrpa_c.cpp): a "Production translation unit for <ov>/<Class>"
line over the unchanged reverse-source-order note and the unchanged
assembled-from list. Nothing reads the banner text -- tools/tubuild.py only
writes it -- and the separate "Local shadow declarations ... NOT reconciled"
comment is left alone because it is still true.

src/actors/daObjPathLift_c.cpp keeps its SHADOW banner. It is the one promoted
TU that was never stripped, a standing oversight rather than a precedent, and
not this PR's file to touch.

Verified in a worktree off a13cfc3:
  tiers_ratchet.py --check      PASS  baseline 2549  current 2546
                                      (+1 gained, not yet banked)
                                      (4 moved into a promoted TU)
  langmode_audit.py --json      byte-identical before and after
  check_dead_references.py      exit 0, output identical before and after

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdCK1xgrJdiJzPCh3bAJfQ
@andrewboudreau

Copy link
Copy Markdown
Collaborator Author

Two review fixes applied on the authoring session's behalf

Posted by the gatekeeper. The authoring session is gone, so I made exactly the two changes I asked for, on top of a13cfc343. Pushed as 41a52556c. No merge, no rebase, nothing else touched. I am not merging this myself — this is for the next green.


Fix 1 — config/converted-baseline.json: the regeneration is reverted

tiers_ratchet.py --update regenerates the whole tree, and it swept three unrelated things into this PR's delta. There is no targeted regeneration entry point — write_baseline() only ever writes sorted(current) for the entire tree, so there is no way to ask it for a subset. I rebuilt the file by hand from the merge base (eabf1a0bd), preserving indentation, key order, existing entry order and the trailing newline.

What came out, and why each was not this PR's business:

swept in by --update why it is unrelated
dropped src/_ZN7dBase_c{18AfterInitResourcesEj,5SpawnEjP7fBase_cii,D0Ev,D1Ev}.cpp absorbed into src/actors/ActorDerived.cpp by a promotion that already landed. None of the four exists at the merge base, on this branch, or on origin/main. Pre-existing staleness.
added src/actors/ActorDerived.cpp qualifies, but was never banked — again from that earlier promotion, not this one.
re-sorted src/_ZN11daDsnBase_c16CleanupResourcesEv.cpp sits out of sorted() order on main; --update silently moved it. Reverted too, which is why the delta is 5 lines and not 7.

This PR's own delta on the file is now exactly five lines — the four paths ov004/dScMgBase_c genuinely absorbed, plus the count:

@@ -7,7 +7,7 @@
     "no_codegen_trick",
     "no_mangled_refs"
   ],
-  "count": 2553,
+  "count": 2549,
   "converted": [
     "src/ARMMathLoadState.c",
     "src/ARMMathSaveState.c",
@@ -545,10 +545,6 @@
     "src/_ZN11dScMgBase_c15graphCallback_c14GraphCallback1Ev.cpp",
     "src/_ZN11dScMgBase_c15graphCallback_c14GraphCallback2Ev.cpp",
     "src/_ZN11dScMgBase_c15graphCallback_c14GraphCallback3Ev.cpp",
-    "src/_ZN11dScMgBase_c16OnPendingDestroyEv.cpp",
-    "src/_ZN11dScMgBase_c18AfterInitResourcesEj.cpp",
-    "src/_ZN11dScMgBase_c6RenderEv.cpp",
-    "src/_ZN11dScMgBase_c8BehaviorEv.cpp",
     "src/_ZN11dScMgBase_cD0Ev.cpp",
     "src/_ZN11dScMgBase_cD1Ev.cpp",
     "src/_ZN11dScMgBase_cD2Ev.cpp",

Count sanity check: 2549 is correct. count is written as len(converted) by the generator, and it equals the entry count at every revision I checked — merge base 2553/2553, this branch 2546/2546, origin/main 2553/2553. The rebuilt file is 2549 field / 2549 entries, 0 duplicates, trailing newline present, LF blob (CRLF=0 measured on the staged blob). 2553 − 4 = 2549. ✅

⚠️ A correction to my own review. I asked for "a five-line delta relative to origin/main's version". That is not achievable, and my framing was wrong. origin/main has moved 31 commits past this branch's merge base, and #2012 renamed WaterfallMistdaObjWaterfall_c in the interim — so origin/main's baseline carries src/_ZN16daObjWaterfall_c*.cpp where this branch still carries src/_ZN13WaterfallMist*.cpp. Chasing a literal five-line diff against origin/main would have meant importing main's rename into this PR, which would be wrong. The five-line delta is measured against the merge base — i.e. against what this PR actually changes — which is what I meant. The rename resolves at merge time.


Fix 2 — the stale SHADOW banner is stripped from all seven

All seven opened with byte-identical banner text (lines 2–11, differing only in the ov00X/Name (N function(s)) line) asserting three things this PR makes false: NOT ENROLLED, NOT CANONICAL, contributes nothing to the ROM build, and a STARTING POINT.

I mirrored the shape the already-promoted TUs use rather than inventing one. src/actors/daObjAbuku_c.cpp (#1996) is the closest precedent — same tubuild origin, same promotion shape — and daKpFr_c.cpp / daKrpa_c.cpp do the same thing: drop the shadow paragraph, keep an accurate provenance header above the unchanged reverse-source-order note and the unchanged assembled-from list.

  //cpp
- /* SHADOW translation unit -- generated by tools/tubuild.py create.
-  * ov004/dScMgBase_c  (8 function(s))
+ /* Production translation unit for ov004/dScMgBase_c  (8 function(s)).
   *
-  * NOT ENROLLED, NOT CANONICAL. This file contributes nothing to the ROM
-  * build. It is a STARTING POINT (plan sec 7.3): local shadow declarations
-  * below were carried verbatim from the legacy files, not reconciled against
-  * real project headers -- that judgement call is left to a human/LLM review,
-  * the way pilot #1 reconciled PoleLift::Render and ::CleanupResources by hand
-  * (see notes/tu-reconstruction-pilot-report.md sec 5.2).
+  * Enrolled and canonical: this file's licensed text ranges are linked into
+  * the ROM build. Originally generated by tools/tubuild.py create.
   *
   * FUNCTION ORDER IS DELIBERATELY THE REVERSE OF THE ROM'S -- mwccarm 2004/b56

Identical edit in all seven. //cpp stays the first bytes of the file. The separate /* Local shadow declarations ... NOT reconciled against real project headers */ comment further down is left alone — those declarations are still there and that comment is still true.

Nothing reads the banner. tools/tubuild.py (lines 736–740) is the only mention of the text anywhere in tools/, config/ or .github/, and it only writes it.

src/actors/daObjPathLift_c.cpp still has its SHADOW banner. Per my review that is a standing oversight, not a precedent, and not this PR's file — left untouched.


Optional item — skipped, because it was already done

I asked for a comment naming which member required #pragma opt_strength_reduction off. src/actors/dScMgBase_c.cpp already carries one, directly above the pragma:

/* FILE-GLOBAL, and deliberately so: the last state set anywhere applies to
   every function in the TU. Carried over from
   src/_ZN11dScMgBase_c14BeforeBehaviorEv.cpp, the only member of the real
   0x020b04e8..0x020b2adc run that had one. */
#pragma opt_strength_reduction off

Independently confirmed against the legacy sources at the merge base: _ZN11dScMgBase_c14BeforeBehaviorEv.cpp carries the pragma at line 50, and the other seven members of the run (12BeforeRenderEv, 16OnPendingDestroyEv, 18AfterInitResourcesEj, 19BeforeInitResourcesEv, 21AfterCleanupResourcesEj, 6RenderEv, 8BehaviorEv) carry zero pragma lines between them. The existing comment is accurate and matches the TUBUILD WARNING text; a second one would be noise. No change made.


Verification

git diff a13cfc343..HEAD --stat — exactly the eight expected files, nothing else:

 config/converted-baseline.json      |  9 ++++++---
 src/actors/MgBingoBallSlotsShot.cpp | 11 +++--------
 src/actors/MgMushroomRoulette.cpp   | 11 +++--------
 src/actors/MgPairAGoneAndOn.cpp     | 11 +++--------
 src/actors/MgPicturePoker.cpp       | 11 +++--------
 src/actors/MgTrampolineTerror.cpp   | 11 +++--------
 src/actors/MgWhichWiggler.cpp       | 11 +++--------
 src/actors/dScMgBase_c.cpp          | 11 +++--------
 8 files changed, 27 insertions(+), 59 deletions(-)

The seven .cpp diffs are comment-only — proved, not asserted. I stripped // and /* */ comments from each file before and after (honouring string and char literals), dropped blank lines, and hashed the remaining token stream. Not one non-comment line was added, removed, or changed:

MgBingoBallSlotsShot   code-lines   26 ->   26   sha fec77b5348d26421 -> fec77b5348d26421   IDENTICAL
MgMushroomRoulette     code-lines   32 ->   32   sha 2aa163ceac10f419 -> 2aa163ceac10f419   IDENTICAL
MgPairAGoneAndOn       code-lines   31 ->   31   sha d62ae46c0b246d33 -> d62ae46c0b246d33   IDENTICAL
MgPicturePoker         code-lines   37 ->   37   sha 3084555cc1199420 -> 3084555cc1199420   IDENTICAL
MgTrampolineTerror     code-lines   71 ->   71   sha 631870f6eaa11dad -> 631870f6eaa11dad   IDENTICAL
MgWhichWiggler         code-lines   24 ->   24   sha 44e3a32c26154d4c -> 44e3a32c26154d4c   IDENTICAL
dScMgBase_c            code-lines  232 ->  232   sha 40f9c1e61716d55c -> 40f9c1e61716d55c   IDENTICAL

ALL SEVEN NON-COMMENT CONTENT IDENTICAL: True

Gates, run in the worktree, verbatim:

$ python tools/tiers_ratchet.py --check
CONVERTED ratchet PASS   baseline 2549   current 2546   (+1 gained, not yet banked)   (4 moved into a promoted TU)
(exit 0)

$ python tools/check_dead_references.py
check_dead_references: 367 prose file(s), 3308 repo-rooted path reference(s), 132 that do not resolve
  1 baselined reference(s) now resolve -- run --update to shrink the baseline (not a failure)
  no new dead references
(exit 0)

The 1 baselined reference(s) now resolve line is pre-existing at a13cfc343 — I captured check_dead_references output with the seven files reverted to HEAD and the two runs are byte-identical. Not introduced here, and the tool itself calls it "not a failure".

On tiers_ratchet: 4 moved into a promoted TU is the four restored dBase_c paths being correctly resolved through the manifest as moves, not backslides — exactly how origin/main is green today. +1 gained, not yet banked is ActorDerived.cpp. Both are the pre-existing staleness, now correctly left for its own change instead of riding along here.

langmode_audit.py --check takes a baseline path that lives in the chaos-data repo, so running it locally would only have produced a stale-baseline verdict. Instead I ran the stronger equivalent for a text-only change — --json before and after — and the output is byte-identical. Zero langmode delta.

Pre-push hook, not bypassed (no --no-verify):

port-refcheck: 405 checked - all references resolve
duplicate-sources: 11237 stems, none doubled
check_src_tu_compiles: 89/89 translation unit(s) compiled in 3.8s

No ROM build was run, deliberately: build/ is shared with several live sessions and a concurrent run reports fake failures. Both changes are text-only and cannot move a byte of compiled output — the seven .cpp edits are provably comment-only, and converted-baseline.json is not a compiler input. The validator's own run on 41a52556c is the byte authority here.


Not merged. Over to the next green.

@andrewboudreau
andrewboudreau merged commit 7bcadd1 into main Aug 30, 2026
7 checks passed
@andrewboudreau
andrewboudreau deleted the tu/promote-slice-1 branch August 30, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant