Skip to content

Player::CleanupResources compiles again, and it was a match all along - #1393

Merged
andrewboudreau merged 1 commit into
mainfrom
fix/player-cleanupresources-compiles
Aug 11, 2026
Merged

Player::CleanupResources compiles again, and it was a match all along#1393
andrewboudreau merged 1 commit into
mainfrom
fix/player-cleanupresources-compiles

Conversation

@andrewboudreau

Copy link
Copy Markdown
Collaborator

A match that has been sitting unreadable in the tree

src/_ZN6Player16CleanupResourcesEv.cpp has not compiled since it gained
#include "decl_common.h". That header declares, inside extern "C":

extern void func_ov002_020bdd2c(void*);
extern void func_02073244(void*, int, int, void (*)(void*));

and the file then declared the same names again taking char * and
(int, int, int, void (*)(void)). Same C linkage, different parameter types — so it
is not a redeclaration but an attempt to overload a C-linkage name, which mwccarm
rejects outright:

src/_ZN6Player16CleanupResourcesEv.cpp:17: illegal function overloading
... :18, :19, :20, :23
Errors caused tool to abort.

Nothing caught it, because the failure removes the file from every gate that could
have.
A file that will not compile is not eligible, so it is not enrolled; its
delinks entry carries no complete, so dsd hands the range to the original ROM bytes
and the build stays 106/106 without the source ever being touched. It went unbuilt
through #919, #994, #1032 and #1382 — the last of which edited it.

This is #1367's NO-SYM category doing exactly what it was added for. It only
surfaced now because the fan-out of #1390's header change reached the file, and
because #1392 stops the validator crashing before it can say so.

The function is a byte match

That is the real result. Once it compiles, all 0x730 bytes at 0x020e32d4 in
ov002
reproduce under the pin with correct relocation destinations. Nothing about
the logic needed repair — only the declarations. So it is enrolled here rather than
left as rombytes, and the tree gains a function it has had all along.

The fix is to declare only what decl_common.h does not. Three names remain because
that header lacks them; _ZN7Vector3D1Ev is spelled with the this it actually
takes, which is also what makes the func_02073244 argument type-correct without a
cast.

Verified

gate result
build_pin (True, '2004/b56') — 0x020e32d4, size 0x730, ov002
pr_linkcheck ok, 1 slot, correct destinations
eligible.py 10,814 / 11,162 — main is 10,813, and the diff is this symbol alone
enroll.py 1 delinks file changed, 1 line added
rombuild -j16 106/106 exact, 10,814 source-built, 0 mismatching
progress 1,943,084 → 1,944,924 code bytes, 87.88% → 87.96%
check_data_definitions 10,972 objects, none define a ROM data symbol
check_duplicate_sources 11,282 stems, none doubled
port_refcheck 393 references, all resolve
prepush_attribution 11,288 tracked, 0 changed, 0 lost

Worth a follow-up

331 delink entries are unbuilt and carry no NONMATCHING banner. Most will be
ineligible for ordinary reasons, but this file proves at least one of them was a
finished match held out of the ROM by a declaration mismatch. Sweeping them for the
same signature is a separate change; I have not measured how many are in that state
and am not claiming a number.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe

The file has not compiled since it gained `#include "decl_common.h"`. That header
declares

    extern void func_ov002_020bdd2c(void*);
    extern void func_02073244(void*, int, int, void (*)(void*));

inside `extern "C"`, and the file then declared the same names again taking `char *`
and `(int, int, int, void (*)(void))`. Same C linkage, different parameter types, so
it is not a redeclaration but an attempt to overload a C-linkage name -- which
mwccarm rejects outright:

    src/_ZN6Player16CleanupResourcesEv.cpp:17: illegal function overloading
    ... :18, :19, :20, :23
    Errors caused tool to abort.

Nothing caught it, because the failure removes the file from every gate that could
have. A file that will not compile is not eligible, so it is not enrolled; its
delinks entry has no `complete`, so dsd hands the range to the original ROM bytes and
the build stays 106/106 without ever touching the source. It went unbuilt through
#919, #994, #1032 and #1382 -- the last of which edited it.

Fix is to declare only what decl_common.h does not. The three that remain are absent
from it; `_ZN7Vector3D1Ev` is spelled with the `this` it actually takes, which is
also what makes the `func_02073244` argument type-correct without a cast.

THE FUNCTION IS A BYTE MATCH. Once it compiles, all 0x730 bytes at 0x020e32d4 in
ov002 reproduce under the pin, with correct relocation destinations -- so this is not
a repair of a near-miss but the recovery of a match that has been sitting unreadable
in the tree. Enrolled here rather than left as rombytes.

