Skip to content

Declare dScMgBase_c slot 34 (Virtual88) across the minigame family - #2110

Merged
andrewboudreau merged 1 commit into
mainfrom
cpp/minigame-slot34
Sep 1, 2026
Merged

Declare dScMgBase_c slot 34 (Virtual88) across the minigame family#2110
andrewboudreau merged 1 commit into
mainfrom
cpp/minigame-slot34

Conversation

@andrewboudreau

Copy link
Copy Markdown
Collaborator

Slot 34 of the 18-35 keystone range. One slot left after this one.

Stacked on #2108 (slot 33). Base is cpp/minigame-slot33; review the top commit only.

What slot 34 is

It is the brush. ov004:0x020ae3b4 walks a size x size square centred on (cx, cy) and, for each cell inside it, computes the address of one 4-bit pixel in BG character VRAM — (x/8 + (y/8)*32)*32 + (y&7)*4, the standard DS 4bpp char layout — reads the containing word, splices colour into the nibble at (x&7)*4, and writes it back. Sixteen colours, one palette index per pixel. It clips on all four sides.

The two object fields slot 33 initialises are the two this one reads. Slot 33 (Virtual84, engine bring-up) sets obj+0x68 to 0 and obj+0x6c to -1. Here obj+0x6c selects which background layer to draw into — 0..3 index G2S::GetBG0CharPtr through GetBG3CharPtr, anything else returns without drawing, which is what -1 buys — and obj+0x68 gates the wrapped region above the touch screen, where the main engine's G2::GetBG*CharPtr are used instead and y is folded by + data_ov004_020beb6c + 0xc0. So slot 33 leaves the brush disabled and a minigame arms it by picking a layer. Two slots, one mechanism.

Who calls it: ov004:0x020ae5c4, a line rasteriser sitting immediately after the brush in the image, which dispatches through +0x88 at seven separate sites as it steps along a segment. That is the whole of the in-family call graph for this slot — all seven are inside that one function. A line-drawing minigame is exactly the set that overrides it.

The signature is measured, not assumed

This is the first slot in the campaign that takes arguments, so the arity had to come from the image rather than from "no parameters, nothing to get wrong".

Arity — four explicit parameters, unanimous across all seven call sites. Each sets up r1, r2, r3 and one stack word before the call. At 0x020ae690, for instance:

