TextureSequence::Prepare and MaterialChanger::Prepare are static - #1390
Open
andrewboudreau wants to merge 4 commits into
Open
TextureSequence::Prepare and MaterialChanger::Prepare are static#1390andrewboudreau wants to merge 4 commits into
andrewboudreau wants to merge 4 commits into
Conversation
config/unresolved-baseline.json had drifted behind: it recorded 10805
eligible against main's 10813, and check_references fails on clean
origin/main with no feature branch involved. Reproduce before this commit:
git checkout origin/main
python tools/eligible.py
python tools/check_references.py # FAIL
python tools/check_references.py --update
# refusing to update: symbols left the backlog by failing differently
The ratchet could not unstick itself. --update refuses, correctly, because
one symbol left the backlog without being fixed:
func_ov091_021339fc (ov091:0x021339fc) -> no longer a candidate
That one was deliberately converted to a hand-written asm hatch by an
earlier merged PR. Its own header says "NONMATCHING: hand-written asm, not
a C decompilation ... does NOT count as matched", which takes it out of
eligible.py's candidate set. It did not degrade, it was reclassified -- and
the reason-switching guard cannot tell those apart, which is exactly why
unsticking it has to be a deliberate decision and not a --update.
The other two entries are main's too, and neither has a correct fix:
- func_02057410 (arm9) references _ll_udiv and _ull_mod, mwccarm's names
for the 64-bit division helpers it emits for the (u64)v / (u64)pf[1] at
func_02057410.c:304. The ROM's implementations are __aeabi_uldiv and
__aeabi_ulmod in ITCM (0x01ffa9dc, 0x01ffa9e8), but check_references
has no alias mechanism, and adding _ll_udiv as a second symbol at that
address would create the duplicate-address condition that makes CI
report lost matched functions.
- _ZN5Whomp13InitResourcesEv references func_021135d4, which dsd resolves
to module:overlays(9,14,15,16,18,19,20,22,24,25,26,27,29,32,36). All
fifteen load at 0x021111a0, so that one instruction targets different
data depending on which level is loaded and no single name is correct.
This file was generated on a clean origin/main checkout, so it carries
nothing from any feature branch. To verify: check out origin/main, run
tools/eligible.py, and regenerate -- the result is byte-identical.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
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
_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
tools/check_references.py --update, which is downward-only by construction.
Two names leave the backlog and nothing is added. Both were removed by the
two commits before this one:
_Z49_ZN15TextureSequence7PrepareER8BMD_FileR8BTP_Fileii
a double-mangled TextureSequence::Prepare. A call site declared the
already-mangled ROM name with a bare `extern` inside a //cpp file, so
it mangled a second time and the call targeted a symbol that does not
exist. Converting the call sites to the static member spelling deleted
the declaration and the bad reference with it -- the point of the
Prepare change, showing up in the one gate that can see it.
_Z88_ZN16MeshColliderBase16UpdatePosAndAngsERS_P5ActorR10ClsnResultR7Vector3P10Vector3_16S8_v
the same defect in Whomp::InitResources, fixed by giving that
declaration the extern "C" its neighbours already had.
The ratchet now refuses to let either name come back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe
|
This was referenced Aug 11, 2026
andrewboudreau
added a commit
that referenced
this pull request
Aug 11, 2026
Since #1367 the PR validator has been unable to report on any PR that contains a non-reproducing file. The draft-banner rule reads the module as `asm_policy` while the import binds it as `AP`: import asm_policy as AP ... if w == "NO-REPRO" and asm_policy.has_draft_banner(text): so the first NO-REPRO verdict raises NameError mid-loop. The worker then reports "worker error: pr_linkcheck exited 1 without a report" with no verdict for any file, including the ones it had already graded. PR #1390 is the first PR to hit it. The rule is only reachable when a file FAILS, which is the one path a green CI run never exercises -- so it was untestable where it sat, and untested. Both halves are fixed here: the two source-text overrides move out of main() into `source_policy`, and tools/test_pr_linkcheck_verdict.py drives them directly, including the collision the old inline comment asserted could not happen (a bannered transcription grades DRAFT, not RAW-ASM). Behaviour is otherwise unchanged: the extracted function preserves the original order, and a draft banner still does not excuse WRONG. Confirmed with pyflakes in both directions -- one undefined name on main, zero across all of tools/ with this applied. There is no lint step in CI today; adding one is a separate change, but the baseline for it is clean. Claude-Session: https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TextureSequence::PrepareandMaterialChanger::Preparewere declared as ordinary members and defined as forwarding(this, &model, &file). They take two arguments and nothis.Why the wrong reading survived
Both ROM bodies are
0xclong-call veneers —ldr ip,[pc]; bx ip; .word <impl>. A pure tail jump reproduces under any signature, so the definition files never constrained the question. The constraint lives on the two sides nobody had consulted:The callee.
func_02046d50andfunc_020470e8read onlyr0andr1:r2is never touched, which rules out a third argument. And the offsetsr1is read at are exactlyBTP_File's declared fields — sor1is the animation file, the second argument. Under the old reading it would have been the BMD.The callers. Every call site in the tree passes two. So do both matched implementations,
src/func_02046d50.candsrc/func_020470e8.c.A static member mangles identically, so no symbol changes and no config moves.
TextureTransformer, the third sibling, was corrected long ago. Its header already names these two veneers as "called the same way" and notes the old reading "cost the PC port a wrong-register hunt" — the follow-up just never happened. This is it.What changed
Twenty-two call sites drop the mangled extern for
TextureSequence::Prepare(model, file). The six.ccallers keep theirs — C has no::.SnowmanHeadandInvisibleSecretdeclare the class locally rather than includingTextureSequence.h: both carrytypedef int Fix12;, which collides with the realmath/Fix12.htemplate.Several of the removed externs were a bare
externon an already-mangled name inside a//cppfile, which mangles a second time. That shows up in the one gate that can see it:_Z49_ZN15TextureSequence7PrepareER8BMD_FileR8BTP_Fileiileaves the unresolved backlog.Eyerok's linkcheck improvesBLIND-39 -> BLIND-35as four call destinations become resolvable.The other three commits
Whomp's
MeshColliderBaseaddress was double-mangled. Found while running the gates, in a file this change already touches. The declaration sat outside theextern "C"block above it, so the address taken pointed at a_Z88_ZN16...that does not exist. The byte gate cannot see this — relocated words are wildcards. Linkcheck goesBLIND-1 -> VERIFIEDand the function still reproduces.The unresolved-reference baseline is re-pinned to main.
config/unresolved-baseline.jsonhad drifted: it recorded 10805 eligible against main's 10813, andcheck_referencesfails on cleanorigin/mainwith no feature branch involved:The ratchet could not unstick itself.
--updaterefuses, correctly, because one symbol left the backlog without being fixed:func_ov091_021339fcwas deliberately converted to a hand-written asm hatch by an earlier merged PR, which takes it out ofeligible.py's candidate set. It did not degrade, it was reclassified — and the reason-switching guard cannot tell those apart, which is exactly why unsticking it has to be a deliberate decision rather than a--update.The other two entries are main's as well, and neither has a correct fix:
func_02057410references_ll_udiv/_ull_mod, mwccarm's names for the 64-bit division helpers it emits for the(u64)v / (u64)pf[1]atfunc_02057410.c:304. The ROM's implementations are__aeabi_uldiv/__aeabi_ulmodin ITCM (0x01ffa9dc,0x01ffa9e8), butcheck_referenceshas no alias mechanism, and adding_ll_udivas a second symbol at that address would create the duplicate-address condition that makes CI report lost matched functions.Whomp::InitResourcesreferencesfunc_021135d4, which dsd resolves tomodule:overlays(9,14,15,16,18,19,20,22,24,25,26,27,29,32,36). All fifteen load at0x021111a0, so that one instruction targets different data depending on the level and no single name is correct.The baseline was generated on a clean
origin/maincheckout, so it carries nothing from this branch. Verify by checking outorigin/main, runningtools/eligible.py, and regenerating — the result is byte-identical.Then two fixes are banked back down with
--update, which is downward-only: the two double-mangled names above leave the backlog, nothing is added, and the ratchet now refuses to let either come back.Gates
rombuild106/106 exact, 10,813 reproducing, 0 mismatchingeligiblename list byte-identical to main at 10813check_referencesOK: no new unresolvable references (pre-push hook passes; no--no-verify)prepush_linkcheck23 checked, 21 verified, 0 blockingprepush_attribution0 changed / 0 lost ·port_refcheck393 resolve · header offsets, data definitions, duplicate sources cleanNot fixed, stated plainly
Goomba::InitResourcesandMrI::InitResourcesdo not reproduce — before or after. Both fail identically onorigin/main(999 and 7 words), and their compiled bytes are unchanged by this branch: 916 → 916 and 664 → 664, identical in both cases. So the edits there are provably neutral, not merely still-failing.🤖 Generated with Claude Code
https://claude.ai/code/session_01XxDmkQ47fWa3GB6mj8xEQe