VERIFIED

  build_pin      (True, '2004/b56') at 0x020e32d4, size 0x730, ov002
  pr_linkcheck   ok, 1 slot, correct destinations
  eligible.py    10,814 / 11,162 -- main is 10,813, and the diff is this symbol alone
  rombuild -j16  106/106 exact, 10,814 source-built, 0 mismatching, 87.88% -> 87.96%

Found while diagnosing why #1390's validation reported a worker error: with the
NameError in tools/pr_linkcheck.py fixed (#1392), the gate could finally grade this
file, and graded it NO-SYM. The verdict is correct and predates #1390 -- clean main
fails to compile it identically.

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

tangos-validator Bot commented Aug 11, 2026

Copy link
Copy Markdown

✅ PR validation — Passed

Committed merge introduces no reconstruction or attribution regression.

Full merge validation

Check Result
Committed test merge yes
Matched functions 11,178 / 11,347 (98.5%, +0)
Matched code bytes 2,078,800 / 2,211,124 (94.0%, +0)
Tracked source enrollment 10,786 functions, 1,938,036 bytes (87.65%, +1,840)
Perfect source moves 0 R100
Contributor credit 0 added, 0 changed, 0 lost
Relocation check 1 checked; 1 VERIFIED
Port reference check 393 checked; 0 stale
Module fidelity 106/106 exact; 100.000000% compared bytes
Code linked from verified source 10,814 functions, 1,944,924 bytes (87.96%)
Per-file link-check detail

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

File Symbol Result Slots checked
src/_ZN6Player16CleanupResourcesEv.cpp _ZN6Player16CleanupResourcesEv ✅ verified 1

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

@andrewboudreau
andrewboudreau merged commit 5d37a11 into main Aug 11, 2026
3 checks passed
@andrewboudreau
andrewboudreau deleted the fix/player-cleanupresources-compiles branch August 11, 2026 14:33
andrewboudreau added a commit that referenced this pull request Aug 11, 2026
…nce ratchet is unstuck (#1397)

* Re-pin the unresolved-reference baseline to main

The reference ratchet is stuck on main, and has been since the merges that moved it.
`--update` refuses whenever `bad` is non-empty, and main's own drift lands there, so
no PR can bank progress past it. The failure reproduces byte-identically on a clean
`origin/main` checkout with nothing applied.

Regenerated from a clean main. Ten lines change, and each is main's own state:

  eligible 10805 -> 10814   #1382, #1391 and #1393 all added functions.

  func_02057410             recorded as missing `_ll_mod`; it actually misses
                            `_ll_udiv` and `_ull_mod`. Those are mwccarm runtime
                            helper names -- the ROM implements them as
                            __aeabi_uldiv / __aeabi_ulmod in ITCM at 0x01ffa9dc and
                            0x01ffa9e8 -- and there is no alias mechanism, so the
                            entry is permanent rather than a to-do.

  _Z13func_020072c0v        gone: #1382 renamed that symbol to _ZN7Vector3D1Ev.

  ov079 Whomp::InitResources
                            ADDED. It is unresolvable on main today and was simply
                            missing from the baseline. The next commit fixes half of
                            it; the other half, func_021135d4, resolves to 15 overlays
                            sharing base 0x021111a0, so no single name is correct.

  ov091 func_ov091_021339fc gone: a merged PR converted it to a hand-written asm
                            hatch, which removes it from the candidate set. That
                            reclassification is indistinguishable from a regression to
                            the reason-switching guard -- which is exactly what jammed
                            the ratchet.

Its own commit, before any content, so the next commits' effect on the backlog reads
as a diff against an honest starting point instead of being buried in a rewrite.

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

* TextureSequence::Prepare and MaterialChanger::Prepare are static

Both were declared as ordinary members and defined as forwarding
(this, &model, &file). They take two arguments and no this. Four
independent lines of evidence agree:

  - func_02046d50 and func_020470e8, the real bodies behind the two 0xc
    veneers, read only r0 and r1. r2 is never touched, which rules out a
    third argument.
  - the offsets r1 is read at are exactly the animation file's fields
    (BTP_File: 0x2, 0x4, 0x8, 0xc, 0x1c, 0x20), so r1 is the SECOND
    argument, not the third.
  - both matched implementations, src/func_02046d50.c and
    src/func_020470e8.c, already take two parameters.
  - every call site in the tree passes two.

The veneer bodies are pure tail jumps (ldr ip,[pc]; bx ip), so they
reproduce under either signature. That is how the wrong reading survived:
the definition files never constrained it, and the callee and callers --
which do -- were not consulted. A static member mangles identically, so no
symbol changes and no config moves.

TextureTransformer, the third sibling, was already corrected. Its header
names these two veneers as "called the same way" and notes that the old
reading "cost the PC port a wrong-register hunt"; the follow-up never
happened. This is it.

Twenty-two call sites drop the mangled extern for the member spelling.
Several of those externs were a bare `extern` on a mangled name inside a
//cpp file, which mangles a second time, so removing them retires that
latent defect as well. Eyerok's linkcheck improves BLIND-39 -> BLIND-35 as
four call destinations become resolvable. SnowmanHead and InvisibleSecret
declare the class locally rather than including TextureSequence.h: both
carry `typedef int Fix12;`, which collides with the real math/Fix12.h
template.

The six .c callers keep the mangled extern -- C has no ::.

Gates: rombuild 106/106 exact, 10,813 reproducing, 0 mismatching; eligible
name list byte-identical to main at 10813; linkcheck 23 checked, 21
verified, 0 blocking; attribution 0 changed / 0 lost; port_refcheck 393
resolve; header offsets, data definitions and duplicate sources clean.

Goomba::InitResources and MrI::InitResources do not reproduce -- before or
after. Both fail identically on origin/main (999 and 7 words), and their
compiled bytes are unchanged by this commit: 916 -> 916 and 664 -> 664,
identical in both cases.

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

* Whomp: the MeshColliderBase address was double-mangled

_ZN5Whomp13InitResourcesEv.cpp declares
_ZN16MeshColliderBase16UpdatePosAndAngs... with a bare `extern`, and that
declaration sits OUTSIDE the extern "C" block above it. In a //cpp file
that mangles the already-mangled name a second time, so the address taken
at the func_020393d4 call points at a _Z88_ZN16... that does not exist.

The byte gate cannot see this -- match.py compares relocated words as
wildcards, so the function reproduces either way. check_references and
prepush_linkcheck are the only gates that look at what a reference
targets, and both flagged it: linkcheck goes BLIND-1 -> VERIFIED with the
fix, and the function still reproduces.

Found while running the gates for the Prepare change, in a file that
change already touches. The other entry in the same check_references
report, func_02057410's _ll_udiv/_ull_mod, is unrelated to this branch
and reproduces identically on origin/main.

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

* Bank the two double-mangled names this PR resolves

Downward only, and both deletions are attributable to a commit above:

  _Z49_ZN15TextureSequence7PrepareER8BMD_FileR8BTP_Fileii
      resolved by making Prepare static -- the two trailing 'ii' were a
      shadow declaration disagreeing with the real two-argument signature.

  _Z88_ZN16MeshColliderBase16UpdatePosAndAngsERS_P5ActorR10ClsnResultR7Vector3P10Vector3_16S8_v
      resolved by spelling Whomp's extern the way decl_common.h does, so the
      already-mangled name is not mangled a second time.

Nothing is added. Whomp keeps its entry for func_021135d4, which resolves to 15
overlays sharing base 0x021111a0 -- no single name is correct, so it stays.

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

* Use the real headers, not local class declarations

The Prepare change first spelled the call surface inline at each of the 11 call
sites:

    struct TextureSequence { static void Prepare(BMD_File &, BTP_File &); };

That compiles and byte-matches, but it is a shadow declaration, and the langmode
ratchet counts those: shadow_decls.local_body_no_include 1507 -> 1510, with nothing
falling to offset it. The workflow has an escape hatch for a deliberate rise, but
using it here would mean adding debt in exactly the direction the plan is draining.
The headers cost nothing, so they win:

    shadow_decls.local_body_no_include   1507 -> 1503  (-4)

A 7-point swing, and the ROM does not move -- the call is a direct bl either way.

Two files needed the Fix12 shadow removed first. `typedef int Fix12;` is harmless
while a file includes nothing, but the real Fix12 is a TEMPLATE, so pulling in a
real header turns every use into "template argument list expected" rather than a
plain redefinition. SnowmanHead and InvisibleSecret now take those extern
parameters as plain int, which is what BowserFire, BowserTail, KoopaShell,
LavaBubble and BabyPenguin already do. That trap is why the local-declaration route
was taken in the first place; it is not specific to those two files.

VERIFIED  rombuild -j16: 106/106 exact, 10,814 source-built, 0 mismatching, 87.96%
          langmode ratchet PASS

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant