On this page
  1. Energy and ammunition
  2. The jump
  3. X parasites
  4. Related
  5. Mechanics
  6. References
  7. Sources

Metroid Fusion came out on the Game Boy Advance two years before Metroid: Zero Mission, and the same community decompiles both. The atlas reads it through metroidret’s C decompilation, which rebuilds the North American ROM1. Names and comments cited here are the decompilers’. Nothing here has been measured in the ROM yet.

#Energy and ammunition

A new game sets Samus’s energy to 99 energy2, as Zero Mission does. Her missile and power bomb counters already read 10, though she has no weapon to fire them2. An energy tank adds 100 per tank, up to 20993 and refills her, up to 2099. Zero Mission stops at 1299, or 699 on Hard3. A missile tank adds 5 missiles per tank, up to 2554, up to 255. Super, ice, and diffusion missiles all draw on that one count, where Zero Mission keeps super missiles apart4.

#The jump

A jump sets the vertical speed to 5.3 px/frame (6.9 with the High Jump), capped at 45, but movement caps it at 4 px/frame, so the first 8 frames of a jump rise 4 px each5. Gravity is 0.1875 px/frame²6, three-fifths of Zero Mission’s (computed)6. Held to the top, a jump rises about 71 px (104 with the High Jump)7, about 12 px more than Zero Mission’s, and the High Jump about 21 px more (computed)7.

Letting go of A while Samus rises stops the rise at once, at any height, as in Zero Mission8.

#X parasites

In Fusion a dying enemy turns into an X parasite, and absorbing it refills Samus by its color: 10 energy (yellow), 2 missiles (green), 500 energy and 100 missiles (red)9,9. Each enemy carries weights out of 1024 for red and green; yellow takes what’s left, if the enemy allows yellow at all. The draw also bends toward what Samus lacks: a green X turns yellow when her missiles are full and her energy isn’t, and the reverse10. Metroid’s enemies dropped energy balls and missiles from one table instead11.

Mechanics

MechanicKindHow
Health upgradeWhole containersenergy tanks of 100 from 99 at the start, each refilling her energy, up to 2099
AmmunitionSeveral poolsone missile count that every missile type shares, and power bombs; 5 and 2 per tank
JumpSpeed by state5.3 px/frame up, 6.9 with the High Jump, held to 4 px/frame by the speed cap
GravityOne gravity0.1875 px/frame² in air, held or not, two-thirds of that in liquid without the Gravity Suit
Variable jump heightRelease capletting go during the rise sets the vertical speed to 0 at any height
Item drop tableWeighted, by needa killed enemy becomes an X parasite whose color, picked from 1024 by per-enemy weights, sets its refill
Every claim about this game, by concept (10)
generalThe mf decompilation's README names mf_us.gba, the North American release, as its default and only supported build and gives its SHA-1 as ca33f4348c2c05dd330d37b97e2c5a69531dfe87, which is the SHA-1 of the No-Intro file Metroid Fusion (USA).gba. The README counts 376 functions not yet decompiled. The atlas hasn't built it.Code
AmmunitionSamus has one missile count and one power bomb count, each with a maximum. A missile tank adds 5 to both the missile count and its maximum, unless the maximum would pass 255; a power bomb tank adds 2 to both, up to 254. The missile types the downloads add (super, ice, and diffusion missiles, flags in weaponsStatus) draw on that one count. Zero Mission keeps separate counts for missiles, super missiles, and power bombs, and its missile tank also adds 5, or 2 on Hard.Code
GravityOut of liquid, SamusUpdatePhysics sets the vertical acceleration to 6 and the vertical speed cap to 128, and SamusUpdateVelocityPosition takes 6 from yVelocity on every frame of the airborne poses while the velocity is above -220, whether A is held or not: 0.1875 px/frame², with rises and falls both capped at 4 px/frame (computed). In water, lava, or acid without the Gravity Suit or the flag the decompilers name SMF_SA_X_SUIT, the acceleration drops to 4 and the cap to 48, 0.125 px/frame² and 1.5 px/frame. Zero Mission's gravity is 10, 0.3125 px/frame², with the same 4 px/frame fall cap (computed).Code
Health upgradeWhen SamusInit runs with no file loading (gIsLoadingFile clear) and an unnamed flag, gUnk_3000be3, clear, it copies sEquipment_Empty into gEquipment: 99 energy of 99, 10 missiles of 10, 10 power bombs of 10, and no weapon flags (MBF_NONE). The atlas reads this as a new game's start. Zero Mission starts with the same 99 energy, but with every ammunition count at 0.Code
An energy tank adds 100 to Samus's maximum energy and sets her energy to the new maximum, unless the new maximum would pass MAX_ENERGY_AMOUNT, 20 tanks plus the starting 99, or 2099. The tank code reads no difficulty. Zero Mission's tank also adds 100 and refills, or 50 on Hard, and stops at 12 tanks: 1299 on Easy and Normal and 699 on Hard (computed).Code
Item drop tableTouching an absorbable X parasite runs SamusAbsorbX, which refills by its color: a red X (SSC_RED_X_PARASITE) 500 energy, 100 missiles, and 10 power bombs; a green X 2 missiles; any other (yellow) X 10 energy. SpriteUtilRefillSamus adds missiles only once a missile flag is set and power bombs only once the power bomb flag is, and stops each at its maximum.Code
When a dying sprite's countdown ends, SpriteDying sets its pose to 0x5B (the decompilers' SPRITE_POSE_TURNING_INTO_X) unless it is a secondary sprite, and an enemy's AI (Choot's, for one) runs XParasiteInit in that pose. The X it becomes takes its color from a number from 1 to 1024, made from the low 2 bits of gSpriteRandomNumber and the 8-bit frame counter, against three weights in that sprite's row of sPrimarySpriteStats: the top red-weight values give red and the next green-weight values green, each only when its weight is above 0, and everything left below the green bound gives yellow, whatever the yellow weight is, unless it is 0, which deletes the X instead. A would-be green X turns yellow when missiles are full and energy isn't, or Samus has no missiles yet; a would-be yellow X turns green when energy is full and missiles aren't. An X from a core X is green when gSpriteRandomNumber is below 5 or that energy test holds, and otherwise yellow; an X placed in a room is yellow. All of this applies only to an X whose sprite property is SSP_X_ABSORBABLE_BY_SAMUS: one marked SSP_X_UNABSORBABLE_BY_SAMUS, or the unnamed SSP_UNKNOWN_40, is always yellow, and any other property deletes the X.Code
JumpA jump sets Samus's upward yVelocity to 170 (SAMUS_LOW_JUMP_VELOCITY), or 220 with the High Jump, with no separate suitless speed. The velocity counts eighths of a subpixel and a pixel is 4 subpixels, so these are 5.3125 and 6.875 px/frame (computed). Movement caps the velocity at 128 (16 subpixels, 4 px), so a jump's first 8 frames each rise 4 px, and a High Jump's first 16 (computed). Zero Mission's jump takes off at 192, or 232 with the High Jump, under a cap of 192: 6 px/frame from the first frame.Code
Stepping the code's arithmetic with A held, out of liquid, a jump from the ground peaks about 71 px up (283 subpixels) on its 28th frame, and a High Jump 104 px (416 subpixels) on its 36th (computed: each frame moves yVelocity capped at 128 and shifted right 3, then takes 6 from it). The same stepping gives Zero Mission about 59 px on the 19th frame and 83 px on the 23rd (mzm/jump-height), so Fusion's jump is higher and slower.Code
Variable jump heightOn any frame Samus rises (yVelocity above 0) without A held, the mid-air handlers set yVelocity to 0, at any height: SamusMidAir, SamusTurningAroundMidAir, SamusSpinning (which runs the spin jump, the Space Jump, and the Screw Attack), and SamusWallJumping, which also drops a wall jump into a spin. The test is each handler's last step but the wall jump's, so a frame whose earlier branch returns skips it. Zero Mission's first three handlers end with the same test.Code

