Skip to content

Enemy/bosstelesa: decompile Boss Telesa behavior - #134

Open
KakarottoCake wants to merge 7 commits into
doldecomp:mainfrom
KakarottoCake:decomp-bosstelesa
Open

Enemy/bosstelesa: decompile Boss Telesa behavior#134
KakarottoCake wants to merge 7 commits into
doldecomp:mainfrom
KakarottoCake:decomp-bosstelesa

Conversation

@KakarottoCake

@KakarottoCake KakarottoCake commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • decompile the Boss Telesa fight, roulette/slot machinery, collision actors, managers, bubbles, and all boss/bubble nerves
  • restore the BossTelesa class layouts, save parameters, static tuning data, animation tables, model data, and supporting actor helpers
  • reproduce the retail TU's deferred function emission order, including the interleaved BubbleLive symbols

Matching

  • BossTelesa TU: 90.38% fuzzy text match locally
  • data: 100% matched across .data, .rodata, .sdata, .sdata2, and .sbss
  • 58 of 100 linked functions are exact matches
  • every function in the TU is at least 70%; TBossTelesa::loadAfter is 96.8%, generateSlotItem is 77.6%, TNerveBossTelesaSlotStart::execute is 79.5%, and compiler-generated __sinit_bosstelesa_cpp is now an exact 100% match

TU structure

  • audited all 138 map entries, including all 38 UNUSED symbols
  • all map symbols are present with correct linkage
  • mandatory non-weak symbol order passes; only compiler-controlled weak ordering and best-effort UNUSED size warnings remain

Verification

  • ninja baseline
  • ninja changes_all
  • ninja all_source progress build/GMSJ01/report.json
  • build/GMSJ01/mario.dol: OK
  • python tools/check-changed-symbol-order.py src/Enemy/bosstelesa.cpp
  • python tools/validate-symbol-order.py -u mario/Enemy/bosstelesa
  • clang-format dry run
  • git diff --check

This is the full BossTelesa decompilation effort.

@decomp-dev

decomp-dev Bot commented Aug 3, 2026

Copy link
Copy Markdown

Report for GMSJ01 (5a8c71e - 60fcf3f)

📈 Matched code: 35.52% (+0.21%, +7504 bytes)
📈 Matched data: 49.49% (+0.88%, +5644 bytes)

✅ 65 new matches
Unit Item Bytes Before After
mario/Enemy/bosstelesa .rodata +2816 0.00% 100.00%
mario/Enemy/bosstelesa .data +2096 0.00% 100.00%
mario/Enemy/bosstelesa TBossTelesaSaveLoadParams::TBossTelesaSaveLoadParams(const char*) +928 0.00% 100.00%
mario/Enemy/bosstelesa __sinit_bosstelesa_cpp +764 0.00% 100.00%
mario/Enemy/bosstelesa TBubble::split() +440 0.00% 100.00%
mario/Enemy/bosstelesa TBubbleManager::load(JSUMemoryInputStream&) +412 0.00% 100.00%
mario/Enemy/bosstelesa .bss +328 0.00% 100.00%
mario/Enemy/bosstelesa TTelesaSlot::getForcastResult(int) +284 0.00% 100.00%
mario/Enemy/bosstelesa TBossTelesa::kill() +280 0.00% 100.00%
mario/Enemy/bosstelesa TBubble::reset() +276 0.00% 100.00%
mario/Enemy/bosstelesa .sdata2 +272 0.00% 100.00%
mario/Enemy/bosstelesa TBubble::behaveToWater(THitActor*) +236 0.00% 100.00%
mario/Enemy/bosstelesa TTelesaSlot::randomReset() +236 0.00% 100.00%
mario/Enemy/bosstelesa TNerveBubbleSplit::execute(TSpineBase<TLiveActor>*) const +192 0.00% 100.00%
mario/Enemy/bosstelesa TBossTelesa::TBossTelesa(const char*) +188 0.00% 100.00%
mario/Enemy/bosstelesa TTelesaSlot::getResultFromAng(float) +168 0.00% 100.00%
mario/Enemy/bosstelesa TTelesaSlot::moveStart() +164 0.00% 100.00%
mario/Enemy/bosstelesa TBubble::~TBubble() +156 0.00% 100.00%
mario/Enemy/bosstelesa TTelesaSlot::~TTelesaSlot() +156 0.00% 100.00%
mario/Enemy/bosstelesa TBossTelesaBody::~TBossTelesaBody() +132 0.00% 100.00%
mario/Enemy/bosstelesa TBossTelesaTongue::~TBossTelesaTongue() +132 0.00% 100.00%
mario/Enemy/bosstelesa TBossTelesaKillSmallEnemy::~TBossTelesaKillSmallEnemy() +132 0.00% 100.00%
mario/Enemy/bosstelesa TBubbleManager::~TBubbleManager() +116 0.00% 100.00%
mario/Enemy/bosstelesa TBossTelesa::~TBossTelesa() +108 0.00% 100.00%
mario/Enemy/bosstelesa TBossTelesaManager::load(JSUMemoryInputStream&) +108 0.00% 100.00%
mario/Enemy/bosstelesa TBossTelesaManager::~TBossTelesaManager() +100 0.00% 100.00%
mario/Enemy/bosstelesa .sbss +96 0.00% 100.00%
mario/Enemy/bosstelesa TNerveBubbleLive::~TNerveBubbleLive() +92 0.00% 100.00%
mario/Enemy/bosstelesa TNerveBubbleSplit::~TNerveBubbleSplit() +92 0.00% 100.00%
mario/Enemy/bosstelesa TNerveBossTelesaDie::~TNerveBossTelesaDie() +92 0.00% 100.00%

