The Legend of Zelda: The Minish Cap
Capcom's 2004 Game Boy Advance Zelda for Nintendo, read through zeldaret's decompilation of the US release and that release's ROM.
The Legend of Zelda: The Minish Cap is Capcom’s 2004 Zelda for the Game Boy Advance, published by Nintendo. The atlas reads it through zeldaret’s decompilation, which builds the US ROM and matches it. The tables that code reads by address (the collision matrix, the fusers’ data) are cited in that ROM. Nothing here has been measured in an emulator yet.
#Hearts
Health counts in eighths of a heart, as in A Link to the Past and Link’s Awakening, and a new file starts at three hearts1. Every fourth Piece of Heart makes a container, up to 20 hearts, and each piece also refills Link’s health1.
#Getting hit
What a touch does comes from a collision matrix indexed by the attacker’s hit type and Link’s hurt type. An Octorok’s touch costs a quarter heart and gives Link 30 frames of invincibility, where Link’s Awakening sets a count of 80 and A Link to the Past 582. The same entry sets a knockback of 8. The push uses a speed twice the walking-speed constant, though the atlas hasn’t read the routine that applies it, and the player can’t steer3.
#Drops
A monster’s drop table gives weights for 16 entries, nothing among them4. Weights rise for hearts at one heart or less, and for bombs, arrows, and rupees Link lacks, and one random value picks the entry. An Octorok drops nothing about 70% of the time4.
#Kinstone fusion
Each fuser has its own percent chance to offer a fusion, and none offers before Link has the Kinstone Bag. The one call the atlas has read rolls it when the Blade Brothers are set up. Most fusers always offer, and the rest offer half or 30% of the time5.
Mechanics
| Mechanic | Kind | How |
|---|---|---|
| Health upgrade | Pieces | four Pieces of Heart make a container, and every piece refills Link's health; up to 20 hearts |
| Invincibility frames | Frame timer | 30 frames after an Octorok's touch, read from a collision matrix |
| Knockback | Fixed shove | a set speed for as long as the collision matrix says, with no steering |
| Item drop table | Weighted, by need | per-monster weights, raised for hearts, bombs, arrows, and rupees Link lacks, and one roll |
| Kinstone fusion | Chance offer | each fuser offers with its own percent chance, rolled on each call |
Every claim about this game, by concept (5)
References
- ^ abThe Minish Cap counts Link's health in eighths of a heart, as A Link to the Past does and as the zladx annotators' comment on Link's Awakening's health byte says ("$08 = 1 heart"); a new file starts him at 24, three hearts. When Link holds up a Piece of Heart, the code refills his health to its maximum and adds 1 to the piece count. The fourth piece resets the count to 0 and then does what a Heart Container does: it adds 8 to the maximum, capped at 160 (20 hearts, A Link to the Past's cap), and refills his health again. Both earlier games also make a container from every fourth piece.Codezeldaret/tmc · spannerisms/usdasm · zladx/LADX-Disassemblyrevised
How we know
134 case 2: 135 if (super->type != ITEM_HEART_CONTAINER) { 136 if (super->type != ITEM_HEART_PIECE) { 137 break; ⋮ 139 ModHealth(160); 140 gSave.stats.heartPieces++; 141 if (gSave.stats.heartPieces < 4) { 142 break; ⋮ 144 gSave.stats.heartPieces = 0; ⋮ 146 gSave.stats.maxHealth += 8; 147 if (gSave.stats.maxHealth > 160) { 148 gSave.stats.maxHealth = 160; ⋮
src/object/linkHoldingItem.c lines 131–150 at 6fb6dfb · upstream ↗ 204s32 ModHealth(s32 delta) { ⋮ 208 newHealth = stats->health + delta; ⋮ 212 if (stats->maxHealth < newHealth) { 213 newHealth = stats->maxHealth; ⋮ 215 stats->health = newHealth;
src/gameUtils.c lines 204–216 at 6fb6dfb · upstream ↗ 1688 save->stats.health = 24; 1689 save->stats.maxHealth = 24;
src/fileselect.c lines 1688–1689 at 6fb6dfb · upstream ↗ 776 maxQuarterHearts = gSave.stats.maxHealth / 2; ⋮ 797 maxHearts = maxQuarterHearts / 4;
src/fileselect.c lines 776–797 at 6fb6dfb · upstream ↗ 20390|05F08A| LDA.l $7EF36B 20391|05F08E| INC A 20392|05F08F| AND.b #$03 20393|05F091| STA.l $7EF36B 20394|05F095| BNE .not_full_set ⋮ 20400|05F09C| LDY.b #$26 ; ITEMGET 26
bank_05.asm lines 20390–20402 at bcdd96e · upstream ↗ 13529|08C48B| LDA.l $7EF36C 13530|08C48F| CMP.b #$A0 13531|08C491| BEQ .not_heart ⋮ 13533|08C493| CLC 13534|08C494| ADC.b #$08 13535|08C496| STA.l $7EF36C
bank_08.asm lines 13527–13535 at bcdd96e · upstream ↗ 2922 ; If found 4 heart pieces… ⋮ 2929 ; Clear heart pieces count ⋮ 2935 ; Increase the maximum number of hearts
src/code/entities/bank3.asm lines 2922–2937 at 2a5c2c0 · upstream ↗ 3382; Amount of health ($08 = 1 heart) 3383wHealth::
src/constants/memory/wram.asm lines 3382–3384 at 2a5c2c0 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
- ^The Minish Cap takes a touch's effects from a collision matrix: the entry at the toucher's hit type × 34 + the touched one's hurt type. For an Octorok or a Leever (hit type 25) touching Link in his normal state (hurt type 0), the US ROM's entry gives Link 30 invincibility frames, a knockback duration of 8, and 2 damage, a quarter heart. The collision loop skips a pair while either has a nonzero iframe count, unless either has bit 7 of its collision flags set, and Link's count steps one toward 0 each frame his update runs, so the protection lasts 30 frames. Link's Awakening sets its count to 80 and A Link to the Past to 58.Codezeldaret/tmc · “Legend of Zelda, The - The Minish Cap (USA)” · zladx/LADX-Disassembly · spannerisms/usdasmrevised
How we know
436 bl arm_CalcCollisionDirection 437 mov r6, r0 438 ldrb r1, [OTHER, #0x3f] // hitType 439 mov r0, #0x22 440 mul r1, r0, r1 441 ldrb r0, [THIS, #0x40] // hurtType 442 add r0, r0, r1 443 mov r1, #0xc 444 mul r2, r0, r1 445 ldr r1, _080B2014 @ =gCollisionMtx ⋮ 447 mov r1, #0
asm/src/intr.s lines 436–452 at 6fb6dfb · upstream ↗ 329 ldrb r0, [OTHER, #0x3c] // collisionFlags 330 orr r0, r0, r3 331 lsrs r0, r0, #7 // top 332 bhs _080B1D20 333 ldrb r0, [OTHER, #0x3d] // iframes 334 orrs r0, r0, r2 335 bne next_other
asm/src/intr.s lines 329–335 at 6fb6dfb · upstream ↗ 731 org->knockbackSpeed = 16 * settings->orgKnockbackSpeed; 732 org->iframes = settings->orgIframes; 733 if (org->iframes == 0) 734 org->iframes = -1; 735 org->knockbackDuration = settings->orgKnockbackDuration; 736 tgt->damage = settings->tgtDamage;
src/collision.c lines 729–737 at 6fb6dfb · upstream ↗ 224 if (org->kind == PLAYER) { 225 damage = tgt->damage; ⋮ 236 health = ModHealth(-damage);
src/collision.c lines 224–236 at 6fb6dfb · upstream ↗ 424 super->hurtType = 0;src/player.c line 424 at 6fb6dfb · upstream ↗ 87 super->hitType = definition->damageType;src/enemyUtils.c line 87 at 6fb6dfb · upstream ↗ 239const EnemyDefinition gEnemyDefinition_0[] = { 240 { 1, 0, &gHitbox_0, SPRITE_OCTOROK, { 3, 1, 1, 1 }, 2, 96, 25, 15 },
src/enemy.c lines 239–240 at 6fb6dfb · upstream ↗ 185const EnemyDefinition gEnemyDefinition_2[] = { 186 { 2, 175, &gHitbox_0, SPRITE_LEEVER, { 3, 0, 0, 1 }, 6, 128, 25, 139 },
src/enemy.c lines 185–186 at 6fb6dfb · upstream ↗ 224 HandlePlayerLife(super); 225 DoPlayerAction(this); ⋮ 228 sub_080171F0();
src/interrupts.c lines 224–228 at 6fb6dfb · upstream ↗ 322static void sub_080171F0(void) { ⋮ 335 sub_080028E0(&gPlayerEntity.base);
src/interrupts.c lines 322–335 at 6fb6dfb · upstream ↗ 1167sub_080028E0: @ 0x080028E0 1168 movs r1, #0x3d 1169 movs r3, #1 1170 ldrsb r2, [r0, r1] 1171 cmp r2, #0 1172 beq _080028F2 1173 bmi _080028EE 1174 rsbs r3, r3, #0 1175_080028EE: 1176 adds r2, r2, r3 1177 strb r2, [r0, r1]
asm/src/code_08001A7C.s lines 1167–1177 at 6fb6dfb · upstream ↗ In the cartridge (“Legend of Zelda, The - The Minish Cap (USA)”): the literal `_080B2014` that intr.s loads as gCollisionMtx; it holds 0x080B7B74; gCollisionMtx entry 34 × 25 + 0 = 850, at 0x080B7B74 + 12 × 850; its 12 bytes in ColSettings order (collision.c L26-L39) are 24, 30, 8, 2, 0, 16, 255, 1, 0, 0, 0, 0: Link's knockback speed 24, iframes 30, knockback duration 8, the Octorok's damage to him 2, then the Octorok's own speed 16, iframes −1, and duration 1. We don't reproduce the bytes.
5347 ld a, $50 ;; 03:6DB2 $3E $50src/code/entities/bank3.asm lines 5347–5348 at 2a5c2c0 · upstream ↗ 146|0780B4| LDA.w $031F 147|0780B7| BNE .have_iframes ⋮ 149|0780B9| LDA.b #$3A 150|0780BB| STA.w $031F
bank_07.asm lines 146–150 at bcdd96e · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
- ^While Link's knockback duration is nonzero, a routine that his normal state calls each frame counts it one step toward 0 and, if it's still nonzero, moves him
0x280along the knockback direction; the code's walking speedkWalkSpeedisQ_8_8(1.25),0x140, so the atlas reads the push as twice walking speed, though the move routine wasn't read. His normal state skips the code that reads the d-pad while the duration is nonzero, so the player can't steer. The collision loop stores the direction for both parties, Link's the reverse of the toucher's. Among the cancels: a jump, a dash, a roll, a fall, swimming (a swim state above 1), a held object (states 1–4), and climbing unless the push is south. After an Octorok's touch (duration 8) that's 7 moves (computed). Link's Awakening's push runs a 16-frame countdown.Codezeldaret/tmc · zladx/LADX-DisassemblyrevisedHow we know
1889void sub_080792D8(void) { ⋮ 1892 if (playerEntity->knockbackDuration == 0) 1893 return; ⋮ 1895 if (playerEntity->action == PLAYER_08071DB8 || gPlayerState.dash_state || (u8)(gPlayerState.heldObject - 1) < 4 || ⋮ 1898 gPlayerState.flags & PL_ROLLING) { 1899 playerEntity->knockbackDuration = 0; 1900 } else if (playerEntity->action == PLAYER_CLIMB && playerEntity->knockbackDirection != DirectionSouth) { 1901 playerEntity->knockbackDuration = 0; ⋮ 1903 if ((s8)playerEntity->knockbackDuration >= 1) { 1904 playerEntity->knockbackDuration--; ⋮ 1909 if (playerEntity->knockbackDuration == 0) 1910 return; ⋮
src/playerUtils.c lines 1889–1923 at 6fb6dfb · upstream ↗ 37#define kWalkSpeed Q_8_8(1.25)src/player.c line 37 at 6fb6dfb · upstream ↗ 498 sub_080792D8();src/player.c line 498 at 6fb6dfb · upstream ↗ 522 if (super->knockbackDuration == 0) { ⋮ 543 super->direction = gPlayerState.direction;
src/player.c lines 522–543 at 6fb6dfb · upstream ↗ 564 } else { 565 if (gPlayerState.item == NULL) 566 UpdateAnimationSingleFrame(super); ⋮ 569 return;
src/player.c lines 564–569 at 6fb6dfb · upstream ↗ 475_080B1F10: 476 strb r6, [OTHER, #0x3e] 477 eor r6, r6, #0x10 478 strb r6, [THIS, #0x3e]
asm/src/intr.s lines 475–478 at 6fb6dfb · upstream ↗ 735 org->knockbackDuration = settings->orgKnockbackDuration;src/collision.c line 735 at 6fb6dfb · upstream ↗ ld a, $10 … ld [wIgnoreLinkCollisionsCountdown], a … cp ENTITY_MOLDORM … ld a, $14 … ld a, $18 … call func_003_7565
src/code/entities/bank3.asm#L5376-L5398 · upstream ↗ 588func_002_44C2::src/code/bank2.asm lines 588–614 at 2a5c2c0 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
- ^ abWhen a monster dies, its death effect calls the drop routine with the monster's drop table: 6 for Octoroks, ChuChus, Leevers, and Peahats. Unless the room disables random drops, the routine adds the table's 16 weights to an active Picolyte's row, then adds 5 to the heart weight at 8 health (one heart) or less, 3 to bombs with none carried, 3 to arrows with none, and 1 to the 5-rupee weight at 10 rupees or fewer; a weight below 0 (the tables rule an entry out with −999) counts as 0. One
Random()value then picks the entry: its bits 24–27 choose where to start, and the value modulo the weights' sum walks the 16 entries from there. Table 6 weighs nothing 73, a green rupee 25, a heart 5, bombs 1, and arrows 1, so with no Picolyte and no need bonus an Octorok drops nothing about 70% of the time (computed). Bombs and arrows drop only once Link has the bag or the bow. A Link to the Past instead steps each prize pack through 8 prizes in order.Codezeldaret/tmc · “Legend of Zelda, The - The Minish Cap (USA)” · spannerisms/usdasmrevisedHow we know
123 static const u8 DeathFx_EnemyDroptables[] = { 124 [OCTOROK] = 6, 125 [CHUCHU] = 6, 126 [LEEVER] = 6, 127 [PEAHAT] = 6,
src/object/deathFx.c lines 123–127 at 6fb6dfb · upstream ↗ 237 if (this->parentId < 0xf0) { 238 CreateRandomItemDrop(super, DeathFx_EnemyDroptables[this->parentId]);
src/object/deathFx.c lines 237–238 at 6fb6dfb · upstream ↗ 427 if (gRoomVars.randomDropsDisabled != TRUE) { 428 ptr2 = &gDroptableModifiers[DROPTABLE_NONE]; 429 ptr4 = NULL; 430 switch (r3) { 431 case 1 ... 12: 432 ptr4 = &gEnemyDroptables[r3];
src/itemUtils.c lines 427–432 at 6fb6dfb · upstream ↗ 456 if (ptr4 != 0) { 457 if ((r1 = gSave.stats.picolyteType) == 0) { ⋮ 464 ptr3 = &gEnemyDroptables[r1 + 6]; ⋮ 468 SumDropProbabilities(droptable.a, ptr4->a, ptr2->a, ptr3->a); 469 if (gSave.stats.health <= 8) { 470 droptable.s.hearts += 5; ⋮ 472 if (gSave.stats.bombCount == 0) { 473 droptable.s.bombs += 3; ⋮ 475 if (gSave.stats.arrowCount == 0) { 476 droptable.s.arrows += 3; ⋮ 478 if (gSave.stats.rupees <= 10) { 479 droptable.s.rupee5++;
src/itemUtils.c lines 456–480 at 6fb6dfb · upstream ↗ 495 summOdds = SumDropProbabilities2(droptable.a, droptable.a, ptr2->a, ptr3->a); 496 rand = Random(); 497 item = (rand >> 0x18); 498 item &= 0xF; 499 rand = rand % summOdds; ⋮ 502 for (r3 = 0, r1 = 0; r3 < 0x10; r3++, item = (item + 1) & 0xF) { 503 if ((r1 += droptable.a[item]) > rand) { 504 break; ⋮ 508 r1 = gUnk_080FE1B4[item]; 509 if (r1 != ITEM_NONE) { 510 return CreateItemDrop(arg0, r1, 0);
src/itemUtils.c lines 495–510 at 6fb6dfb · upstream ↗ 30 // sum 3 drop probabilities, clamp to 0, return scalar sum ⋮ 42 bpl positive_drop_chance // clamp to 0 43 movs r5, #0 44positive_drop_chance: 45 strh r5, [r0, r4] // store in output 46 adds r7, r5
asm/src/code_08000F10.s lines 29–47 at 6fb6dfb · upstream ↗ 19Random: @ 0x08000E50 20 ldr r2, =gRand 21 ldr r0, [r2] 22 lsls r1, r0, #1 23 adds r0, r0, r1 24 movs r1, #0xd 25 rors r0, r1 26 str r0, [r2] 27 lsrs r0, r0, #1
asm/src/code_08000E44.s lines 18–27 at 6fb6dfb · upstream ↗ 112 [6] = { { 113 .none = 73, 114 .rupee1 = 25, 115 .rupee5 = -999, 116 .rupee20 = -999, 117 .hearts = 5, 118 .fairy = 0, 119 .bombs = 1, 120 .arrows = 1, 121 .mysteriousShells = 0,
src/droptables.c lines 112–129 at 6fb6dfb · upstream ↗ 528 case ITEM_BOMBS5: 529 if (!GetInventoryValue(ITEM_BOMBBAG)) { 530 return ITEM_NONE; ⋮ 533 case ITEM_ARROWS5: 534 if (!GetInventoryValue(ITEM_BOW)) { 535 return ITEM_NONE;
src/itemUtils.c lines 526–536 at 6fb6dfb · upstream ↗ In the cartridge (“Legend of Zelda, The - The Minish Cap (USA)”): gUnk_080FE1B4, the entry-to-item table, whose values the decompilers give only in a comment (itemUtils.c L416-L420); the ROM's 16 bytes are 0, 84, 85, 86, 95, 96, 93, 94, 63, 252, 253, 254, 255, 0, 0, 0, which include/item.h names none, the 1-, 5-, and 20-rupee pieces, heart, fairy, 5 bombs, 5 arrows, shells, the red, blue, and green Kinstones, and the enemy beetle. We don't reproduce the bytes.
24136|06F9B2| JSL GetRandomNumber ⋮ 24138|06F9B6| PLY ⋮ 24140|06F9B7| AND.w PrizePackRarities,Y 24141|06F9BA| BNE .you_dont_get_a_drop ⋮ 24155|06F9C4| LDA.w $0FC7,Y 24156|06F9C7| INC A 24157|06F9C8| AND.b #$07 24158|06F9CA| STA.w $0FC7,Y
bank_06.asm lines 24107–24162 at bcdd96e · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
- ^Each Kinstone fuser in The Minish Cap has a data entry whose second byte, which the decompilers call its stability, is the percent chance it offers a fusion:
GetFusionToOfferreturns no fusion when that byte is at mostRandom() % 100, none before Link has the Kinstone Bag, and none before the story's progress reaches the entry's first byte. The one call the atlas read is in the Blade Brothers' first action, which sets them up. An entry of0xFFin a fuser's list stands for a shared fusion: from a random start among 18, the first not yet fused. In the US ROM's table, of the 119 fusers after entry 0, 77 have 100, 20 have 50, 21 have 30, and one has 60 (computed).Codezeldaret/tmc · “Legend of Zelda, The - The Minish Cap (USA)”revisedHow we know
1235 fuserId = GetFuserId(entity); 1236 fuserData = gUnk_08001DCC[fuserId]; 1237 if (GetInventoryValue(ITEM_KINSTONE_BAG) == 0 || fuserData[0] > gSave.global_progress) { 1238 return KINSTONE_NONE;
src/common.c lines 1235–1239 at 6fb6dfb · upstream ↗ 1257 if (offeredFusion == KINSTONE_RANDOM) { // random shared fusion 1258 offeredFusion = GetRandomSharedFusion(fuserData);
src/common.c lines 1257–1259 at 6fb6dfb · upstream ↗ 1274 randomMood = Random(); 1275 fuserStability = fuserData[1]; 1276 if (fuserStability <= randomMood % 100) { 1277 return KINSTONE_NONE; // fickleness
src/common.c lines 1274–1278 at 6fb6dfb · upstream ↗ 1943const u8 SharedFusions[] = { ⋮ 1948u32 GetRandomSharedFusion(u8* fuserData) { 1949 s32 r = (s32)Random() % 18; 1950 u32 i; 1951 for (i = 0; i < 18; ++i) { 1952 u32 kinstoneId = SharedFusions[r]; 1953 if (!CheckKinstoneFused(kinstoneId)) 1954 return kinstoneId; 1955 r = (r + 1) % 18; ⋮ 1957 return KINSTONE_JUST_FUSED;
src/common.c lines 1943–1958 at 6fb6dfb · upstream ↗ 154 KINSTONE_RANDOM = 0xFF,include/kinstone.h line 154 at 6fb6dfb · upstream ↗ 314void sub_08068AFC(BladeBrothersEntity* this) { 315 super->action = 1; 316 super->spriteSettings.draw = 1; 317 this->fusionOffer = GetFusionToOffer(super);
src/npc/bladeBrothers.c lines 314–317 at 6fb6dfb · upstream ↗ 307gUnk_08001DCC:: @ 08001DCC 308 .4byte gUnk_08001FAC 309 .4byte gUnk_08001FB4
asm/src/code_08001A7C.s lines 307–309 at 6fb6dfb · upstream ↗ In the cartridge (“Legend of Zelda, The - The Minish Cap (USA)”): the fusers' pointer table, 120 words, the first pointing at 0x08001FAC where the table ends; the fusers' entries, every pointer landing in this range; second bytes counted: entry 0 (first byte 99) has 0, and of entries 1–119, 77 have 100, 20 have 50, 21 have 30, one has 60. We don't reproduce the bytes.
Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
Sources
Code
- zeldaret. 2026. “tmc: a decompilation of The Legend of Zelda: The Minish Cap (zeldaret/tmc).” Commit 6fb6dfb. zeldaret/tmc @ 6fb6dfb · licence, all uses
- (spannerisms), kan. 2026. “usdasm: A Link to the Past US Disassembly.” Commit bcdd96e. spannerisms/usdasm @ bcdd96e · licence, all uses
- zladx contributors. 2026. “LADX-Disassembly: Disassembly of Link's Awakening DX.” zladx. Commit 2a5c2c0. zladx/LADX-Disassembly @ 2a5c2c0 · licence, all uses
- n.d. “Legend of Zelda, The - The Minish Cap (USA).” · licence, all uses