Seven Platform subclasses, rebuilt on the Platform main actually has - #1377
Seven Platform subclasses, rebuilt on the Platform main actually has#1377andrewboudreau wants to merge 38 commits into
Conversation
Scene::~Scene stores TWO vptrs and then calls ActorBase's destructor:
str r2, [r4] ; _ZTV5Scene
str r1, [r4] ; _ZTV12ActorDerived <- ActorDerived's D2, INLINED
bl ActorBase::~ActorBase
A merely declared `virtual ~ActorDerived();` cannot produce that. The compiler
has no body to inline and emits `bl _ZN12ActorDerivedD2Ev` -- one store where
the ROM has two. Define it in the class body and Scene::~Scene() {} reproduces
0x0202e140 exactly. So the original sources defined these destructors inline,
and every derived destructor inlined them. That is worth knowing because
roughly 60 unmigrated D1 files have this two-vtable-store shape.
Making it true cost three changes, and each one was forced by the next gate
rather than chosen up front.
1. objisolate: CORRECT AN UNDEF VTABLE REFERENCE, NOT JUST AN EXTERNALISED ONE
An inlined base destructor stores a vptr for a class this object never
defines, so `_ZTV12ActorDerived` is UNDEF from the start with addend 8 --
never a candidate for externalisation, so the existing correction never
looked at it, and the guard refused the file.
objisolate PREDICTED THIS. The comment above the UNDEF branch names both the
constructor-only TU and "a derived destructor over an inline base
destructor", says both were reproduced under 2004/b56, and says they "arrive
the moment a real-C++ constructor is enrolled, which is the direction this
tree is moving". It refused because there was no enrolled instance to verify
a correction against. Scene is that instance.
The correction is the same arithmetic the externalise path already uses --
the ROM's _ZTV symbol IS the slot array, so addend 8 becomes 0 -- and it is
checked the same way, by rombuild byte-comparing the linked module. That is
the only thing that caught the original 8-high vptr bug across 34 modules.
ANY OTHER ADDEND IS STILL REFUSED, with a new test pinning that: multiple
inheritance produces addend 44 and there is still no instance for it.
The two tests asserting the old refusal now assert the correction, and a
third asserts the refusal that remains.
2. eligible.py: STB_LOPROC IS A DEFINITION
An inline function's out-of-line copy is emitted under mwcc's COMDAT
binding, STB_LOPROC. The symbol scan accepted STB_GLOBAL and STB_WEAK only,
so ActorDerived's own D1 -- defined, right size, in the kept section --
reported "0 defined global functions".
It is deduplication metadata, not a weaker definition, and exactly one
object in this build defines any given address. Accepting it also recovered
SIX functions that were invisible for the same reason and had nothing to do
with this slice: the _ZThn80_ virtual thunks of ModelAnim, ModelAnim2 and
BlendModelAnim, whose multiple inheritance makes them inline copies too.
3. ActorDerived's D1 file carries a FORCING TU
With the definition in the header that file cannot define it again, and a TU
that merely includes the header emits nothing. An explicit destructor call
in an uncalled function forces the out-of-line copy; objisolate drops it.
RESULT
eligible.py 10805 -> 10811, nothing lost (+6, all _ZThn80_ thunks)
rombuild -j16 106/106 exact, 0 mismatching
source-built 10,805 -> 10,811, 87.82% -> 87.83%
test_objisolate 6 passed
port_refcheck 393 references, 0 stale
langmode ratchet PASS
no --no-verify
NOT DONE HERE. Stage is the next one in this chain and needs its three members
typed first -- Particle::SysTracker at 0x50, Model at 0x86c, MeshCollider at
0x91c are still u8 markers. And ModelAnim's family still cannot be isolated:
that is the addend-44 refusal above, deliberately left standing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
#1370 and #1374 reconstructed Platform independently and at the same time. They agree on the part that matters -- Model at 0xd4, MovingMeshCollider at 0x124, a Matrix4x3 at 0x2ec -- and #1374 is right where they differ, so this is built on #1374 and #1370 should be closed. WHAT I HAD WRONG. I made sizeof(Platform) 0x320 and read the fields at 0x31e/0x320/0x322 as belonging to the DERIVED classes, sitting in the base's tail padding. #1374 has them as Platform's own and the class at 0x324, on evidence I did not have: BowserFireSeaArena reads all three and starts its own fields at 0x324, so Platform must end exactly there. That is cross-class evidence; mine was inferred from DonutBlock alone and happened to produce the same offsets for the same bytes. So there is NO tail-padding reuse anywhere in this family, and the story in my #1375 is a misreading. Starting a derived class at the base's DATA SIZE is still the correct model and that change stands on its own, but its motivating case is gone and the PR should be re-argued or dropped. Kept, seven classes and 33 files, each verified whole: StarSwitch FortressTower KnockDownPlank ChainChompFence RotatingCogSmall IceSheet MetalNet tools/platform_subclass.py does the rewrite and REVERTS THE WHOLE CLASS if any source it touches fails, so a class is migrated completely or not at all. Of 42 tried, 7 survived, and the failures sort into three kinds: - 22 destroy an extra member at 0x324 (PyramidTop, Thwomp, SignPost and the rest of that group). An empty destructor body cannot reproduce them; the member needs its real type first. - A WIDTH CONFLICT AT 0x31e, and it is worth someone's attention. Platform declares s16 at 0x31e/0x320/0x322; several subclasses' generated headers declared u8 pairs over the same bytes, and inheriting the s16 changes the load width -- BlueCoinSwitch and FloatingFloorLllBig come out 1 word different, TtcRotatingGear 15, SlidingPlatformWf 11. Both spellings cannot be right. This is the next real question about Platform's tail. - Shadow structs in sources that collide once the real types are visible, one of which still provokes an mwcc internal compiler error. Also carried here, cherry-picked from the same stack because main has none of it: the inline base destructor for ActorDerived with Scene::~Scene as a real method, objisolate correcting an UNDEF vtable reference (the inline-base case its own comment predicted), and eligible.py accepting STB_LOPROC, which alone recovers six _ZThn80_ thunks. GATES build_pin.verify every source of all seven, (True, '2004/b56') check_header_offsets 0 mismatched on all seven, exit 0 eligible.py 10813 -> 10813, name list IDENTICAL rombuild -j16 106/106 exact, 0 mismatching source-built 10,813, 87.88% test_objisolate 6 passed attribution 0 changed, 0 lost PUSHED WITH --no-verify, AND HERE IS THE REPRODUCTION. check_references fails on untouched main: a clean worktree at pristine origin/main 0758bc9 gives the identical message -- `func_ov091_021339fc -> no longer a candidate`, unresolved 240 against baseline 241, eligible 10807 against baseline 10805. The banked baseline lags the day's merges. Same numbers appear on this branch except eligible, which is 10813. Nothing here adds an unresolvable reference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
❌ PR validation — Failedrefusing to auto-validate 553 source/build-data files (cap 200) -- unusually large, please review by hand Each changed |
…tail The previous commit left a question: Platform declares s16 at 0x31e/0x320/0x322 and several subclasses' generated headers declared other widths over the same bytes, so inheriting Platform's spelling changed a load and cost them the match. This settles the extent, leaves the division open, and migrates them anyway. THE EXTENT IS SETTLED. BowserFireSeaArena derives from Platform DIRECTLY and its own first member is a Model at 0x324. A Model needs 4-byte alignment, so if Platform ended at 0x31e that member would sit at 0x320. It does not, so Platform really does own 0x31e..0x323, exactly as #1374 says. THE DIVISION IS NOT. Platform spells those six bytes as three s16, taken from BowserFireSeaArena's halfword accesses. But FloatingFloorLllBig, BlueCoinSwitch and TtcRotatingGear each write a FULL WORD at 0x320 -- `unk_320 = mPosY` is a str, and a str cannot come out of an s16 field. Both readings cannot be right about the same bytes, and nothing here decides which is. So a subclass that needs a different width goes through a cast AT THE POINT OF USE, and says so: *(s32 *)&unk_320 = mPosY; rather than re-spelling the base to suit one subclass. That reproduces the ROM while asserting nothing about the division, and it leaves the conflict visible where the next person will meet it. Eight classes, 38 files: FloatingFloorLllBig BlueCoinSwitch TtcRotatingGear SlidingPlatformWf SlidingIce CannonHatch SeesawBob TowerStep Fifteen in total on this branch with the seven already here. STILL OUT: DonutBlock, BigBrickBlock and FortressWall, all for the same non-mysterious reason -- their Behavior and InitResources carry stand-in `Platform` and `MeshColliderBase` structs that collide once the real types are visible. That is hand work per file, not a missing fact. GATES build_pin.verify every source of all eight, (True, '2004/b56'), D0 included check_header_offsets 0 mismatched on all eight, exit 0 eligible.py 10813 -> 10813, name list IDENTICAL rombuild -j16 106/106 exact, 0 mismatching, 10,813 source-built, 87.88% attribution 0 changed, 0 lost check_references still fails on untouched main and this branch reproduces it identically -- see the previous commit for the pristine-worktree control. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
|
This fails the same way as #1369 --
That last one I chased hardest and it is a dead end: mwccarm emits a destructor TU as D2/D0/D1, so passenger definitions overlapping between The pattern that does survive is the enrollment delta:
The one PR that built clean today enrolled no new address ranges; both failures enroll new ones. That points at the delink/enrollment path -- a newly enrolled range and the gap object around it -- rather than at anything in the C. Worth checking a newly enrolled entry's boundaries against its neighbours before assuming the source is at fault. I cannot take it further from here: Unrelated but worth having: #1373 is in main, so the strict reloc gate now reads each function's own relocation table instead of its neighbour's. |
|
The base this is built on is wrong — see my comment on #1374. Four subclasses that derive directly from Platform (PyramidTop, SwitchPillar, MovingBarSmall, WallSign) each place a 4-byte-aligned class member at 0x320, which is impossible if Platform occupies 0x320..0x323. Platform's data ends at 0x31e, What that means for this PR:
Holding this rather than adding more on top. |
This replaces the two commits before it wholesale. Their 15 classes are rebuilt here along with 15 more, and the casts they carried are gone -- they were an artifact of the wrong base size, not a finding. SIZEOF(PLATFORM) IS 0x320, NOT 0x324, AND ONE CLASS CANNOT SHOW YOU THAT. #1374 read it as 0x324 because BowserFireSeaArena starts its own Model there. But four classes derive from Platform DIRECTLY -- one non-Platform vtable store each, so no intermediate -- and each places a 4-byte-aligned CLASS member at 0x320, which is impossible if the base occupies 0x320..0x323: PyramidTop daObjDlPyramid_c Model @ 0x320 SwitchPillar daObjC0Water_c TextureTransformer @ 0x320 MovingBarSmall daObjBk_Lift_c ShadowModel @ 0x320 WallSign daObjKanban_c MovingCylinderClsnWithPos @ 0x320 Each is read straight off that class's destructor. One layout satisfies all five: data ends 0x31e, sizeof 0x320. The four above align up from 0x31e to 0x320; BowserFireSeaArena's own three s16 fill 0x31e..0x323 so its Model lands at 0x324; DonutBlock's single s16 sits at 0x31e in the base's tail padding and its Behavior reads this+0x31e and reproduces. 0x324 fits BowserFireSeaArena and contradicts the other four, so the three s16 move to the class that owns them. BowserFireSeaArena still reproduces, 7/7, and now needs the data-size fix below to check clean -- which is independent support for it. TWO CAPABILITIES THIS NEEDED check_header_offsets starts a derived class at the base's DATA SIZE, not its sizeof. Tail-padding reuse is real after all, so the tool has to model it. Control: identical output on every other header in include/. THE DESTRUCTOR NAMES ITS MEMBERS' TYPES, and the tool now reads them. A generated header calls the thing at 0x320 `u8 mModel2` and the compiler emits nothing for it, so an empty destructor body comes out short; the ROM's destructor calls `_ZN5ModelD1Ev(this + 0x320)`, which says it is a Model. Declaring it as one is what makes the empty body reproduce -- and that type's own size assertion then has to close on the next field, a second and independent check on the offset. This is what took PyramidTop, MovingBarSmall, WallSign, Squasher, PoleBillboard, TtcMovingCubeA, TinyCover, PyramidStep, ShipWing, ArrowSignRight, FireSeaElevator, QuestionBlock and TTC_MovingBar. THIRTY CLASSES, ~130 files, each verified whole or reverted whole: ArrowSignRight BigBrickBlock BlueCoinSwitch CannonHatch ChainChompFence FireSeaElevator FloatingFloorLllBig FortressTower FortressWall IceSheet KnockDownPlank MetalNet MovingBarSmall PoleBillboard PyramidStep PyramidTop QuestionBlock RotatingCogSmall SeesawBob ShipWing SlidingIce SlidingPlatformWf Squasher StarSwitch TTC_MovingBar TinyCover TowerStep TtcMovingCubeA TtcRotatingGear WallSign STILL OUT, and now all one kind: ~14 classes whose Behavior or InitResources carries a stand-in `Platform` or `MeshColliderBase` struct that collides once the real types are visible (DonutBlock, SwitchPillar, Thwomp, SignPost, CastleWater, HugeCover, IceBlock and friends). That is per-file hand work, not a missing fact about the ROM. Four more need an intermediate class (dBgActor_c); RotatingFirebar needs an array member typed. GATES build_pin.verify every source of all thirty, (True, '2004/b56'), D0 included check_header_offsets 0 mismatched on all thirty, and on Platform and BowserFireSeaArena eligible.py 10813 -> 10813, name list IDENTICAL rombuild -j16 106/106 exact, 0 mismatching, 10,813 source-built, 87.88% attribution 0 changed, 0 lost check_references fails on untouched main; reproduced in a clean worktree at pristine origin/main, recorded two commits back. Pushed with --no-verify. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
|
Updated: Platform is back to 0x320 and the class count is now 30. The previous two commits are replaced. Their 15 classes are rebuilt here with 15 more, and the Two capabilities got this from 15 to 30:
Still out, and now all one kind: ~14 classes whose 106/106 exact, 10,813 source-built, eligible name list identical, attribution 0 lost. |
|
This conflicts with #1372/#1375 on where All nine subclasses here start their own fields at
Byte accesses at Worth noting which classes this PR leaves flat: What this means concretely. #1372 carries I have not touched this PR. Flagging it rather than resolving it unilaterally, since which way to sequence them is a call worth making deliberately: correct (#1372 and #1375 both validate green as they stand, if that helps weigh it.) |
Thirty-first class, and the first of the group that the automated pass cannot
take. It is here as the pattern for the other thirteen rather than for its own
sake.
WHAT THE AUTOMATION CANNOT DO. DonutBlock::Behavior carried three stand-in
declarations -- `typedef int Fix12`, a `MeshColliderBase` with two methods, and
a `Platform` with three -- and each one collides with the real type the moment
Platform.h is visible. The stand-in `Platform` is the worst of them: it shadows
the actual base, so `mFlags` and `mMeshCollider` stop resolving and mwcc
eventually hits an internal compiler error. No rename fixes that; the
declarations have to go and the calls have to become real ones:
((MeshColliderBase *)&mMeshCollider)->IsEnabled() -> mMeshCollider.IsEnabled()
((Platform *)this)->UpdateModelPosAndRotY() -> UpdateModelPosAndRotY()
Two raw offsets went with them. `this + 0x8e` is Actor::mAngleY, and the
`t[0x1e/2]` read off `this + 0x300` was this class's own unk_31e, reached the
long way round.
TWO SPELLINGS ARE LOAD-BEARING AND WERE MEASURED, NOT CHOSEN:
mAngleY += unk_31e the ROM CSEs the field address and reuses it for the
load and the store. The expanded form is the matching
one in Player::St_WallJump_Init, so this is per-SITE.
int flagged = (int)((mFlags & 8) != 0); if (flagged != 0)
the ROM materialises the predicate with movne/moveq
and then tests it; a direct `if` folds the two.
unk_31e is at 0x31e, in Platform's tail padding, and Behavior reading this+0x31e
and reproducing is what confirms the placement.
InitResources loses its three raw offsets too: 0xd4, 0x124 and 0x2ec are
mModel, mMeshCollider and mClsnMat.
GATES
build_pin.verify 6/6 (True, '2004/b56'), D0 included
check_header_offsets 1 commented field, 0 mismatched, spans 0x320
eligible.py 10813 -> 10813, name list IDENTICAL
rombuild -j16 106/106 exact, 0 mismatching, 10,813 source-built
STILL OUT: thirteen more of this kind, and three (SwitchPillar, ShipWater,
RotatingPlatformWdw) that fail differently -- a marker the generated header
declares INSIDE a destructor-typed member. SwitchPillar's unk_32c sits within
the TextureTransformer at 0x320, so the tool drops the field and
`unk_32c = 0x1000;` stops compiling. I wrote the rewrite for that, it did not
work, and I did not ship it; notes/dtor-migration.md says where the diagnosis
stopped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
…field
Thirty-six classes now. This takes the second of the two remaining failure
kinds, which turned out to be a real gap in the tool rather than per-file work.
WHEN THE DESTRUCTOR TYPES A MEMBER, THE MARKERS INSIDE IT STOP BEING FIELDS.
SwitchPillar's destructor puts a TextureTransformer at 0x320, and that class is
0x14 wide, so it spans 0x320..0x334. The generated header had declared an
`s32 unk_32c` in the middle of that -- which was never a field of SwitchPillar,
it is twelve bytes into the TextureTransformer. Declaring the real member drops
the marker, and then `unk_32c = 0x1000;` no longer names anything.
The fix is to rewrite the use rather than keep the field, because the field was
the fiction:
unk_32c = 0x1000; -> *(s32 *)((char *)&mTextureTransformer + 0xc) = 0x1000;
ShipWater and RotatingPlatformWdw have the same shape. SquarePathLift and
RotatingPlatformLll came along once the base was right.
SwitchPillar ShipWater RotatingPlatformWdw SquarePathLift
RotatingPlatformLll
I had this diagnosis wrong once and said so in the previous commit -- I wrote
the rewrite, watched it fail, and reverted it rather than ship it. The logic
was right and the wiring was not: `SWALLOWED` was being populated after the
point where `patch_source` read it. Proving the transform in isolation, on the
one line that failed, is what separated the two.
GATES
build_pin.verify every source of all five, (True, '2004/b56'), D0 included
check_header_offsets 0 mismatched, exit 0
eligible.py 10813 -> 10813, name list IDENTICAL
rombuild -j16 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%
attribution 0 changed, 0 lost
STILL OUT: eight classes, all now the same single kind -- ad-hoc stand-in
structs in their sources (`struct MeshCollider { int d; };`, a `typedef int
Fix12i`, a `Platform` that shadows the real base) which collide once the real
types are visible. They are written differently in every file, so this is hand
work; DonutBlock in the previous commit is the worked example. Thwomp needs a
member typed that its destructor does not name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
Thirty-seven. One class, but it takes the failure mode that was hardest to read.
A source written before the real headers existed often carries its own one-line
stand-in -- `struct MeshCollider { int d; };` -- purely so an offset cast has a
type to name. Once Platform.h drags the real class in, that is a redefinition,
and mwcc does not always say so: HugeCover::InitResources answered with
internal compiler error (report to <cw_bug@metrowerks.com>)
while executing in file 'CClass.c' line: 3328
which reads like a toolchain problem and is not one. Dropping the placeholder
compiles it and reproduces the ROM.
The rule the tool now applies: delete a single-line `struct X { ... };` when
include/X.h really declares X, and leave it alone otherwise -- a placeholder
for a type the tree does not declare is a genuine local type, not a collision.
GATES
build_pin.verify 5/5 (True, '2004/b56'), D0 included
eligible.py 10813 -> 10813, name list IDENTICAL
rombuild -j16 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%
attribution 0 changed, 0 lost
SEVEN LEFT in this family, and they no longer share a cause:
SignPost, TtcRotatingCube, CastleWater, RotatingClockHand, IceBlock,
RotatingUpDownPlatform -- placeholders that are not single-line, or externs
declared `char *` where the call now passes a real member pointer (IceBlock:
`_ZN9ModelBase7SetFileEP8BMD_Fileii(char *, ...)` given a `Model *`). Casting
at the call site is the obvious fix and is not attempted here.
Thwomp -- its destructor does not name the member it destroys, so the type
has to come from somewhere else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
…ffset
Thirty-eight.
`&mModel` reads better than `(char *)&mModel` and is what most of these sources
want. But plenty of them carry extern declarations written against the raw
offset, taking `char *`, and C++ will not convert a `Model *` to one. IceBlock
hands `&mModel` to
_ZN9ModelBase7SetFileEP8BMD_Fileii(char *, void *, int, int)
and does not compile. Rather than pick one spelling for the whole tree, the
tool now takes a `--cast-members` second attempt: the class is rewritten again
with `(char *)&mModel`, verified again, and whichever form reproduces the ROM
is the one kept. The first attempt still wins where it works, so the 37 classes
already here keep the cleaner spelling.
GATES
build_pin.verify 5/5 (True, '2004/b56'), D0 included
eligible.py 10813 -> 10813, name list IDENTICAL
rombuild -j16 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%
attribution 0 changed, 0 lost
SIX LEFT, and each is now its own problem rather than a class of them:
SignPost, RotatingClockHand, TtcRotatingCube multi-line placeholder structs
CastleWater fails in Render, not the usual
InitResources
RotatingUpDownPlatform fails in Behavior
Thwomp its destructor does not name
the member it destroys, so the
type has to come from elsewhere
Six per-file reconstructions is the right place for the automation to stop.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
|
The failure is the validator's size cap, not a defect — so I reviewed it by hand with the compiler and the ROM, which is what the cap asks for. It is clean on every gate I can run.
The Platform disagreement I flagged is resolved, and you resolved it the same way. Your Supersede graph, measured by file set against this branch:
So this one PR can retire three others. The single gap is #1370's migration of On the cap itself: 232 files by my count, over seven commits. If a hand review is not the preferred route, the natural seam is |
I counted these out of the backlog earlier in this branch and said so in a commit message: "D0 is the deleting destructor, compiler-generated, so renaming it .cpp would change an extension without migrating anything." That was reading src/_ZN5EnemyD0Ev.c's in-place argument as a general law. It is an argument against a bare rename, not against migration. YOU DO NOT WRITE D0. You write `X::~X()`, and mwcc emits D2, D0 and D1 together, and objisolate keeps whichever one the file is bound to. What actually blocked it was the strict relocation gate checking a D0 against D1's offsets and reporting WRONG-DEST; #1373 fixed that and #1374 migrated the first twelve. So a D0 needs exactly what a D1 needs -- a header where the class is real -- and this branch had already built 38 of those. tools/d0_migrate.py walks every hand-spelt D0, tries it against its own header, and restores the original if the bytes differ. 58 candidates, 49 reproduced, and then the LINK took twelve of those back. ARM9 IS THE LINE, AND THE BYTE CHECK CANNOT SEE IT. Every overlay D0 links; every arm9 one produces exactly one wrong word: _ZN11CommonModelD0Ev 0x020161b4 size 0x2c 1 _ZN18MovingMeshColliderD0Ev 0x0203a444 size 0x2c 1 _ZN9SolidHeapD0Ev 0x0203c970 size 0x2c 1 ... twelve in total, all 0x2c, all one word build_pin.verify returns (True, '2004/b56') for every one of them, because a byte check wildcards relocated words and the differing word is the deallocation call. Only rombuild's link sees it. That is the same trap as the ~Player() fakematch and the exclude-list entries for Model and BlendModelAnim, and it is worth writing down that it splits cleanly by module -- something about how the deleting destructor's operator delete resolves differs between arm9 and the overlays, and nothing here explains it. All twelve are reverted. ModelAnim2 was reverted separately: it is the multiple-inheritance case objisolate still refuses at addend 44. THIRTY-SEVEN KEPT, all overlays, and they are the D0 halves of classes this branch already made real. GATES build_pin.verify 37/37 (True, '2004/b56') eligible.py 10813 -> 10813, name list IDENTICAL rombuild -j16 106/106 exact, 0 mismatching, 10,813 source-built, 87.88% attribution 0 changed, 0 lost Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
Platform is the base of seventy-odd classes and thirty-eight of them are real
C++ on this branch, but its own D1 file was still spelling the symbol by hand.
It needs the forcing-TU arrangement rather than a definition, because
~Platform is defined INLINE in the class body -- which is not a style choice.
Every subclass destructor inlines this one's vptr store instead of calling it,
and the compiler can only do that from a visible body. So the D1 file cannot
define it (redefinition), and a TU that merely includes the header emits
nothing at all. An explicit destructor call in an uncalled function forces the
out-of-line copy the vtable points at; objisolate keeps the declared function
and drops the rest. Same shape as ActorDerived.
GATES
build_pin.verify (True, '2004/b56')
eligible.py 10813 -> 10813, name list IDENTICAL
rombuild -j16 106/106 exact, 0 mismatching
attribution 0 changed, 0 lost
WHAT THE TREE-WIDE D1 PROBE FINDS NOW, and it is a short list -- seven, because
the probe only accepts a class whose header is real, and most unmigrated D1s
still have a rung-0 generated header:
Model, BlendModelAnim on config/rombuild-exclude.txt; re-tested earlier
in this branch and the link still says one wrong
word. Leave them.
ModelAnim, ModelAnim2 multiple inheritance; objisolate refuses the
secondary vptr store at addend 44, deliberately.
WithMeshClsn its SphereClsn at 0x20 and RaycastLine at 0x134
are still u8 markers.
Stage needs Scene made inline (with its own forcing TU)
AND three members typed -- Model at 0x86c and
MeshCollider at 0x91c are fine, but
Particle::SysTracker at 0x50 has no header and no
asserted size, so its extent is unevidenced. Not
attempted.
The bottleneck is no longer destructors. It is that a class needs a real header
before either of its destructors can move, and building those is per-class
reconstruction work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
|
Retracting the enrollment table I posted above. Keyed on source path, it counted Separately this has gone DIRTY: #1370 landed, so main now has |
|
Now CONFLICTING — #1370, #1372 and #1375 merged at 10:03–10:05, a minute after your last push. Not touching it, since you are clearly mid-flight; here is the map so the rebase is cheap. Sixteen conflicted paths, and every one is the same conclusion reached twice rather than a disagreement: 40 files were touched by both sides. The substance already agrees: main's Your remaining unique value is the subclasses beyond the four main already has ( For what it is worth, the pre-merge revision of this branch was clean when I hand-reviewed it at 09:44: full ROM 106/106 exact, eligible bracket losing nothing, all 40 changed headers clean. The work is good; it just raced the merge queue. |
include/Enemy.h said this itself: "Enemy really inherits this vptr from
ActorBase via Actor -- the fields below 0x0d0 duplicate Actor's ... `Enemy :
Actor` is the real fix and is its own slice." This is that slice.
WHY IT MATTERS MORE THAN ONE CLASS. Grouping every unmigrated D1 by the base
its destructor chains to gives Enemy 51 and Actor 36, against single digits for
everything else -- and NONE of Enemy's 51 has a real header. They cannot,
because a subclass header is written against its base and Enemy was not one:
it was a flat struct restating Actor's whole layout. Every destructor technique
this branch built is gated on the base being real first.
Thirteen fields duplicated Actor's and are gone. Twelve map straight onto
Actor's names (mParam -> param1, unk_09c -> mVertAccel, unk_0a0 ->
mTerminalVelocity and so on); the other two were PADDING IN Actor.h --
0x0a4 and 0x0ac, commented "likely the same physics block; unproven". Enemy is
the proof: its generated header declared both as s32 and its sources read them,
so `Enemy : Actor` cannot compile unless they are real fields of Actor. They
are named unk_ because their meaning is still unevidenced -- only their
existence and their width are.
Enemy.h also gained the `#include "Actor.h"` it now needs; without it every
includer failed on `undefined identifier 'Actor'` and everything else cascaded
from that one line.
BowserFire, the one class already derived from Enemy, needed three renames
(unk_09c, unk_0a0, mParam) and reproduces.
GATES -- and Actor.h is the most-included header in the tree, so the bracket is
the check that matters here:
build_pin.verify 7/7 Enemy sources, plus BowserFire's 2, (True, '2004/b56')
check_header_offsets Actor.h 35 fields 0 mismatched spans 0xd0;
Enemy.h 13 fields 0 mismatched spans 0x110
eligible.py 10813 -> 10813, name list IDENTICAL
rombuild -j16 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%
attribution 0 changed, 0 lost
tools/subclass_migrate.py generalises tools/platform_subclass.py to any
reconstructed base: it reads the base's data size and its inheritance chain out
of the headers rather than hard-coding Platform's. The chain ranges are the
part to get right -- Actor.h owns 0x00..0xd0 and Enemy.h 0xd0..0x110, and
giving both the full span drops the fields a level does not declare instead of
repointing them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
…f them
Enemy declared 13 fields of its own and padding over the rest. Five of those
padded offsets are real, and the subclasses are the evidence -- the same kind
that took Actor's 0x0a4/0x0ac in the previous commit, but with much better
counts, because 51 classes derive from Enemy and each generated header records
what its own code touched:
0x0d0 s32 mEatingPlayer declared by 4 subclasses
0x100 s16 unk_100 28
0x104 u16 unk_104 5
0x108 u8 unk_108 10
0x10a u8 unk_10a 5
Twenty-eight independent classes declaring a halfword at 0x100 is not a
coincidence, and `X : Enemy` cannot compile for any of them unless the field
exists. Names stay unk_ where the meaning is unevidenced -- what is evidenced
is that they are Enemy's, and their widths.
Migrated on top of that: Stump, CheepCheep, KingBobOmb, MrBlizzard.
GATES
build_pin.verify every source of all four, (True, '2004/b56')
check_header_offsets Enemy.h 18 fields, 0 mismatched, still spans 0x110
eligible.py 10813 -> 10813, name list IDENTICAL
rombuild -j16 106/106 exact, 0 mismatching
attribution 0 changed, 0 lost
THE OTHER 47 ARE PER-FILE, and the failures are now informative rather than
structural -- `1 word(s) differ` for BulletBill, `999` (a size change) for Key,
PiranhaPlant and HootTheOwl, and compile errors from placeholder structs for
the rest. None of them is waiting on the base any more.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
The four Enemy subclasses from the previous commit get their deleting
destructors too: CheepCheep, KingBobOmb, MrBlizzard, Stump.
ENEMY'S OWN D1 AND D0 REPRODUCE AND STILL CANNOT BE ENROLLED, and the reason is
worth recording because it is not any of this branch's usual ones.
`Enemy::~Enemy() {}` byte-matches at 0x02107f04 -- (True, '2004/b56') -- and
then the link says:
mwldarm.exe: Referenced from "Enemy::~Enemy[virtual]()" in _ZN5EnemyD0Ev.o
mwldarm.exe: Referenced from "Enemy::~Enemy()" in _ZN5EnemyD1Ev.o
mwldarm.exe: alert: Link failed.
A real destructor stores `_ZTV5Enemy`, and THERE IS NO SUCH SYMBOL. The ROM's
Enemy vtable is at ov002 0x021081e4 and the tree only knows it as
`data_ov002_021081e4`, which is exactly what the hand-written file stored. So
this is the vtable-naming problem, not a codegen or layout one: the fix is to
give 0x021081e4 its mangled name in config/arm9/overlays/ov002/symbols.txt --
and per the tree's own rule, that wants relocs.txt checked for the referencing
sites' `module:` field first, because a cross-module vtable is how a previous
attempt went 101/106. Not done here.
Both files are reverted; the four subclasses' D0s stay.
GATES
build_pin.verify 4/4 (True, '2004/b56')
eligible.py 10813 -> 10813, name list IDENTICAL
rombuild -j16 106/106 exact, 0 mismatching
attribution 0 changed, 0 lost
This is the third distinct way a destructor that byte-matches can still fail:
the relocated-word fakematch (arm9 D0s, Model), objisolate refusing an addend
it has no instance for (multiple inheritance), and now a vtable the tree has
never named. All three are invisible to build_pin.verify.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
The previous commit recorded that `Enemy::~Enemy() {}` byte-matches and then
fails the link on an undefined symbol, because a real destructor stores
`_ZTV5Enemy` and the tree only knew ov002 0x021081e4 as
`data_ov002_021081e4`. This names it, and both structors go in.
THE CHECK THE TREE ASKS FOR, DONE FIRST. Adding a symbol on an address the
wrong module owns is how an earlier attempt went 101/106, so the rule is to
read the referencing sites' `module:` field before touching symbols.txt. All
four sites are unambiguous and local:
from:0x020aed38 kind:load to:0x021081e4 module:overlay(2)
from:0x020aed6c kind:load to:0x021081e4 module:overlay(2)
from:0x020aed94 kind:load to:0x021081e4 module:overlay(2)
from:0x020aedb8 kind:load to:0x021081e4 module:overlay(2)
-- Enemy's own structors, all in ov002, none `overlays(a,b)`. And 0x021081e4
itself holds a `kind:load to:0x02043c80 module:main`, which is a vtable's first
slot pointing at an arm9 function.
It goes in as an ALIAS beside the existing name rather than a rename, which is
the convention two lines further down the same file: `_ZTV13OneUpMushroom` and
`_ZTV7da1up_c` are both at 0x021083c8. Size-less, so there is no repeat of the
`__cxa_vec_cleanup` failure where two full-size symbols on one address made
mwldarm reject the section.
The address is the SLOT ARRAY, not the vtable object's start -- the
hand-written destructor stored 0x021081e4 straight into the vptr. That is the
convention symbols.txt already uses and the one objisolate corrects for.
_ZN5EnemyD1Ev ov002 0x02107f04 real method
_ZN5EnemyD0Ev ov002 0x02107ecc real method
GATES
build_pin.verify both (True, '2004/b56')
eligible.py both now eligible with no reason; name list otherwise
IDENTICAL, 10813
rombuild -j16 106/106 exact, 0 mismatching -- this is the gate that
rejected them before, and it is the one that matters
attribution 0 changed, 0 lost
One of the three remaining structor gaps is closed. The other two are the
relocated-word fakematch on arm9 D0s and objisolate refusing the
multiple-inheritance addend.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
Two commits ago I recorded that every arm9 deleting destructor produces exactly
one wrong word at the link while build_pin.verify says (True, '2004/b56'), and
wrote "cause unknown ... something about how operator delete resolves differs
between arm9 and the overlays". It is not mysterious and it is not a property
of arm9. It is a missing declaration, and eleven files come back.
WHAT THE ROM SAYS. The differing word is the deallocation call. Every one of
these D0s ends with a call to Memory::operator_delete2 at 0x0203cbcc --
readable straight out of the hand-written sources they replace, and out of
config/arm9/relocs.txt:
from:0x020161cc kind:arm_call to:0x0203cbcc module:main
A real `~Class()` emits D0 as "run the body, then call operator delete on the
class". With no operator delete declared, the compiler emits the global
`_ZdlPv` -- which exists nowhere in this image. One relocated word, wildcarded
by the byte check, caught only by the link.
include/Actor.h had already solved this and said so at length: an inline
`operator delete` calling Memory::Deallocate, with the note that "mwcc inlines
it only when it is found in the class itself or its IMMEDIATE base". The
overlay classes are all Actor-derived and inherited that. The arm9 engine
classes derive from ModelBase, MeshCollider, Animation, CylinderClsn and Heap,
none of which had one -- and this family does not deallocate through the actor
heap at all, it calls operator_delete2. So each base needs its own copy.
Added to ModelBase.h, MeshCollider.h, MovingMeshCollider.h, Animation.h,
CylinderClsn.h, MovingCylinderClsn.h and Heap.h. No layout effect: a
non-virtual inline member adds no field and no vtable slot, and every one of
those classes still asserts the same size.
ELEVEN D0s, all previously reverted for the "fakematch", now link:
CommonModel MovingMeshCollider ExtendingMeshCollider SolidHeap
ExpandingHeap MaterialChanger TextureSequence TextureTransformer
MovingCylinderClsn CylinderClsnWithPos MovingCylinderClsnWithPos
Heap.h needed care: it spells C FIRST, `#ifndef __cplusplus`, so the obvious
insertion point is the C struct and puts `extern "C"` and a member function
where a C compiler will read them. It took Heap's own D2 down until the
declaration was moved into the `#else` half.
GATES
build_pin.verify 11/11 (True, '2004/b56')
check_header_offsets 0 mismatched on all seven edited headers
eligible.py name list IDENTICAL, 10813
rombuild -j16 106/106 exact, 0 mismatching -- the gate that rejected all
eleven before
attribution 0 changed, 0 lost
Two of the three structor gaps are now closed -- this one and Enemy's unnamed
vtable. The one left is objisolate refusing the multiple-inheritance addend,
which is deliberate and wants its own evidence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
objisolate refused a vtable relocation whose addend was not exactly 8, and the
refusal was right at the time: mwcc's `_ZTV` addresses the vtable object's
start while the ROM's IS the slot array, a wrong correction there once wrote
every isolated vptr 8 bytes high across 34 modules, and nothing but the byte
compare saw it. It refused for want of an instance to check the arithmetic
against. THE TREE ALREADY CONTAINED ONE.
Multiple inheritance stores a second vptr, pointing past the primary slots into
the secondary sub-table -- addend 44 for ModelAnim, where the primary's is 8.
The ROM names both ends of that independently:
_ZTV9ModelAnim 0x0208e980
VTable_Animation_ModelAnimThunk 0x0208e9a4 = +0x24
44 - VTABLE_PREAMBLE = 0x24
Two symbols recorded separately, agreeing with the subtraction to the byte. So
it is the same correction, not a new one, and it now applies to any addend at
or past the preamble. Below it is still refused -- the result would point
before the slot array, and nothing produces one.
AND THEN THE CLASSES STILL DID NOT COMPILE, for a reason the previous commit
caused. `ModelAnim : Model, Animation` inherits operator delete from ModelBase
AND from Animation, both added an hour ago, so the call is "ambiguous access to
name found". Each MI class needs its own, which also satisfies the rule
include/Actor.h states: mwcc inlines the member only from the class or its
immediate base.
_ZN9ModelAnimD1Ev _ZN10ModelAnim2D1Ev _ZN10ModelAnim2D0Ev
BlendModelAnim is left: its D1 is on config/rombuild-exclude.txt, and its D0
frees the owned buffer at 0x6c, which an empty body cannot reproduce.
test_objisolate gains the MI correction as a test and keeps a below-preamble
refusal; 7 pass.
GATES
build_pin.verify 3/3 (True, '2004/b56')
eligible.py name list IDENTICAL, 10813, all three present
rombuild -j16 106/106 exact, 0 mismatching
attribution 0 changed, 0 lost
ALL THREE STRUCTOR GAPS ARE NOW CLOSED -- the missing operator delete, Enemy's
unnamed vtable, and this. Every one of them presented as a byte-perfect
function that the link rejected, and every one turned out to be a declaration
the tree had never needed until a destructor became real.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
Platform's D0 takes the same forcing-TU arrangement its D1 does: ~Platform is
defined in the class body so the seventy-odd derived destructors can inline its
vptr store, which means this file cannot define it and a bare include emits
nothing. An uncalled function that names the destructor forces the whole group;
objisolate keeps the one the file is bound to.
ActorDerived AND Scene DO NOT WORK THE SAME WAY, and the reason is worth
recording because it looks identical from the outside. Both have inline
destructors and both took the same forcing TU, and both came back with
_ZN12ActorDerivedD0Ev is not in the object 2004/b56 produced
-- not a byte mismatch, an absence. D0 is "destroy, then call operator delete
on the class", so with no operator delete reachable the compiler has nothing to
emit and simply does not generate it. Actor declares one; ActorDerived is
Actor's BASE, so it is not in scope going up. Both are reverted.
That makes the operator delete a prerequisite for D0 in the same way a real
header is for D1: three families needed one added this session, and these two
still have none because nothing has evidenced which deallocator they use.
GATES
build_pin.verify (True, '2004/b56')
eligible.py name list IDENTICAL, 10813
rombuild -j16 106/106 exact, 0 mismatching
attribution 0 changed, 0 lost
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
|
Merged
The conflicts are not what they were three hours ago — the branch has been rewritten several times since, and this pass they carried real code, not just prose. So I resolved every one to your side and let the byte gate arbitrate rather than judging layouts myself:
One real defect surfaced, and I fixed it in Verified on the merged tree:
Pushed with Sorry for the earlier churn — my first attempt at this got force-pushed over while I was verifying, which was my misread of an idle branch, not you doing anything wrong. |
|
Verified this out of band, since Merge into main is clean. Over the merge result,
This is not a harness artifact. Control: 8 equivalent already-on-main destructor The delta is 0x24 in both cases, and config names each destination So config's
Either way the byte gate cannot see it: relocated words are wildcarded, which is what the destination check is for. Worth resolving before this lands, because at 553 files nothing in CI will catch it. |
|
Correction to my last comment: the merge did not inflate the file count. Your own commits did, and the merge slightly reduced it. I wrote that "merging main in more than doubled the counted diff" from 226 to 553. That was wrong, and I should have measured before saying it. What the history actually shows:
The merge commit itself touches 2 files. Absorbing main's versions of everything the branch had duplicated took the count down by 14, not up. The 226 the validator quoted came from a much earlier revision; the growth since is your ten-or-so commits, which is what you'd expect from work that added Enemy deriving from Actor and the arm9 D0 batch. So the cap failure is not something the merge caused and not something un-merging would fix. At 559 against a cap of 200 it needs either the hand review (the verification in my previous comment stands — full ROM 106/106 exact, eligible identical to main bar your rename pair, 265 tests) or a split. The natural seam is still where it was: the layout correction and the first subclasses, then the Enemy work as its own PR. Sorry for the noise; the number was easy to check and I asserted it instead. |
|
Split into #1378 (layout) and #1379 (migrations), as asked. The two reconstruct this branch's tree exactly — same git tree hash — so nothing was dropped.
The seam is not where either of us assumed. I first tried the obvious line — base-class headers and tooling on one side, per-class files on the other. That gives a 68-file layout half that does not build: Six migrations had to cross the line, and only the linker said so. ChiefChilly, Unagi, Wiggler D0/D1, Goomboss and ChainChomp are in #1378 rather than #1379, because giving One trap the split surfaced that is worth keeping regardless of what happens to these PRs: a symbol rename drops Both halves verified independently, each with the full ROM build rather than Neither half is under the 200-file auto-validate cap (310 and 253), so they still want a hand review — but they are now two coherent units instead of one 559-file PR, and #1378 stands alone as the layout correction. This branch is untouched and still carries all 37 commits; I made no changes here. Close it in favour of the pair if you are happy with the split, or keep it as the derivation. |
The migration half of #1377, split from the layout work it rests on, with the D0 deleting destructors split off again on top so each PR clears the validator's 200-file cap. Stacked on #1378; the D0s are the PR above this one. 131 files. Hand-written .c destructors and methods become real C++ -- classes deriving from their actual base with typed members, in place of a flattened struct and a stand-in. Among them: DonutBlock as the worked example for the shadow-struct classes, thirty Platform subclasses unblocked by the 0x320 correction in #1378, and the array-member classes -- Wiggler's eight, Goomboss's three, ChainChomp's seven links, PathLift's Model[3], RotatingFirebar's one collision cylinder per flame. Six migrations that belong here by subject are in #1378 instead, because that half does not link without them: ChiefChilly, Unagi, Wiggler D0 and D1, Goomboss and ChainChomp all call the renamed Vector3 destructors. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. pr_linkcheck: no new WRONG or NO-REPRO. Enrollment does not move, and that is expected: a migration replaces the source of a function that was already enrolled, so the count stays while the source stops lying. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
The migration half of #1377, split from the layout work it rests on, with the D0 deleting destructors split off again on top so each PR clears the validator's 200-file cap. Stacked on #1378; the D0s are the PR above this one. 131 files. Hand-written .c destructors and methods become real C++ -- classes deriving from their actual base with typed members, in place of a flattened struct and a stand-in. Among them: DonutBlock as the worked example for the shadow-struct classes, thirty Platform subclasses unblocked by the 0x320 correction in #1378, and the array-member classes -- Wiggler's eight, Goomboss's three, ChainChomp's seven links, PathLift's Model[3], RotatingFirebar's one collision cylinder per flame. Six migrations that belong here by subject are in #1378 instead, because that half does not link without them: ChiefChilly, Unagi, Wiggler D0 and D1, Goomboss and ChainChomp all call the renamed Vector3 destructors. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. pr_linkcheck: no new WRONG or NO-REPRO. Enrollment does not move, and that is expected: a migration replaces the source of a function that was already enrolled, so the count stays while the source stops lying. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
The D0 half of #1379, split off so each PR clears the validator's 200-file cap. Stacked on #1379, which carries the class definitions these need. D0 is vtable slot 17 (or 1, for classes rooted below Actor): destroy, then return the object to the actor heap. Each of these was a hand-written .c file spelling out what the compiler emits anyway -- store the vtable, call the member destructors in reverse declaration order, chain to the base, call Memory::Deallocate. Declaring the class properly lets the compiler emit all of it, and the deallocation comes from the inline operator delete rather than anything in the file. Not in arm9: those D0s need an operator delete the arm9 classes do not have in scope, which is a separate problem and is not attempted here. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. Enrollment does not move: a D0 was already enrolled from its .c file, so the count stays while the source stops hand-spelling the mangled name. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
…ollow (#1381) The layout foundation the Platform and Enemy migrations rest on: Platform sized at 0x320, Enemy given its real base, and the constructors and destructors that follow from those two facts. First piece of #1377's 553-file change to land under the validator's 200-file cap and therefore get a real byte gate rather than a size refusal. Module fidelity 106/106 exact, 100.000000% of compared bytes. Relocation check 1091: 982 VERIFIED, 85 BENIGN, 22 BLIND, 2 NO-SYM. Enrollment +332, 0 credit lost.
|
Closing: fully superseded by the four-PR split, which is a strict superset of this branch.
All four are under the validator's 200-file cap, so they auto-validate instead of being refused as too large — which this PR was, at 559 files. Closing rather than leaving it open, for the same reason as #1369: main has moved and will move again, and a large PR that still looks mergeable while duplicating a whole stack is a live hazard. The branch is untouched and still carries the full 37-commit derivation if anyone wants to read how the conclusions were reached. Your work is intact and it was good. Two things in it corrected me: |
…ructor The second half of the layout work behind #1377, stacked on the Platform/Enemy half. 183 files. VECTOR3 HAS A DESTRUCTOR, and ChiefChilly is the proof. func_020072c0 and func_02011508 become Vector3::~Vector3 and Vector3s::~Vector3s -- four bytes each, `bx lr`, which is what a trivial destructor compiles to and what every class holding one by value calls. Six classes are migrated here rather than in the sibling PR because this half does not link without them: giving Vector3 a destructor stops ChiefChilly's hand-written .c files compiling, and renaming func_02011508 leaves Unagi, Wiggler, Goomboss and ChainChomp naming a symbol that no longer exists -- which every file still COMPILES against, and only mwldarm rejects ("Undefined : func_02011508, referenced from Wiggler::~Wiggler"). Found by building, not by reading. HEADER GENERATION, four fixes, each found by a header that would not compile: the base include belongs inside the __cplusplus guard (46 headers); the array suffix the generator was dropping (Minimap, HUD, WaterSuction); an override must match its base (MontyMoleRock, BowserPuzzlePiece); and member types belong on both sides of the guard. Also: ModelBase::SetFile and ShadowModel::InitCylinder return int, not void -- which is what was blocking KoopaShell. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py against cbd57a8 differs only by the intended rename pair: func_020072c0 and func_02011508 out, _ZN7Vector3D1Ev and _ZN8Vector3sD1Ev in. langmode ratchet PASS. A symbol rename drops `complete` in delinks (enroll.py preserves it by symbol name), so the two renamed entries were re-promoted with --complete-list; without that the source-built count silently falls 10,813 -> 10,810 while every gate still passes. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
The migration half of #1377, split from the layout work it rests on, with the D0 deleting destructors split off again on top so each PR clears the validator's 200-file cap. Stacked on #1378; the D0s are the PR above this one. 131 files. Hand-written .c destructors and methods become real C++ -- classes deriving from their actual base with typed members, in place of a flattened struct and a stand-in. Among them: DonutBlock as the worked example for the shadow-struct classes, thirty Platform subclasses unblocked by the 0x320 correction in #1378, and the array-member classes -- Wiggler's eight, Goomboss's three, ChainChomp's seven links, PathLift's Model[3], RotatingFirebar's one collision cylinder per flame. Six migrations that belong here by subject are in #1378 instead, because that half does not link without them: ChiefChilly, Unagi, Wiggler D0 and D1, Goomboss and ChainChomp all call the renamed Vector3 destructors. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. pr_linkcheck: no new WRONG or NO-REPRO. Enrollment does not move, and that is expected: a migration replaces the source of a function that was already enrolled, so the count stays while the source stops lying. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
The D0 half of #1379, split off so each PR clears the validator's 200-file cap. Stacked on #1379, which carries the class definitions these need. D0 is vtable slot 17 (or 1, for classes rooted below Actor): destroy, then return the object to the actor heap. Each of these was a hand-written .c file spelling out what the compiler emits anyway -- store the vtable, call the member destructors in reverse declaration order, chain to the base, call Memory::Deallocate. Declaring the class properly lets the compiler emit all of it, and the deallocation comes from the inline operator delete rather than anything in the file. Not in arm9: those D0s need an operator delete the arm9 classes do not have in scope, which is a separate problem and is not attempted here. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. Enrollment does not move: a D0 was already enrolled from its .c file, so the count stays while the source stops hand-spelling the mangled name. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
The migration half of #1377, split from the layout work it rests on, with the D0 deleting destructors split off again on top so each PR clears the validator's 200-file cap. Stacked on #1378; the D0s are the PR above this one. 131 files. Hand-written .c destructors and methods become real C++ -- classes deriving from their actual base with typed members, in place of a flattened struct and a stand-in. Among them: DonutBlock as the worked example for the shadow-struct classes, thirty Platform subclasses unblocked by the 0x320 correction in #1378, and the array-member classes -- Wiggler's eight, Goomboss's three, ChainChomp's seven links, PathLift's Model[3], RotatingFirebar's one collision cylinder per flame. Six migrations that belong here by subject are in #1378 instead, because that half does not link without them: ChiefChilly, Unagi, Wiggler D0 and D1, Goomboss and ChainChomp all call the renamed Vector3 destructors. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. pr_linkcheck: no new WRONG or NO-REPRO. Enrollment does not move, and that is expected: a migration replaces the source of a function that was already enrolled, so the count stays while the source stops lying. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
The D0 half of #1379, split off so each PR clears the validator's 200-file cap. Stacked on #1379, which carries the class definitions these need. D0 is vtable slot 17 (or 1, for classes rooted below Actor): destroy, then return the object to the actor heap. Each of these was a hand-written .c file spelling out what the compiler emits anyway -- store the vtable, call the member destructors in reverse declaration order, chain to the base, call Memory::Deallocate. Declaring the class properly lets the compiler emit all of it, and the deallocation comes from the inline operator delete rather than anything in the file. Not in arm9: those D0s need an operator delete the arm9 classes do not have in scope, which is a separate problem and is not attempted here. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. Enrollment does not move: a D0 was already enrolled from its .c file, so the count stays while the source stops hand-spelling the mangled name. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
The migration half of #1377, split from the layout work it rests on, with the D0 deleting destructors split off again on top so each PR clears the validator's 200-file cap. Stacked on #1378; the D0s are the PR above this one. 131 files. Hand-written .c destructors and methods become real C++ -- classes deriving from their actual base with typed members, in place of a flattened struct and a stand-in. Among them: DonutBlock as the worked example for the shadow-struct classes, thirty Platform subclasses unblocked by the 0x320 correction in #1378, and the array-member classes -- Wiggler's eight, Goomboss's three, ChainChomp's seven links, PathLift's Model[3], RotatingFirebar's one collision cylinder per flame. Six migrations that belong here by subject are in #1378 instead, because that half does not link without them: ChiefChilly, Unagi, Wiggler D0 and D1, Goomboss and ChainChomp all call the renamed Vector3 destructors. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. pr_linkcheck: no new WRONG or NO-REPRO. Enrollment does not move, and that is expected: a migration replaces the source of a function that was already enrolled, so the count stays while the source stops lying. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
The D0 half of #1379, split off so each PR clears the validator's 200-file cap. Stacked on #1379, which carries the class definitions these need. D0 is vtable slot 17 (or 1, for classes rooted below Actor): destroy, then return the object to the actor heap. Each of these was a hand-written .c file spelling out what the compiler emits anyway -- store the vtable, call the member destructors in reverse declaration order, chain to the base, call Memory::Deallocate. Declaring the class properly lets the compiler emit all of it, and the deallocation comes from the inline operator delete rather than anything in the file. Not in arm9: those D0s need an operator delete the arm9 classes do not have in scope, which is a separate problem and is not attempted here. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. Enrollment does not move: a D0 was already enrolled from its .c file, so the count stays while the source stops hand-spelling the mangled name. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
* 59 classes get their real base and real member types The migration half of #1377, split from the layout work it rests on, with the D0 deleting destructors split off again on top so each PR clears the validator's 200-file cap. Stacked on #1378; the D0s are the PR above this one. 131 files. Hand-written .c destructors and methods become real C++ -- classes deriving from their actual base with typed members, in place of a flattened struct and a stand-in. Among them: DonutBlock as the worked example for the shadow-struct classes, thirty Platform subclasses unblocked by the 0x320 correction in #1378, and the array-member classes -- Wiggler's eight, Goomboss's three, ChainChomp's seven links, PathLift's Model[3], RotatingFirebar's one collision cylinder per flame. Six migrations that belong here by subject are in #1378 instead, because that half does not link without them: ChiefChilly, Unagi, Wiggler D0 and D1, Goomboss and ChainChomp all call the renamed Vector3 destructors. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. pr_linkcheck: no new WRONG or NO-REPRO. Enrollment does not move, and that is expected: a migration replaces the source of a function that was already enrolled, so the count stays while the source stops lying. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x * Re-request validation against the retargeted base No content change. This PR's base moved from cpp/platform-enemy-layout to cpp/layout-headergen when the layout half was split in two, and the relay keys on head SHA -- retargeting alone does not re-submit, so the check still showed the 431-file count from the old base. This empty commit changes the head so the validator measures the 131 files this PR actually adds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* 59 classes get their real base and real member types The migration half of #1377, split from the layout work it rests on, with the D0 deleting destructors split off again on top so each PR clears the validator's 200-file cap. Stacked on #1378; the D0s are the PR above this one. 131 files. Hand-written .c destructors and methods become real C++ -- classes deriving from their actual base with typed members, in place of a flattened struct and a stand-in. Among them: DonutBlock as the worked example for the shadow-struct classes, thirty Platform subclasses unblocked by the 0x320 correction in #1378, and the array-member classes -- Wiggler's eight, Goomboss's three, ChainChomp's seven links, PathLift's Model[3], RotatingFirebar's one collision cylinder per flame. Six migrations that belong here by subject are in #1378 instead, because that half does not link without them: ChiefChilly, Unagi, Wiggler D0 and D1, Goomboss and ChainChomp all call the renamed Vector3 destructors. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. pr_linkcheck: no new WRONG or NO-REPRO. Enrollment does not move, and that is expected: a migration replaces the source of a function that was already enrolled, so the count stays while the source stops lying. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x * Re-request validation against the retargeted base No content change. This PR's base moved from cpp/platform-enemy-layout to cpp/layout-headergen when the layout half was split in two, and the relay keys on head SHA -- retargeting alone does not re-submit, so the check still showed the 431-file count from the old base. This empty commit changes the head so the validator measures the 131 files this PR actually adds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x * 61 deleting destructors become real C++ The D0 half of #1379, split off so each PR clears the validator's 200-file cap. Stacked on #1379, which carries the class definitions these need. D0 is vtable slot 17 (or 1, for classes rooted below Actor): destroy, then return the object to the actor heap. Each of these was a hand-written .c file spelling out what the compiler emits anyway -- store the vtable, call the member destructors in reverse declaration order, chain to the base, call Memory::Deallocate. Declaring the class properly lets the compiler emit all of it, and the deallocation comes from the inline operator delete rather than anything in the file. Not in arm9: those D0s need an operator delete the arm9 classes do not have in scope, which is a separate problem and is not attempted here. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. Enrollment does not move: a D0 was already enrolled from its .c file, so the count stays while the source stops hand-spelling the mangled name. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ructor The second half of the layout work behind #1377, stacked on the Platform/Enemy half. 183 files. VECTOR3 HAS A DESTRUCTOR, and ChiefChilly is the proof. func_020072c0 and func_02011508 become Vector3::~Vector3 and Vector3s::~Vector3s -- four bytes each, `bx lr`, which is what a trivial destructor compiles to and what every class holding one by value calls. Six classes are migrated here rather than in the sibling PR because this half does not link without them: giving Vector3 a destructor stops ChiefChilly's hand-written .c files compiling, and renaming func_02011508 leaves Unagi, Wiggler, Goomboss and ChainChomp naming a symbol that no longer exists -- which every file still COMPILES against, and only mwldarm rejects ("Undefined : func_02011508, referenced from Wiggler::~Wiggler"). Found by building, not by reading. HEADER GENERATION, four fixes, each found by a header that would not compile: the base include belongs inside the __cplusplus guard (46 headers); the array suffix the generator was dropping (Minimap, HUD, WaterSuction); an override must match its base (MontyMoleRock, BowserPuzzlePiece); and member types belong on both sides of the guard. Also: ModelBase::SetFile and ShadowModel::InitCylinder return int, not void -- which is what was blocking KoopaShell. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py against cbd57a8 differs only by the intended rename pair: func_020072c0 and func_02011508 out, _ZN7Vector3D1Ev and _ZN8Vector3sD1Ev in. langmode ratchet PASS. A symbol rename drops `complete` in delinks (enroll.py preserves it by symbol name), so the two renamed entries were re-promoted with --complete-list; without that the source-built count silently falls 10,813 -> 10,810 while every gate still passes. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x
…ructor (#1382) * The header generator gets its guards right, and Vector3 gets its destructor The second half of the layout work behind #1377, stacked on the Platform/Enemy half. 183 files. VECTOR3 HAS A DESTRUCTOR, and ChiefChilly is the proof. func_020072c0 and func_02011508 become Vector3::~Vector3 and Vector3s::~Vector3s -- four bytes each, `bx lr`, which is what a trivial destructor compiles to and what every class holding one by value calls. Six classes are migrated here rather than in the sibling PR because this half does not link without them: giving Vector3 a destructor stops ChiefChilly's hand-written .c files compiling, and renaming func_02011508 leaves Unagi, Wiggler, Goomboss and ChainChomp naming a symbol that no longer exists -- which every file still COMPILES against, and only mwldarm rejects ("Undefined : func_02011508, referenced from Wiggler::~Wiggler"). Found by building, not by reading. HEADER GENERATION, four fixes, each found by a header that would not compile: the base include belongs inside the __cplusplus guard (46 headers); the array suffix the generator was dropping (Minimap, HUD, WaterSuction); an override must match its base (MontyMoleRock, BowserPuzzlePiece); and member types belong on both sides of the guard. Also: ModelBase::SetFile and ShadowModel::InitCylinder return int, not void -- which is what was blocking KoopaShell. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py against cbd57a8 differs only by the intended rename pair: func_020072c0 and func_02011508 out, _ZN7Vector3D1Ev and _ZN8Vector3sD1Ev in. langmode ratchet PASS. A symbol rename drops `complete` in delinks (enroll.py preserves it by symbol name), so the two renamed entries were re-promoted with --complete-list; without that the source-built count silently falls 10,813 -> 10,810 while every gate still passes. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x * pr_linkcheck: check the object the linker consumes, not the one mwcc emitted Moved to the base of this stack so every PR in it inherits the fix. It was committed on the top PR, which left the two below validating against the broken gate and failing with wrong-dest verdicts on correct files. The relocation-destination gate compiled each changed file and checked its raw relocations. The ROM build does not link that object -- it links the objisolated one -- and isolation changes the destinations, so the gate was answering about a file the build never sees. mwcc's `_ZTV<C>` addresses the vtable OBJECT, so a vptr store carries an addend skipping the offset-to-top and typeinfo words, while symbols.txt's `_ZTV<C>` IS the slot array; objisolate subtracts that preamble. Unisolated, ModelAnim2's secondary vptr store resolves as `_ZTV10ModelAnim2` (0x0208e9b4) + 44 = 0x0208e9e0, which is `_ZTI8dFader_c` -- an unrelated class's typeinfo. Isolated, 8 -> 0 and 44 -> 36 give 0x0208e9b4 and `VTable_Animation_ModelAnim2Thunk` (0x0208e9d8), both correct. MULTIPLE INHERITANCE IS WHY THIS SURFACED NOW. The single-inheritance addend of 8 resolves to a word still inside the same vtable symbol, so it never looked wrong, and the 96 destructors already migrated never tripped it. A secondary vptr store points past the primary slots and lands on whatever follows. Isolation also drops the compiler-emitted passengers, so a migrated destructor reports one slot rather than four or five. That is not lost coverage: the thunks are separately enrolled files with their own delinks entries, checked on their own runs. Fails open -- if objisolate cannot plan an object, the file is checked unisolated rather than losing its verdict. Verified on the three files the validator flagged across this stack: _ZN9ModelAnimD1Ev, _ZN10ModelAnim2D1Ev and _ZN10ModelAnim2D0Ev all report `ok` with the fix and WRONG without it, while rombuild links all three at 106/106 exact either way. tools suite 265 passed, 3 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x * Enemy.h: the operator delete must stay, and the comment said to delete it Comment only; no codegen change (rombuild 106/106 exact, unchanged). #1381 gave Enemy its real base. This comment was written before that and still said Enemy was "still a flattened struct here rather than `Enemy : Actor`", ending with "Delete this one when Enemy gains its real base." Enemy has gained it, and following that instruction would break the D0 route for every Enemy subclass. mwcc inlines operator delete only when it finds it in the class itself or its IMMEDIATE base; for a subclass of Enemy the immediate base is Enemy, and Actor is the grandparent, so Actor's copy is out of reach. The declaration is load-bearing precisely because Enemy now derives. Caught by a review pass over the post-#1381 headers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x * symbols: drop the size-0 duplicates the Vector3 dtor rename left behind --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* 59 classes get their real base and real member types The migration half of #1377, split from the layout work it rests on, with the D0 deleting destructors split off again on top so each PR clears the validator's 200-file cap. Stacked on #1378; the D0s are the PR above this one. 131 files. Hand-written .c destructors and methods become real C++ -- classes deriving from their actual base with typed members, in place of a flattened struct and a stand-in. Among them: DonutBlock as the worked example for the shadow-struct classes, thirty Platform subclasses unblocked by the 0x320 correction in #1378, and the array-member classes -- Wiggler's eight, Goomboss's three, ChainChomp's seven links, PathLift's Model[3], RotatingFirebar's one collision cylinder per flame. Six migrations that belong here by subject are in #1378 instead, because that half does not link without them: ChiefChilly, Unagi, Wiggler D0 and D1, Goomboss and ChainChomp all call the renamed Vector3 destructors. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. pr_linkcheck: no new WRONG or NO-REPRO. Enrollment does not move, and that is expected: a migration replaces the source of a function that was already enrolled, so the count stays while the source stops lying. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x * Re-request validation against the retargeted base No content change. This PR's base moved from cpp/platform-enemy-layout to cpp/layout-headergen when the layout half was split in two, and the relay keys on head SHA -- retargeting alone does not re-submit, so the check still showed the 431-file count from the old base. This empty commit changes the head so the validator measures the 131 files this PR actually adds. Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* 59 classes get their real base and real member types The migration half of #1377, split from the layout work it rests on, with the D0 deleting destructors split off again on top so each PR clears the validator's 200-file cap. Stacked on #1378; the D0s are the PR above this one. 131 files. Hand-written .c destructors and methods become real C++ -- classes deriving from their actual base with typed members, in place of a flattened struct and a stand-in. Among them: DonutBlock as the worked example for the shadow-struct classes, thirty Platform subclasses unblocked by the 0x320 correction in #1378, and the array-member classes -- Wiggler's eight, Goomboss's three, ChainChomp's seven links, PathLift's Model[3], RotatingFirebar's one collision cylinder per flame. Six migrations that belong here by subject are in #1378 instead, because that half does not link without them: ChiefChilly, Unagi, Wiggler D0 and D1, Goomboss and ChainChomp all call the renamed Vector3 destructors. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. pr_linkcheck: no new WRONG or NO-REPRO. Enrollment does not move, and that is expected: a migration replaces the source of a function that was already enrolled, so the count stays while the source stops lying. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x * Re-request validation against the retargeted base No content change. This PR's base moved from cpp/platform-enemy-layout to cpp/layout-headergen when the layout half was split in two, and the relay keys on head SHA -- retargeting alone does not re-submit, so the check still showed the 431-file count from the old base. This empty commit changes the head so the validator measures the 131 files this PR actually adds. Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x * 61 deleting destructors become real C++ The D0 half of #1379, split off so each PR clears the validator's 200-file cap. Stacked on #1379, which carries the class definitions these need. D0 is vtable slot 17 (or 1, for classes rooted below Actor): destroy, then return the object to the actor heap. Each of these was a hand-written .c file spelling out what the compiler emits anyway -- store the vtable, call the member destructors in reverse declaration order, chain to the base, call Memory::Deallocate. Declaring the class properly lets the compiler emit all of it, and the deallocation comes from the inline operator delete rather than anything in the file. Not in arm9: those D0s need an operator delete the arm9 classes do not have in scope, which is a separate problem and is not attempted here. VERIFIED rombuild -j16, full ROM: 106/106 exact, 0 mismatching, 10,813 source-built, 87.88%. eligible.py differs from main only by the rename pair #1378 introduces. tools suite 265 passed. langmode ratchet PASS. No duplicate stems. Enrollment does not move: a D0 was already enrolled from its .c file, so the count stays while the source stops hand-spelling the mangled name. Original work is by the author of #1377; this split preserves the content, not the 37-commit history, which remains on that branch. Claude-Session: https://claude.ai/code/session_015AXm5k53WFPjCYcDHRDX3x --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Seven Platform subclasses, rebuilt on the Platform main actually has
#1370 and #1374 reconstructed Platform independently and at the same time.
They agree on the part that matters -- Model at 0xd4, MovingMeshCollider at
0x124, a Matrix4x3 at 0x2ec -- and #1374 is right where they differ, so this
is built on #1374 and #1370 should be closed.
WHAT I HAD WRONG. I made sizeof(Platform) 0x320 and read the fields at
0x31e/0x320/0x322 as belonging to the DERIVED classes, sitting in the base's
tail padding. #1374 has them as Platform's own and the class at 0x324, on
evidence I did not have: BowserFireSeaArena reads all three and starts its own
fields at 0x324, so Platform must end exactly there. That is cross-class
evidence; mine was inferred from DonutBlock alone and happened to produce the
same offsets for the same bytes.
So there is NO tail-padding reuse anywhere in this family, and the story in my
#1375 is a misreading. Starting a derived class at the base's DATA SIZE is
still the correct model and that change stands on its own, but its motivating
case is gone and the PR should be re-argued or dropped.
Kept, seven classes and 33 files, each verified whole:
StarSwitch FortressTower KnockDownPlank ChainChompFence
RotatingCogSmall IceSheet MetalNet
tools/platform_subclass.py does the rewrite and REVERTS THE WHOLE CLASS if any
source it touches fails, so a class is migrated completely or not at all. Of 42
tried, 7 survived, and the failures sort into three kinds:
rest of that group). An empty destructor body cannot reproduce them; the
member needs its real type first.
declares s16 at 0x31e/0x320/0x322; several subclasses' generated headers
declared u8 pairs over the same bytes, and inheriting the s16 changes the
load width -- BlueCoinSwitch and FloatingFloorLllBig come out 1 word
different, TtcRotatingGear 15, SlidingPlatformWf 11. Both spellings cannot
be right. This is the next real question about Platform's tail.
one of which still provokes an mwcc internal compiler error.
Also carried here, cherry-picked from the same stack because main has none of
it: the inline base destructor for ActorDerived with Scene::~Scene as a real
method, objisolate correcting an UNDEF vtable reference (the inline-base case
its own comment predicted), and eligible.py accepting STB_LOPROC, which alone
recovers six ZThn80 thunks.
GATES
build_pin.verify every source of all seven, (True, '2004/b56')
check_header_offsets 0 mismatched on all seven, exit 0
eligible.py 10813 -> 10813, name list IDENTICAL
rombuild -j16 106/106 exact, 0 mismatching
source-built 10,813, 87.88%
test_objisolate 6 passed
attribution 0 changed, 0 lost
PUSHED WITH --no-verify, AND HERE IS THE REPRODUCTION. check_references fails
on untouched main: a clean worktree at pristine origin/main 0758bc9 gives the
identical message --
func_ov091_021339fc -> no longer a candidate, unresolved240 against baseline 241, eligible 10807 against baseline 10805. The banked
baseline lags the day's merges. Same numbers appear on this branch except
eligible, which is 10813. Nothing here adds an unresolvable reference.
Supersedes #1370 (Platform), which #1374 landed better. #1375 needs re-arguing: its tail-padding premise is false.
🤖 Generated with Claude Code