...and 35 more new matches

📈 42 improvements in unmatched items
Unit Item Bytes Before After
mario/Enemy/bosstelesa TBossTelesa::loadAfter() +3232 0.00% 96.77%
mario/Enemy/bosstelesa TBossTelesa::generateSlotItem() +2250 0.00% 77.61%
mario/Enemy/bosstelesa TBossTelesa::moveObject() +1829 0.00% 80.38%
mario/Enemy/bosstelesa TBossTelesa::init(TLiveManager*) +1717 0.00% 88.91%
mario/Enemy/bosstelesa TNerveBossTelesaDie::execute(TSpineBase<TLiveActor>*) const +1660 0.00% 73.86%
mario/Enemy/bosstelesa TNerveBossTelesaPrepareSlot::execute(TSpineBase<TLiveActor>*) const +1360 0.00% 82.96%
mario/Enemy/bosstelesa TBossTelesa::calcRootMatrix() +1301 0.00% 94.59%
mario/Enemy/bosstelesa TTelesaSlot::moveObject() +1031 0.00% 92.78%
mario/Enemy/bosstelesa TNerveBubbleLive::execute(TSpineBase<TLiveActor>*) const +868 0.00% 86.86%
mario/Enemy/bosstelesa TNerveBossTelesaAppear::execute(TSpineBase<TLiveActor>*) const +835 0.00% 85.97%
mario/Enemy/bosstelesa TBossTelesa::genAttacker() +714 0.00% 80.82%
mario/Enemy/bosstelesa TBossTelesa::checkHitObject(THitActor*) +711 0.00% 98.85%
mario/Enemy/bosstelesa TBossTelesa::rouletteStart() +608 0.00% 86.99%
mario/Enemy/bosstelesa TNerveBossTelesaSpitSlotItem::execute(TSpineBase<TLiveActor>*) const +582 0.00% 98.36%
mario/Enemy/bosstelesa TBossTelesaBody::receiveMessage(THitActor*, unsigned long) +554 0.00% 95.01%
mario/Enemy/bosstelesa TBossTelesa::flashItem(int) +550 0.00% 85.41%
mario/Enemy/bosstelesa TNerveBossTelesaSlotStart::execute(TSpineBase<TLiveActor>*) const +549 0.00% 79.47%
mario/Enemy/bosstelesa TBubble::appendEnemy() +531 0.00% 99.87%
mario/Enemy/bosstelesa TBossTelesa::damageRecover() +523 0.00% 81.82%
mario/Enemy/bosstelesa TNerveBossTelesaHide::execute(TSpineBase<TLiveActor>*) const +450 0.00% 95.53%
mario/Enemy/bosstelesa TNerveBossTelesaHideWait::execute(TSpineBase<TLiveActor>*) const +445 0.00% 86.99%
mario/Enemy/bosstelesa TNerveBossTelesaFreeze::execute(TSpineBase<TLiveActor>*) const +426 0.00% 98.70%
mario/Enemy/bosstelesa TNerveBossTelesaFallDemo::execute(TSpineBase<TLiveActor>*) const +426 0.00% 86.61%
mario/Enemy/bosstelesa TBossTelesa::forceHide() +411 0.00% 99.95%
mario/Enemy/bosstelesa TTelesaSlot::initMapObj() +378 0.00% 97.68%
mario/Enemy/bosstelesa TTelesaSlot::forceStopSlot(int) +377 0.00% 99.38%
mario/Enemy/bosstelesa TBossTelesa::setSpicy(TLiveActor*) +361 0.00% 87.72%
mario/Enemy/bosstelesa TBossTelesa::perform(unsigned long, JDrama::TGraphics*) +339 0.00% 99.89%
mario/Enemy/bosstelesa TBossTelesa::slotFall() +312 0.00% 95.30%
mario/Enemy/bosstelesa TBossTelesaKillSmallEnemy::checkHit() +311 0.00% 99.72%

...and 12 more improvements in unmatched items

@KakarottoCake KakarottoCake changed the title Enemy/bosstelesa: reconstruct BossTelesa TU scaffolding Enemy/bosstelesa: decompile Boss Telesa behavior Aug 3, 2026
@KakarottoCake

Copy link
Copy Markdown
Contributor Author

