Promote ov006/dScMgBSC_c to a single 19-function C++ TU - #2067
Conversation
✅ PR validation — PassedCommitted merge passes; 17 contributor credit reassignment(s) noted, not a blocker. Full merge validation
Contributor credit moved (17)
Byte-verified means the range carries Warnings: contributor attribution changed, not a blocker (17 changed, 0 lost -- src/actors/dScMgBSC_c.cpp: tangosdev -> github-actions[bot]; src/actors/dScMgBSC_c.cpp: tangosdev -> github-actions[bot]; src/actors/dScMgBSC_c.cpp: tangosdev -> github-actions[bot]; +14 more); 19 address range(s) left the byte-verified set while enrolled totals held steady: ov006:0x02124908-0x0212497c, ov006:0x0212497c-0x02124a04, ov006:0x02124a04-0x02124a08, ov006:0x02124a08-0x02124ae4, ov006:0x02124ae4-0x02124b58, +14 more; 18 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 detailAll 1 changed file(s) compile to the ROM byte-for-byte with correct relocation targets.
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. |
Changes requested — eleven
|
| address | symbol | marker |
|---|---|---|
0x02124908 |
_ZN10dScMgBSC_cD1Ev |
lifecycle fallback — fine |
0x0212497c |
_ZN10dScMgBSC_cD0Ev |
lifecycle fallback — fine |
0x02124a04 |
func_ov006_02124a04 |
missing |
0x02124a08 |
func_ov006_02124a08 |
missing |
0x02124ae4 |
func_ov006_02124ae4 |
missing |
0x02124b58 |
func_ov006_02124b58 |
missing |
0x02124bb4 |
func_ov006_02124bb4 |
missing |
0x02124cb4 |
func_ov006_02124cb4 |
missing |
0x02124dc0 |
func_ov006_02124dc0 |
missing |
0x02124e1c |
func_ov006_02124e1c |
missing |
0x02124ec4 |
func_ov006_02124ec4 |
missing |
0x02124fd8 |
func_ov006_02124fd8 |
missing |
0x021250e4 |
func_ov006_021250e4 |
missing |
0x02125248 |
func_ov006_02125248 |
ok |
0x0212527c |
func_ov006_0212527c |
ok |
0x02125364 |
func_ov006_02125364 |
ok |
0x021253bc |
_ZN10dScMgBSC_c6RenderEv |
ok |
0x021254c0 |
_ZN10dScMgBSC_c8BehaviorEv |
ok |
0x0212551c |
_ZN10dScMgBSC_c13InitResourcesEv |
ok |
The consequence is worse than eleven members losing their boundary. _marked_member_fragment slices from the end of one marker to the start of the next marker. Your markers sit at lines 170, 209, 232, 278, 301 and 341, and the file is 695 lines. There is no marker after 341, so the fragment for func_ov006_02125248 runs 341 → EOF: 354 lines covering eleven other functions. That one member is currently scored against three quarters of the TU.
And the TU is not clean text — all four poisonable criteria are present in it:
| criterion | present at |
|---|---|
no_unk_field |
unk_51b8 at lines 223, 312, 316, 319, 325 |
no_codegen_trick |
*(volatile unsigned char *)&data_020a0e40 at line 463 |
no_raw_offset |
*(int *)(c + 0xa8) line 200, *(int*)(base + i * 8 + 0x51a8) line 374, and others |
no_mangled_refs |
_ZN5Sound12PlayBank2_2DEj line 476, _Z14ApproachLinearRiii lines 486-487, _ZN3GXS11LoadOBJPlttEPKvjj line 192 |
So this is not a hypothetical loss. config/converted-baseline.json banks three identities out of nineteen, and the fragment geometry is part of why.
The fix is eleven lines. Every function in the file already carries a /* ROM ordinal N -- <symbol>, <addr>, size 0x… */ comment with the exact symbol in it — the marker goes on its own line next to that comment, spelled as the manifest's functions[].symbol spells it. You already did this for six; the same treatment for ordinals 2 through 12.
I am not asking you to clean up the unk_/raw-offset/mangled text. That is a separate readability pass and the byte match is what matters first. I am asking that when someone does it, the credit lands on the member they fixed instead of being blocked by a neighbour.
What I verified independently, and what it showed
The linked range is exactly contiguous and abuts both neighbours
Nineteen delink entries collapse into one, and the endpoints are unchanged:
MgTrampolineTerror.cpp .text 0x0212471c..0x02124908
src/actors/dScMgBSC_c.cpp .text 0x02124908..0x021255f8 <- new
Chaining the nineteen removed ranges by hand: 908 -> 97c -> a04 -> a08 -> ae4 -> b58 -> bb4 -> cb4 -> dc0 -> e1c -> ec4 -> fd8 -> 0e4 -> 248 -> 27c -> 364 -> 3bc -> 4c0 -> 51c -> 5f8. No gap, no overlap, first start and last end preserved.
Every licensing address in compiler_only_output reads back correctly from the ROM
This is the check that SCRATCH-LINK-VERIFIED does not perform — the link round proves the bytes, not that the canonical_module/canonical_address rows point at the records they claim. I resolved all of them against extracted/arm9_dec.bin and the overlay images, preferring each row's own module:
_ZTI7fBase_c arm9 0x02086d70 -> name '7fBase_c' OK
_ZTS7fBase_c arm9 0x02086e60 -> '7fBase_c' OK
_ZTS7dBase_c arm9 0x02086e54 -> '7dBase_c' OK
_ZTS8dScene_c arm9 0x020914b0 -> '8dScene_c' OK
_ZTI7dBase_c arm9 0x02086e78 -> name '7dBase_c' OK
_ZTI11dScMgBase_c ov004 0x020bbf6c -> name '11dScMgBase_c' OK
_ZTS11dScMgBase_c ov004 0x020bbf84 -> '11dScMgBase_c' OK
_ZTI8dScene_c arm9 0x020914d4 -> name '8dScene_c' OK
_ZTI10dScMgBSC_c ov006 0x0213fd8c -> name '10dScMgBSC_c' OK
_ZTS10dScMgBSC_c ov006 0x0213fd98 -> '10dScMgBSC_c' OK
_ZTI19dScMgSingle3DBase_c ov006 0x0213bc64 -> name '19dScMgSingle3DBase_c' OK
_ZTS19dScMgSingle3DBase_c ov006 0x0213bd00 -> '19dScMgSingle3DBase_c' OK
Thirteen for thirteen, counting the vtable below.
The class's own RTTI chain, walked from the vtable
_ZTV10dScMgBSC_c @ 0x0213fec8 (ov006, base 0x020bfec0)
[V-8] offset-to-top = 0x00000000
[V-4] typeinfo = 0x0213fd8c manifest says 0x0213fd8c MATCH
TI[1] name ptr = 0x0213fd98 -> '10dScMgBSC_c'
TI[2] base's _ZTI = 0x0213bc64 = _ZTI19dScMgSingle3DBase_c MATCH
10 is exactly len("dScMgBSC_c"), so the read verifies itself, and TI[2] independently confirms the base clause dScMgBSC_c : dScMgSingle3DBase_c from the cartridge rather than from our headers.
This class's name agrees with the ROM, which is why the promotion can land at all. #2066 is the counter-example: PoleLift is daObjKm2_Ami_Bou_c in the cartridge, so consolidating its RTTI emitted _ZTS8PoleLift/_ZTI8PoleLift with no ROM symbol to license them and tubuild refused with HOMELESS. Measured tree-wide, 259 of 541 _ZTV-bearing classes are in that position — see #2069. dScMgBSC_c and dScMgSingle3DBase_c are both in the agreeing 276.
The converted ratchet is a clean re-key, not a backslide
Three entries move and the set size is unchanged at 2573:
- src/_ZN10dScMgBSC_c6RenderEv.cpp -> src/actors/dScMgBSC_c.cpp#_ZN10dScMgBSC_c6RenderEv
- src/_ZN10dScMgBSC_cD0Ev.cpp -> src/actors/dScMgBSC_c.cpp#_ZN10dScMgBSC_cD0Ev
- src/_ZN10dScMgBSC_cD1Ev.cpp -> src/actors/dScMgBSC_c.cpp#_ZN10dScMgBSC_cD1Ev
The other five .cpp files this deletes — InitResources, Behavior, func_ov006_02124e1c, _0212527c, _02125364 — I checked against the base baseline and none of them were members, so nothing is silently dropped. This is the shape a promotion should have.
The inline destructor, and the brace
The header's reasoning is correct and it is the good kind of evidence — D1 at 0x02124908 sits before D0 at 0x0212497c, which an out-of-line destructor cannot produce, because mwcc emits the synthesized D0 ahead of the written D1. Inline in the class body, the destructor cannot be the key function, so InitResources (the next declared non-inline virtual) becomes it and emits the vtable, whose slots 16/17 odr-use D1 then D0 in the cartridge's order.
Worth saying out loud: dScMgBSC_c is the first case in the tree that actually sits on the check_header_offsets hazard shape — derived class, structor declared before any field, multi-statement body. You put the brace on the signature line, which is the one position that keeps the field walk alive. That is exactly what #2068 documented two commits ago, and this PR is the first real instance of it. Please leave it that way.
The notes are the best part of this PR
notes/dScMgBase_c-slots-18-35.md confirms the slot map from a second, independent direction — the base's table was walked from _ZTV11dScMgBase_c, the overrides from _ZTV10dScMgBSC_c, and the recovered-name comments predate both walks and land on the same two addresses. Slot 18 staying unnamed on both sides is consistent, not a hole. And measuring what the follow-up costs — _ZTV10dScMgBSC_c is 0x50 bytes, truncating exactly one slot short of 18, so widening the base widens every descendant's table too — is the kind of thing that stops a follow-up PR from being opened blind.
The notes/minigame-provenance.md line was correctly re-pointed at the new path. That would otherwise have gone red on check_dead_references.
Two smaller things, neither blocking
1. The manifest's five CONFLICT notes describe something that is no longer in the file. They each end "kept the first, the other is commented out for review" — for func_ov006_020c0c80, func_ov006_020c0d68, _ZN5Sound12PlayBank2_2DEj, func_ov006_020c1718 and func_ov006_020c0aa8. There is no commented-out extern anywhere in src/actors/dScMgBSC_c.cpp, so a reader who tries to do the review the note asks for finds nothing to compare against. If you resolved them, say so — replace the five with one line noting that the kept declaration is confirmed by the 19/19 byte match, and which one lost. If you did not, the alternatives should be restored.
2. boundary_evidence says corroborated=False. The sinit line is module-wide (31/31 for all of ov006), not narrowed to this TU, and the manifest is honest about that. The contiguous linker run plus the vtable-slot derivation of the three overrides carry the boundary here, so this is not a gap I am asking you to close — just noting that the strong evidence is the other two rows, not this one.
Verdict
Everything I could check from the cartridge checks out: the range, the licensing addresses, the RTTI chain, the base clause, the ratchet set. 19/19 byte-matched under SCRATCH-LINK-VERIFIED with a full ROM build.
Add the eleven markers and I will merge it. This is stacked on #2064, which is also awaiting fixes, so both can be pushed in one round — and #2064 needs one marker of its own (func_ov006_0210a534, the only unmarked member there, and it is the last function in that file so it is currently absorbed into func_ov006_0210a600's fragment). I have commented there separately.
tiers.score_member slices a member at `// @symbol` markers and, finding
none after a marker, runs to end of file. Only 5 of the 21 in-file members
carried one, so _ZN13dScMgMCarlo_c6RenderEv was scored over everything
below it -- the flat-C bodies with their raw casts and mangled callees --
and the CONVERTED ratchet read that as a backslide of a banked entry:
src/actors/dScMgMCarlo_c.cpp#_ZN13dScMgMCarlo_c6RenderEv
No raw offset arithmetic; Calls things by real names, not mangled _Z
Neither is true of Render's own body. The 16 missing markers are added, so
each member is scored against itself. Comments only:
tiers_ratchet --check PASS baseline 2568 current 2568
rombuild -j16 106/106 exact, 100.000000%; 11,088/11,088
reproducing, 0 mismatching; the ROM is still
sha256 d1506e90..., identical to the stock build
dScMgBSC_c.cpp has the same gap -- 6 markers for 17 members -- but none of
its banked members currently mis-score, so it is a latent hazard rather
than a regression, and it belongs to #2067, not here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh
|
Latent
That is not currently a failure here: none of BSC's banked It did fire one PR up the stack. Neither is true of So the same 11 missing markers here are a trap waiting on the next banked entry, not a defect in what this PR proves. Adding them is comment-only and byte-neutral. I have not touched this branch, because doing so would force a restack of #2071 for a hazard that is not yet armed — flagging it so the choice is explicit rather than silently deferred. |
…IFIED) Nineteen functions over 0x02124908..0x021255f8 compile from one object and byte-match the cartridge: 19/19 MATCH, objisolate clean, reloc-destinations clean. The unit emits its own _ZTV10dScMgBSC_c and the full RTTI chain. Three things needed hand work beyond what tubuild reconstructs: * `#pragma opt_strength_reduction off` is carried once, file-global, at the top. Two members (func_ov006_02124cb4, func_ov006_021250e4) had it in their legacy one-function sources and tubuild left it out with a [NOT carried -- review] note, because a file-global pragma in a merged TU recompiles every other member. Measured both ways. Bracketing it around just those two does nothing -- unlike `long_calls`, this pragma is not positional in mwccarm 2004/b56, it is file-global last-wins, so the trailing `on` won and both members still grew. Set once at the top, all nineteen match: the two that need it reproduce, and the seventeen that matched without it are unchanged. * Two legacy files each spelled their own incompatible `struct Obj` and tubuild carried both verbatim. The layout typedef keeps the name; the other -- which exists only to reach vtable slot 18, undeclared in dScMgBase_c.h, so the self-dispatch cannot yet be a real virtual call -- is now `SelfVtblShim`, named for what it is instead of colliding. * tubuild carried both the D1 and D0 legacy bodies as two identical source destructors. One source destructor now emits both. The 14 compiler-only rows are generated from tubuild.all_symbol_homes(), not hand-written. The D2 takes plain `deadstrip` on proven grounds, not on the boilerplate reason: it has no configured ROM symbol, its bytes are identical to the licensed D1 (0x74, compared section-for-section in the object), and objisolate's plan confirms no surviving relocation reaches it. The other 13 are RTTI/vtable records taking `deadstrip-data` with canonical_module and canonical_address, so romdata_check compares their bytes against the cartridge before the duplicates are discarded.
src_tu/actors/dScMgBSC_c.cpp -> src/actors/dScMgBSC_c.cpp, replacing 19
per-function legacy sources (14 flat-C, 5 already C++). The whole .text
range 0x02124908..0x021255f8 now reproduces from one object.
The destructor is defined inline in the class body rather than out of
line here. An out-of-line ~dScMgBSC_c() makes mwcc emit the synthesized
D0 ahead of the written D1, and the cartridge has D1 first; linkcheck's
pre-link licensing audit refuses a TU whose licensed .text is not in ROM
address order, and it refused this one. Inlining hands the key-function
role to InitResources -- the next virtual declared, and non-inline, and
defined in this TU -- which emits _ZTV10dScMgBSC_c here. Slots 16 and 17
name D1 then D0, odr-using both, so the compiler emits the pair for us,
in cartridge order. It also deletes the homeless D2 outright, so that
policy row is gone (14 compiler_only_output rows -> 13).
Verification:
tubuild verify ov006/dScMgBSC_c 19/19 MATCH, exact deadstrip,
ROM-ascending section order
tubuild linkcheck --module ov006 SCRATCH-LINK-VERIFIED; 106/106
exact; ROM sha256 d1506e90... is
IDENTICAL to the stock build
rombuild -j16 11,088 source-built functions,
reproducing 11,088, mismatching 0;
storage aliases exact; ROM data from
source 462 verified (was 461)
The +1 verified record and the +1 partial are the RTTI and the emitted
_ZTV10dScMgBSC_c -- 18 of the cartridge's 36 slots, byte-compared rather
than trusted. Naming slots 18-35 on dScMgBase_c widens the table past
slot 17 and is deliberately left to its own change; notes/dScMgBase_c-
slots-18-35.md records the two independent confirmations this TU gives
for slots 18, 19 and 21.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh
Now DIRTY — and the conflict set says restack, do not resolve#2064 merged as Four of those five are not yours. They are #2064's files, colliding with themselves. Resolving them by hand is the wrong move twice over: it is work you already did, and The restackThis branch has exactly two commits of its own: Cut a fresh branch from current
Roll the eleven
One correction to something in my review, in your favour: I said the run-on fragment poisons four criteria. It poisons them because it swallows eleven live function bodies — I re-checked all eight lines I cited ( |
tiers.score_member slices a member at `// @symbol` markers and, finding no
next marker, runs to end of file (tools/tiers.py:317-325). This TU carried
6 markers for 17 in-file members, so the first-marked member's fragment
swallowed eleven live function bodies below it -- their raw casts and
mangled callees included.
Nothing banked mis-scores on it today, so the gate was green either way;
it is a trap waiting on the next banked entry rather than a present
defect. One PR up the stack the identical gap did fire, scoring
_ZN13dScMgMCarlo_c6RenderEv over the flat-C members after it and reading
as a CONVERTED backslide that Render's own body does not deserve.
Comments only:
tiers_ratchet --check PASS baseline 2568 current 2568
rombuild -j16 106/106 exact, 100.000000%; 11,088/11,088
reproducing, 0 mismatching; the ROM is still
sha256 d1506e90..., identical to the stock build
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh
7c0f9a9 to
3194e6d
Compare
tiers.score_member slices a member at `// @symbol` markers and, finding
none after a marker, runs to end of file. Only 5 of the 21 in-file members
carried one, so _ZN13dScMgMCarlo_c6RenderEv was scored over everything
below it -- the flat-C bodies with their raw casts and mangled callees --
and the CONVERTED ratchet read that as a backslide of a banked entry:
src/actors/dScMgMCarlo_c.cpp#_ZN13dScMgMCarlo_c6RenderEv
No raw offset arithmetic; Calls things by real names, not mangled _Z
Neither is true of Render's own body. The 16 missing markers are added, so
each member is scored against itself. Comments only:
tiers_ratchet --check PASS baseline 2568 current 2568
rombuild -j16 106/106 exact, 100.000000%; 11,088/11,088
reproducing, 0 mismatching; the ROM is still
sha256 d1506e90..., identical to the stock build
dScMgBSC_c.cpp had the same gap -- 6 markers for 17 members -- and none of
its banked members mis-scored, so it was a latent hazard rather
than a regression; it is fixed one commit down, in #2067, where it lives.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh
Restack and markers verified — approving, merging once the ROM validator reportsRe-reviewed at The markers17 of 19 declared functions now carry The last marker now sits at line 681 of 706, so the final fragment is 25 lines instead of the 354 it was. The eleven bodies that were being scored against The markers commit is The restackThree commits on current I checked the thing I was actually worried about — that a hand-resolve would take this branch's side on #2064's files and silently revert its post-review fixes. It did not: All four are byte-identical to And the promotion content itself survived the re-cut unchanged: So everything I verified from the cartridge in my first review — the contiguous Gates on the real merge tree
8/8, and the −18 is arithmetic: nineteen delink entries become one. The converted ratchet is a clean 3-way re-key with the set size unchanged: Approved. I will merge as soon as One carry-forward, still not blockingThe manifest's five
|
tiers.score_member slices a member at `// @symbol` markers and, finding
none after a marker, runs to end of file. Only 5 of the 21 in-file members
carried one, so _ZN13dScMgMCarlo_c6RenderEv was scored over everything
below it -- the flat-C bodies with their raw casts and mangled callees --
and the CONVERTED ratchet read that as a backslide of a banked entry:
src/actors/dScMgMCarlo_c.cpp#_ZN13dScMgMCarlo_c6RenderEv
No raw offset arithmetic; Calls things by real names, not mangled _Z
Neither is true of Render's own body. The 16 missing markers are added, so
each member is scored against itself. Comments only:
tiers_ratchet --check PASS baseline 2568 current 2568
rombuild -j16 106/106 exact, 100.000000%; 11,088/11,088
reproducing, 0 mismatching; the ROM is still
sha256 d1506e90..., identical to the stock build
dScMgBSC_c.cpp had the same gap -- 6 markers for 17 members -- and none of
its banked members mis-scored, so it was a latent hazard rather
than a regression; it is fixed one commit down, in #2067, where it lives.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh
* Promote ov006/dScMgMCarlo_c to a single 23-function C++ TU
src_tu/actors/dScMgMCarlo_c.cpp -> src/actors/dScMgMCarlo_c.cpp, replacing
23 per-function legacy sources (18 flat-C, 5 already C++). The whole .text
range 0x020f7634..0x020f8e44 now reproduces from one object.
CONSOLIDATION IS WHERE THE WORK WAS. Each of the 23 legacy files compiled
in isolation with its own private shadow structs and extern declarations,
so they were free to contradict each other and never did so out loud. Put
in one TU they produced ~25 compile errors, every one a real disagreement
about the same object. Each was settled against evidence:
- `Node` and the list-`Obj` are the SAME struct. Five partial views, all
agreeing on every offset they share; unified into one layout with the
fields commented.
- A THIRD, unrelated `Obj` was not a list node at all -- it was a
shape-only shadow of this scene class's OWN vtable, used for the
slot-18 self-dispatch in InitResources. Revived as `SceneVtable` so
the two meanings stop colliding. It becomes a real base-class virtual
call once dScMgBase_c declares slots 18-35.
- data_ov006_02142500 is the list HEAD and _02142504 the TAIL, proven by
func_ov006_020f7740 walking head->next and tail->prev. Render starts
from the tail, so both its loops step `prev`, not `next` -- corrobo-
rated independently by func_ov006_020f8a3c's raw `*(char**)(r3 + 4)`.
- _Z14ApproachLinearRiii and _Z15ApproachLinear2Rsss take REFERENCES;
the mangled names say so, and a reference and a pointer pass the same
address, so the accurate spelling costs no code.
- func_ov006_020f8540 was declared (void) but called with an argument.
ARM passes it in r0 and the body ignores it; spelling the parameter
keeps the call site honest and changes no byte.
- include/dScMgBase_c.h already declared data_ov004_020beb68 as void*,
which settled that conflict against the TU's char*.
The destructor is defined inline in the class body, for the same reason as
dScMgBSC_c: out of line, mwcc emits the synthesized D0 ahead of the written
D1, and the cartridge has D1 first (0x020f7634) then D0 (0x020f76a8).
Inlining hands the key-function role to InitResources -- the next virtual
declared, non-inline, and defined here -- which is what emits
_ZTV13dScMgMCarlo_c. Slots 16 and 17 name D1 then D0, odr-using both, so
the compiler emits the pair in cartridge order, and the homeless D2 is gone.
decl_common.h loses the scalar declarations of the four list globals
(_021424fc/_02142500/_02142504/_02142508). They were arbitrary winners
among 19 contradictory isolated spellings; every consumer is in this TU,
which now declares all four with the recovered pointer type.
Verification:
tubuild verify ov006/dScMgMCarlo_c 23/23 MATCH, objisolate clean,
reloc-destinations clean,
ROM-ascending section order
tubuild linkcheck (pre-promotion) SCRATCH-LINK-VERIFIED; 106/106
exact; full ROM built and IDENTICAL
to the stock build. dsd check
symbols --fail still fails on the
same 9 errors the baseline control
has -- 0 new, so not attributable
to this TU, but NOT green and not
reported as such.
rombuild -j16 (post-promotion) 11,088 source-built functions,
reproducing 11,088, mismatching 0;
106/106 exact, 100.000000%; the ROM
it builds is sha256 d1506e90...,
byte-identical to the stock build.
layout_check / eligible / langmode / port_refcheck / dead-references: clean
All 13 compiler-emitted RTTI and vtable symbols are licensed in
compiler_only_output, every canonical address confirmed against
config/arm9 symbols.txt, and romdata_check byte-compares each against the
cartridge: 6 VERIFIED, 7 PARTIAL (exact prefixes), 0 DIFFER.
_ZTV13dScMgMCarlo_c reproduces 72 of the cartridge's 144 bytes -- 18 of 36
slots -- because dScMgBase_c still leaves slots 18-35 undeclared. That is a
verified prefix, not a disagreement, and widening it is the dScMgBase_c
follow-up, not this change.
The cartridge's own _ZTS strings corroborate the names: reading
extracted/dsd/arm9_overlays/ov006.bin at overlay base 34340544 gives
13dScMgMCarlo_c at 0x0213d594 and 19dScMgSingle3DBase_c at 0x0213bd00 --
so this TU emits no homeless typeinfo record.
Attribution: a 23-delete + 1-add collapse ends 22 git lineages, so
attribution.json takes 23 explicit override keys on the surviving path,
each carrying the first matcher the gate itself computes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh
* Mark every member of the MCarlo TU with its @symbol boundary
tiers.score_member slices a member at `// @symbol` markers and, finding
none after a marker, runs to end of file. Only 5 of the 21 in-file members
carried one, so _ZN13dScMgMCarlo_c6RenderEv was scored over everything
below it -- the flat-C bodies with their raw casts and mangled callees --
and the CONVERTED ratchet read that as a backslide of a banked entry:
src/actors/dScMgMCarlo_c.cpp#_ZN13dScMgMCarlo_c6RenderEv
No raw offset arithmetic; Calls things by real names, not mangled _Z
Neither is true of Render's own body. The 16 missing markers are added, so
each member is scored against itself. Comments only:
tiers_ratchet --check PASS baseline 2568 current 2568
rombuild -j16 106/106 exact, 100.000000%; 11,088/11,088
reproducing, 0 mismatching; the ROM is still
sha256 d1506e90..., identical to the stock build
dScMgBSC_c.cpp had the same gap -- 6 markers for 17 members -- and none of
its banked members mis-scored, so it was a latent hazard rather
than a regression; it is fixed one commit down, in #2067, where it lives.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh
* Spell func_ov006_020f7730 the way the TU recovers it
src/func_ov006_020f8e44.cpp declared the array's element destructor as
`void(void)` while src/actors/dScMgMCarlo_c.cpp recovers it as `void(int *)`.
Both compile -- the factory casts to void* at the call -- but that file is
the corroboration for mArray at 0x51a8 being 0x50 x 0x30 with that element
dtor, so it is the one place the disagreement is expensive to leave.
rombuild: 11,088/11,088 reproducing, 0 mismatching, 106/106 exact,
100.000000%; ROM sha256 unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Stacked on #2064 — review that one first; this branch contains its commit.
src_tu/actors/dScMgBSC_c.cpp→src/actors/dScMgBSC_c.cpp, replacing 19 per-function legacy sources (14 flat-C, 5 already C++). The whole.textrange0x02124908..0x021255f8now reproduces from one object.The destructor is inline, and that is the load-bearing part
An out-of-line
~dScMgBSC_c()makes mwcc emit the synthesizedD0ahead of the writtenD1. The cartridge hasD1first, and linkcheck's[4b/8]pre-link licensing audit refuses a TU whose licensed.textis not in ROM address order — it refused this one:Defining the destructor inline in the class body hands the key-function role to
InitResources— the next virtual declared, non-inline, and defined in this TU. That emits_ZTV10dScMgBSC_chere; slots 16 and 17 nameD1thenD0, odr-using both, so the compiler emits the pair for us, in cartridge order. It also deletes the homelessD2outright, which is whycompiler_only_outputdrops 14 → 13 rows. (A stale row there rejects the whole policy block — all 13deadstrip-datarows come back asEXTRAuntil the deadD2row is dropped.)Verification
tubuild verify ov006/dScMgBSC_ctubuild linkcheck --module ov006d1506e90…identical to stockrombuild -j16premerge_check HEADport_refcheck/ attributionThe +1 verified record and +1 partial are the RTTI and the emitted
_ZTV10dScMgBSC_c— 18 of the cartridge's 36 slots, now byte-compared against the cartridge rather than trusted.Two merge-tree-only reds this had to fix
Both were green on the branch and red only on the merge result, so only
premerge_check.pysurfaces them:header-offsets— the inline body was written with an Allman brace.check_header_offsets.py'sskip_bodyonly engages when the signature line itself contains{, so the bare{fell through to DECL parsing (UNPARSED include/dScMgBSC_c.h:53: {). Worse than a red: one unparsed line suppresses the field walk for the whole header, so it reported0 commented fields— hollow green, not a pass. Brace moved to the signature line, matching every existing inline dtor ininclude/; now4 commented fields, 0 mismatched, 0 unparsedand the span corrects0x4f38→0x51d0.dead-references— deleting 19 legacy sources stranded a prose reference innotes/minigame-provenance.md. Repointed at the consolidated path.Deliberately not in this PR
Naming
dScMgBase_cslots 18–35 would widen the emitted vtable past slot 17 and every descendant's with it. This TU independently confirms three of those slots from its own table (18 →Virtual48, 19 →OnTurnIntoEgg, 21 →OnGroundPounded); that evidence is recorded innotes/dScMgBase_c-slots-18-35.mdfor the atomic change that does the rename.Converted-tier accounting is flat: 3 identities migrated, 3 score 5/5 —
Rendervia its@symbolmarker,D1/D0via the inline lifecycle fragment.🤖 Generated with Claude Code