References

  1. ^The mf decompilation's README names mf_us.gba, the North American release, as its default and only supported build and gives its SHA-1 as ca33f4348c2c05dd330d37b97e2c5a69531dfe87, which is the SHA-1 of the No-Intro file Metroid Fusion (USA).gba. The README counts 376 functions not yet decompiled. The atlas hasn't built it.Codemetroidret/mfrevised
    How we know
    53231/3607 functions decompiled (89.57%, 376 left)
    
    README.md line 5 at a6d6187 · upstream ↗
    13This repository can be used to build multiple ROMs. The ROMs are named like this: `mf_{region}.gba` (or `mf_{region}_debug.gba`)
    ⋮
    16- us: USA, North America `sha1: ca33f4348c2c05dd330d37b97e2c5a69531dfe87`
    17- eu: Europe **(Not yet supported)**
    
    README.md lines 13–17 at a6d6187 · upstream ↗
    26The default built ROM is mf_us.gba
    
    README.md line 26 at a6d6187 · upstream ↗
    1CA33F4348C2C05DD330D37B97E2C5A69531DFE87  mf_us.gba
    
    mf_us.sha1 line 1 at a6d6187 · upstream ↗
    4REGION ?= us
    ⋮
    7ifeq ($(REGION),us)
    8	TARGET = mf_us
    ⋮
    11	CPPFLAGS += -DREGION_US
    
    Makefile lines 4–11 at a6d6187 · 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.

    In the claim ledger

  2. ^ abWhen SamusInit runs with no file loading (gIsLoadingFile clear) and an unnamed flag, gUnk_3000be3, clear, it copies sEquipment_Empty into gEquipment: 99 energy of 99, 10 missiles of 10, 10 power bombs of 10, and no weapon flags (MBF_NONE). The atlas reads this as a new game's start. Zero Mission starts with the same 99 energy, but with every ammunition count at 0.Codemetroidret/mf · metroidret/mzm
    How we know
    7523    if (gUnk_3000be3 || gIsLoadingFile)
    7524        return;
    ⋮
    7526    gEquipment = sEquipment_Empty;
    
    src/samus.c lines 7523–7526 at a6d6187 · upstream ↗
    47const struct Equipment sEquipment_Empty = {
    48    .currentEnergy = 99,
    49    .maxEnergy = 99,
    50    .currentMissiles = 10,
    51    .maxMissiles = 10,
    52    .currentPowerBombs = 10,
    53    .maxPowerBombs = 10,
    54    .beamStatus = BF_NONE,
    55    .weaponsStatus = MBF_NONE,
    
    src/data/samus/samus_animation_pointers.c lines 47–55 at a6d6187 · upstream ↗
    236// --- Energy and ammo ---
    237#define STARTING_ENERGY 99
    238#define STARTING_MISSILES 10
    239#define STARTING_POWER_BOMBS 10
    
    include/constants/samus.h lines 236–239 at a6d6187 · upstream ↗
    8071        if (!gIsLoadingFile)
    ⋮
    8073            // Zero out most of samus's data
    8074            DMA3_FILL_32(0, &gSamusData, sizeof(gSamusData));
    8075            DMA3_FILL_32(0, &gEquipment, sizeof(gEquipment));
    ⋮
    8087            gEquipment.maxEnergy = 99;
    8088            gEquipment.currentEnergy = 99;
    
    src/samus.c lines 8071–8088 at 2aae289 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  3. ^ abAn energy tank adds 100 to Samus's maximum energy and sets her energy to the new maximum, unless the new maximum would pass MAX_ENERGY_AMOUNT, 20 tanks plus the starting 99, or 2099. The tank code reads no difficulty. Zero Mission's tank also adds 100 and refills, or 50 on Hard, and stops at 12 tanks: 1299 on Easy and Normal and 699 on Hard (computed).Codemetroidret/mf · metroidret/mzmrevised
    How we know
    267                else if (i == TANK_TYPE_ENERGY)
    ⋮
    269                    if (gEquipment.maxEnergy + ENERGY_INCREASE_AMOUNT <= MAX_ENERGY_AMOUNT)
    ⋮
    271                        gEquipment.maxEnergy += ENERGY_INCREASE_AMOUNT;
    272                        gEquipment.currentEnergy = gEquipment.maxEnergy;
    
    src/bg_clip.c lines 267–274 at a6d6187 · upstream ↗
    241#ifndef REGION_JP
    242#define ENERGY_INCREASE_AMOUNT 100
    ⋮
    246// Default 2099
    247#define MAX_ENERGY_AMOUNT (20 * ENERGY_INCREASE_AMOUNT + STARTING_ENERGY)
    
    include/constants/samus.h lines 241–247 at a6d6187 · upstream ↗
    531                else if (i == ITEM_TYPE_ENERGY)
    ⋮
    533                    if (sNumberOfTanksPerArea[MAX_AMOUNT_OF_AREAS - 1].energy * sTankIncreaseAmount[gDifficulty].energy + sStartingHealthAmmo.energy >= gEquipment.maxEnergy + sTankIncreaseAmount[gDifficulty].energy)
    ⋮
    535                        gEquipment.maxEnergy += sTankIncreaseAmount[gDifficulty].energy;
    536                        gEquipment.currentEnergy = gEquipment.maxEnergy;
    
    src/bg_clip.c lines 531–537 at 2aae289 · upstream ↗
    52    [MAX_AMOUNT_OF_AREAS - 1] = {
    53        .energy = 12,
    
    src/data/block_data.c lines 52–53 at 2aae289 · upstream ↗
    60const struct TankList sStartingHealthAmmo = {
    61    .energy = 99,
    ⋮
    67const struct TankList sTankIncreaseAmount[DIFF_COUNT] = {
    68    [DIFF_EASY] = {
    69        .energy = 100,
    ⋮
    74    [DIFF_NORMAL] = {
    75        .energy = 100,
    ⋮
    80    [DIFF_HARD] = {
    81        .energy = 50,
    
    src/data/block_data.c lines 60–81 at 2aae289 · 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.

    In the claim ledger

  4. ^ abSamus has one missile count and one power bomb count, each with a maximum. A missile tank adds 5 to both the missile count and its maximum, unless the maximum would pass 255; a power bomb tank adds 2 to both, up to 254. The missile types the downloads add (super, ice, and diffusion missiles, flags in weaponsStatus) draw on that one count. Zero Mission keeps separate counts for missiles, super missiles, and power bombs, and its missile tank also adds 5, or 2 on Hard.Codemetroidret/mf · metroidret/mzm
    How we know
    258                // Apply tank
    259                if (i == TANK_TYPE_MISSILE)
    ⋮
    261                    if (gEquipment.maxMissiles + MISSILE_INCREASE_AMOUNT <= MAX_MISSILE_AMOUNT)
    ⋮
    263                        gEquipment.maxMissiles += MISSILE_INCREASE_AMOUNT;
    264                        gEquipment.currentMissiles += MISSILE_INCREASE_AMOUNT;
    ⋮
    275                else if (i == TANK_TYPE_POWER_BOMB)
    ⋮
    277                    if (gEquipment.maxPowerBombs + POWER_BOMB_INCREASE_AMOUNT <= MAX_POWER_BOMB_AMOUNT)
    ⋮
    279                        gEquipment.maxPowerBombs += POWER_BOMB_INCREASE_AMOUNT;
    280                        gEquipment.currentPowerBombs += POWER_BOMB_INCREASE_AMOUNT;
    
    src/bg_clip.c lines 258–282 at a6d6187 · upstream ↗
    241#ifndef REGION_JP
    242#define ENERGY_INCREASE_AMOUNT 100
    243#define MISSILE_INCREASE_AMOUNT 5
    244#define POWER_BOMB_INCREASE_AMOUNT 2
    ⋮
    248// Default 255
    249#define MAX_MISSILE_AMOUNT (51 * MISSILE_INCREASE_AMOUNT)
    250// Default 254
    251#define MAX_POWER_BOMB_AMOUNT (127 * POWER_BOMB_INCREASE_AMOUNT)
    
    include/constants/samus.h lines 241–251 at a6d6187 · upstream ↗
    42struct Equipment {
    43    u16 currentEnergy;
    44    u16 maxEnergy;
    45    u16 currentMissiles;
    46    u16 maxMissiles;
    47    u8 currentPowerBombs;
    48    u8 maxPowerBombs;
    49    u8 beamStatus;
    50    u8 weaponsStatus;
    
    include/structs/samus.h lines 42–50 at a6d6187 · upstream ↗
    273#define MBF_NONE 0
    274#define MBF_MISSILES (1 << 0)
    275#define MBF_SUPER_MISSILES (1 << 1)
    276#define MBF_ICE_MISSILES (1 << 2)
    277#define MBF_DIFFUSION_MISSILES (1 << 3)
    ⋮
    280#define MBF_ANY_MISSILE (MBF_MISSILES | MBF_SUPER_MISSILES | MBF_ICE_MISSILES | MBF_DIFFUSION_MISSILES)
    
    include/constants/samus.h lines 273–280 at a6d6187 · upstream ↗
    16struct Equipment {
    17    u16 maxEnergy;
    18    u16 maxMissiles;
    19    u8 maxSuperMissiles;
    20    u8 maxPowerBombs;
    21    u16 currentEnergy;
    22    u16 currentMissiles;
    23    u8 currentSuperMissiles;
    24    u8 currentPowerBombs;
    
    include/structs/samus.h lines 16–24 at 2aae289 · upstream ↗
    67const struct TankList sTankIncreaseAmount[DIFF_COUNT] = {
    68    [DIFF_EASY] = {
    69        .energy = 100,
    70        .missile = 5,
    ⋮
    74    [DIFF_NORMAL] = {
    75        .energy = 100,
    76        .missile = 5,
    ⋮
    80    [DIFF_HARD] = {
    81        .energy = 50,
    82        .missile = 2,
    
    src/data/block_data.c lines 67–84 at 2aae289 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  5. ^ abA jump sets Samus's upward yVelocity to 170 (SAMUS_LOW_JUMP_VELOCITY), or 220 with the High Jump, with no separate suitless speed. The velocity counts eighths of a subpixel and a pixel is 4 subpixels, so these are 5.3125 and 6.875 px/frame (computed). Movement caps the velocity at 128 (16 subpixels, 4 px), so a jump's first 8 frames each rise 4 px, and a High Jump's first 16 (computed). Zero Mission's jump takes off at 192, or 232 with the High Jump, under a cap of 192: 6 px/frame from the first frame.Codemetroidret/mf · metroidret/mzm
    How we know
    4784        case SPOSE_RUNNING:
    4785            if (gSamusDataCopy.forcedMovement == FORCED_MOVEMENT_MID_AIR_JUMP)
    ⋮
    4787                gSamusData.pose = SPOSE_STARTING_SPIN_JUMP;
    ⋮
    4789                if (gEquipment.suitMiscStatus & SMF_HIGH_JUMP)
    4790                    gSamusData.yVelocity = SAMUS_HIGH_JUMP_VELOCITY;
    4791                else
    4792                    gSamusData.yVelocity = SAMUS_LOW_JUMP_VELOCITY;
    
    src/samus.c lines 4784–4793 at a6d6187 · upstream ↗
    4929        default:
    4930            gSamusData.pose = SPOSE_MID_AIR;
    ⋮
    4932            if (gSamusDataCopy.forcedMovement == FORCED_MOVEMENT_MID_AIR_JUMP)
    ⋮
    4936                else
    4937                    gSamusData.pose = SPOSE_MID_AIR;
    ⋮
    4939                if (gEquipment.suitMiscStatus & SMF_HIGH_JUMP)
    4940                    gSamusData.yVelocity = SAMUS_HIGH_JUMP_VELOCITY;
    4941                else
    4942                    gSamusData.yVelocity = SAMUS_LOW_JUMP_VELOCITY;
    
    src/samus.c lines 4929–4942 at a6d6187 · upstream ↗
    321#define SAMUS_HIGH_JUMP_VELOCITY (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE + EIGHTH_BLOCK_SIZE + PIXEL_SIZE - ONE_SUB_PIXEL * .5f))
    322#define SAMUS_LOW_JUMP_VELOCITY (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE + PIXEL_SIZE + ONE_SUB_PIXEL + ONE_SUB_PIXEL * .25f))
    ⋮
    327#define SAMUS_Y_VELOCITY_CAP (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE))
    
    include/constants/samus.h lines 321–327 at a6d6187 · upstream ↗
    192#define VELOCITY_TO_SUB_PIXEL(velocity) (DIV_SHIFT((velocity), 8))
    193#define SUB_PIXEL_TO_VELOCITY(velocity) ((s32)((velocity) * 8))
    
    include/macros.h lines 192–193 at a6d6187 · upstream ↗
    43// The amount of sub pixels a single visible pixel contains
    44#define SUB_PIXEL_RATIO 4
    45// The amount of visible pixels a block contains
    46#define PIXEL_PER_BLOCK 16
    ⋮
    49#define BLOCK_SIZE (PIXEL_PER_BLOCK * SUB_PIXEL_RATIO)
    ⋮
    53#define QUARTER_BLOCK_SIZE (BLOCK_SIZE / 4)
    ⋮
    55#define EIGHTH_BLOCK_SIZE (BLOCK_SIZE / 8)
    ⋮
    57#define PIXEL_SIZE (BLOCK_SIZE / PIXEL_PER_BLOCK)
    ⋮
    59#define ONE_SUB_PIXEL (PIXEL_SIZE / PIXEL_SIZE)
    
    include/types.h lines 43–59 at a6d6187 · upstream ↗
    5526            // Check is within the bounds
    5527            if (gSamusData.yVelocity > gSamusPhysics.yVelocityCap)
    5528                velocity = VELOCITY_TO_SUB_PIXEL(gSamusPhysics.yVelocityCap);
    5529            else if (gSamusData.yVelocity < -gSamusPhysics.yVelocityCap)
    5530                velocity = VELOCITY_TO_SUB_PIXEL(-gSamusPhysics.yVelocityCap);
    5531            else
    5532                velocity = VELOCITY_TO_SUB_PIXEL(gSamusData.yVelocity);
    ⋮
    5538            gSamusData.yPosition -= velocity;
    
    src/samus.c lines 5526–5538 at a6d6187 · upstream ↗
    336#define SAMUS_HIGH_JUMP_VELOCITY (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE + PIXEL_SIZE * 3 + ONE_SUB_PIXEL))
    337#define SAMUS_LOW_JUMP_VELOCITY (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE + EIGHTH_BLOCK_SIZE))
    338#define SAMUS_SUITLESS_JUMP_VELOCITY (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE + EIGHTH_BLOCK_SIZE + PIXEL_SIZE / 2 + ONE_SUB_PIXEL / 2.f))
    
    include/constants/samus.h lines 336–338 at 2aae289 · upstream ↗
    325#define SAMUS_Y_POSITIVE_VELOCITY_CAP 192
    
    include/constants/samus.h line 325 at 2aae289 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  6. ^ abOut of liquid, SamusUpdatePhysics sets the vertical acceleration to 6 and the vertical speed cap to 128, and SamusUpdateVelocityPosition takes 6 from yVelocity on every frame of the airborne poses while the velocity is above -220, whether A is held or not: 0.1875 px/frame², with rises and falls both capped at 4 px/frame (computed). In water, lava, or acid without the Gravity Suit or the flag the decompilers name SMF_SA_X_SUIT, the acceleration drops to 4 and the cap to 48, 0.125 px/frame² and 1.5 px/frame. Zero Mission's gravity is 10, 0.3125 px/frame², with the same 4 px/frame fall cap (computed).Codemetroidret/mf · metroidret/mzm
    How we know
    326#define SAMUS_Y_ACCELERATION (SUB_PIXEL_TO_VELOCITY(ONE_SUB_PIXEL * .75f))
    327#define SAMUS_Y_VELOCITY_CAP (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE))
    ⋮
    335#define SAMUS_TERMINAL_VELOCITY (SUB_PIXEL_TO_VELOCITY(HALF_BLOCK_SIZE - PIXEL_SIZE - ONE_SUB_PIXEL / 2.f))
    
    include/constants/samus.h lines 326–335 at a6d6187 · upstream ↗
    137    switch (currentHazard)
    ⋮
    139        case HAZARD_WATER:
    140            gSamusUnderwaterFlag++;
    ⋮
    142        case HAZARD_LAVA:
    143        case HAZARD_ACID:
    144            if (!(gEquipment.suitMiscStatus & (SMF_GRAVITY_SUIT | SMF_SA_X_SUIT)))
    145                slowed++;
    146            break;
    ⋮
    151                slowed++;
    ⋮
    160        gSamusPhysics.yAcceleration = SAMUS_Y_ACCELERATION * 2.f / 3;
    161        gSamusPhysics.yVelocityCap = SAMUS_Y_VELOCITY_CAP * 0.375f;
    ⋮
    174        gSamusPhysics.yAcceleration = SAMUS_Y_ACCELERATION;
    ⋮
    
    src/samus.c lines 137–175 at a6d6187 · upstream ↗
    5526            // Check is within the bounds
    5527            if (gSamusData.yVelocity > gSamusPhysics.yVelocityCap)
    ⋮
    5529            else if (gSamusData.yVelocity < -gSamusPhysics.yVelocityCap)
    5530                velocity = VELOCITY_TO_SUB_PIXEL(-gSamusPhysics.yVelocityCap);
    5531            else
    ⋮
    5534            // Apply acceleration
    5535            if (gSamusData.yVelocity > -SAMUS_TERMINAL_VELOCITY)
    5536                gSamusData.yVelocity -= gSamusPhysics.yAcceleration;
    ⋮
    5538            gSamusData.yPosition -= velocity;
    
    src/samus.c lines 5526–5538 at a6d6187 · upstream ↗
    324#define SAMUS_Y_ACCELERATION 10
    325#define SAMUS_Y_POSITIVE_VELOCITY_CAP 192
    326#define SAMUS_Y_NEGATIVE_VELOCITY_CAP 128
    327#define SAMUS_Y_SLOWED_VELOCITY_CAP 40
    
    include/constants/samus.h lines 324–327 at 2aae289 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  7. ^ abStepping the code's arithmetic with A held, out of liquid, a jump from the ground peaks about 71 px up (283 subpixels) on its 28th frame, and a High Jump 104 px (416 subpixels) on its 36th (computed: each frame moves yVelocity capped at 128 and shifted right 3, then takes 6 from it). The same stepping gives Zero Mission about 59 px on the 19th frame and 83 px on the 23rd (mzm/jump-height), so Fusion's jump is higher and slower.Codemetroidret/mf · metroidret/mzm
    How we know
    321#define SAMUS_HIGH_JUMP_VELOCITY (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE + EIGHTH_BLOCK_SIZE + PIXEL_SIZE - ONE_SUB_PIXEL * .5f))
    322#define SAMUS_LOW_JUMP_VELOCITY (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE + PIXEL_SIZE + ONE_SUB_PIXEL + ONE_SUB_PIXEL * .25f))
    ⋮
    326#define SAMUS_Y_ACCELERATION (SUB_PIXEL_TO_VELOCITY(ONE_SUB_PIXEL * .75f))
    327#define SAMUS_Y_VELOCITY_CAP (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE))
    
    include/constants/samus.h lines 321–327 at a6d6187 · upstream ↗
    5526            // Check is within the bounds
    5527            if (gSamusData.yVelocity > gSamusPhysics.yVelocityCap)
    5528                velocity = VELOCITY_TO_SUB_PIXEL(gSamusPhysics.yVelocityCap);
    ⋮
    5531            else
    5532                velocity = VELOCITY_TO_SUB_PIXEL(gSamusData.yVelocity);
    ⋮
    5535            if (gSamusData.yVelocity > -SAMUS_TERMINAL_VELOCITY)
    5536                gSamusData.yVelocity -= gSamusPhysics.yAcceleration;
    ⋮
    5538            gSamusData.yPosition -= velocity;
    
    src/samus.c lines 5526–5538 at a6d6187 · upstream ↗
    324#define SAMUS_Y_ACCELERATION 10
    325#define SAMUS_Y_POSITIVE_VELOCITY_CAP 192
    ⋮
    336#define SAMUS_HIGH_JUMP_VELOCITY (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE + PIXEL_SIZE * 3 + ONE_SUB_PIXEL))
    337#define SAMUS_LOW_JUMP_VELOCITY (SUB_PIXEL_TO_VELOCITY(QUARTER_BLOCK_SIZE + EIGHTH_BLOCK_SIZE))
    
    include/constants/samus.h lines 324–337 at 2aae289 · upstream ↗
    6758            if (pData->yVelocity > gSamusPhysics.positiveYVelocityCap)
    ⋮
    6760                velocity = DIV_SHIFT(gSamusPhysics.positiveYVelocityCap, 8);
    ⋮
    6766            else
    ⋮
    6768                velocity = DIV_SHIFT(pData->yVelocity, 8);
    ⋮
    6771            if (pData->yVelocity >= -231)
    ⋮
    6773                pData->yVelocity -= gSamusPhysics.yAcceleration;
    ⋮
    6776            pData->yPosition -= velocity;
    
    src/samus.c lines 6758–6776 at 2aae289 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  8. ^On any frame Samus rises (yVelocity above 0) without A held, the mid-air handlers set yVelocity to 0, at any height: SamusMidAir, SamusTurningAroundMidAir, SamusSpinning (which runs the spin jump, the Space Jump, and the Screw Attack), and SamusWallJumping, which also drops a wall jump into a spin. The test is each handler's last step but the wall jump's, so a frame whose earlier branch returns skips it. Zero Mission's first three handlers end with the same test.Codemetroidret/mf · metroidret/mzm
    How we know
    2124    // Check turning
    2125    if (turning)
    2126        return SPOSE_TURNING_AROUND_MID_AIR;
    ⋮
    2128    // Check terminate Y velocity if not pressing A and going up
    2129    if (!(gButtonInput & KEY_A) && gSamusData.yVelocity > 0)
    2130        gSamusData.yVelocity = 0;
    
    src/samus.c lines 2124–2130 at a6d6187 · upstream ↗
    2195    if (!(gButtonInput & KEY_A) && gSamusData.yVelocity > 0)
    2196        gSamusData.yVelocity = 0;
    
    src/samus.c lines 2195–2196 at a6d6187 · upstream ↗
    2596    if (!(gButtonInput & KEY_A) && gSamusData.yVelocity > 0)
    2597        gSamusData.yVelocity = 0;
    
    src/samus.c lines 2596–2597 at a6d6187 · upstream ↗
    3025    if (!(gButtonInput & KEY_A))
    ⋮
    3027        // Stop upwards Y velocity if released A
    3028        if (gSamusData.yVelocity > 0)
    3029            gSamusData.yVelocity = 0;
    ⋮
    3031        // Set spinning
    3032        gSamusData.pose = SPOSE_SPINNING;
    
    src/samus.c lines 3025–3032 at a6d6187 · upstream ↗
    163    [SPOSE_MID_AIR] = SamusMidAir,
    164    [SPOSE_TURNING_AROUND_MID_AIR] = SamusTurningAroundMidAir,
    ⋮
    169    [SPOSE_STARTING_SPIN_JUMP] = SamusSpinning,
    170    [SPOSE_SPINNING] = SamusSpinning,
    ⋮
    181    [SPOSE_WALL_JUMPING] = SamusWallJumping,
    ⋮
    188    [SPOSE_SPACE_JUMPING] = SamusSpinning,
    189    [SPOSE_SCREW_ATTACKING] = SamusSpinning,
    
    src/data/samus_data.c lines 162–189 at a6d6187 · upstream ↗
    4572    // Check turning
    4573    if (turning)
    4574        return SPOSE_TURNING_AROUND_MIDAIR;
    ⋮
    4576    // Check terminate Y velocity if not pressing A and going up
    4577    if (!(gButtonInput & KEY_A) && pData->yVelocity > 0)
    4578        pData->yVelocity = 0;
    
    src/samus.c lines 4572–4578 at 2aae289 · upstream ↗
    4645    // Check terminate Y velocity if not pressing A and going up
    4646    if (!(gButtonInput & KEY_A) && pData->yVelocity > 0)
    4647        pData->yVelocity = 0;
    
    src/samus.c lines 4645–4647 at 2aae289 · upstream ↗
    4774    // Check terminate Y velocity if not pressing A and going up
    4775    if (!(gButtonInput & KEY_A) && pData->yVelocity > 0)
    4776        pData->yVelocity = 0;
    
    src/samus.c lines 4774–4776 at 2aae289 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  9. ^ abTouching an absorbable X parasite runs SamusAbsorbX, which refills by its color: a red X (SSC_RED_X_PARASITE) 500 energy, 100 missiles, and 10 power bombs; a green X 2 missiles; any other (yellow) X 10 energy. SpriteUtilRefillSamus adds missiles only once a missile flag is set and power bombs only once the power bomb flag is, and stops each at its maximum.Codemetroidret/mf
    How we know
    482void XParasiteGettingAbsorbedInit(void)
    ⋮
    497    SamusAbsorbX();
    
    src/x_parasite_util.c lines 482–497 at a6d6187 · upstream ↗
    433void SamusAbsorbX(void)
    ⋮
    444        case SSP_X_ABSORBABLE_BY_SAMUS:
    445            if (gCurrentSprite.samusCollision == SSC_RED_X_PARASITE)
    ⋮
    447                energy = 500;
    448                missiles = 100;
    449                powerBombs = 10;
    ⋮
    453            else if (gCurrentSprite.samusCollision == SSC_GREEN_X_PARASITE)
    ⋮
    455                energy = 0;
    456                missiles = 2;
    457                powerBombs = 0;
    ⋮
    461            else
    ⋮
    463                energy = 10;
    ⋮
    
    src/x_parasite_util.c lines 433–470 at a6d6187 · upstream ↗
    2686void SpriteUtilRefillSamus(u16 energy, u16 missiles, u16 powerBombs)
    ⋮
    2688    gEquipment.currentEnergy += energy;
    ⋮
    2690    if (gEquipment.currentEnergy > gEquipment.maxEnergy)
    2691        gEquipment.currentEnergy = gEquipment.maxEnergy;
    ⋮
    2693    if (gEquipment.weaponsStatus & MBF_ANY_MISSILE)
    ⋮
    2695        gEquipment.currentMissiles += missiles;
    ⋮
    2697        if (gEquipment.currentMissiles > gEquipment.maxMissiles)
    2698            gEquipment.currentMissiles = gEquipment.maxMissiles;
    ⋮
    2701    if (gEquipment.weaponsStatus & MBF_POWER_BOMBS)
    ⋮
    2703        gEquipment.currentPowerBombs += powerBombs;
    
    src/sprite_util.c lines 2686–2707 at a6d6187 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  10. ^When a dying sprite's countdown ends, SpriteDying sets its pose to 0x5B (the decompilers' SPRITE_POSE_TURNING_INTO_X) unless it is a secondary sprite, and an enemy's AI (Choot's, for one) runs XParasiteInit in that pose. The X it becomes takes its color from a number from 1 to 1024, made from the low 2 bits of gSpriteRandomNumber and the 8-bit frame counter, against three weights in that sprite's row of sPrimarySpriteStats: the top red-weight values give red and the next green-weight values green, each only when its weight is above 0, and everything left below the green bound gives yellow, whatever the yellow weight is, unless it is 0, which deletes the X instead. A would-be green X turns yellow when missiles are full and energy isn't, or Samus has no missiles yet; a would-be yellow X turns green when energy is full and missiles aren't. An X from a core X is green when gSpriteRandomNumber is below 5 or that energy test holds, and otherwise yellow; an X placed in a room is yellow. All of this applies only to an X whose sprite property is SSP_X_ABSORBABLE_BY_SAMUS: one marked SSP_X_UNABSORBABLE_BY_SAMUS, or the unnamed SSP_UNKNOWN_40, is always yellow, and any other property deletes the X.Codemetroidret/mfrevised
    How we know
    561        case SSP_X_ABSORBABLE_BY_SAMUS:
    562            if (type == X_PARASITE_TYPE_FROM_CORE_X)
    ⋮
    564                if (gSpriteRandomNumber < 5 || SpriteUtilCheckEnergyFullAndMissilesNotFull())
    ⋮
    566                    gCurrentSprite.pOam = sXParasiteOam_Green;
    567                    gCurrentSprite.samusCollision = SSC_GREEN_X_PARASITE;
    ⋮
    569                else
    ⋮
    571                    gCurrentSprite.pOam = sXParasiteOam_Yellow;
    
    src/x_parasite_util.c lines 561–572 at a6d6187 · upstream ↗
    575            else if (type == X_PARASITE_TYPE_FROM_SPRITE)
    ⋮
    577                // Calculate random number between 1 and 1024 inclusive
    578                randomNumber = gSpriteRandomNumber & 3;
    579                randomNumber <<= 8;
    580                randomNumber |= gFrameCounter8Bit;
    581                randomNumber++;
    ⋮
    583                yellowXThreshold = sPrimarySpriteStats[spriteId][4];
    584                greenXThreshold = sPrimarySpriteStats[spriteId][5];
    585                redXThreshold = sPrimarySpriteStats[spriteId][6];
    ⋮
    589                    redXThreshold = 1024 - redXThreshold;
    590                    if (randomNumber <= 1024 && randomNumber > redXThreshold)
    ⋮
    598                else
    ⋮
    
    src/x_parasite_util.c lines 574–655 at a6d6187 · upstream ↗
    657        case SSP_X_UNABSORBABLE_BY_SAMUS:
    658            gCurrentSprite.invincibilityStunFlashTimer &= 0x7f;
    659            gCurrentSprite.pOam = sXParasiteOam_Yellow;
    ⋮
    661            break;
    ⋮
    663        case SSP_UNKNOWN_40:
    664            gCurrentSprite.pOam = sXParasiteOam_Yellow;
    665            gCurrentSprite.samusCollision = SSC_YELLOW_X_PARASITE;
    666            break;
    ⋮
    668        default:
    669            gCurrentSprite.status = 0;
    
    src/x_parasite_util.c lines 657–669 at a6d6187 · upstream ↗
    509void SpriteDying(void)
    ⋮
    519    if (--gCurrentSprite.work1 == 0)
    ⋮
    521        if (gCurrentSprite.properties & SP_SECONDARY_SPRITE)
    ⋮
    523            gCurrentSprite.status = 0;
    ⋮
    525        else
    ⋮
    527            gCurrentSprite.pose = 0x5b;
    
    src/x_parasite_util.c lines 509–530 at a6d6187 · upstream ↗
    328        case 0x57:
    329            SpriteDyingInit();
    330        case 0x58:
    331            SpriteDying();
    332            break;
    ⋮
    338            break;
    ⋮
    340        case 0x5b:
    341            XParasiteInit();
    342            break;
    
    src/sprites_AI/choot.c lines 328–342 at a6d6187 · upstream ↗
    477    SPRITE_POSE_TURNING_INTO_X = 0x5B,
    
    include/constants/sprite.h line 477 at a6d6187 · upstream ↗
    2746u8 SpriteUtilCheckEnergyFullAndMissilesNotFull(void)
    ⋮
    2748    if (gEquipment.currentEnergy == gEquipment.maxEnergy)
    ⋮
    2750        if (gEquipment.weaponsStatus & MBF_ANY_MISSILE && gEquipment.currentMissiles < gEquipment.maxMissiles)
    2751            return TRUE;
    ⋮
    2762u8 SpriteUtilCheckMissilesFullAndEnergyNotFull(void)
    ⋮
    2764    if (!(gEquipment.weaponsStatus & MBF_ANY_MISSILE))
    2765        return TRUE;
    ⋮
    2767    if (gEquipment.currentMissiles == gEquipment.maxMissiles && gEquipment.currentEnergy < gEquipment.maxEnergy)
    2768        return TRUE;
    
    src/sprite_util.c lines 2746–2770 at a6d6187 · upstream ↗
    697        case PSPRITE_X_PARASITE_IN_ROOM:
    698        case PSPRITE_X_PARASITE_IN_ROOM_PROXIMITY:
    ⋮
    704            XParasiteDetermineColor(X_PARASITE_TYPE_IN_ROOM, 0);
    705            break;
    ⋮
    720            XParasiteDetermineColor(X_PARASITE_TYPE_FROM_CORE_X, 0);
    ⋮
    726            break;
    ⋮
    728        case PSPRITE_X_PARASITE:
    729        case PSPRITE_X_PARASITE_AQUA_ZEBESIAN:
    730            XParasiteSpawningInit();
    731            XParasiteDetermineColor(X_PARASITE_TYPE_FROM_SPRITE, gCurrentSprite.roomSlot);
    732            break;
    ⋮
    734        default:
    ⋮
    
    src/x_parasite_util.c lines 697–737 at a6d6187 · 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.

    In the claim ledger

  11. ^When an enemy dies, a RandomNumber1 below 16 drops nothing; otherwise its low 3 bits pick from ItemDropTbl, which holds a missile at 0 and 3, energy at 1, 4, and 6, and $89 at 2, 5, and 7.Codenmikstas/metroid-disassembly · “Metroid (USA)”
    How we know
    4575    LDA RandomNumber1
    4576    CMP #$10
    4577    BCC LDD5B
    4578*   AND #$07
    4579    TAY
    4580    LDA ItemDropTbl,y
    4581    STA EnAnimFrame,x
    
    Source_Files/Bank07.asm lines 4575–4581 at 4270d57 · upstream ↗
    4722;The following table determines what, if any, items an enemy will drop when it is killed.
    ⋮
    4724ItemDropTbl:
    4725LDE35:  .byte $80           ;Missile.
    4726LDE36:  .byte $81           ;Energy.
    4727LDE37:  .byte $89           ;No item.
    4728LDE38:  .byte $80           ;Missile.
    4729LDE39:  .byte $81           ;Energy.
    4730LDE3A:  .byte $89           ;No item.
    4731LDE3B:  .byte $81           ;Energy.
    4732LDE3C:  .byte $89           ;No item.
    
    Source_Files/Bank07.asm lines 4722–4732 at 4270d57 · upstream ↗

    In the cartridge (“Metroid (USA)”): bank 7 $DCFC, the drop code; bank 7 $DE35, ItemDropTbl. We don't reproduce the bytes.

    Checked against the evidence by its author, an AI model, 2026-10-20.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

Sources

Code