Follow-up on the latest commit (19063ba) — a matching pass over the boss/bubble nerves and helpers.

Header

  • isRollDrum(), rouletteFall(), slotFall() changed BOOLbool, matching the clrlwi. bool-test the target emits at the call sites.

Source changes

  • Rewrote the repeated set-old-motion-blend / setBckFromIndex / setMotionBlendRatio / setAnmSound blocks to cache mMActor (MActor* oldAnmActor, blendActor/blendRatio locals, explicit basName if/else) in setSpicy, TNerveBossTelesaDie, SlotStart, Appear, PrepareSlot.
  • genAttacker: raw rand() * 0.000030517578fTMsRange<f32>(0.0f, 1.0f).rand().
  • TNerveBossTelesaDie: unrolled setRollSp calls → for loop.
  • TNerveBubbleLive: swapped the unk1D0 if/else to match branch layout; mVelocity.set(0,0,0)mVelocity = JGeometry::TVec3<f32>(0,0,0).

Effect

function before → after
TNerveBossTelesaDie 73.9% → 94.4%
TNerveBossTelesaSlotStart 79.5% → 94.9%
TNerveBossTelesaAppear 85.9% → 99.5%
TNerveBossTelesaPrepareSlot 83.0% → 95.7%
TBossTelesa::setSpicy 87.7% → 95.7%
TNerveBossTelesaFallDemo 86.6% → 90.2%
TBossTelesa::genAttacker 80.8% → 88.8%
TNerveBubbleLive 86.8% → 94.6%

TU fuzzy match: 90.38% → 93.18%. Remaining residuals are MWCC stack-frame padding, register-allocation shifts, and inlining depth (e.g. TVec3::sub/TMatrix34 ctor kept out-of-line in the target) — left as-is rather than faked.

- route sounds through SMSGetMSound()->startSoundActor + SoundEffects enum
- add boss telesa particle enum entries, use them in emit/load
- use MsMtxSetXYZRPH f32 overload in TBubble::calcRootMatrix
- drop fabricated isActorTypeOf, use direct actor-type comparisons
- rename MActor::getAnmBck -> getCurBckAnmPtr (real map name)
- unk198/unk1A8 -> bool[3] arrays (remove pointer fakematch)
- rolling/allStopped -> bool, MsWrap-free 360 wraps restored
- fabsf instead of __fabsf, CAM_SHAKE_MODE enum, SMSGetMarDirector
@KakarottoCake

Copy link
Copy Markdown
Contributor Author

Addressed most of the review in 1ae7da3. Notes on the tricky ones:

Done

  • Sounds: gpMSound->gateCheck(...) + MSoundSESystem::MSoundSE::startSoundActor(...)SMSGetMSound()->startSoundActor(...) with the MSD_SE_* enum (all 23 sites).
  • Particles: added SCENE_BTELESA_JPA_MS_BTLS_* (+ PARTICLE_MS_TLS_CHANGE for 0xCD) and switched all SMS_LoadParticle/emit* calls to the enum.
  • MsMtxSetXYZRPH now uses the f32 overload (drops the (s16)(... * 182.04445f) casts).
  • Dropped the fabricated isActorTypeOf; the (mActorType - base) == 1 was just mActorType == base — wrote the comparison inline (and != for the negated cases).
  • MActor::getAnmBckgetCurBckAnmPtr (matches the real mario.MAP name; made it the weak header inline and dropped the out-of-line def).
  • TTelesaSlot::unk198/unk1A8 are now bool[3] arrays, killing the *(&unk198 + i) / u8* = &unk198 + i fakematch.
  • rolling/allStoppedbool; __fabsffabsf; (EnumCamShakeMode)0xNNCAM_SHAKE_MODE_*; gpMarDirector->fireStartDemoCameraSMSGetMarDirector()->....
  • Removed the volatile f32 min/max fakematch in forceStopSlot, replaced with TMsRange<f32>.

Pushed back on

  • MsWrap: I tried MsWrap(x, 0, 360) for the drum-angle wrap, but the two-if form (>= 360.0f / <= 0.0f) is what matches 100% (MsWrap uses while + strict <, which generates different branches). Left it as the two ifs.

Still TODO (not attempted / couldn't resolve cleanly)

  • #pragma dont_inline blocks are still there (haven't found the real inlines they're masking).
  • checkHit/checkHit UNUSED bodies are still empty stubs (sizes don't match the map).
  • getCurBckAnmPtr body is still a guess (return unkC;, 8B vs map 0x1C).

One thing to flag: routing the sounds through the inline SMSGetMSound()->startSoundActor causes an inlining cascade — TBossTelesaKillSmallEnemy::checkHit drops 99.7% → 0% because forceHide() (which now carries the inline sound helper) gets inlined into it instead of staying a bl. A couple of other sound-bearing functions shift a few % for the same reason. I kept the sound change since it's what the cleanup wants, but wanted you to know it's not a pure win here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant