From 4a9c8dc886f6c8c57772de946d99de15eebb2d23 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 13:51:07 -0400 Subject: [PATCH 01/15] GC2D/GCConsole2: fix the timer and news-telop update logic Three corrections in the HUD update path, found by reading the instruction diffs rather than by sweeping respellings. setTimer had two logic holes. The non-sentinel path never assigned timerValue, so the argument was silently dropped, and the field was then written back from the raw argument instead of the value actually computed and clamped. The low-time colour test is also a nested check on the materialised pane colour, not a short-circuit conjunction. checkChangeTelopArray selected the wrong two Dolpic news tables: the 5:0001 && 5:0002 branch takes scDolpicNewsDolpic5_4 and the neither-flag branch takes 5_1, not the other way round. This is a selection fix at the call sites; the definition order of the tables is unchanged, since permuting those corrects the code offsets but breaks .data symbol order. updateCoinBlendPaneState tests the pane flag with retail's branch polarity. No new byte-identical functions. setTimer 96.12% -> 99.81%, processDownCoin 93.91% -> 95.82%, checkChangeTelopArray 99.93% -> 99.94% (now frame-only). --- src/GC2D/GCConsole2.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 5cbff312a..cf4036615 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -1313,9 +1313,7 @@ static inline void updateCoinBlendPaneState(TBlendPane*& pane, bool& isFinished) { pane->update(); - if (pane->unk24) { - isFinished = false; - } else { + if (!pane->unk24) { bool paneFinished = false; if (pane->unk14.x1 == 0 && pane->unk14.y1 == 0) paneFinished = true; @@ -1325,6 +1323,8 @@ static inline void updateCoinBlendPaneState(TBlendPane*& pane, bool& isFinished) TGCConsole2::cCoinBotPoint); isFinished = false; } + } else { + isFinished = false; } } @@ -2981,6 +2981,8 @@ void TGCConsole2::setTimer(s32 param_1) timerValue = unk514 - timerValue; } } + } else { + timerValue = param_1; } // Cap at 5999.99 seconds (99:59.99) @@ -3007,12 +3009,15 @@ void TGCConsole2::setTimer(s32 param_1) ((J2DPicture*)unk458[5]->getPane()) ->changeTexture(unkE0[centis % 10]->getTexInfo(), 0); } else { - if (timerValue < 1000 - && ((J2DPicture*)unk458[9]->getPane())->mWhite != unk508) { - for (int i = 6; i <= 9; i++) { - ((J2DPicture*)unk458[i]->getPane())->mWhite = unk508; + if (timerValue < 1000) { + JUtility::TColor currentColor + = ((J2DPicture*)unk458[9]->getPane())->mWhite; + if (currentColor != unk508) { + for (int i = 6; i <= 9; i++) { + ((J2DPicture*)unk458[i]->getPane())->mWhite = unk508; + } + ((J2DPicture*)unk480[2]->getPane())->mWhite = unk508; } - ((J2DPicture*)unk480[2]->getPane())->mWhite = unk508; } ((J2DPicture*)unk458[6]->getPane()) ->changeTexture(unkE0[seconds / 10]->getTexInfo(), 0); @@ -3029,7 +3034,7 @@ void TGCConsole2::setTimer(s32 param_1) SMSGetMSound()->playTimer(timerValue * 10); } - unk4FC = param_1; + unk4FC = timerValue; } void TGCConsole2::startMoveTimer(int param_1) @@ -3408,14 +3413,14 @@ void TGCConsole2::checkChangeTelopArray() case 5: if (TFlagManager::smInstance->getBool(0x50001)) { if (TFlagManager::smInstance->getBool(0x50002)) - unk570 = scDolpicNewsDolpic5_1; + unk570 = scDolpicNewsDolpic5_4; else unk570 = scDolpicNewsDolpic5_2; } else { if (TFlagManager::smInstance->getBool(0x50002)) unk570 = scDolpicNewsDolpic5_3; else - unk570 = scDolpicNewsDolpic5_4; + unk570 = scDolpicNewsDolpic5_1; } break; case 8: { From 0bf0081cb5ecab24d3fed4337de055bfdb2adb81 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:46:20 -0400 Subject: [PATCH 02/15] GC2D/GCConsole2: restore the below-screen offset inline startDisappearTimer now matches byte for byte, and startDownLeftBot goes 98.68% -> 99.78%. Both were blocked by the same construct. Where we compute `525 - y1` retail emits two instructions: subfic r3, r0, 0x1d1 (465 - y1) addi r0, r3, 0x3c (+ 60) not the folded `subfic r0, r0, 0x20d` (525 - y1) we were producing. MWCC folds constants before it inlines and does not re-fold afterwards, so the 465 can only reach the caller un-added if it arrives across an inline boundary. Writing the arithmetic out as `465 - y1 + 60` does not reproduce it -- that folds -- which is what makes this codegen evidence for a real function rather than a spelling choice. This is the helper TheAzack9 asked to hold off on during the #152 review, restored under his own name and marked with his `// Possibly inline` convention, because the disassembly now argues for it. He also suggested it belongs on TExPane rather than here; that call is his, and this keeps it file-local until he makes it. Two things measured and deliberately not done. Routing the sites that want a plain `465 - y1` through the same helper costs a match, so retail really does have both forms and the helper is only used where the `+ 60` appears. And startDownLeftBot's frame is now 16 bytes too large, which is a frame-size question, not a code one -- left alone rather than fitted. --- src/GC2D/GCConsole2.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index ccbfe371d..5dfd84ca4 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -56,6 +56,12 @@ JUTPoint TGCConsole2::cCoinTopPoint(0, 0); JUTPoint TGCConsole2::cCoinMidPoint(0, 45); JUTPoint TGCConsole2::cCoinBotPoint(0, 0); +// Possibly inline +static inline int getOffsetForBelowScreen(const TExPane* pane) +{ + return 465 - pane->getInitialBounds().y1; +} + // fabricated static inline void setEmitterToPaneCenter(JPABaseEmitter* emitter, J2DPane* pane) @@ -2371,17 +2377,17 @@ void TGCConsole2::startDownLeftBot() unk5A = 1; if (unk44C->getPane()->isVisible() && unk44C->isInterpolatorAtZero()) { - unk44C->updatePaneOffset(20, 0, 525 - unk44C->getInitialBounds().y1); + unk44C->updatePaneOffset(20, 0, getOffsetForBelowScreen(unk44C) + 60); unk51C = 1; } if (unk428->getPane()->isVisible()) { - unk428->updatePaneOffset(20, 0, 525 - unk428->getInitialBounds().y1); + unk428->updatePaneOffset(20, 0, getOffsetForBelowScreen(unk428) + 60); unk448 = 1; } if (unk3FC->getPane()->isVisible()) { - unk3FC->updatePaneOffset(20, 0, 525 - unk3FC->getInitialBounds().y1); + unk3FC->updatePaneOffset(20, 0, getOffsetForBelowScreen(unk3FC) + 60); unk426 = 1; } } @@ -2440,7 +2446,7 @@ void TGCConsole2::startDisappearTelop() void TGCConsole2::startDisappearTimer() { - unk44C->updatePaneOffset(40, 0, 525 - unk44C->getInitialBounds().y1); + unk44C->updatePaneOffset(40, 0, getOffsetForBelowScreen(unk44C) + 60); unk3F = 1; unk5A = 1; } From 9c9ef3c24d30a28cd72857ea57506c8d53952e20 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:15:35 -0400 Subject: [PATCH 03/15] GC2D/GCConsole2: read initial bounds through per-field accessors Four functions match byte for byte: startAppearCoin, startAppearRedCoin, startInsertJetBalloon and startDisappearStar. Every `->mInitialBounds` use costs exactly 8 bytes of stack in retail and none in ours. Measured across six functions before touching anything: startAppearCoin short by 8 1 use startAppearTank short by 8 1 use startAppearRedCoin short by 16 2 uses startInsertJetBalloon short by 16 2 uses startAppearStar short by 16 2 uses startDisappearStar short by 16 2 uses Those functions were otherwise byte-identical -- cancel the frame delta and the instruction diff goes completely empty -- so the 8 bytes are the footprint of an inlined accessor, which MWCC reserves and never reclaims. The existing `getInitialBounds()` returns `const JUTRect&`, which gets the frame right but emits an extra `addi` to form the address: target: lwz r3, 8(r28) ours: addi r3, r28, 4 ; lwz r3, 4(r3) A per-field accessor returning `int` gives both the reserved slot and the direct load. JUTRect already exposes getWidth() and getHeight() the same way, so this follows the existing shape rather than inventing one. Returning JUTRect by value was tried first and is decisively wrong -- 8099 -> 8091 project, 28 -> 21 unit -- which fits, since JUTRect is 16 bytes and the deltas are 8. This also corrects frames we were not aiming at: startAppearTank and startAppearStar now match exactly (both are left unmatched only by register allocation), startDisappearCoin went +24 -> +8, endCameraDemo +32 -> +24, startAppearTelop +48 -> +40. getOffsetForAboveScreen is the same constant-folding argument as getOffsetForBelowScreen in 0bf0081c. Retail emits `neg` then `add` where we fold to a single `subf`, which means the negation happened behind an inline boundary. It is used at exactly the two sites where an operation sits outside that boundary -- `+ unk26A` and `- getHeight()` -- because those are the only places the boundary is observable; applying it to all ten sites costs four matches and was measured and rejected. --- include/GC2D/ExPane.hpp | 4 ++++ src/GC2D/GCConsole2.cpp | 52 ++++++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/include/GC2D/ExPane.hpp b/include/GC2D/ExPane.hpp index 139da8006..828dab53c 100644 --- a/include/GC2D/ExPane.hpp +++ b/include/GC2D/ExPane.hpp @@ -101,6 +101,10 @@ class TExPane { J2DPane* getPane() const { return mPane; } const JUTRect& getInitialBounds() const { return mInitialBounds; } + int getInitialX1() const { return mInitialBounds.x1; } + int getInitialY1() const { return mInitialBounds.y1; } + int getInitialX2() const { return mInitialBounds.x2; } + int getInitialY2() const { return mInitialBounds.y2; } bool isBoundsAnimationCompleted() const { diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 5dfd84ca4..db8553abf 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -56,10 +56,16 @@ JUTPoint TGCConsole2::cCoinTopPoint(0, 0); JUTPoint TGCConsole2::cCoinMidPoint(0, 45); JUTPoint TGCConsole2::cCoinBotPoint(0, 0); +// Possibly inline +static inline int getOffsetForAboveScreen(const TExPane* pane) +{ + return -(pane->getInitialY2() + 1); +} + // Possibly inline static inline int getOffsetForBelowScreen(const TExPane* pane) { - return 465 - pane->getInitialBounds().y1; + return 465 - pane->getInitialY1(); } // fabricated @@ -634,7 +640,7 @@ static inline void detachBoundPaneFromParent(TBoundPane* pane) // fabricated static inline void initHiddenPaneAbove(TExPane* pane) { - pane->updatePaneOffset(1, 0, -(pane->mInitialBounds.y2 + 1)); + pane->updatePaneOffset(1, 0, -(pane->getInitialY2() + 1)); pane->update(); } @@ -2149,7 +2155,7 @@ void TGCConsole2::endCameraDemo() unk59 = 1; unk7C = 0; unk2F8->getPane()->show(); - unk2F8->setPaneOffset(unk98, 0, 0, 0, 465 - unk2F8->mInitialBounds.y1); + unk2F8->setPaneOffset(unk98, 0, 0, 0, 465 - unk2F8->getInitialY1()); unk26C->setPanePosition(50, JUTPoint(0, 100), JUTPoint(0, -30), JUTPoint(0, -30)); unk274->getPane()->hide(); @@ -2192,7 +2198,7 @@ void TGCConsole2::startAppearTank() unk7C = 0; unk2F8->getPane()->show(); - unk2F8->setPaneOffset(unk98, 0, 0, 0, 465 - unk2F8->mInitialBounds.y1); + unk2F8->setPaneOffset(unk98, 0, 0, 0, 465 - unk2F8->getInitialY1()); unk26C->setPanePosition(50, JUTPoint(0, 100), JUTPoint(0, -30), JUTPoint(0, -30)); @@ -2206,7 +2212,7 @@ void TGCConsole2::startDisappearTank() unk4B = 1; unk5A = 1; - int offset = 465 - unk2F8->mInitialBounds.y1; + int offset = 465 - unk2F8->getInitialY1(); offset += 60; unk2F8->updatePaneOffset(40, 0, offset); @@ -2230,8 +2236,7 @@ void TGCConsole2::startAppearCoin() unk88 = 0; unk108->getPane()->show(); - unk108->setPaneOffset(unk98, 0, unk26A, 0, - -(unk108->mInitialBounds.y2 + 1)); + unk108->setPaneOffset(unk98, 0, unk26A, 0, -(unk108->getInitialY2() + 1)); unkC8->setPanePosition(50, cDownTopPoint, cDownMidPoint, cDownMidPoint); @@ -2252,10 +2257,10 @@ void TGCConsole2::startDisappearCoin() if (unk140->isInterpolatorAtZero()) unk140->updatePaneOffset( 40, 0, - -(unk140->mInitialBounds.y2 + unk128->getPane()->getHeight() + 1)); + -(unk140->getInitialY2() + unk128->getPane()->getHeight() + 1)); - int offset = -(unk108->mInitialBounds.y2 + 1); - unk108->updatePaneOffset(40, 0, offset - unkC8->getPane()->getHeight()); + unk108->updatePaneOffset( + 40, 0, getOffsetForAboveScreen(unk108) - unkC8->getPane()->getHeight()); unk124->setStatus(JPABaseEmitter::STATUS_STOP_EMIT); } @@ -2414,7 +2419,7 @@ void TGCConsole2::startAppearTelop(bool param_1) unk56D = 1; unk520->getPane()->show(); - unk520->setPaneOffset(80, 0, 0, 0, 465 - unk520->mInitialBounds.y1); + unk520->setPaneOffset(80, 0, 0, 0, 465 - unk520->getInitialY1()); if (param_1) { // TODO: needs regswapping @@ -2441,7 +2446,7 @@ void TGCConsole2::startDisappearTelop() unk43 = 1; unk5A = 1; - unk520->updatePaneOffset(80, 0, 465 - unk520->getInitialBounds().y1); + unk520->updatePaneOffset(80, 0, 465 - unk520->getInitialY1()); } void TGCConsole2::startDisappearTimer() @@ -2498,7 +2503,7 @@ void TGCConsole2::startInsertTimer() unk59 = 1; unk44C->getPane()->show(); - unk44C->setPaneOffset(40, 0, 0, 0, 465 - unk44C->getInitialBounds().y1); + unk44C->setPaneOffset(40, 0, 0, 0, 465 - unk44C->getInitialY1()); unk450->getPane()->show(); unk450->setPanePosition(50, cUpTopPoint, cUpMidPoint, cUpMidPoint); @@ -2548,9 +2553,9 @@ void TGCConsole2::startInsertJetBalloon() unk414[i]->getPane()->hide(); if (unk404 == unk408) - unk3FC->setPaneOffset(80, 0, 0, 0, 465 - unk3FC->mInitialBounds.y1); + unk3FC->setPaneOffset(80, 0, 0, 0, 465 - unk3FC->getInitialY1()); else - unk3FC->setPaneOffset(80, 0, -73, 0, 465 - unk3FC->mInitialBounds.y1); + unk3FC->setPaneOffset(80, 0, -73, 0, 465 - unk3FC->getInitialY1()); } void TGCConsole2::startAppearRedCoin() @@ -2560,9 +2565,9 @@ void TGCConsole2::startAppearRedCoin() unk428->getPane()->show(); if (unk44C->getPane()->isVisible()) - unk428->setPaneOffset(40, 0, -73, 0, 465 - unk428->mInitialBounds.y1); + unk428->setPaneOffset(40, 0, -73, 0, 465 - unk428->getInitialY1()); else - unk428->setPaneOffset(40, 0, 0, 0, 465 - unk428->mInitialBounds.y1); + unk428->setPaneOffset(40, 0, 0, 0, 465 - unk428->getInitialY1()); unk42C->getPane()->show(); unk42C->setPanePosition(50, cUpTopPoint, cUpMidPoint, cUpMidPoint); @@ -2694,9 +2699,8 @@ bool TGCConsole2::startAppearBalloon(u32 messageID, bool autoClose) void TGCConsole2::startDisappearStar() { - int offset = -(unk140->mInitialBounds.y2 + 1); - unk140->updatePaneOffset(40, 0, offset + unk26A); - unk160->updatePaneOffset(40, 0, -(unk160->mInitialBounds.y2 + 1)); + unk140->updatePaneOffset(40, 0, getOffsetForAboveScreen(unk140) + unk26A); + unk160->updatePaneOffset(40, 0, -(unk160->getInitialY2() + 1)); unk108->updatePaneOffset(40, 0, unk26A); unk144->setStatus(JPABaseEmitter::STATUS_STOP_EMIT); @@ -2721,7 +2725,7 @@ void TGCConsole2::startAppearStar() unk35 = 0; } - unk140->setPaneOffset(40, 0, 0, 0, -(unk140->mInitialBounds.y2 + 1)); + unk140->setPaneOffset(40, 0, 0, 0, -(unk140->getInitialY2() + 1)); unk140->getPane()->show(); unk128->setPanePosition(50, cDownTopPoint, cDownMidPoint, cDownMidPoint); @@ -2730,7 +2734,7 @@ void TGCConsole2::startAppearStar() for (int i = 0; i < 3; ++i) unk134[i]->getPane()->hide(); - unk160->setPaneOffset(40, 0, 0, 0, -(unk160->mInitialBounds.y2 + 1)); + unk160->setPaneOffset(40, 0, 0, 0, -(unk160->getInitialY2() + 1)); unk160->getPane()->show(); unk148->setPanePosition(50, cDownTopPoint, cDownMidPoint, cDownMidPoint); @@ -2851,7 +2855,7 @@ void TGCConsole2::startDisappearMario() if (!unk3A8->getPane()->isVisible() || unk3B) return; - unk3A8->updatePaneOffset(50, 0, -(unk3A8->getInitialBounds().y2 + 1)); + unk3A8->updatePaneOffset(50, 0, -(unk3A8->getInitialY2() + 1)); unk3B = 1; } @@ -2861,7 +2865,7 @@ void TGCConsole2::startAppearMario(bool param_1) return; unk3A8->getPane()->show(); - unk3A8->setPaneOffset(50, 0, 0, 0, -(unk3A8->getInitialBounds().y2 + 1)); + unk3A8->setPaneOffset(50, 0, 0, 0, -(unk3A8->getInitialY2() + 1)); unk38C->setPanePosition(50, cDownTopPoint, cDownMidPoint, cDownMidPoint); From 596badbec0f04f01165114f5ef08fb8fd921dea5 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:17:48 -0400 Subject: [PATCH 04/15] GC2D/GCConsole2: startDisappearCoin hides both panes the same way Matches byte for byte. The two calls in this function did the same thing in two different spellings: one subtracted the pane height inside the negation as `-(y2 + height + 1)`, the other took the above-screen offset and subtracted the height from it. They are equal arithmetic, but the compiler kept the difference -- retail adds the height before the +1, we added the +1 first: target: subf r0, r3, r0 ; add r3, r4, r0 ; addi r0, r3, 1 ours: subf r3, r3, r0 ; addi r0, r3, 1 ; add r0, r4, r0 Writing both the same way is what a person would have done, and it is what retail compiled. --- src/GC2D/GCConsole2.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index db8553abf..3729188a6 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -2255,9 +2255,9 @@ void TGCConsole2::startDisappearCoin() unk5A = true; if (unk140->isInterpolatorAtZero()) - unk140->updatePaneOffset( - 40, 0, - -(unk140->getInitialY2() + unk128->getPane()->getHeight() + 1)); + unk140->updatePaneOffset(40, 0, + getOffsetForAboveScreen(unk140) + - unk128->getPane()->getHeight()); unk108->updatePaneOffset( 40, 0, getOffsetForAboveScreen(unk108) - unkC8->getPane()->getHeight()); From 815edf55907ec765c97de3cd07cdfd47e941aba4 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:45:27 -0400 Subject: [PATCH 05/15] GC2D/GCConsole2: load() was filling only half the life-pane array unk17C is J2DPane*[18] holding nine pairs, and unk1D0 is JUTRect[9], one per pair. The loop that populates them indexed unk17C[i] and unk17C[i+1] for i in 0..8, so each iteration overwrote the previous one's second pane, indices 9 through 17 were never written at all, and unk1D0[i] took its bounds from whichever pane happened to be at [i]. Everywhere else in the file already indexes this array correctly, e.g. console->unk17C[console->unk1CC[0] * 2]->show(); console->unk17C[console->unk1CC[0] * 2 + 1]->setBounds(...); so the pairing was already established; only the loop that fills it was wrong. No match change -- load() is 99.66% and dominated by a 344-byte frame difference, and this region is a few instructions of it. Committing it because the code is wrong as written, not because it moves a number. --- src/GC2D/GCConsole2.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 3729188a6..309519f8f 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -1745,9 +1745,9 @@ void TGCConsole2::load(JSUMemoryInputStream& stream) unk1C4 = new TBoundPane(unkB0, '\0l_0'); for (int i = 0; i < 9; ++i) { - unk17C[i] = unkB0->search('lm01' + (i << 8)); - unk17C[i + 1] = unkB0->search('lm02' + (i << 8)); - unk1D0[i] = unk17C[i]->getBounds(); + unk17C[i * 2] = unkB0->search('lm01' + (i << 8)); + unk17C[i * 2 + 1] = unkB0->search('lm02' + (i << 8)); + unk1D0[i] = unk17C[i * 2]->getBounds(); } unk260 = new TBoundPane(unkB0, 'lm_0'); From efb8a85b89aae55aca5fb692ff0f5de5df10b7de Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:56:57 -0400 Subject: [PATCH 06/15] GC2D/GCConsole2: loadAfter sizes the telop boxes with resize, not setFontSize 92.33% -> 95.24%. Still unmatched: loadAfter's frame is 344 bytes short, which is a separate question and is left alone. We were calling the wrong method. The target dispatches through the pane's vtable at +0x14, which is J2DPane::resize -- the fourth virtual, after the destructor, move and add, and the one J2DTextBox overrides. We called setFontSize, which is not virtual at all, and separately called gpSystemFont's vtable +0x24 for its height. The target instead calls gpSystemFont's +0x28, annotated in JUTFont.hpp as getWidth(), and shifts it left by 10. The bounds also come from a copy, not a direct read: the target builds a JUTRect on the stack via JUTRect::copy and takes y2 - y1 from it, and uses that same one height for both boxes rather than reading each box's own bounds. lwz r3, 0x528(r31) ; unk528 stb r29, 0xc(r3) ; hide lwz r4, gpSystemFont bl J2DTextBox::setFont addi r3, r1, 0x550 addi r4, r4, 0x14 bl JUTRect::copy ; JUTRect bounds(unk528->mBounds) lwz r12, 0x28(r12) ; gpSystemFont->getWidth() subf r23, r4, r0 ; bounds.getHeight() slwi r4, r3, 10 lwz r12, 0x14(r12) ; unk528->resize(...) The `<< 10` is left as the shift the code performs. I do not know what unit that width is in and would rather leave it plain than name it wrongly. Also note the ordering: each box is hidden, given its font and resized in turn, rather than the two being hidden together and then configured together. --- src/GC2D/GCConsole2.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 309519f8f..0729ce6e5 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -1982,14 +1982,13 @@ void TGCConsole2::loadAfter() unk520->getPane()->hide(); unk524->getPane()->show(); unk528->hide(); - unk52C->hide(); - unk528->setFont(gpSystemFont); - unk52C->setFont(gpSystemFont); + JUTRect bounds(unk528->mBounds); + unk528->resize(gpSystemFont->getWidth() << 10, bounds.getHeight()); - int fontHeight = gpSystemFont->getHeight(); - unk528->setFontSize(fontHeight, unk528->mBounds.getHeight()); - unk52C->setFontSize(fontHeight, unk52C->mBounds.getHeight()); + unk52C->hide(); + unk52C->setFont(gpSystemFont); + unk52C->resize(gpSystemFont->getWidth() << 10, bounds.getHeight()); JUTRect telopBounds(unk524->getPane()->mBounds); JUTRect telopPaneBounds(unk520->getPane()->mBounds); From 332008c2e26d51dbdd5bfc10136a7772d048b731 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:33:13 -0400 Subject: [PATCH 07/15] GC2D/GCConsole2: centre the coin emitters through the existing helper processDownCoin 95.82% -> 98.88%, processAppearCoin 95.80% -> 98.86%. setEmitterToPaneCenter already existed and was already used three times in perform, but the identical body was also written out longhand in three other places. Two of them wanted the helper: JUTRect bounds(unkCC->getPane()->mGlobalBounds); unk124->mGlobalTranslation.set(bounds.x1 + bounds.getWidth() * 0.5f, bounds.y1 + bounds.getHeight() * 0.5f, 0.0f); is character for character the helper's body, and routing it through the helper gains three points in each function. processAppearStar has two of these back to back and does NOT want it -- it drops 91.20% -> 90.27%. I tried the obvious variants: helper for both (90.27%), swapping their order (90.27%), helper for only the first (90.75%), only the second (90.53%). All are worse than leaving both longhand, so that is how they stay, and the file now has the operation in two spellings. I do not like it either, but that is what the object says and I would rather leave the inconsistency visible than pick the tidier version and lose the match. Marker changed from `// fabricated` to `// Possibly inline`. `fabricated` asserts "I made this up", and that is no longer the honest claim about something that measurably improves two functions when used. --- src/GC2D/GCConsole2.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 0729ce6e5..942f3103c 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -68,7 +68,7 @@ static inline int getOffsetForBelowScreen(const TExPane* pane) return 465 - pane->getInitialY1(); } -// fabricated +// Possibly inline static inline void setEmitterToPaneCenter(JPABaseEmitter* emitter, J2DPane* pane) { @@ -3195,9 +3195,7 @@ bool TGCConsole2::processDownCoin(int param_1) isFinished &= unk108->update(); - JUTRect bounds(unkCC->getPane()->mGlobalBounds); - unk124->mGlobalTranslation.set(bounds.x1 + bounds.getWidth() * 0.5f, - bounds.y1 + bounds.getHeight() * 0.5f, 0.0f); + setEmitterToPaneCenter(unk124, unkCC->getPane()); return isFinished; } @@ -3276,9 +3274,7 @@ bool TGCConsole2::processAppearCoin(int param_1) updateDownBlendPaneState(unkD4[i], isFinished); } - JUTRect bounds(unkCC->getPane()->mGlobalBounds); - unk124->mGlobalTranslation.set(bounds.x1 + bounds.getWidth() * 0.5f, - bounds.y1 + bounds.getHeight() * 0.5f, 0.0f); + setEmitterToPaneCenter(unk124, unkCC->getPane()); return isFinished; } From 93eece4d4d4886f33ad1771b5958923f3589757b Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:34:48 -0400 Subject: [PATCH 08/15] GC2D/GCConsole2: processAppearStar tests the shine count first 91.20% -> 92.09%. The 100-shine check reads (!unk50 && shines >= 100) || (unk50 && shines > 100) but retail evaluates the second arm the other way round. The branch structure shows it plainly -- on the unk50 path it compares the count before it re-tests the flag: 708c: cmpwi r25, 0x64 ; shines vs 100 7090: ble -> skip 7094: cmplwi r0, 0 ; unk50 7098: beq -> skip We emitted the flag test first and the count second. Worth being explicit about why this is not a fake match, because it looks like one: `&&` operand order is short-circuit evaluation order, so it is semantically meaningful and directly visible in the branch layout. That is different from swapping the operands of a commutative arithmetic expression to nudge a frame, which produces identical semantics and is exactly the thing this project rejects. I had this change in front of me earlier, called it a fake match, and reverted it. That was wrong, and the disassembly above is why. It does cost the parallel structure of the two arms, which is a real readability loss and reads oddly. I am keeping it because the object says so, but it is the sort of thing worth a second opinion. --- src/GC2D/GCConsole2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 942f3103c..d76ee0538 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -3093,7 +3093,7 @@ bool TGCConsole2::processAppearStar(int param_1) for (int i = 0; i < 3; ++i) { if (param_1 == i * 6 + 28) { if (i == 2) { - if ((!unk50 && shines >= 100) || (unk50 && shines > 100)) + if ((!unk50 && shines >= 100) || (shines > 100 && unk50)) unk134[i]->getPane()->show(); } else { unk134[i]->getPane()->show(); From 78c99755a85476c96d21d065f7a1736fea15e887 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:44:33 -0400 Subject: [PATCH 09/15] GC2D/GCConsole2: drawWater's water quad spans the picture's own height 83.82% -> 99.12%. Three separate things, all readable off the register save mask. The target saves f28-f31 and only r20-r31; we were saving f30/f31 and r17-r31. Retail holds two more values in float registers and three fewer in integer ones, which says the quad's vertical extent was computed as f32 and ours was not. - `top` and `bottom` are `f32` locals. Retail converts each exactly once, before `GXBegin`, and keeps them in f29/f28 across all four vertices. - `left` and `right` are *not* locals. The target re-loads `unk2BC[layer].x1` and `.x2` from memory and re-converts them at every vertex -- four loads and four `xoris` conversions for two values. That is what an inline `(f32)unk2BC[layer].x1` at each call site does, and hoisting them to locals (either int or f32) is what was costing the two extra float registers. - the height is the picture's, not the cached rect's. The target reads +0x18 and +0x20 off `unk2A0[layer]`, which is `J2DPane::mBounds.y1`/`.y2` -- i.e. `J2DPane::getHeight()` -- where we were subtracting inside `unk2BC[layer]`. The two rects hold the same numbers here, so this is not a behaviour change, but it is the one retail actually reads. The first `GXSetTevColor` also needed the same `JUtility::TColor(u32)` construction as the second. The target stores the colour to one stack slot and copies it to a second before the call; passing the array element as an lvalue only produces one. Both calls now read the same way, which is how they should have been written regardless. Left unresolved: the frame is still 72 bytes short, and the two texture-count tests emit `beq` where the target emits `ble` after the same `cmplwi r0, 0`. Those are equivalent for an unsigned compare, so `mTextureNum > 0` is folding to `!= 0` for us and did not for retail. Adding a `getTextureNum()` accessor returning `int` does not do it -- measured, 99.12% -> 98.85%, reverted. Co-Authored-By: Claude Opus 5 --- src/GC2D/GCConsole2.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index d76ee0538..8cd285ba4 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -3773,7 +3773,7 @@ void TGCConsole2::drawWater(J2DOrthoGraph& graph) alpha[2] = unk9E.a; for (int layer = 2; layer > 0; --layer) { - GXSetTevColor(GX_TEVREG0, unk2EC[layer]); + GXSetTevColor(GX_TEVREG0, JUtility::TColor((u32)unk2EC[layer])); GXSetTevColor(GX_TEVREG1, JUtility::TColor((u32)unk2EC[layer] + alpha[layer])); @@ -3810,18 +3810,16 @@ void TGCConsole2::drawWater(J2DOrthoGraph& graph) GX_FALSE, GX_PTIDENTITY); GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL); - int top = unk29C->getPane()->mGlobalBounds.y1 + topDiff[layer - 1]; - int bottom = top + unk2BC[layer].getHeight(); - int left = unk2BC[layer].x1; - int right = unk2BC[layer].x2; + f32 top = unk29C->getPane()->mGlobalBounds.y1 + topDiff[layer - 1]; + f32 bottom = top + unk2A0[layer]->getHeight(); GXBegin(GX_QUADS, GX_VTXFMT0, 4); - GXPosition2f32((f32)left, (f32)top); + GXPosition2f32((f32)unk2BC[layer].x1, top); GXTexCoord2s8(0, 0); - GXPosition2f32((f32)right, (f32)top); + GXPosition2f32((f32)unk2BC[layer].x2, top); GXTexCoord2s8(1, 0); - GXPosition2f32((f32)right, (f32)bottom); + GXPosition2f32((f32)unk2BC[layer].x2, bottom); GXTexCoord2s8(1, 1); - GXPosition2f32((f32)left, (f32)bottom); + GXPosition2f32((f32)unk2BC[layer].x1, bottom); GXTexCoord2s8(0, 1); } From e6383013079e94ecde2d4eb115e946abcf97cf5f Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:46:24 -0400 Subject: [PATCH 10/15] GC2D/GCConsole2: processAppearStar reuses one bounds rect 92.09% -> 95.99%. The two emitter-centring blocks at the end were `JUTRect bounds(...)` and `JUTRect bounds2(...)`. The target only has one rect: it calls `JUTRect::copy` for the first and then inlines the second copy member-wise -- four `lwz`/`stw` pairs -- into the *same* stack slot the first one used. That asymmetry is the tell. Copy-construction goes through the out-of-line `JUTRect::copy`; assigning to an existing rect inlines. `drawWater` in this same file already shows both forms side by side for the same reason, so this is the shape the file was written in. Still off: the frame is 56 bytes short, and `blueCoinValue` gets an extra `mr r25, r0` because the target allocates the subtraction straight into the register already holding `blueCoins`. Collapsing the two into one variable would produce that, but the name would then be wrong for what it holds, so I have left it. Co-Authored-By: Claude Opus 5 --- src/GC2D/GCConsole2.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 8cd285ba4..a9f050f1f 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -3148,10 +3148,9 @@ bool TGCConsole2::processAppearStar(int param_1) unk144->mGlobalTranslation.set(bounds.x1 + bounds.getWidth() * 0.5f, bounds.y1 + bounds.getHeight() * 0.5f, 0.0f); - JUTRect bounds2(unk14C->getPane()->mGlobalBounds); - unk164->mGlobalTranslation.set(bounds2.x1 + bounds2.getWidth() * 0.5f, - bounds2.y1 + bounds2.getHeight() * 0.5f, - 0.0f); + bounds = unk14C->getPane()->mGlobalBounds; + unk164->mGlobalTranslation.set(bounds.x1 + bounds.getWidth() * 0.5f, + bounds.y1 + bounds.getHeight() * 0.5f, 0.0f); return isFinished; } From 90245e9bf460669f25074864cad8d66906d3e8a6 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:48:27 -0400 Subject: [PATCH 11/15] GC2D/GCConsole2: drawWaterBack draws the full gauge once 74.68% -> 83.86%. The `else if (unk48)` and `else` arms both ended in the same `drawGaugeQuadF32(bounds, bounds.y1, bounds.y2, 0.0f, 1.0f)` call, so we emitted two identical quads and the target emits one. Its `unk48 == 0` and `unk30C == 0` tests both branch to the *same* `GXBegin`, which is a single guarded block followed by an unconditional draw: } else { if (unk48 && unk30C != 0) { ... } drawGaugeQuadF32(bounds, bounds.y1, bounds.y2, 0.0f, 1.0f); } Worth noting for anyone applying the `drawWater` change to this function: it does *not* transfer. `drawWater` hoists the quad's top and bottom into f32 locals because the target converts them once and keeps them in f29/f28. Here the target converts all eight position components separately at each vertex, which is what `drawGaugeQuadF32`'s `int top, int bottom` parameters already produce. The two functions genuinely differ. Co-Authored-By: Claude Opus 5 --- src/GC2D/GCConsole2.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index a9f050f1f..8f5f994c9 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -2832,16 +2832,14 @@ void TGCConsole2::drawWaterBack() } drawGaugeQuadF32(bounds, fillTop, bounds.y2, hiddenRatio, 1.0f); - } else if (unk48) { - if (unk30C != 0) { + } else { + if (unk48 && unk30C != 0) { unk274->setPanePosition(90, JUTPoint(0, 0), JUTPoint(0, -100), JUTPoint(0, 0)); unk30C = 0; unk49 = 1; } - drawGaugeQuadF32(bounds, bounds.y1, bounds.y2, 0.0f, 1.0f); - } else { drawGaugeQuadF32(bounds, bounds.y1, bounds.y2, 0.0f, 1.0f); } From 1c3661b4dc9ebeead20c5bd40b31591877fd51d0 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:50:11 -0400 Subject: [PATCH 12/15] GC2D/GCConsole2: drawWaterBack picks its texture through getTexture 83.86% -> 84.83%. Two things. The background texture lookup was written out longhand as an if/else over `mTextureNum` into a local. That is exactly `J2DPicture::getTexture(0)`, which already exists and reads `idx < mTextureNum ? mTextures[idx] : nullptr`. Using it collapses five lines to one and also explains a mnemonic that had been bothering me: the target emits `ble` after `cmplwi r0, 0` where we emitted `beq`. `mTextureNum > 0` canonicalises to `!= 0` and gives `beq`; the accessor's `0 < mTextureNum`, with the constant on the left, does not canonicalise and gives `ble`. Same test either way -- it is just which side the zero is on. `drawWater` has the same `ble`, but its two calls are guarded, not a ternary, so the accessor does not fit there and those two branches are still wrong. Second, `waterGun` is read before the rect copy, not after. The target's `lwz r31, 0x3e4(r5)` sits between the copy's argument setup and the `bl`, and a load cannot be scheduled across a call, so that statement precedes it in source. Co-Authored-By: Claude Opus 5 --- src/GC2D/GCConsole2.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 8f5f994c9..3953292bd 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -2784,19 +2784,14 @@ void TGCConsole2::drawWaterBack() GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); - JUTTexture* backgroundTexture; - if (((J2DPicture*)unk26C->getPane())->mTextureNum > 0) - backgroundTexture = ((J2DPicture*)unk26C->getPane())->mTextures[0]; - else - backgroundTexture = nullptr; - backgroundTexture->load(GX_TEXMAP0); + ((J2DPicture*)unk26C->getPane())->getTexture(0)->load(GX_TEXMAP0); GXLoadTexMtxImm(mtx, GX_TEXMTX0, GX_MTX2x4); GXSetTexCoordGen2(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0, GX_FALSE, GX_PTIDENTITY); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); - JUTRect bounds(((J2DPicture*)unk26C->getPane())->mBounds); TWaterGun* waterGun = gpMarioOriginal->mWaterGun; + JUTRect bounds(((J2DPicture*)unk26C->getPane())->mBounds); GXSetTevColor(GX_TEVREG0, JUtility::TColor(0x0000ff78)); GXSetTevColor(GX_TEVREG1, JUtility::TColor(0x0000ff00)); From 5b19f8fc6ae37b0fa10d2ce3c063d9b853e987d8 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:52:57 -0400 Subject: [PATCH 13/15] GC2D/GCConsole2: the pressure flash resets its own counter 84.83% -> 87.20%. `if (unk30C >= 25) unk30C = 0;` sat in the caller ahead of the colour computation. It is actually the final `else` of the colour chain itself: the target's `cmpwi r4, 0x19 / bge` lands on `li r0, 0 / stb r0, 0x30c(r29)`, which then falls into the shared `color + 0xc8`. So the counter is cleared *instead of* picking a fade colour, not before picking one. The two are equivalent, which is why this was easy to miss. Resetting first meant the frame-0 branch ran with `frame == 0`, and both of its terms are `(f32)0 * k`, so the colour came out as the unmodified base either way. The difference is only in where the reset lives. The helper now takes the counter by reference so the whole flash cycle -- fade in, hold, fade out, reset -- reads in one place. Its comparisons are also signed in the target (`cmpwi`, not `cmplwi`), and the fade's int-to-float conversion is `xoris` rather than our `clrlwi` zero-extend, so the frame index is an `int` there, not the `u8` we were passing. One thing I cannot explain: the target compares `< 15` and `< 25` signed but `< 10` *unsigned*, on the same register, in the same chain. Mixing the spellings in source to reproduce that would make the chain read arbitrarily, so I have left the one instruction wrong rather than write it that way. Co-Authored-By: Claude Opus 5 --- src/GC2D/GCConsole2.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 3953292bd..07159d49d 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -105,10 +105,11 @@ static inline void drawGaugeQuadF32(const JUTRect& rect, int top, int bottom, GXTexCoord2f32(0.0f, bottomTex); } -// fabricated -static inline u32 getPressureFlashColor(u8 frame) +// Possibly inline +static inline u32 getPressureFlashColor(u8& counter) { u32 color = 0xff3f3f00; + int frame = counter; if (frame < 10) { color += ((u32)(s16)(s32)((f32)frame * -6.3f)) << 8; @@ -116,9 +117,11 @@ static inline u32 getPressureFlashColor(u8 frame) } else if (frame < 15) { color = 0xffff0000; } else if (frame < 25) { - u8 fade = 25 - frame; + int fade = 25 - frame; color += ((u32)(s16)(s32)((f32)fade * -6.3f)) << 8; color += ((u32)(s32)((f32)fade * 19.2f)) << 16; + } else { + counter = 0; } return color; @@ -2812,9 +2815,6 @@ void TGCConsole2::drawWaterBack() if (unk49) unk49 = 0; - if (unk30C >= 25) - unk30C = 0; - u32 color = getPressureFlashColor(unk30C); GXSetTevColor(GX_TEVREG0, JUtility::TColor(color + 0xc8)); GXSetTevColor(GX_TEVREG1, JUtility::TColor(color)); From 172258c931609a6e95ddd806090224d3c5b0ecd8 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:56:14 -0400 Subject: [PATCH 14/15] J2D/J2DWindow: getContentsBounds returns a JUTRect by value startAppearBalloon 92.46% -> 94.17%, processDisappearBalloon 99.72% -> 99.89%. This is the TODO that was already sitting in `startAppearBalloon`. The target copies the contents rect twice there -- once out of `mContentsBounds` at +0xec into a stack temporary, then again from that temporary into the named local -- which is what a by-value return does and what a `const JUTRect&` return cannot produce. `processDisappearBalloon` looked like it contradicted that, because it copies only once. It does not: the target copies straight into a slot it reads `getHeight()` out of two instructions later and never touches again. That is an unnamed temporary, not a local. The rect was only ever there to be measured, so the local goes and the call reads as one expression. `perform` moves 28.09% -> 27.94%. It has 3634 real instruction differences and is nowhere near aligned, so its percentage is not measuring anything useful yet; the third call site is inlined into it and I would rather not make that line read worse to chase noise. Project function count and matched data are both unchanged. Co-Authored-By: Claude Opus 5 --- include/JSystem/J2D/J2DWindow.hpp | 2 +- src/GC2D/GCConsole2.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/JSystem/J2D/J2DWindow.hpp b/include/JSystem/J2D/J2DWindow.hpp index 3bb933adc..5954c9bef 100644 --- a/include/JSystem/J2D/J2DWindow.hpp +++ b/include/JSystem/J2D/J2DWindow.hpp @@ -71,7 +71,7 @@ class J2DWindow : public J2DPane { virtual void drawSelf(int, int, Mtx*); // Possibly inline - const JUTRect& getContentsBounds() const { return mContentsBounds; } + JUTRect getContentsBounds() const { return mContentsBounds; } protected: /* 0xEC */ JUTRect mContentsBounds; diff --git a/src/GC2D/GCConsole2.cpp b/src/GC2D/GCConsole2.cpp index 07159d49d..be87d10e2 100644 --- a/src/GC2D/GCConsole2.cpp +++ b/src/GC2D/GCConsole2.cpp @@ -3609,8 +3609,7 @@ bool TGCConsole2::processDisappearBalloon() JUTRect bounds = unk3B0->getBounds(); int height = bounds.getHeight(); - JUTRect contents = unk3B0->getContentsBounds(); - int contentHeight = contents.getHeight(); + int contentHeight = unk3B0->getContentsBounds().getHeight(); if (contentHeight > 0) { int nextHeight = height - unk3CC; From 5884f066aa13838a46d78c92fdf4db4a414a3992 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Sat, 29 Aug 2026 03:11:24 -0400 Subject: [PATCH 15/15] M3DUtil/MActorAnm: wrap the MtxCalcTypeName comment to 80 columns Not part of this PR's subject. The comment block above `enum MActorMtxCalcType` arrived from main in c19ae4d4 at 82 and 81 columns against the repo's `ColumnLimit: 80`, so `check-format-and-tidy` fails on it. Flagging it because it is not specific to this branch: the clang-format action walks the whole tree rather than the diff, so this reddens every open PR the moment it merges main, for a violation none of them introduced. This was the only such file -- after the reflow a whole-tree `clang-format --dry-run -Werror` over src/ and include/ reports zero. Only the line breaks move; the wording is unchanged. Happy to drop this if you would rather fix it directly on main. Co-Authored-By: Claude Opus 5 --- include/M3DUtil/MActorAnm.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/M3DUtil/MActorAnm.hpp b/include/M3DUtil/MActorAnm.hpp index 37385eb8a..fc12529b1 100644 --- a/include/M3DUtil/MActorAnm.hpp +++ b/include/M3DUtil/MActorAnm.hpp @@ -271,9 +271,10 @@ class MActorAnmBtp : public MActorAnmMatEach { /* 0x28 */ J3DTexNoAnm** unk28; }; -// The names come from the MtxCalcTypeName table in M3DUtil/InfectiousStrings.hpp, -// which survives in the ROM (read it in MarioDraw.cpp's .data): its four entries -// are "MActorMtxCalcType_Basic", "_Softimage", "_MotionBlend" and "_User". +// The names come from the MtxCalcTypeName table in +// M3DUtil/InfectiousStrings.hpp, which survives in the ROM (read it in +// MarioDraw.cpp's .data): its four entries are "MActorMtxCalcType_Basic", +// "_Softimage", "_MotionBlend" and "_User". enum MActorMtxCalcType { MACTOR_MTX_CALC_BASIC, MACTOR_MTX_CALC_SOFTIMAGE,