mov  r1, sb
mov  r2, r8
ldr  r3, [sp, #0x3c]
...                      ; [sp] already holds r7
blx  r1

So the callee takes this plus four. Image-wide the +0x88 dispatch pair appears 14 times, 7 in ov004/ov006 and 7 in ov064.

Return type — void, and here the ROM says so rather than merely permitting it. Two of the seven sites (0x020ae7e4, 0x020ae83c) overwrite r0 on the very next instruction with ldr r0,[sp,#0xc]; the rest tail-return without reading it. All five bodies fall off the end without setting a result.

The dispatch scanner is the one validated at slot 33 by re-running it at +0x80 and reproducing slot 32's known site: mask insn & 0x0FF00FFF == 0x05900000 | off (not 0x0FFF0FFF — bits 19-16 are Rn), Rn != 15 to exclude pc-relative literal pools, and up to three intervening instructions before the blx/bx. Strict adjacency returns zero here and would have produced a false "never dispatched" claim.

Census

vtables carrying the slot 32
inherit it unchanged 28
override it 4
distinct addresses 4
bodies shared between classes 0
declarations added 5 (base + 4)

dScMgAmida_c (ov006:0x020d14c0), dScMgTeresa_c (0x021200dc), dScMgTrampoline_c (0x02120da8), dScMgTrampoline2_c (0x02122cb0). dScMgD3DBase_c does not override this slot, so unlike slots 26-31 and 33 the two trampoline classes do not share an ancestor body here — each declares and defines its own.

Nothing to correct, as at slot 33: no recovered name: line exists on the base body or on any of the four overrides, so no borrowed label is being retired. Virtual88 is this tree's own no-name spelling after the +0x88 vtable offset, not a ROM name.

The one new hazard: a cross-file reference

dScMgAmida_c's override is the only one in the family that calls the base rather than replacing it. Amida is the ghost-leg game — you draw a line and it must not cross one already there — so this override is the collision half and the base is the drawing half. It probes the 0x158-stride occupancy grid at +0x4710 (the cell under the point, plus for a diagonal step the two cells the diagonal would cut between), sets the foul flag at +0x4709 if any is already >= 3, and then calls the brush.

That means it held an extern on the old func_ov004_020ae3b4 name plus six call sites. apply_rename renames the definition, symbols.txt and delinks.txt — it does not touch callers, so without the retarget in this commit the link breaks. A post-rename grep for every old symbol is now a standing step in this campaign.

Why that body decompiles with only three explicit parameters: the fourth arrives on the stack and it never reads it — it supplies its own size (2 or 4) per case. So the reconstruction had nothing to name there. The call sites are unanimous and fix the slot's signature; an ignored stack argument costs the callee nothing.

Independent corroboration

include/dScMgTeresa_c.h predicted this slot before it was declared, and the comment is quoted and re-counted in this diff:

Slot 34 takes four extra parameters and barely touches this (draws a HUD digit/glyph), same multi-arg shape as dScMgAmida_c's own slot 34 — both point at dScMgBase_c's slot 34 being a multi-argument virtual, not reconstructed tree-wide yet.

Both halves check out. That class's override stamps a shape by testing data_ov006_0213f9e4[row] one bit per column — a stipple pattern, i.e. a glyph, not a solid square — and it really does never read its object pointer, because unlike the base it always draws into sub BG0 instead of consulting the layer index at +0x6c. The same guess in notes/minigame-provenance.md is re-counted too (it was also the one dead prose reference this rename created, caught by check_dead_references).

Verification

  • rombuild.py -j 16 --no-rom: 11,088 / 11,088 reproducing, 106/106 modules exact, 100.000000% of compared bytes.
  • romdata_check --json, diffed against slot 33 in both directions: verifiedBytes 35,424 -> 35,428 (+4) and partialBytes 11,996 -> 12,120 (+124). 128 bytes = 32 tables x 4 — the campaign's per-slot signature. No key added or removed; every other counter and the whole differing set unchanged.
  • check_dead_references, port_refcheck, check_duplicate_sources, check_header_offsets --changed origin/main, check_src_tu_compiles (97/97), langmode_audit --check langmode-baseline.json: all pass.
  • premerge_check.py --base origin/main --fetch over the whole stack: nothing goes green -> red.

What this does NOT establish

  • Virtual88 is a placeholder, not a recovered ROM name. The ROM's RTTI carries class names only, never method names.
  • The parameter names cx, cy, colour, size are inferred from behaviour, not from the ROM. The arity and the return type are measured; the names are a reading of what the body does with them.
  • Retro-renaming slots 18-30, which borrowed dActor_c's names by index, remains the owner's call and is deliberately not touched here.
  • src_tu/actors/unit_ov004_020b0a38.cpp regeneration is still outstanding as its own follow-up PR (harmless today: text-verified, not enrolled, src_tu/ is not in the ROM build).

attribution-override applied per the standing instruction.

Slot 34 is the first slot in this campaign that takes ARGUMENTS, the
first whose job is legible from the body alone, and the first whose
rename breaks a cross-file reference rather than just a name.

IT IS THE BRUSH.  ov004:0x020ae3b4 walks a size x size square centred on
(cx, cy) and for each cell computes the address of one 4-bit pixel in BG
character VRAM -- (x/8 + (y/8)*32)*32 + (y&7)*4, the standard DS 4bpp
char layout -- reads the containing word, splices `colour` into the
nibble at (x&7)*4, and writes it back.  Sixteen colours, one palette
index per pixel, clipping on all four sides.

It reads exactly the two object fields slot 33 initialises.  Virtual84
sets obj+0x68 to 0 and obj+0x6c to -1; here obj+0x6c selects which BG
layer to draw into (0..3 index G2S::GetBG0CharPtr..GetBG3CharPtr,
anything else returns without drawing) and obj+0x68 gates the wrapped
region above the touch screen.  So slot 33 leaves the brush disabled and
a minigame arms it by picking a layer.  Two slots, one mechanism.

Signature measured, not assumed.  The only in-family caller is
ov004:0x020ae5c4, a line rasteriser sitting immediately after the brush
in the image, which dispatches through +0x88 at seven sites as it steps
along a segment.  Every one of the seven sets up r1, r2, r3 AND one
stack word -- this plus four.  Two of them (0x020ae7e4, 0x020ae83c)
overwrite r0 on the very next instruction with `ldr r0,[sp,#0xc]`, so
`void` is what the ROM says rather than merely what it permits.

Nothing to correct, as at slot 33: no `recovered name:` line exists on
the base body or on any of the four overrides, so no borrowed label is
being retired.  Virtual88 is this tree's own no-name spelling after the
+0x88 vtable offset.

Four overrides, four addresses, nothing inherited and nothing shared:
dScMgAmida_c (ov006:0x020d14c0), dScMgTeresa_c (0x021200dc),
dScMgTrampoline_c (0x02120da8), dScMgTrampoline2_c (0x02122cb0).
dScMgD3DBase_c does NOT override this slot, so its two trampoline
children declare it themselves instead of inheriting a shared body the
way they do at 26-31 and 33.

dScMgAmida_c's override is the only one in the family that CALLS the
base rather than replacing it -- it is the collision half of the
ghost-leg rule and the base is the drawing half -- so it held an extern
on the old func_ov004_020ae3b4 name at six call sites.  Retargeted here;
without that the link breaks.  Its body reads only three of the four
parameters because the fourth arrives on the stack and it supplies its
own size (2 or 4) per case, which is why the reconstruction had nothing
to name there.  The call sites are unanimous and fix the signature.

include/dScMgTeresa_c.h had independently predicted this slot -- "takes
four extra parameters and barely touches `this` (draws a HUD
digit/glyph) ... not reconstructed tree-wide yet".  Both halves check
out: that class's override stamps a stipple pattern from
data_ov006_0213f9e4 and never reads its object pointer, because unlike
the base it always draws into sub BG0.  That prose and the matching
guess in notes/minigame-provenance.md are re-counted here.

Verified: rombuild 11,088/11,088 reproducing, 106/106 modules exact,
100.000000% of compared bytes.  romdata_check verifiedBytes
35,424 -> 35,428 (+4) and partialBytes 11,996 -> 12,120 (+124) -- 128
bytes, 32 tables x 4, the campaign's per-slot signature -- with no key
added or removed and every other counter and the differing set
unchanged.  dead-references, port_refcheck, duplicate-sources,
header-offsets --changed, src_tu-compiles 97/97 and the langmode ratchet
all pass.

One slot left: 35.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh
@andrewboudreau andrewboudreau added the attribution-override Maintainer accepts this PR's contributor-credit changes; validation reports them as warnings label Aug 31, 2026
@tangos-validator

tangos-validator Bot commented Aug 31, 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
Byte-verified functions 10,781 / 11,347 (95.01%, +0)
Byte-verified code bytes 2,004,460 / 2,211,124 (90.65%, +0)
Claimed, not byte-verified 436 functions, 101,220 bytes (+0)
Perfect source moves 0 R100
Enrolled ranges (delinks complete) 11,059 functions, 2,053,148 bytes (92.86%, +0) -- differs from byte-verified by +278
Contributor credit 0 added, 0 changed, 0 lost
Relocation check 305 checked; 305 VERIFIED
Port reference check 405 checked; 0 stale
Module fidelity 106/106 exact; 100.000000% compared bytes
Code linked from verified source 11,088 functions, 2,067,148 bytes (93.49%)
Module bytes from source 2,067,148 / 3,049,600 (67.8%); 811,492 (26.6%) are data no delink entry reaches
ROM data reproduced from source 465 symbol(s) exact, 253 partial, 6 differ

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

Per-file link-check detail

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

File Symbol Result Slots checked
src/_ZN10dScMgCup_c13InitResourcesEv.cpp _ZN10dScMgCup_c13InitResourcesEv ✅ verified 1
src/_ZN10dScMgCup_c6RenderEv.cpp _ZN10dScMgCup_c6RenderEv ✅ verified 1
src/_ZN10dScMgCup_c8BehaviorEv.cpp _ZN10dScMgCup_c8BehaviorEv ✅ verified 1
src/_ZN10dScMgCup_cD0Ev.cpp _ZN10dScMgCup_cD0Ev ✅ verified 1
src/_ZN10dScMgCup_cD1Ev.cpp _ZN10dScMgCup_cD1Ev ✅ verified 1
src/_ZN11dScMgBase_c15OnHitByMegaCharEv.cpp _ZN11dScMgBase_c15OnHitByMegaCharEv ✅ verified 1
src/_ZN11dScMgBase_c15graphCallback_c14GraphCallback0Ev.cpp _ZN11dScMgBase_c15graphCallback_c14GraphCallback0Ev ✅ verified 1
src/_ZN11dScMgBase_c15graphCallback_c14GraphCallback1Ev.cpp _ZN11dScMgBase_c15graphCallback_c14GraphCallback1Ev ✅ verified 1
src/_ZN11dScMgBase_c15graphCallback_c14GraphCallback2Ev.cpp _ZN11dScMgBase_c15graphCallback_c14GraphCallback2Ev ✅ verified 1
src/_ZN11dScMgBase_c15graphCallback_c14GraphCallback3Ev.cpp _ZN11dScMgBase_c15graphCallback_c14GraphCallback3Ev ✅ verified 1
src/_ZN11dScMgBase_c16OnAimedAtWithEggEv.cpp _ZN11dScMgBase_c16OnAimedAtWithEggEv ✅ verified 1
src/_ZN11dScMgBase_c19OnHitFromUnderneathEv.cpp _ZN11dScMgBase_c19OnHitFromUnderneathEv ✅ verified 1
src/_ZN11dScMgBase_c25OnAimedAtWithEggReturnVecEv.cpp _ZN11dScMgBase_c25OnAimedAtWithEggReturnVecEv ✅ verified 1
src/_ZN11dScMgBase_c8OnPushedEv.cpp _ZN11dScMgBase_c8OnPushedEv ✅ verified 1
src/_ZN11dScMgBase_c9Virtual88Eiiii.c _ZN11dScMgBase_c9Virtual88Eiiii ✅ verified 1
src/_ZN11dScMgBase_cC2Ev.cpp _ZN11dScMgBase_cC2Ev ✅ verified 1
src/_ZN11dScMgBase_cD0Ev.cpp _ZN11dScMgBase_cD0Ev ✅ verified 1
src/_ZN11dScMgBase_cD1Ev.cpp _ZN11dScMgBase_cD1Ev ✅ verified 1
src/_ZN11dScMgBase_cD2Ev.cpp _ZN11dScMgBase_cD2Ev ✅ verified 1
src/_ZN11dScMgCoin_c13InitResourcesEv.cpp _ZN11dScMgCoin_c13InitResourcesEv ✅ verified 1
src/_ZN11dScMgCoin_c13OnYoshiTryEatEi.cpp _ZN11dScMgCoin_c13OnYoshiTryEatEi ✅ verified 1
src/_ZN11dScMgCoin_c6RenderEv.cpp _ZN11dScMgCoin_c6RenderEv ✅ verified 1
src/_ZN11dScMgCoin_c8BehaviorEv.cpp _ZN11dScMgCoin_c8BehaviorEv ✅ verified 1
src/_ZN11dScMgCoin_cD0Ev.cpp _ZN11dScMgCoin_cD0Ev ✅ verified 1
src/_ZN11dScMgCoin_cD1Ev.cpp _ZN11dScMgCoin_cD1Ev ✅ verified 1
src/_ZN11dScMgJump_c13InitResourcesEv.cpp _ZN11dScMgJump_c13InitResourcesEv ✅ verified 1
src/_ZN11dScMgJump_c13OnTurnIntoEggEi.cpp _ZN11dScMgJump_c13OnTurnIntoEggEi ✅ verified 1
src/_ZN11dScMgJump_c16CleanupResourcesEv.cpp _ZN11dScMgJump_c16CleanupResourcesEv ✅ verified 1
src/_ZN11dScMgJump_c6RenderEv.cpp _ZN11dScMgJump_c6RenderEv ✅ verified 1
src/_ZN11dScMgJump_c8BehaviorEv.cpp _ZN11dScMgJump_c8BehaviorEv ✅ verified 1
src/_ZN11dScMgJump_cD0Ev.cpp _ZN11dScMgJump_cD0Ev ✅ verified 1
src/_ZN11dScMgJump_cD1Ev.cpp _ZN11dScMgJump_cD1Ev ✅ verified 1
src/_ZN12dScMg3DEsp_c13InitResourcesEv.cpp _ZN12dScMg3DEsp_c13InitResourcesEv ✅ verified 1
src/_ZN12dScMg3DEsp_c13OnYoshiTryEatEi.cpp _ZN12dScMg3DEsp_c13OnYoshiTryEatEi ✅ verified 1
src/_ZN12dScMg3DEsp_c16CleanupResourcesEv.cpp _ZN12dScMg3DEsp_c16CleanupResourcesEv ✅ verified 1
src/_ZN12dScMg3DEsp_c6RenderEv.cpp _ZN12dScMg3DEsp_c6RenderEv ✅ verified 1
src/_ZN12dScMg3DEsp_c8BehaviorEv.cpp _ZN12dScMg3DEsp_c8BehaviorEv ✅ verified 1
src/_ZN12dScMg3DEsp_cD0Ev.cpp _ZN12dScMg3DEsp_cD0Ev ✅ verified 1
src/_ZN12dScMg3DEsp_cD1Ev.cpp _ZN12dScMg3DEsp_cD1Ev ✅ verified 1
src/_ZN12dScMgAmida_c13InitResourcesEv.cpp _ZN12dScMgAmida_c13InitResourcesEv ✅ verified 1
src/_ZN12dScMgAmida_c21AfterCleanupResourcesEj.cpp _ZN12dScMgAmida_c21AfterCleanupResourcesEj ✅ verified 1
src/_ZN12dScMgAmida_c5Unk36Ev.cpp _ZN12dScMgAmida_c5Unk36Ev ✅ verified 1
src/_ZN12dScMgAmida_c6RenderEv.cpp _ZN12dScMgAmida_c6RenderEv ✅ verified 1
src/_ZN12dScMgAmida_c8BehaviorEv.cpp _ZN12dScMgAmida_c8BehaviorEv ✅ verified 1
src/_ZN12dScMgAmida_c9Virtual88Eiiii.c _ZN12dScMgAmida_c9Virtual88Eiiii ✅ verified 1
src/_ZN12dScMgAmida_cD0Ev.cpp _ZN12dScMgAmida_cD0Ev ✅ verified 1
src/_ZN12dScMgAmida_cD1Ev.cpp _ZN12dScMgAmida_cD1Ev ✅ verified 1
src/_ZN12dScMgJump2_c13InitResourcesEv.cpp _ZN12dScMgJump2_c13InitResourcesEv ✅ verified 1
src/_ZN12dScMgJump2_c16CleanupResourcesEv.cpp _ZN12dScMgJump2_c16CleanupResourcesEv ✅ verified 1
src/_ZN12dScMgJump2_c6RenderEv.cpp _ZN12dScMgJump2_c6RenderEv ✅ verified 1
src/_ZN12dScMgJump2_c8BehaviorEv.cpp _ZN12dScMgJump2_c8BehaviorEv ✅ verified 1
src/_ZN12dScMgJump2_cD0Ev.cpp _ZN12dScMgJump2_cD0Ev ✅ verified 1
src/_ZN12dScMgJump2_cD1Ev.cpp _ZN12dScMgJump2_cD1Ev ✅ verified 1
src/_ZN12dScMgLuigi_c13InitResourcesEv.cpp _ZN12dScMgLuigi_c13InitResourcesEv ✅ verified 1
src/_ZN12dScMgLuigi_c21AfterCleanupResourcesEj.cpp _ZN12dScMgLuigi_c21AfterCleanupResourcesEj ✅ verified 1
src/_ZN12dScMgLuigi_c6RenderEv.cpp _ZN12dScMgLuigi_c6RenderEv ✅ verified 1
src/_ZN12dScMgLuigi_c8BehaviorEv.cpp _ZN12dScMgLuigi_c8BehaviorEv ✅ verified 1
src/_ZN12dScMgLuigi_cD0Ev.cpp _ZN12dScMgLuigi_cD0Ev ✅ verified 1
src/_ZN12dScMgLuigi_cD1Ev.cpp _ZN12dScMgLuigi_cD1Ev ✅ verified 1
src/_ZN12dScMgPanel_c13InitResourcesEv.cpp _ZN12dScMgPanel_c13InitResourcesEv ✅ verified 1
src/_ZN12dScMgPanel_c6RenderEv.cpp _ZN12dScMgPanel_c6RenderEv ✅ verified 1
src/_ZN12dScMgPanel_c8BehaviorEv.cpp _ZN12dScMgPanel_c8BehaviorEv ✅ verified 1
src/_ZN12dScMgPanel_cD0Ev.cpp _ZN12dScMgPanel_cD0Ev ✅ verified 1
src/_ZN12dScMgPanel_cD1Ev.cpp _ZN12dScMgPanel_cD1Ev ✅ verified 1
src/_ZN12dScMgSlot1_c13InitResourcesEv.cpp _ZN12dScMgSlot1_c13InitResourcesEv ✅ verified 1
src/_ZN12dScMgSlot1_c15OnHitByMegaCharEv.cpp _ZN12dScMgSlot1_c15OnHitByMegaCharEv ✅ verified 1
src/_ZN12dScMgSlot1_c19OnHitFromUnderneathEv.cpp _ZN12dScMgSlot1_c19OnHitFromUnderneathEv ✅ verified 1
src/_ZN12dScMgSlot1_c6RenderEv.cpp _ZN12dScMgSlot1_c6RenderEv ✅ verified 1
src/_ZN12dScMgSlot1_c9betIcon_c6RenderEv.cpp _ZN12dScMgSlot1_c9betIcon_c6RenderEv ✅ verified 1
src/_ZN12dScMgSlot1_c9betIcon_c8BehaviorEv.cpp _ZN12dScMgSlot1_c9betIcon_c8BehaviorEv ✅ verified 1
src/_ZN12dScMgSlot1_cD0Ev.cpp _ZN12dScMgSlot1_cD0Ev ✅ verified 1
src/_ZN12dScMgSlot1_cD1Ev.cpp _ZN12dScMgSlot1_cD1Ev ✅ verified 1
src/_ZN12dScMgSlot3_c13InitResourcesEv.cpp _ZN12dScMgSlot3_c13InitResourcesEv ✅ verified 1
src/_ZN12dScMgSlot3_c6RenderEv.cpp _ZN12dScMgSlot3_c6RenderEv ✅ verified 1
src/_ZN12dScMgSlot3_c8BehaviorEv.cpp _ZN12dScMgSlot3_c8BehaviorEv ✅ verified 1
src/_ZN12dScMgSlot3_cD0Ev.cpp _ZN12dScMgSlot3_cD0Ev ✅ verified 1
src/_ZN12dScMgSlot3_cD1Ev.cpp _ZN12dScMgSlot3_cD1Ev ✅ verified 1
src/_ZN12dScMgSound_c13InitResourcesEv.cpp _ZN12dScMgSound_c13InitResourcesEv ✅ verified 1
src/_ZN12dScMgSound_c6RenderEv.cpp _ZN12dScMgSound_c6RenderEv ✅ verified 1
src/_ZN12dScMgSound_c8BehaviorEv.cpp _ZN12dScMgSound_c8BehaviorEv ✅ verified 1
src/_ZN12dScMgSound_cD0Ev.cpp _ZN12dScMgSound_cD0Ev ✅ verified 1
src/_ZN12dScMgSound_cD1Ev.cpp _ZN12dScMgSound_cD1Ev ✅ verified 1
src/_ZN13dScMgFlower_c13InitResourcesEv.cpp _ZN13dScMgFlower_c13InitResourcesEv ✅ verified 1
src/_ZN13dScMgFlower_c6RenderEv.cpp _ZN13dScMgFlower_c6RenderEv ✅ verified 1
src/_ZN13dScMgFlower_c8BehaviorEv.cpp _ZN13dScMgFlower_c8BehaviorEv ✅ verified 1
src/_ZN13dScMgFlower_cD0Ev.cpp _ZN13dScMgFlower_cD0Ev ✅ verified 1
src/_ZN13dScMgFlower_cD1Ev.cpp _ZN13dScMgFlower_cD1Ev ✅ verified 1
src/_ZN13dScMgMemory_c13InitResourcesEv.cpp _ZN13dScMgMemory_c13InitResourcesEv ✅ verified 1
src/_ZN13dScMgMemory_c13OnTurnIntoEggEi.c _ZN13dScMgMemory_c13OnTurnIntoEggEi ✅ verified 1
src/_ZN13dScMgMemory_c13OnYoshiTryEatEi.c _ZN13dScMgMemory_c13OnYoshiTryEatEi ✅ verified 1
src/_ZN13dScMgMemory_c15OnGroundPoundedEv.c _ZN13dScMgMemory_c15OnGroundPoundedEv ✅ verified 1
src/_ZN13dScMgMemory_c6RenderEv.cpp _ZN13dScMgMemory_c6RenderEv ✅ verified 1
src/_ZN13dScMgMemory_c8BehaviorEv.cpp _ZN13dScMgMemory_c8BehaviorEv ✅ verified 1
src/_ZN13dScMgMemory_cD0Ev.cpp _ZN13dScMgMemory_cD0Ev ✅ verified 1
src/_ZN13dScMgMemory_cD1Ev.cpp _ZN13dScMgMemory_cD1Ev ✅ verified 1
src/_ZN13dScMgTeresa_c13InitResourcesEv.cpp _ZN13dScMgTeresa_c13InitResourcesEv ✅ verified 1
src/_ZN13dScMgTeresa_c6RenderEv.cpp _ZN13dScMgTeresa_c6RenderEv ✅ verified 1
src/_ZN13dScMgTeresa_c8BehaviorEv.cpp _ZN13dScMgTeresa_c8BehaviorEv ✅ verified 1
src/_ZN13dScMgTeresa_c9Virtual88Eiiii.c _ZN13dScMgTeresa_c9Virtual88Eiiii ✅ verified 1
src/_ZN13dScMgTeresa_cD0Ev.cpp _ZN13dScMgTeresa_cD0Ev ✅ verified 1
src/_ZN13dScMgTeresa_cD1Ev.cpp _ZN13dScMgTeresa_cD1Ev ✅ verified 1
src/_ZN14dScMgBomroom_c13InitResourcesEv.cpp _ZN14dScMgBomroom_c13InitResourcesEv ✅ verified 1
src/_ZN14dScMgBomroom_c6RenderEv.cpp _ZN14dScMgBomroom_c6RenderEv ✅ verified 1
src/_ZN14dScMgBomroom_c8BehaviorEv.cpp _ZN14dScMgBomroom_c8BehaviorEv ✅ verified 1
src/_ZN14dScMgBomroom_cD0Ev.cpp _ZN14dScMgBomroom_cD0Ev ✅ verified 1
src/_ZN14dScMgBomroom_cD1Ev.cpp _ZN14dScMgBomroom_cD1Ev ✅ verified 1
src/_ZN14dScMgCurling_c13InitResourcesEv.cpp _ZN14dScMgCurling_c13InitResourcesEv ✅ verified 1
src/_ZN14dScMgCurling_c13OnYoshiTryEatEi.cpp _ZN14dScMgCurling_c13OnYoshiTryEatEi ✅ verified 1
src/_ZN14dScMgCurling_c6RenderEv.cpp _ZN14dScMgCurling_c6RenderEv ✅ verified 1
src/_ZN14dScMgCurling_c8BehaviorEv.cpp _ZN14dScMgCurling_c8BehaviorEv ✅ verified 1
src/_ZN14dScMgCurling_cD0Ev.cpp _ZN14dScMgCurling_cD0Ev ✅ verified 1
src/_ZN14dScMgCurling_cD1Ev.cpp _ZN14dScMgCurling_cD1Ev ✅ verified 1
src/_ZN14dScMgD3DBase_c11AfterRenderEj.cpp _ZN14dScMgD3DBase_c11AfterRenderEj ✅ verified 1
src/_ZN14dScMgD3DBase_c12BeforeRenderEv.cpp _ZN14dScMgD3DBase_c12BeforeRenderEv ✅ verified 1
src/_ZN14dScMgD3DBase_c14BeforeBehaviorEv.cpp _ZN14dScMgD3DBase_c14BeforeBehaviorEv ✅ verified 1
src/_ZN14dScMgD3DBase_c16OnAimedAtWithEggEv.cpp _ZN14dScMgD3DBase_c16OnAimedAtWithEggEv ✅ verified 1
src/_ZN14dScMgD3DBase_c18AfterInitResourcesEj.cpp _ZN14dScMgD3DBase_c18AfterInitResourcesEj ✅ verified 1
src/_ZN14dScMgD3DBase_c19BeforeInitResourcesEv.cpp _ZN14dScMgD3DBase_c19BeforeInitResourcesEv ✅ verified 1
src/_ZN14dScMgD3DBase_c21AfterCleanupResourcesEj.cpp _ZN14dScMgD3DBase_c21AfterCleanupResourcesEj ✅ verified 1
src/_ZN14dScMgD3DBase_c25OnAimedAtWithEggReturnVecEv.c _ZN14dScMgD3DBase_c25OnAimedAtWithEggReturnVecEv ✅ verified 1
src/_ZN14dScMgD3DBase_c9Virtual7CEv.c _ZN14dScMgD3DBase_c9Virtual7CEv ✅ verified 1
src/_ZN14dScMgD3DBase_cD0Ev.cpp _ZN14dScMgD3DBase_cD0Ev ✅ verified 1
src/_ZN14dScMgD3DBase_cD1Ev.cpp _ZN14dScMgD3DBase_cD1Ev ✅ verified 1
src/_ZN14dScMgMemory2_c13InitResourcesEv.cpp _ZN14dScMgMemory2_c13InitResourcesEv ✅ verified 1
src/_ZN14dScMgMemory2_c13OnTurnIntoEggEi.c _ZN14dScMgMemory2_c13OnTurnIntoEggEi ✅ verified 1
src/_ZN14dScMgMemory2_c13OnYoshiTryEatEi.c _ZN14dScMgMemory2_c13OnYoshiTryEatEi ✅ verified 1
src/_ZN14dScMgMemory2_c15OnGroundPoundedEv.c _ZN14dScMgMemory2_c15OnGroundPoundedEv ✅ verified 1
src/_ZN14dScMgMemory2_c6RenderEv.cpp _ZN14dScMgMemory2_c6RenderEv ✅ verified 1
src/_ZN14dScMgMemory2_c8BehaviorEv.cpp _ZN14dScMgMemory2_c8BehaviorEv ✅ verified 1
src/_ZN14dScMgMemory2_cD0Ev.cpp _ZN14dScMgMemory2_cD0Ev ✅ verified 1
src/_ZN14dScMgMemory2_cD1Ev.cpp _ZN14dScMgMemory2_cD1Ev ✅ verified 1
src/_ZN15dScMgCurling2_c13InitResourcesEv.cpp _ZN15dScMgCurling2_c13InitResourcesEv ✅ verified 1
src/_ZN15dScMgCurling2_c13OnYoshiTryEatEi.cpp _ZN15dScMgCurling2_c13OnYoshiTryEatEi ✅ verified 1
src/_ZN15dScMgCurling2_c6RenderEv.cpp _ZN15dScMgCurling2_c6RenderEv ✅ verified 1
src/_ZN15dScMgCurling2_c8BehaviorEv.cpp _ZN15dScMgCurling2_c8BehaviorEv ✅ verified 1
src/_ZN15dScMgCurling2_cD0Ev.cpp _ZN15dScMgCurling2_cD0Ev ✅ verified 1
src/_ZN15dScMgCurling2_cD1Ev.cpp _ZN15dScMgCurling2_cD1Ev ✅ verified 1
src/_ZN15dScMgHanachan_c13InitResourcesEv.cpp _ZN15dScMgHanachan_c13InitResourcesEv ✅ verified 1
src/_ZN15dScMgHanachan_c16CleanupResourcesEv.cpp _ZN15dScMgHanachan_c16CleanupResourcesEv ✅ verified 1
src/_ZN15dScMgHanachan_c6RenderEv.cpp _ZN15dScMgHanachan_c6RenderEv ✅ verified 1
src/_ZN15dScMgHanachan_c8BehaviorEv.cpp _ZN15dScMgHanachan_c8BehaviorEv ✅ verified 1
src/_ZN15dScMgHanachan_cD0Ev.cpp _ZN15dScMgHanachan_cD0Ev ✅ verified 1
src/_ZN15dScMgHanachan_cD1Ev.cpp _ZN15dScMgHanachan_cD1Ev ✅ verified 1
src/_ZN15dScMgPachinko_c13InitResourcesEv.cpp _ZN15dScMgPachinko_c13InitResourcesEv ✅ verified 1
src/_ZN15dScMgPachinko_c6RenderEv.cpp _ZN15dScMgPachinko_c6RenderEv ✅ verified 1
src/_ZN15dScMgPachinko_c8BehaviorEv.cpp _ZN15dScMgPachinko_c8BehaviorEv ✅ verified 1
src/_ZN15dScMgPachinko_cD0Ev.cpp _ZN15dScMgPachinko_cD0Ev ✅ verified 1
src/_ZN15dScMgPachinko_cD1Ev.cpp _ZN15dScMgPachinko_cD1Ev ✅ verified 1
src/_ZN15dScMgRoulette_c13InitResourcesEv.cpp _ZN15dScMgRoulette_c13InitResourcesEv ✅ verified 1
src/_ZN15dScMgRoulette_c13OnYoshiTryEatEi.cpp _ZN15dScMgRoulette_c13OnYoshiTryEatEi ✅ verified 1
src/_ZN15dScMgRoulette_c16CleanupResourcesEv.cpp _ZN15dScMgRoulette_c16CleanupResourcesEv ✅ verified 1
src/_ZN15dScMgRoulette_c6RenderEv.cpp _ZN15dScMgRoulette_c6RenderEv ✅ verified 1
src/_ZN15dScMgRoulette_c8BehaviorEv.cpp _ZN15dScMgRoulette_c8BehaviorEv ✅ verified 1
src/_ZN15dScMgRoulette_cD0Ev.cpp _ZN15dScMgRoulette_cD0Ev ✅ verified 1
src/_ZN15dScMgRoulette_cD1Ev.cpp _ZN15dScMgRoulette_cD1Ev ✅ verified 1
src/_ZN15dScMgSnowball_c13InitResourcesEv.cpp _ZN15dScMgSnowball_c13InitResourcesEv ✅ verified 1
src/_ZN15dScMgSnowball_c16CleanupResourcesEv.cpp _ZN15dScMgSnowball_c16CleanupResourcesEv ✅ verified 1
src/_ZN15dScMgSnowball_c6RenderEv.cpp _ZN15dScMgSnowball_c6RenderEv ✅ verified 1
src/_ZN15dScMgSnowball_c8BehaviorEv.cpp _ZN15dScMgSnowball_c8BehaviorEv ✅ verified 1
src/_ZN15dScMgSnowball_c8OnKickedEv.c _ZN15dScMgSnowball_c8OnKickedEv ✅ verified 1
src/_ZN15dScMgSnowball_cD0Ev.cpp _ZN15dScMgSnowball_cD0Ev ✅ verified 1
src/_ZN15dScMgSnowball_cD1Ev.cpp _ZN15dScMgSnowball_cD1Ev ✅ verified 1
src/_ZN16dScMgPachinko2_c13InitResourcesEv.cpp _ZN16dScMgPachinko2_c13InitResourcesEv ✅ verified 1
src/_ZN16dScMgPachinko2_c6RenderEv.cpp _ZN16dScMgPachinko2_c6RenderEv ✅ verified 1
src/_ZN16dScMgPachinko2_c8BehaviorEv.cpp _ZN16dScMgPachinko2_c8BehaviorEv ✅ verified 1
src/_ZN16dScMgPachinko2_cD0Ev.cpp _ZN16dScMgPachinko2_cD0Ev ✅ verified 1
src/_ZN16dScMgPachinko2_cD1Ev.cpp _ZN16dScMgPachinko2_cD1Ev ✅ verified 1
src/_ZN16dScMgSmartball_c21AfterCleanupResourcesEj.cpp _ZN16dScMgSmartball_c21AfterCleanupResourcesEj ✅ verified 1
src/_ZN16dScMgSmartball_c6RenderEv.cpp _ZN16dScMgSmartball_c6RenderEv ✅ verified 1
src/_ZN16dScMgSmartball_c8BehaviorEv.cpp _ZN16dScMgSmartball_c8BehaviorEv ✅ verified 1
src/_ZN16dScMgSmartball_cD0Ev.cpp _ZN16dScMgSmartball_cD0Ev ✅ verified 1
src/_ZN16dScMgSmartball_cD1Ev.cpp _ZN16dScMgSmartball_cD1Ev ✅ verified 1
src/_ZN17dScMgTrampoline_c13InitResourcesEv.cpp _ZN17dScMgTrampoline_c13InitResourcesEv ✅ verified 1
src/_ZN17dScMgTrampoline_c13OnYoshiTryEatEi.c _ZN17dScMgTrampoline_c13OnYoshiTryEatEi ✅ verified 1
src/_ZN17dScMgTrampoline_c16CleanupResourcesEv.cpp _ZN17dScMgTrampoline_c16CleanupResourcesEv ✅ verified 1
src/_ZN17dScMgTrampoline_c6RenderEv.cpp _ZN17dScMgTrampoline_c6RenderEv ✅ verified 1
src/_ZN17dScMgTrampoline_c8BehaviorEv.cpp _ZN17dScMgTrampoline_c8BehaviorEv ✅ verified 1
src/_ZN17dScMgTrampoline_c9Virtual88Eiiii.c _ZN17dScMgTrampoline_c9Virtual88Eiiii ✅ verified 1
src/_ZN17dScMgTrampoline_cD0Ev.cpp _ZN17dScMgTrampoline_cD0Ev ✅ verified 1
src/_ZN17dScMgTrampoline_cD1Ev.cpp _ZN17dScMgTrampoline_cD1Ev ✅ verified 1
src/_ZN18dScMgTrampoline2_c13InitResourcesEv.cpp _ZN18dScMgTrampoline2_c13InitResourcesEv ✅ verified 1
src/_ZN18dScMgTrampoline2_c16CleanupResourcesEv.cpp _ZN18dScMgTrampoline2_c16CleanupResourcesEv ✅ verified 1
src/_ZN18dScMgTrampoline2_c6RenderEv.cpp _ZN18dScMgTrampoline2_c6RenderEv ✅ verified 1
src/_ZN18dScMgTrampoline2_c8BehaviorEv.cpp _ZN18dScMgTrampoline2_c8BehaviorEv ✅ verified 1
src/_ZN18dScMgTrampoline2_c9Virtual88Eiiii.c _ZN18dScMgTrampoline2_c9Virtual88Eiiii ✅ verified 1
src/_ZN18dScMgTrampoline2_cD0Ev.cpp _ZN18dScMgTrampoline2_cD0Ev ✅ verified 1
src/_ZN18dScMgTrampoline2_cD1Ev.cpp _ZN18dScMgTrampoline2_cD1Ev ✅ verified 1
src/actors/dScMgBSC_c.cpp _ZN10dScMgBSC_cD1Ev + _ZN10dScMgBSC_cD0Ev + func_ov006_02124a04 + func_ov006_02124a08 + func_ov006_02124ae4 + func_ov006_02124b58 + func_ov006_02124bb4 + func_ov006_02124cb4 + func_ov006_02124dc0 + func_ov006_02124e1c + func_ov006_02124ec4 + func_ov006_02124fd8 + func_ov006_021250e4 + _ZN10dScMgBSC_c15OnGroundPoundedEv + _ZN10dScMgBSC_c13OnTurnIntoEggEi + _ZN10dScMgBSC_c13OnYoshiTryEatEi + _ZN10dScMgBSC_c6RenderEv + _ZN10dScMgBSC_c8BehaviorEv + _ZN10dScMgBSC_c13InitResourcesEv ✅ verified 19
src/actors/dScMgBase_c.cpp _ZN11dScMgBase_c16OnPendingDestroyEv + _ZN11dScMgBase_c6RenderEv + _ZN11dScMgBase_c12BeforeRenderEv + _ZN11dScMgBase_c8BehaviorEv + _ZN11dScMgBase_c14BeforeBehaviorEv + _ZN11dScMgBase_c21AfterCleanupResourcesEj + _ZN11dScMgBase_c18AfterInitResourcesEj + _ZN11dScMgBase_c19BeforeInitResourcesEv ✅ verified 8
src/actors/dScMgCard_c.cpp _ZN11dScMgCard_cD1Ev + _ZN11dScMgCard_cD0Ev + func_ov006_020d96e0 + func_ov006_020d96f0 + func_ov006_020d970c + func_ov006_020d978c + func_ov006_020d9998 + func_ov006_020d99a4 + func_ov006_020d99ec + func_ov006_020d9a14 + func_ov006_020d9bd0 + func_ov006_020d9bdc + func_ov006_020d9c5c + func_ov006_020da00c + func_ov006_020da0ac + func_ov006_020da154 + func_ov006_020da174 + func_ov006_020da420 + func_ov006_020da4ac + func_ov006_020da5e8 + func_ov006_020da834 + func_ov006_020da860 + func_ov006_020da88c + func_ov006_020da8b8 + func_ov006_020da8e4 + func_ov006_020da974 + _ZN11dScMgCard_c16CleanupResourcesEv + _ZN11dScMgCard_c6RenderEv + _ZN11dScMgCard_c8BehaviorEv + func_ov006_020dac34 + _ZN11dScMgCard_c15OnGroundPoundedEv + _ZN11dScMgCard_c13OnTurnIntoEggEi + _ZN11dScMgCard_c13OnYoshiTryEatEi + _ZN11dScMgCard_c13InitResourcesEv ✅ verified 34
src/actors/dScMgMCarlo2_c.cpp _ZN14dScMgMCarlo2_cD1Ev + _ZN14dScMgMCarlo2_cD0Ev + func_ov006_020f8ff0 + func_ov006_020f9000 + func_ov006_020f94f4 + func_ov006_020f9560 + func_ov006_020f95f0 + func_ov006_020f9668 + func_ov006_020f96e0 + func_ov006_020f9760 + func_ov006_020f98dc + func_ov006_020f9994 + func_ov006_020f9bec + func_ov006_020f9cbc + func_ov006_020f9d68 + func_ov006_020f9db8 + func_ov006_020f9f40 + _ZN14dScMgMCarlo2_c16CleanupResourcesEv + _ZN14dScMgMCarlo2_c6RenderEv + _ZN14dScMgMCarlo2_c8BehaviorEv + _ZN14dScMgMCarlo2_c13OnTurnIntoEggEi + _ZN14dScMgMCarlo2_c13OnYoshiTryEatEi + _ZN14dScMgMCarlo2_c13InitResourcesEv ✅ verified 23
src/actors/dScMgMCarlo_c.cpp _ZN13dScMgMCarlo_cD1Ev + _ZN13dScMgMCarlo_cD0Ev + func_ov006_020f7730 + func_ov006_020f7740 + func_ov006_020f7994 + func_ov006_020f7a00 + func_ov006_020f7a90 + func_ov006_020f7b10 + func_ov006_020f7b90 + func_ov006_020f7c10 + func_ov006_020f7e2c + func_ov006_020f7ee4 + func_ov006_020f8154 + func_ov006_020f8224 + func_ov006_020f82d0 + func_ov006_020f8320 + func_ov006_020f84a8 + func_ov006_020f8540 + _ZN13dScMgMCarlo_c6RenderEv + _ZN13dScMgMCarlo_c8BehaviorEv + _ZN13dScMgMCarlo_c13OnTurnIntoEggEi + _ZN13dScMgMCarlo_c13OnYoshiTryEatEi + _ZN13dScMgMCarlo_c13InitResourcesEv ✅ verified 23
src/actors/dScMgSingle3DBase_c.cpp _ZN19dScMgSingle3DBase_cD1Ev + _ZN19dScMgSingle3DBase_cD0Ev + func_ov006_0210a534 + _ZN19dScMgSingle3DBase_c24OnHitByCannonBlastedCharEv + _ZN19dScMgSingle3DBase_c21AfterCleanupResourcesEj + _ZN19dScMgSingle3DBase_c12BeforeRenderEv + _ZN19dScMgSingle3DBase_c14BeforeBehaviorEv + _ZN19dScMgSingle3DBase_c18AfterInitResourcesEj + _ZN19dScMgSingle3DBase_c9Virtual84Ev ✅ verified 9
src/func_ov004_020b0a54.cpp func_ov004_020b0a54 ✅ verified 1
src/func_ov006_0210d6b8.cpp func_ov006_0210d6b8 ✅ 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

Copy link
Copy Markdown
Collaborator Author

Self-review

One account owns this repo, so nobody can press Approve here. This is the sign-off in writing.

Merge-tree gate, not the branch. tools/premerge_check.py --base origin/main --fetch 2102 2106 2107 2108 2110 (tool extracted from origin/main, run against the actual merge tree d1f1904b6439, head bdb2510ca600):

gate                base     merge    verdict
------------------  -------  -------  ------------
converted-ratchet   pass     pass     ok
dead-references     pass     pass     ok
duplicate-sources   pass     pass     ok
header-offsets      pass     pass     ok
langmode-ratchet    pass     pass     ok
layout-check        pass     pass     ok
src-tu-refs         pass     pass     ok
source-coverage     pass     pass     ok

RESULT: nothing goes green -> red.

Every earlier PR in the stack was checked in the same run and is clean on all eight.

Bytes. rombuild -j16 --no-rom: 11,088 / 11,088 reproducing, 106/106 modules exact, 100.000000%. romdata_check against the slot-33 baseline moved exactly two counters — verifiedBytes 35,424 → 35,428 and partialBytes 11,996 → 12,120. That is +4 / +124 = 128 bytes = 32 tables × one word, the per-slot signature this campaign has produced at every slot from 18 up. Nothing else in the report changed in either direction.

Two things worth flagging in this one specifically

1. This is the first slot whose rename broke a link, not just a name. dScMgAmida_c::Virtual88 is the only override in the family that calls the base body instead of replacing it, so it held an extern on func_ov004_020ae3b4. apply_rename renames the definition, symbols.txt and delinks.txtnever callers. A grep after the rename found one extern and six call sites, not the four an earlier truncated grep had shown. All seven retargeted to _ZN11dScMgBase_c9Virtual88Eiiii. The post-rename sweep over .c/.cpp/.h/.md/.json/.txt is now standing procedure for this campaign.

2. check_dead_references prints "no broken markdown links" even when it fails. Its success line is "no new dead references", and the tail looks identical either way. Checked by exit code, it was red: notes/minigame-provenance.md named src/func_ov006_020d14c0.c, which this rename had just moved. Fixed; the gate now exits 0. Worth knowing for anyone reading a green-looking tail on this tool.

What the slot turned out to be

Virtual88(int cx, int cy, int colour, int size) is the family's pixel brush — walks a size×size square centred on (cx,cy), computes a 4bpp BG-char-VRAM address (x/8 + (y/8)*32)*32 + (y&7)*4, splices colour into the nibble at (x&7)*4. Arity is unanimous across all seven call sites (r1, r2, r3 and one stack word set up at every one).

It reads exactly the two object fields slot 33 initialises: obj+0x6c (BG layer 0-3, anything else returns without drawing — which is what slot 33's -1 buys) and obj+0x68 (the wrap-region gate). So slot 33 leaves the brush disabled and a minigame arms it by picking a layer. Two slots, one mechanism, and neither was legible alone.

A guess recorded independently in include/dScMgTeresa_c.h — "draws a HUD digit/glyph" — turned out right, and both halves of it are now confirmed in that header rather than left as a prediction.

Ready to land from my side. Ordering is yours.

@andrewboudreau andrewboudreau left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at bdb2510ca600. Every vtable claim in this PR is correct, and the arity claim — the one that carries the whole thing — is right and better-evidenced than the PR realises. I read the bytes independently rather than agreeing with the description, and I came back with five corrections. Three of them make your claims stronger. Two are wrong in a way I have flagged once already on this stack, and one of those is now printed in a header. There is also one concrete ask, which I measured rather than guessed.

The arity claim is earned

On #2107 I struck MEASURED from the slot-32 evidence because the three sites there put the pointer in r1/r2/r3 and the mov r4,r1 park tracks caller liveness, not callee signature. Here the evidence is structurally decisive and I am not asking for anything on it. Site at 0x020ae690, as you cite it:

0x020ae684  str  r7, [sp]         <- arg 4
0x020ae688  ldr  r4, [r0]
0x020ae68c  ldr  r3, [sp, #0x3c]  <- arg 3
0x020ae690  ldr  r4, [r4, #0x88]
0x020ae694  mov  r1, sb           <- arg 1
0x020ae698  mov  r2, r8           <- arg 2
0x020ae69c  blx  r4

r0 = this, r1/r2/r3 = args 1-3, [sp,#0] = arg 4. That is AAPCS for a four-argument member call, it is unanimous across every site, and the pointer sits in r4 so it competes with nothing. virtual void Virtual88(int, int, int, int) is the right declaration.

I also confirmed the caller extent: func_ov004_020ae5c4, size 0x294, spanning 0x020ae5c4..0x020ae858, sitting immediately after the brush at 0x020ae3b4 (size 0x210) exactly as described.

Independent verification of the six vtable claims

Dumped from the cartridge, slot indices from each _ZTV's recorded address:

table addr slot 33 slot 34 agrees?
_ZTV11dScMgBase_c 020bc0c0 (ov004) 020b265c 020ae3b4 yes
_ZTV14dScMgD3DBase_c 0213c62c 020e7124 020ae3b4 inherits, does not override — yes
_ZTV12dScMgAmida_c 0213b918 020b265c 020d14c0 yes
_ZTV13dScMgTeresa_c 0213fa0c 020b265c 021200dc yes
_ZTV17dScMgTrampoline_c 0213fb34 020e7124 02120da8 yes
_ZTV18dScMgTrampoline2_c 0213fc7c 020e7124 02122cb0 yes

The structural claim is the interesting one and it holds: dScMgD3DBase_c carries the base's 020ae3b4 at slot 34, so the two trampolines really do have to declare it themselves rather than inheriting a shared body the way they do at 26-31 and 33. Four tables, four declarations, nothing shared — as written.

Config half is clean: five renames, all address- and size-preserving function renames, zero coined data symbols. I check for that specifically after #2096 and #2104.

Three corrections that strengthen the PR

1. Eight dispatch sites in the rasteriser, not seven. The header says "seven separate sites" twice. I count eight:

ldr@0x020ae608 -> blx@0x020ae60c   r4
ldr@0x020ae62c -> blx@0x020ae640   r5    <- not in your seven
ldr@0x020ae690 -> blx@0x020ae69c   r4
ldr@0x020ae6e4 -> blx@0x020ae6f0   r12
ldr@0x020ae73c -> blx@0x020ae748   r12
ldr@0x020ae784 -> blx@0x020ae790   r4
ldr@0x020ae7d8 -> blx@0x020ae7e4   r12
ldr@0x020ae830 -> blx@0x020ae83c   r12

The missed one is a genuine dispatch with the full four-argument setup:

0x020ae61c  ldr  r0, [sp, #4]      <- this
0x020ae620  str  r7, [sp]          <- arg 4
0x020ae624  ldr  r5, [r0]
0x020ae628  ldr  r4, [sp, #0x38]
0x020ae62c  ldr  r5, [r5, #0x88]
0x020ae630  ldr  r3, [sp, #0x3c]   <- arg 3
0x020ae634  mov  r1, sb            <- arg 1
0x020ae638  mov  r2, r8            <- arg 2
0x020ae63c  str  r4, [sp, #0xc]    <- local spill, non-contiguous, not an argument
0x020ae640  blx  r5

Four instructions separate the ldr from the blx, which is why a tight scan drops it. Worth saying because I made the same mistake first: my initial pass required the blx to follow immediately and found 8 image-wide against your 14, which would have "refuted" a correct claim. A dispatch scan needs a lookahead window with a break on redefinition of the loaded register, and the count is window-dependent — please state the window when you quote a census.

2. Four sites overwrite r0, not two. The header names 0x020ae7e4 and 0x020ae83c. Add 0x020ae6f0 and 0x020ae748:

after blx@0x020ae6f0:  ldr r0, [sp, #8]     OVERWRITES r0
after blx@0x020ae748:  ldr r0, [sp, #8]     OVERWRITES r0
after blx@0x020ae7e4:  ldr r0, [sp, #0xc]   OVERWRITES r0
after blx@0x020ae83c:  ldr r0, [sp, #0xc]   OVERWRITES r0

Half the sites discard the return value on the next instruction rather than two of seven. void is better supported than you claim.

3. Teresa's "barely touches this" is stronger than "barely". It never touches it. The first reference to r0 in 0x021200dc is add r0, sl, sl, lsr #31 at 0x021200ec — a write into a dead register. And ldr sl,[sp,#0x40] at 0x021200e4 is the incoming fourth argument (9 pushed registers + 0x1c = 0x40), so that body genuinely reads args 1-4 and not this. The five-parameter definition with an unused a0 is the correct shape.

Two corrections that cut against a claim, one of them repeated

The header says:

Image-wide the +0x88 dispatch pair appears 14 times, 7 in ov004/ov006 and 7 in ov064

ov006 contains zero +0x88 dispatches. All of the in-family sites are in ov004. Saying "ov004/ov006" implies the overriding classes dispatch through this slot; they do not — the call graph really is one function in ov004, which is the more striking claim and the one your own prose makes two lines earlier.

The ov064 seven are not this virtual, and this is the same misattribution I corrected on #2107 at +0x80. All seven are one shape:

0x02116828  mov  r1, #0
0x0211682c  mov  r0, r5
0x02116830  strb r1, [r5, #0x3f9]
0x02116834  ldr  r1, [r0]
0x02116838  ldr  r1, [r1, #0x88]   <- pointer occupies r1
0x0211683c  blx  r1
0x02116840  add  sp, sp, #0x24

The loaded pointer is in r1, so r1 cannot also carry cx; r2, r3 and [sp] are never written. Zero explicit arguments. Virtual88(int,int,int,int) takes four. This is exactly the structural refutation your own slot-33 argument turned on at +0x84, running the other direction. Corroborating: config/arm9/overlays/ov064/symbols.txt contains zero dScMg symbols, and all seven sites live inside func_ov064_021166f0 and func_ov064_02116754 — a different hierarchy that happens to put something else at vtable offset 0x88.

So the honest census is 8 in ov004, 0 in ov006, and 7 unrelated in ov064 (15 raw at window 8; your 14 is the window-4 figure). The fix is one sentence, but it matters more here than on #2107 because on #2107 it lived in a PR description and here it is being written into a header that outlives the PR.

The one ask: close the arity split in Amida, and it is free

include/dScMgAmida_c.h declares four parameters. src/_ZN12dScMgAmida_c9Virtual88Eiiii.c defines three:

void _ZN12dScMgAmida_c9Virtual88Eiiii(Ctx* ctx, int y, int x, int arg3)

under a symbol mangled Eiiii. Your justification for this is correct — I verified it. The prologue is push {r4,r5,r6,r7,r8,sb,lr} + sub sp,sp,#4, putting the incoming fourth argument at [sp,#32], and across the whole 0x498-byte body there is not one load from any positive sp offset. All six sp accesses are stores to [sp,#0], which is the outgoing fifth argument for the base-brush call. It really does ignore its fourth parameter and hardcode 2 or 4 instead.

But "the reconstruction had nothing to name" is a reason to name it anyway, not a reason to leave the surfaces disagreeing — and this is the same header-declares-X / definition-defines-Y shape I have changes-requested on five times in this stack, just in arity rather than return type. It bites concretely at promotion: an out-of-line void dScMgAmida_c::Virtual88(int,int,int) has no matching declaration, and this family is already being promoted (#2064, #2067, #2077, #2079).

I measured the fix rather than assuming it. Adding the unused parameter:

void _ZN12dScMgAmida_c9Virtual88Eiiii(Ctx* ctx, int y, int x, int arg3, int size)

compiled under the file's own pin:

pin:                2004/b56
3-param (as-is):    (True, '2004/b56')
4-param (unused):   (True, '2004/b56')

Byte-identical to the ROM either way, which is what you would expect for a stack argument that is never loaded. It costs nothing and it removes the only surface disagreement this PR introduces. Please add it, and reword the closing paragraph — the interesting fact is that Amida ignores the size and picks 2 or 4 per case, which is a real observation about the game, not that the parameter is absent from the signature.

Note that tools/check_decl_return_types.py will not catch this: it compares return types only. Extending it to compare arity is on my list, not yours.

Return type: this PR follows #2108's answer, and that is the news

It declares void, all five bodies fall off the end, and it adds zero new header/definition splits. It is the second PR in the stack to do that and the first that had a real chance to get it wrong — a four-argument slot is exactly where a speculative int would have been tempting. This is what I asked for at the bottom of the stack on #2108, arriving on its own. Nothing to correct here.

Off-by-one, and it is smaller than I previously said

Correcting my own note from #2106/#2107: slot 30's line is right. The chain is 28 first, 29 second, 30 third, so only two lines are stale:

:607  slot 31 -- "the third consecutive slot no body pins."       -> fourth
:694  slot 32 -- "and the FOURTH consecutive slot no body pins."  -> fifth

Still fix them in whichever of #2106/#2107 lands second, not here — all three PRs touch those lines and would conflict.

Gates

vs origin/cpp/minigame-slot33 @a3d94efa1a0d   merge tree 16b1db00dc58   8/8 pass -> pass

mergeStateStatus: CLEAN, PR validation SUCCESS. The branch is a clean 8-commit stack of this campaign's own slot declarations — no foreign ancestry, unlike #2109. Merge order stays #2099#2100#2102#2106#2107#2108#2110.

Summary

Approve on content, with one code ask and two prose fixes.

  • Add the unused fourth parameter to Amida's definition. Measured byte-neutral above.
  • Drop "ov006" from the image-wide census, and either drop the ov064 seven or say explicitly that they are a different hierarchy with zero arguments. Do not leave a header asserting they are the same virtual.
  • Optionally take the three strengthening corrections: eight sites, four r0-overwrites, Teresa never reads this.

The vtable work is right, the arity work is right and hard-won, and the return type follows the answer the stack needed. My only real objection is that a census I already corrected once has been promoted from a PR description into a header.

Base automatically changed from cpp/minigame-slot33 to main September 1, 2026 07:02
@andrewboudreau
andrewboudreau merged commit 18a8ed1 into main Sep 1, 2026
8 of 9 checks passed
@andrewboudreau
andrewboudreau deleted the cpp/minigame-slot34 branch September 1, 2026 07:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

attribution-override Maintainer accepts this PR's contributor-credit changes; validation reports them as warnings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant