data/claims/mega-man-3.yamlMega Man 3 (plasticsmoke ca65 disassembly)
Game: Mega Man 3.
A fresh A press on the ground without Down sets Mega Man's upward speed to $04.$E5, 4.89 px/frame, and runs the airborne state at once. mega-man-3/jump-speed
About: Mega Man 3 Jump
Value: 1253 1/256 px/frame
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L59-L78player_ground_jump_or_slide: lda joy1_press ; check new button presses
and #BTN_A ; A button pressed?
beq player_ground_anim_dispatch ; no → animate walk/idle
lda joy1_held ; check held buttons
and #BTN_DOWN ; holding down?
beq player_ground_normal_jump ; not holding down, jump
jmp slide_initiate ; initiate slide
…
player_ground_normal_jump: lda joy2_held ; P2 held buttons
and #$01 ; bit 0 = Right held?
bne player_ground_super_jump ; yes → debug super jump
lda #$E5 ; normal jump: Y vel = $04.E5
…Checked by claude, 2026-10-21 — read player_ground_jump_or_slide and player_ground_normal_jump at the pinned commit1253/256 = 4.89453125 px/frame. Holding Right on the second controller jumps at $08.$00 instead (L80-L84), a debug jump left in the release.
Cited by: Jump, Mega Man 3
Mega Man 3 raises the take-off speed from Mega Man 2's $04.$DF (4.87 px/frame) to $04.$E5 (4.89) and keeps gravity at $40, but caps a fall at 7 px/frame ($F9) where Mega Man 2 allows 12 ($F4). mega-man-3/jump-line
About: Mega Man 3 Gravity
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L74-L77lda #$E5 ; normal jump: Y vel = $04.E5
sta ent_yvel_sub,x ; store Y velocity sub-pixel
lda #$04 ; Y velocity whole = $04
sta ent_yvel,x ; store Y velocity wholeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/game_loop.asm#L648-L649prelude_setup_per_frame: lda #$40 ; gravity = $40 for player physics
sta gravity ; (process_sprites sets $55 for entities)Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/movement.asm#L393-L402apply_gravity_player_check: lda ent_yvel_sub,x ; Y sub velocity -= gravity
sec ; prepare for subtraction
sbc gravity ; subtract gravity value
sta ent_yvel_sub,x ; store new Y sub velocity
lda ent_yvel,x ; Y velocity -= borrow
sbc #$00 ; propagate borrow to whole byte
sta ent_yvel,x ; store new Y velocity
bpl apply_gravity_exit ; still positive (rising)? done
cmp #$F9 ; check against terminal velocity $F9
bcs apply_gravity_exit ; within limit (-7 to -1)? donemm2: Mega Man 2 disassembly (lsmmega/mm2) · engine/check_object_horizontal_move.asm#L175-L185tsa_ycoord_speed_accelerate_fraction_table:
.BYTE $40, $1E
…
tsa_regular_water_jump_speed_table:
.BYTE $04, $05
…
tsa_regular_water_jump_speed_fraction_table:
.BYTE $DF, $80mm2: Mega Man 2 disassembly (lsmmega/mm2) · engine/check_object_vertical_move.asm#L81-L87BPL @skip
CMP #$F4
BCS @skip
LDA #$00
STA aobject_ycoord_speed_fraction
LDA #$F4
STA aobject_ycoord_speedChecked by claude, 2026-10-21 — read Mega Man 3's jump start, the prelude that sets `gravity` before the player's state runs, and apply_gravity (the clamp below L402 stores `$F9`.`$00`), and Mega Man 2's tables and fall cap, at their pinned commitsThe prelude writes $40 every frame; enemies processed later overwrite gravity for themselves, so the player always reads $40 out of water.
Cited by: Gravity, Mega Man 3
On every rising frame without A held, Mega Man 3 sets the vertical speed to $FF.$C0, 0.25 px/frame downward, whatever the speed was, before the frame's move. Mega Man 2 only lowers a rise of 2 px/frame or more to 1, after the move. mega-man-3/release-cut
About: Mega Man 3 Variable jump height
Value: -64 1/256 px/frame
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L260-L267player_airborne: lda ent_yvel ; check Y velocity sign
bmi player_airborne_variable_jump ; negative (falling) → skip to clear flag
lda force_full_jump ; check force-full-jump flag
bne player_airborne_wall_check ; nonzero → skip variable jump
lda joy1_held ; check if A button held
and #BTN_A ; A button mask
bne player_airborne_variable_jump ; held → keep rising (variable jump)
jsr reset_gravity ; A released → clamp Y speed to 0 (variable jump)Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/movement.asm#L424-L428reset_gravity_player: lda #$C0 ; player rest vel sub = $C0
sta ent_yvel_sub,x ; store Y sub velocity
lda #$FF ; player rest vel = $FF ($FFC0 = -0.25)
sta ent_yvel,x ; store Y velocity whole byte
rts ; player rest vel = $FFC0Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L272-L277player_airborne_wall_check: ldy #$06 ; Y=6: check at foot height
jsr check_tile_horiz ; (Y=6: check at foot height)
lda $10 ; A = tile collision result
sta $0F ; save in $0F
ldy #$00 ; Y=0 for vertical movement
jsr move_vertical_gravity ; apply gravity and move verticallymm2: Mega Man 2 disassembly (lsmmega/mm2) · engine/run_megaman.asm#L208-L219LDA z:zjoy1_pressed
AND #a_button
BNE @skip
LDA aobject_ycoord_speed
BMI @skip
CMP #$01
BCC @skip
BEQ @skip
LDA #$01
STA aobject_ycoord_speed
LDA #$00
STA aobject_ycoord_speed_fractionChecked by claude, 2026-10-21 — read player_airborne, reset_gravity, and move_vertical_gravity (move first, then apply_gravity) in Mega Man 3, and _run_megaman_jumping in Mega Man 2 (mega-man-2/release-cap, mega-man-2/release-order), at their pinned commits. The comment at L267 says 0; the code stores `$FF`.`$C0`The test reads only the sign of the whole byte, so it also cuts a rise slower than 1 px/frame. A Rush Coil bounce sets force_full_jump, which skips the cut until the fall (rush-coil).
Cited by: Mega Man 3, Variable jump height
Mega Man 3 drops Mega Man 2's slow step speed but keeps a pause. Before each frame's state runs, the prelude sets the speed to $01.$4C (1.30 px/frame) unless he is sliding. From standing, a held direction moves him by it at once and starts the turning animation ($0D); while that animation is on its first frame the ground state doesn't move him, and once it advances he walks at the same 1.30. In the air a held direction moves him at 1.30 on every frame. mega-man-3/walk-speed
About: Mega Man 3 Running
Value: 332 1/256 px/frame
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/game_loop.asm#L636-L645prelude_check_slide_speed: lda ent_xvel ; if X speed whole == $02 (slide speed):
cmp #$02 ; slide speed?
bne prelude_reset_walk_speed ; no → reset to walk speed
lda player_state ; load current player state
cmp #PSTATE_SLIDE ; check if sliding
beq prelude_setup_per_frame ; sliding, keep slide speed
prelude_reset_walk_speed: lda #$4C ; reset walk speed to $01.4C
sta ent_xvel_sub ; store walk sub-pixel speed
lda #$01 ; walk speed whole = $01
sta ent_xvel ; store walk whole speedMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L115-L128player_ground_turning_anim: lda #$0D ; OAM $0D = turning animation
jsr reset_sprite_anim ; set turning animation
lda shoot_anim_timer ; check if shooting
beq player_ground_move_facing ; not shooting, move facing
jsr shoot_oam_advance_variant ; update shoot animation variant
player_ground_move_facing: lda player_facing ; move in facing direction
jmp player_ground_move_horizontal ; move in facing direction
…
player_ground_walk_handler: lda joy1_held ; D-pad L/R held?
and #$03 ; mask L/R bits
beq player_ground_idle ; no → idle
sta player_facing ; update facing directionMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L94-L102 — on the turning animation's first frame (state 0) the handler skips the movecmp #$0D ; $0D/$0E/$0F = turning/special anims
beq player_ground_special_anim ; OAM $0D → check if anim complete
…
player_ground_special_anim: lda ent_anim_state ; check animation state
bne player_ground_walk_handler ; → normal walk handler
beq player_ground_slide_weapon ; anim done → slide/crouch checkMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/sprites.asm#L210-L217lda ent_anim_state,x ; ent_anim_state = current frame index
and #$7F ; (bit 7 preserved)
inc ent_anim_state,x ; advance to next frame
…
lda #$00 ; reached end: loop back to frame 0
sta ent_anim_state,x ; reset to frame 0Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L360-L364player_airborne_walk_shoot: lda joy1_held ; D-pad L/R held?
and #$03 ; mask L/R bits
beq player_airborne_shoot_check ; no → skip walk
sta player_facing ; update facing direction
jsr player_ground_move_horizontal ; move L/R with collisionMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/movement.asm#L259-L265move_sprite_right: lda ent_x_sub,x ; load X subpixel position
clc ; X subpixel += X subpixel speed
adc ent_xvel_sub,x ; add X subpixel velocity
sta ent_x_sub,x ; store new X subpixel
lda ent_x_px,x ; load X pixel position
adc ent_xvel,x ; add X pixel velocity + carry
sta ent_x_px,x ; store new X pixelmm2: Mega Man 2 disassembly (lsmmega/mm2) · engine/turn_megaman.asm#L222-L243megaman_status_xcoord_speed_table:
…
megaman_status_xcoord_speed_fraction_table:
.BYTE $00
.BYTE $00
.BYTE $90
.BYTE $00
.BYTE $20
.BYTE $60
.BYTE $50
.BYTE $80Checked by claude, 2026-10-21 — read the prelude, the ground animation dispatch and walk handler (from idle, animation `$01` goes to player_ground_turning_anim, which sets `$0D` and moves), the animation step in sprites.asm, the airborne walk, and move_sprite_right in Mega Man 3, and Mega Man 2's state speed table (mega-man-2/state-speeds), at their pinned commits332/256 = 1.296875 px/frame. The turning animation holds its first frame for 6 ticks (turn-animation), which makes a pause of 5 frames after the step (ground-measured). Mega Man 2 instead moves at 0.125 px/frame during its step.
Cited by: Air control, Mega Man 3, Running
Down and a fresh A press on the ground start a slide if no wall is ahead: 2 px lower, at 2.5 px/frame ($02.$80), with a timer of 20 ($14). The set-up frame moves once, and each slide frame moves before it tests the timer, so an unbroken slide moves on 21 frames. A turn, a wall, the timer's end, or (from the 9th frame) a fresh A press all lead to the standing test, where a solid tile at standing height keeps the slide going; otherwise it ends, with a jump at walking speed if A was pressed. mega-man-3/slide
About: Mega Man 3 Ground slide
Value
{
"speed": 640,
"frames": 21,
"jumpFromFrame": 9
}src/fixed/player_states.asm#L286-L299slide_initiate_confirmed: lda #PSTATE_SLIDE ; state $02 = player_slide
sta player_state ; set player state to slide
lda #$14 ; slide timer = 20 frames
sta $33 ; store slide timer
lda #$10 ; OAM anim $10 = slide sprite
jsr reset_sprite_anim ; set slide sprite animation
lda ent_y_px ; player Y += 2 (crouch posture)
clc ; prepare for addition
adc #$02 ; lower Y by 2 for slide crouch
sta ent_y_px ; store adjusted Y position
lda #$80 ; X speed = $02.80 = 2.5 px/frame
sta ent_xvel_sub ; X velocity sub-pixel
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_states.asm#L318 — the end of the set-up, which moves on the same framebeq slide_move_check_facing ; always → skip to slide movementMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_states.asm#L344-L366slide_collision_wall_solid: lda $10 ; hit a wall?
and #$10 ; bit 4 = solid wall
bne slide_end_ground_check ; yes → end slide
lda $33 ; timer expired?
beq slide_end_ground_check ; yes → end slide
dec $33 ; decrement timer
lda $33 ; check slide timer phase
cmp #$0C ; >= $0C? (uncancellable phase)
bcs slide_continue_moving ; yes → uncancellable, keep sliding
lda joy1_press ; A button pressed? (cancellable phase)
and #BTN_A ; check A button
bne slide_end_ground_check ; yes → cancel into slide jump
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_states.asm#L328-L334lda joy1_held ; read held D-pad
and #$03 ; mask left/right bits
beq slide_move_check_facing ; no direction → keep sliding
cmp player_facing ; same as current facing?
beq slide_move_check_facing ; yes → keep sliding
sta player_facing ; changed direction → end slide
bne slide_end_ground_check ; always taken → end slideMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_states.asm#L367-L390sta player_state ; state = on_ground
lda joy1_press ; A button pressed?
and #BTN_A ; check A button
bne slide_jump_walk_speed_init ; yes → slide jump
…
slide_jump_walk_speed_init: lda #$4C ; walk speed X = $01.4C
sta ent_xvel_sub ; X velocity sub = $4C
lda #$01 ; X velocity whole = 1
sta ent_xvel ; X velocity = 1
jmp player_ground_normal_jump ; jump from slide (uses slide_jump velocity)Checked by claude, 2026-10-26 — ran movement.mjs (slide, slidejump8, slidejump9 from Top Man's stage start in Mesen) and re-read; earlier, read player_ground_jump_or_slide (Down + A), slide_initiate with its wall test, player_slide, and slide_end at the pinned commit. The set-up frame moves and takes the timer to 19; frames 2 to 20 take it to 0, and frame 21 moves, finds 0, and goes to the standing test. The timer reaches 11 on the 9th frame, the first that tests A. Any solid result from check_tile_horiz with Y=1 returns before the state changes (L361-L365), and the turn (L333-L334), wall (L344-L346), and A (L353-L355) exits all branch thereProbe: probes/jump-physics/movement.mjsThe comments call the Y=1 test "ground at standing height"; with the 2 px drop it reads as headroom, as Mega Man 4's labels name the same test (mega-man-4/slide-line). Measured in Mesen from Top Man's stage start (movement.mjs), a slide moves 2.5 px on 21 frames, and a fresh A on its 8th frame keeps it going while one on its 9th jumps; the lab model matches the cartridge on the conformance probe's slide scripts.
Cited by: Ground slide, Mega Man 2, Mega Man 3
A hit (state $06) sets Mega Man's vertical speed to the resting $FF.$C0 on its first frame, then each frame applies gravity and pushes him against his facing at 0.5 px/frame ($00.$80) until the hurt animation's state reaches 9; a hit during a slide doesn't push him. mega-man-3/knockback
About: Mega Man 3 Knockback
Value
{
"back": 128
}src/fixed/player_states.asm#L750-L753player_damage_anim_set: jsr reset_sprite_anim ; set player damage animation
lda #$00 ; clear walk/shoot flag
sta shoot_anim_timer ; clear walk/shoot flag
jsr reset_gravity ; reset vertical velocityMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_states.asm#L776-L790player_knockback_physics: lda $3E ; check saved OAM for special cases:
cmp #$10 ; $10 = sliding (no knockback physics)
beq player_knockback_landing ; sliding → skip knockback
cmp #$79 ; >= $79 = special state (skip physics)
bcs player_knockback_landing ; special OAM → skip knockback physics
ldy #$00 ; Y = 0 (player slot)
jsr move_vertical_gravity ; apply gravity, carry = landed
lda #$80 ; knockback speed = $00.80 (half pixel/frame)
sta ent_xvel_sub ; set knockback X speed sub
lda #$00 ; knockback X speed whole = 0
sta ent_xvel ; x_speed whole = 0
lda ent_flags ; save facing before move
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_states.asm#L806-L808player_knockback_landing: lda ent_anim_state ; ent_anim_state = animation/collision state
cmp #$09 ; $09 = landed after knockback
bne player_knockback_done ; not landed → continue knockbackThe atlas reads this differently: State 9 is a step of the hurt animation, not a landing: the code compares only the animation state, so the push lasts as long as the animation takes to reach it.Checked by claude, 2026-10-21 — read player_damage from its first-frame setup through player_knockback_done at the pinned commit; bit 6 set (facing right) branches to move_left_collideThe comment reads ent_anim_state 9 as a landing; the code only compares the animation state, so the push lasts as long as the hurt animation takes to reach it. Mega Man 2 lifts him at 1.25 px/frame and pushes at 0.5625 (mm2-line/knockback).
Cited by: Knockback, Mega Man 2, Mega Man 3
Mega Man 3 starts the invincibility after the knockback: when the hurt state ends it sets invincibility_timer to 60 ($3C), which the prelude counts down every frame. Mega Man 2 sets 111 at the moment of the hit. mega-man-3/i-frames
About: Mega Man 3 Invincibility frames
Value: 60 frames
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_states.asm#L828-L830player_knockback_set_invincible: sta player_state ; set return player state
lda #$3C ; $39 = $3C (60 frames invincibility)
sta invincibility_timer ; 60 frames of invincibilityMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/game_loop.asm#L657-L659prelude_invincibility_timer: lda invincibility_timer ; load invincibility timer
beq prelude_charge_shot_check ; zero → skip
dec invincibility_timer ; decrement timerMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/collision.asm#L141-L142lda invincibility_timer ; skip if invincibility active
bne metatile_cleanup_return ; immune → skip hazard checkmm2: Mega Man 2 disassembly (lsmmega/mm2) · home/megaman_hit.asm#L11-L12LDA #$6F
STA z:zmegaman_invincibility_timeChecked by claude, 2026-10-21 — read player_knockback_set_invincible, the prelude's countdown, and the hazard test in Mega Man 3, and _megaman_hit in Mega Man 2, at their pinned commitsThe atlas hasn't traced how Mega Man 3 ignores enemy contact during the hurt state itself, nor measured its length; collision.asm L317 tests the same timer before entity contact.
Cited by: Invincibility frames, Mega Man 3
Mega Man 3 moves weapon costs into two tables indexed by weapon: weapon_cost, the units taken, and weapon_framerate, how many uses (or frames, for Rush Marine and Rush Jet) each take costs, counted on one shared counter. Needle Cannon costs 1 unit per 4 shots and Gemini Laser 2 per shot. Mega Man 2 writes the costs into the weapons' own routines, as its Air Shooter, Crash Bomber, and Metal Blade code shows. mega-man-3/weapon-cost
About: Mega Man 3 Weapon energy
Value
{
"cost": [
0,
2,
1,
2,
2,
0,
1,
3,
1,
1,
1,
1
],
"uses": [
0,
1,
4,
1,
1,
0,
2,
1,
1,
30,
2,
30
]
}src/fixed/player_warp.asm#L254-L268decrease_ammo_tick: ldy current_weapon ; Y = current weapon index
inc $B5 ; increment usage frame counter
lda $B5 ; load current count
cmp weapon_framerate,y ; compare to drain rate for this weapon
bne decrease_ammo_return ; not at threshold → return
lda #$00 ; reset frame counter to 0
sta $B5 ; store reset counter
lda player_hp,y ; load weapon ammo
and #$1F ; mask to ammo value (low 5 bits)
sec ; subtract ammo cost
sbc weapon_cost,y ; subtract ammo cost
bcs ammo_store_clamped ; no underflow → store result
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_warp.asm#L289-L296weapon_cost: .byte $00,$02,$01,$02,$02,$00,$01,$03
.byte $01,$01,$01,$01
…
weapon_framerate: .byte $00,$01,$04,$01,$01,$00,$02
.byte $01,$01,$1E,$02,$1E ; Spark $01, R.Marine $1E, Shadow $02, R.Jet $1EMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · include/constants.inc#L64-L75WPN_BUSTER = $00 ; Mega Buster (arm cannon)
WPN_GEMINI = $01 ; Gemini Laser
WPN_NEEDLE = $02 ; Needle Cannon
…
WPN_RUSH_JET = $0B ; Rush Jetmm2: Mega Man 2 disassembly (lsmmega/mm2) · home/weapons.asm#L95-L97 — Air ShooterLDA z:zweapon_energy + $01
SBC #$02
STA z:zweapon_energy + $01mm2: Mega Man 2 disassembly (lsmmega/mm2) · home/weapons.asm#L212-L215 — Crash BomberLDA z:zweapon_energy + $07
SBC #$04
BCC @skip_8
STA z:zweapon_energy + $07mm2: Mega Man 2 disassembly (lsmmega/mm2) · home/weapons.asm#L244-L250INC z:zweapon_consume_fraction
LDA z:zweapon_consume_fraction
CMP #$04
BNE @not_max_3
LDA #$00
STA z:zweapon_consume_fraction
DEC z:zweapon_energy + $06Checked by claude, 2026-10-21 — read decrease_ammo, decrease_ammo_tick, both tables, and the weapon ids in Mega Man 3, and Mega Man 2's Metal Blade cost (mm2-line/weapon-energy), at their pinned commits. The comment above weapon_framerate says frames; for shots the routine runs once per shot (weapon_fire, L387-L388 of player_ground.asm)Top Spin's row is 0 here; it costs energy elsewhere, on contact, which the atlas hasn't traced.
Cited by: Mega Man 3, Weapon energy
Mega Man 3 indexes each weapon's 256-byte damage table by the target's main routine id, one table for regular enemies and bosses alike, where Mega Man 2 gives each weapon a separate 14-entry boss table. In the Spark Shock table the entry for routine id $C1 is 7, and $58 marks targets the shock freezes instead of damaging. That $C1 is Magnet Man is the disassembly's labeling, not checked here. mega-man-3/boss-weakness
About: Mega Man 3 Boss weakness
Value
{
"sparkShockVsMagnetMan": 7
}src/bank1C_1D_entity_ai.asm#L290-L299ldy current_weapon ; weapon ID as table index
lda weapon_damage_ptr_lo,y ; grab damage table pointer for
sta temp_00 ; currently equipped weapon
lda weapon_damage_ptr_hi,y ; high byte of damage ptr
sta $01 ; store to pointer at $00-$01
ldy ent_routine,x ; if damage for main routine index
lda (temp_00),y ; is nonzero, do stuff
bne check_weapon_hit_spark_check ; nonzero damage → check weapon type
jsr check_weapon_hit_tink_sound ; else tink shot
jmp check_weapon_hit_restore_bank ; zero damage → tink and exitMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/bank1C_1D_entity_ai.asm#L562-L565weapon_damage_ptr_lo: .byte $00,$00,$00,$00,$00 ; Buster, Gemini, Needle, Hard, Magnet
weapon_damage_ptr_lo_special: .byte $00,$00,$00,$00,$00,$00,$00 ; TopSpin, Snake, RushCoil, Spark, RushMarine, Shadow, RushJet
weapon_damage_ptr_hi: .byte $A1,$A4,$A2,$A5,$A3 ; Buster, Gemini, Needle, Hard, Magnet
weapon_damage_ptr_hi_special: .byte $A6,$A7,$A1,$A8,$A1,$A9,$A1 ; TopSpin, Snake, RushCoil, Spark, RushMarine, Shadow, RushJetMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/bank0A_damage_tables.asm#L424-L425spark_damage_table:
.byte $00,$00,$58,$58,$58,$58,$00,$58 ; $00-$07: $58=freezeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/bank0A_damage_tables.asm#L451.byte $00,$07,$01,$01,$00,$00,$00,$00 ; $C0: Needle=0, Magnet=7 (weakness!), Top=1, Shadow=1Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/bank1C_1D_entity_ai.asm#L301-L307check_weapon_hit_spark_check: lda current_weapon ; if weapon is
cmp #WPN_SPARK ; anything but spark
bne check_weapon_hit_ack_check ; apply normal damage
lda (temp_00),y ; if damage value is zero,
beq check_weapon_hit_spark_freeze_done ; don't do anything
cmp #$58 ; if damage isn't magic number $58,
bne check_weapon_hit_ack_check ; apply normal damagemm2: Mega Man 2 disassembly (lsmmega/mm2) · engine/weapons_bosses_damage.asm#L388-L392LDA aobject_flag + 1
AND #objects_can_recoiled
BNE @immune_8
LDY z:zcurrent_boss
LDA @metal_blade_damage_table, YChecked by claude, 2026-10-21 — read check_weapon_hit's lookup, the pointer tables (Spark Shock, weapon 8, is the fourth special entry, `$A8`), and the Spark Shock table's first and `$C0` rows in Mega Man 3, and Mega Man 2's Metal Blade lookup (mm2-line/boss-weakness), at their pinned commitsWhich routine id belongs to which Robot Master comes from the disassembly's annotations, which the atlas hasn't checked against the spawn data.
Cited by: Boss weakness, Mega Man 3
Landing on Rush Coil launches Mega Man at $06.$EE, 6.93 px/frame, once per bounce, and sets force_full_jump, so releasing A doesn't cut that rise. mega-man-3/rush-coil
About: Mega Man 3 Jump
Value: 1774 1/256 px/frame
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L32-L38lda $05A1 ; if Rush already bounced: skip
bne player_ground_normal_walk ; already bounced, normal walk
inc force_full_jump ; force full jump (no variable-jump cutoff)
lda #$EE ; Y speed = $06.EE (Rush Coil bounce)
sta ent_yvel_sub ; strong upward bounce
lda #$06 ; Y velocity whole = $06 (upward)
sta ent_yvel ; store Y velocity wholeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L260-L263player_airborne: lda ent_yvel ; check Y velocity sign
bmi player_airborne_variable_jump ; negative (falling) → skip to clear flag
lda force_full_jump ; check force-full-jump flag
bne player_airborne_wall_check ; nonzero → skip variable jumpChecked by claude, 2026-10-21 — read the Rush Coil handler reached from player_ground's Rush dispatch and player_airborne at the pinned commit; the flag clears at L268-L269 only once the speed turns negative1774/256 = 6.9296875 px/frame. The bounce also takes Rush Coil's energy through decrease_ammo_tick (L45).
Cited by: Jump, Mega Man 3
Mega Man 3 moves Mega Man vertically before horizontally. Every frame on the ground or in the air first runs the airborne routine: a rise without A held is cut (release-cut), then move_vertical_gravity moves him by his vertical speed, takes gravity from it, and tests the floor under a fall or the ceiling over a rise. Only then does the frame's horizontal move run: the air move if he is still airborne, the ground state's move if he stood on a floor. mega-man-3/motion-order
About: Mega Man 3 Gravity
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L4-L7 — the ground state runs the airborne routine before anything elseplayer_on_ground:
jsr player_airborne ; run $99/collision first
bcc prelude_exit ; carry clear = still airborne → RTSMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L260-L278player_airborne: lda ent_yvel ; check Y velocity sign
bmi player_airborne_variable_jump ; negative (falling) → skip to clear flag
lda force_full_jump ; check force-full-jump flag
bne player_airborne_wall_check ; nonzero → skip variable jump
lda joy1_held ; check if A button held
and #BTN_A ; A button mask
bne player_airborne_variable_jump ; held → keep rising (variable jump)
jsr reset_gravity ; A released → clamp Y speed to 0 (variable jump)
…
player_airborne_wall_check: ldy #$06 ; Y=6: check at foot height
jsr check_tile_horiz ; (Y=6: check at foot height)
lda $10 ; A = tile collision result
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L343-L364 — still airborne: the air move comes after the vertical oneplayer_airborne_state_dispatch: lda player_state ; check current player state
cmp #PSTATE_LADDER ; state $03 = on ladder
beq player_airborne_exit ; on ladder → just return
cmp #PSTATE_AIRBORNE ; state $01 = already airborne
beq player_airborne_walk_shoot ; already airborne → walk+shoot
…
player_airborne_walk_shoot: lda joy1_held ; D-pad L/R held?
and #$03 ; mask L/R bits
beq player_airborne_shoot_check ; no → skip walk
sta player_facing ; update facing direction
jsr player_ground_move_horizontal ; move L/R with collisionMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/movement.asm#L176-L223gravity_save_y: lda ent_yvel,x ; Y velocity whole byte
bpl gravity_rising ; positive = rising
jsr apply_y_velocity_fall ; pos -= vel (moves down)
…
gravity_apply_physics: jsr apply_gravity ; increase downward velocity
jsr check_tile_horiz ; vertical tile collision check
…
gravity_rising: iny ; Y=1: upward collision check offset
jsr apply_y_velocity_rise ; pos -= vel (moves up)
…
gravity_apply_rise: jsr apply_gravity ; apply gravity (slows rise)
jsr check_tile_horiz ; vertical tile collision checkChecked by claude, 2026-10-26 — read player_on_ground, player_airborne through the state dispatch and the air move, and move_vertical_gravity's falling and rising branches at the pinned commit; the falling branch moves (apply_y_velocity_fall), applies gravity, then tests with check_tile_horiz, and the rising branch does the same after INY selects the ceiling pointsMega Man 2 runs the other way round: the horizontal move with its wall test first, then the vertical move (mega-man-2/collision-order). A landing ends the airborne routine before the air move, so the landing frame moves him only vertically (landing).
Gravity takes $40/256 = 0.25 px/frame from Mega Man's upward speed every frame out of water, after the frame's vertical move. mega-man-3/gravity
About: Mega Man 3 Gravity
Value: 64 1/256 px/frame²
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/game_loop.asm#L648-L649prelude_setup_per_frame: lda #$40 ; gravity = $40 for player physics
sta gravity ; (process_sprites sets $55 for entities)Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/movement.asm#L393-L399apply_gravity_player_check: lda ent_yvel_sub,x ; Y sub velocity -= gravity
sec ; prepare for subtraction
sbc gravity ; subtract gravity value
sta ent_yvel_sub,x ; store new Y sub velocity
lda ent_yvel,x ; Y velocity -= borrow
sbc #$00 ; propagate borrow to whole byte
sta ent_yvel,x ; store new Y velocityChecked by claude, 2026-10-26 — read the prelude, which sets `gravity` before the player's state runs, and apply_gravity at the pinned commitThe same as Mega Man 2 (mega-man-2/gravity). A water landing's cooldown in $B9 skips gravity for a few frames (apply_gravity L387-L392); the atlas models ordinary floor only.
A fall is capped at 7 px/frame: when gravity takes the speed's whole byte below $F9, apply_gravity sets it to $F9.$00. mega-man-3/max-fall
About: Mega Man 3 Gravity
Value: -7 px/frame
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/movement.asm#L400-L409bpl apply_gravity_exit ; still positive (rising)? done
cmp #$F9 ; check against terminal velocity $F9
bcs apply_gravity_exit ; within limit (-7 to -1)? done
lda ent_anim_id,x ; check entity animation ID
cmp #$13 ; $13 = exempt from terminal vel clamp
beq apply_gravity_exit ; special entity → no clamp
lda #$F9 ; clamp to terminal velocity
sta ent_yvel,x ; clamp Y velocity to $F9.00
lda #$00 ; clear sub velocity
sta ent_yvel_sub,x ; clear Y sub velocityChecked by claude, 2026-10-26 — read the clamp at the end of apply_gravity at the pinned commit; the exemption is for animation `$13`, which the player's normal states don't useMega Man 2 caps a fall at 12 px/frame (mega-man-2/max-fall). The cap isn't reached on the flat floor of the start area, where a full jump lands at about 4.9 px/frame.
Mega Man's position is his center. A fall tests the floor at x − 7, x, and x + 7, 12 px below it, and a rise tests the ceiling at x − 7 and x + 7, 12 px above it; a horizontal move tests x + 8 or x − 8 at 11 px above, level with, and 11 px below the center. Each point reads the 16 px tile holding it. mega-man-3/terrain-box
About: Mega Man 3 Hitbox
Value
{
"tile": 16,
"floor": {
"x": [
-7,
0,
7
],
"y": 12
},
"ceiling": {
"x": [
-7,
7
],
"y": -12
},
"wall": {
"x": 8,
"y": [
-11,
0,
11
]
}
}src/fixed/collision.asm#L491-L499 — hitbox_h_* rows 0 and 1: from start index 0, 3 points ($02) at Y + $0C, X −7 then +7 and +7 again; from index 5, 2 points ($01) at Y − $0C, X −7 then +14hitbox_h_start_idx: .byte $00,$05,$09,$0E,$12,$16,$1A,$1F
…
hitbox_h_count: .byte $02
hitbox_h_y_offset: .byte $0C
hitbox_h_x_offsets: .byte $F9,$07,$07,$01,$F4,$F9,$0E,$02Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/collision.asm#L525-L532 — hitbox_v_* rows 0 and 1: 3 points at X + 8, Y −11 then +11 and +11 again; from index 5, the same at X − 8hitbox_v_start_idx: .byte $00,$05,$0A,$0E,$12,$17,$1C,$21
…
hitbox_v_count: .byte $02
hitbox_v_x_offset: .byte $08
hitbox_v_y_offsets: .byte $F5,$0B,$0B,$02,$F8,$F5,$0B,$0BMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/collision.asm#L64-L77 — each point's X offset adds to the last one's; the tile column is X / 16lda hitbox_h_x_offsets,y ; first X offset (signed)
bpl metatile_compute_x_pos ; positive → skip sign extension
dec $04 ; negative: $04 = $FF
metatile_compute_x_pos: clc ; $12/$13 = entity_X + X_offset
adc ent_x_px,x ; $12 = X pixel
sta $12 ; store X pixel position
…
lda $12 ; X >> 4 = tile column
lsr a ; divide X by 16
lsr a ; to get tile column index
lsr a ; (16 pixels per tile)
lsr a ; A = X / 16Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L153-L159 — a walk or air move to the right uses wall row 0, to the left row 1player_ground_move_horizontal: and #$01 ; bit 0: right?
beq player_ground_move_left ; left direction, branch
ldy #$00 ; move right with collision
jmp move_right_collide ; jump to right move handler
player_ground_move_left: ldy #$01 ; move left with collision
jmp move_left_collide ; jump to left move handlerMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/movement.asm#L208 — a rise moves the floor row 0 to the ceiling row 1gravity_rising: iny ; Y=1: upward collision check offsetChecked by claude, 2026-10-26 — read check_tile_horiz and check_tile_collision, which add each offset to the running point, the two offset tables, player_ground_move_horizontal's row choice, and move_vertical_gravity's INY at the pinned commit; the offsets are signed bytesMega Man 2 tests the same floor and ceiling corners without the middle floor point, and its wall points at 12 and 4 px above and 11 px below the center (mega-man-2/terrain-box). With 16 px tiles a middle point 7 px from both corners never finds a tile they miss.
When a fall's floor points find a solid tile, snap_y_to_floor subtracts (Y + 12) mod 16 from Mega Man's Y pixel, which puts his feet on the tile's top, and leaves the Y fraction as it was; his vertical speed becomes the resting $FF.$C0, 0.25 px/frame down. Every grounded frame repeats the fall, so standing still his Y pixel holds while the fraction steps by $40 each frame: in Top Man's stage start in Mesen it cycles through $00, $40, $80, and $C0. mega-man-3/floor-snap
About: Mega Man 3 Jump
Value: -64 1/256 px/frame
VerifiedMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/collision.asm#L626-L634 — only ent_y_px changes; ent_y_sub is never writtensnap_y_to_floor: lda $11 ; save original $11
pha ; save original $11
and #$0F ; offset = $11 & $0F
sta $11 ; (sub-tile penetration depth)
lda ent_y_px,x ; Y pixel -= offset
sec ; (push entity up)
sbc $11 ; subtract penetration depth
sta ent_y_px,x ; store corrected Y pixel
bcs metatile_restore_result ; no underflow? doneMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/movement.asm#L203-L206gravity_snap_floor: jsr snap_y_to_floor ; snap to tile boundary (down)
gravity_reset_vel: jsr reset_gravity ; reset velocity to rest value
sec ; C=1: landed
rts ; return C=1 (on ground)Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/movement.asm#L424-L428reset_gravity_player: lda #$C0 ; player rest vel sub = $C0
sta ent_yvel_sub,x ; store Y sub velocity
lda #$FF ; player rest vel = $FF ($FFC0 = -0.25)
sta ent_yvel,x ; store Y velocity whole byte
rts ; player rest vel = $FFC0Checked by claude, 2026-10-26 — read snap_y_to_floor, the landing branch of move_vertical_gravity, and reset_gravity at the pinned commit, and ran movement.mjs, whose stand case records the Y fraction over 12 frames from Top Man's stage start in MesenProbe: probes/jump-physics/movement.mjsMega Man 2 zeroes the Y fraction on every snap (mega-man-2/floor-snap), so its jumps all start from a whole pixel; in Mega Man 3 a jump starts from whichever fraction that frame's cycle reached, and a landing keeps the fraction the fall left, so his height can differ by under a pixel between two jumps with the same input.
A grounded frame tests the floor before it moves Mega Man: if no floor point under his last position is solid, he falls 0.25 px, takes the jumping animation and the airborne state, and that frame's horizontal move is the air move, 1.30 px/frame toward a held direction or none. No grace period follows: only the ground state reads a jump press, and it runs only on frames that found a floor. mega-man-3/walk-off
About: Mega Man 3 Coyote time
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L4-L7player_on_ground:
jsr player_airborne ; run $99/collision first
bcc prelude_exit ; carry clear = still airborne → RTSMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L343-L364player_airborne_state_dispatch: lda player_state ; check current player state
cmp #PSTATE_LADDER ; state $03 = on ladder
beq player_airborne_exit ; on ladder → just return
cmp #PSTATE_AIRBORNE ; state $01 = already airborne
beq player_airborne_walk_shoot ; already airborne → walk+shoot
; --- first airborne frame: set jump animation ---
lda #$07 ; OAM $07 = jump/fall animation
jsr reset_sprite_anim ; set jump/fall animation
lda #PSTATE_AIRBORNE ; set state to airborne ($01)
sta player_state ; store player state
…
player_airborne_walk_shoot: lda joy1_held ; D-pad L/R held?
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L59-L61 — the jump press is read only past the bcc at L7, on a frame that found a floorplayer_ground_jump_or_slide: lda joy1_press ; check new button presses
and #BTN_A ; A button pressed?
beq player_ground_anim_dispatch ; no → animate walk/idleChecked by claude, 2026-10-26 — read player_on_ground, which returns at L7 when the airborne routine finds no floor, and the airborne routine's first-frame branch at the pinned commitSo he leaves a ledge one frame later than Mega Man 2, which tests the floor at the frame's new X (mega-man-2/walk-off), and the walk-off frame gives him the air move even where the walk would have paused.
When a horizontal move puts a wall point in a solid tile, the move routine pushes Mega Man back by the point's depth into the tile, so x + 8 ends on the wall's first column moving right and x − 8 on the first column past it moving left. The X fraction stays as it was, and his speed and state are untouched, so he walks in place against the wall. mega-man-3/wall-stop
About: Mega Man 3 Hitbox
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/movement.asm#L45-L53mr_tile_check: jsr check_tile_collision ; check tiles at entity position
jsr update_collision_flags ; update ladder/collision flags
clc ; clear carry (no collision yet)
lda $10 ; tile collision result
and #$10 ; isolate solid tile bit
beq move_right_collision_exit ; not solid → return clear
jsr snap_x_to_wall_right ; snap to tile boundary (right)
sec ; set carry = hit solid
move_right_collision_exit: rts ; return collision result in CMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/collision.asm#L565-L571 — pushing left: X − (point mod 16); ent_x_sub is never writtensnap_x_to_wall_right: lda $12 ; offset = $12 & $0F
and #$0F ; (sub-tile penetration depth)
sta $12 ; store sub-tile penetration
lda ent_x_px,x ; X pixel -= offset
sec ; (push entity left)
sbc $12 ; subtract penetration depth
sta ent_x_px,x ; store corrected X pixelMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/collision.asm#L585-L590 — pushing right: X + 16 − (point mod 16)snap_x_to_wall_left: lda $12 ; offset = ($12 & $0F) ^ $0F
and #$0F ; = 15 - sub-tile position
eor #$0F ; invert to get complement
sec ; X pixel += (16 - sub_tile_pos)
adc ent_x_px,x ; SEC + ADC = add offset + 1
sta ent_x_px,x ; (push entity right)Checked by claude, 2026-10-26 — read mr_tile_check and snap_x_to_wall_right and snap_x_to_wall_left at the pinned commit; neither touches ent_x_subMega Man 2 zeroes the X fraction at a wall (mega-man-2/wall-stop).
When a rise puts a ceiling point, x − 7 or x + 7 at 12 px above his center, in a solid tile, snap_y_to_ceil adds 16 − ((Y − 12) mod 16) to Mega Man's Y pixel, which puts that point on the row under the tile, keeps the Y fraction, and sets the resting speed, 0.25 px/frame down; he stays airborne. Since a jump runs the airborne routine on its press frame, a ceiling within the first frame's 4.89 px is met on that frame. mega-man-3/ceiling-hit
About: Mega Man 3 Hitbox
Value: -64 1/256 px/frame
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/movement.asm#L222-L231gravity_apply_rise: jsr apply_gravity ; apply gravity (slows rise)
jsr check_tile_horiz ; vertical tile collision check
jsr update_collision_flags ; update ladder/collision flags
lda $10 ; bit 4 = solid tile?
and #$10 ; isolate solid tile bit
beq gravity_airborne_exit ; no solid → continue rising
jsr snap_y_to_ceil ; snap to tile boundary (up)
gravity_snap_ceil: jsr reset_gravity ; reset velocity
gravity_airborne_exit: clc ; C=0: airborne
rts ; return C=0 (in air)Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/collision.asm#L604-L609snap_y_to_ceil: lda $11 ; offset = ($11 & $0F) ^ $0F
and #$0F ; isolate sub-tile position
eor #$0F ; invert to get complement
sec ; Y pixel += (16 - sub_tile_pos)
adc ent_y_px,x ; (push entity down)
sta ent_y_px,x ; store corrected Y pixelMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L71-L78 — the jump press runs the airborne routine at onceplayer_ground_normal_jump: lda joy2_held ; P2 held buttons
and #$01 ; bit 0 = Right held?
bne player_ground_super_jump ; yes → debug super jump
lda #$E5 ; normal jump: Y vel = $04.E5
sta ent_yvel_sub,x ; store Y velocity sub-pixel
lda #$04 ; Y velocity whole = $04
sta ent_yvel,x ; store Y velocity whole
jmp player_airborne ; enter airborne stateChecked by claude, 2026-10-26 — read the rising branch of move_vertical_gravity, snap_y_to_ceil, and player_ground_normal_jump at the pinned commitMega Man 2 meets a ceiling only from the frame after the press (mega-man-2/jump-under-ceiling) and zeroes the Y fraction at the hit (mega-man-2/ceiling-hit).
The turning animation, $0D, has 2 frames of 6 ticks each: its sequence's count byte is 1 and its tick byte 5, and the sprite engine advances a frame when the tick that started equal to the tick byte ends. While it shows its first frame the ground state doesn't move Mega Man. mega-man-3/turn-animation
About: Mega Man 3 Running
Value
{
"frames": 2,
"ticks": 6
}src/bank1A_1B_oam_sequences.asm#L199 — the record at $8332, animation $0D's (pointer bytes $32 and $83 in the tables at $8000 and $8080): count $01, ticks $05, sprites $0C and $0C.byte $08,$19,$01,$05,$0C,$0C,$01,$05Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/sprites.asm#L201-L217lda ent_anim_frame,x ; ent_anim_frame = frame tick counter
and #$7F ; (bit 7 = damage flash flag)
inc ent_anim_frame,x ; increment tick
ldy #$01 ; Y=1 → offset to duration byte
cmp (temp_00),y ; compare tick vs duration at seq[1]
bne sprite_drawn_flag_check ; not reached? keep waiting
lda ent_anim_frame,x ; tick reached duration:
and #$80 ; reset tick to 0, preserve bit 7
sta ent_anim_frame,x ; (damage flash flag)
lda ent_anim_state,x ; ent_anim_state = current frame index
and #$7F ; (bit 7 preserved)
inc ent_anim_state,x ; advance to next frame
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L94-L102 — frame 0 of $0D goes to the fire check, which doesn't move himcmp #$0D ; $0D/$0E/$0F = turning/special anims
beq player_ground_special_anim ; OAM $0D → check if anim complete
cmp #$0E ; OAM $0E = special anim variant
beq player_ground_special_anim ; OAM $0E → check if anim complete
cmp #$0F ; OAM $0F = special anim variant
bne player_ground_walk_handler ; other → normal walk handler
player_ground_special_anim: lda ent_anim_state ; check animation state
bne player_ground_walk_handler ; → normal walk handler
beq player_ground_slide_weapon ; anim done → slide/crouch checkChecked by claude, 2026-10-26 — read animation $0D's pointer and record in bank $1A, the tick step in setup_sprite_render, and the ground state's dispatch at the pinned commit; the ROM's bytes at the record match the listingreset_sprite_anim (movement.asm L435-L441) clears both counters when the animation starts.
On the ground Mega Man 3 picks the move from his animation. Standing, a held direction faces him that way, starts the turning animation, and moves him 1.30 px; on the turning animation's first frame he doesn't move; once it reaches its second, a held direction starts the walking animation and moves him, and nothing held stands him. Walking, a direction matching his facing moves him; anything else, letting go or the other direction, starts the turning animation and moves him once more toward his facing. Down alone does nothing on open ground. mega-man-3/ground-states
About: Mega Man 3 Running
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L89-L102player_ground_anim_dispatch: lda ent_anim_id ; A = current animation ID
cmp #$04 ; $04/$05 = shoot-walk anims
beq player_ground_shoot_walk ; OAM $04 → shoot-walk handler
cmp #$05 ; OAM $05 = shoot-walk variant
beq player_ground_shoot_walk ; shoot-walk variant match
cmp #$0D ; $0D/$0E/$0F = turning/special anims
beq player_ground_special_anim ; OAM $0D → check if anim complete
cmp #$0E ; OAM $0E = special anim variant
beq player_ground_special_anim ; OAM $0E → check if anim complete
cmp #$0F ; OAM $0F = special anim variant
bne player_ground_walk_handler ; other → normal walk handler
player_ground_special_anim: lda ent_anim_state ; check animation state
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L105-L121player_ground_shoot_walk: lda joy1_held ; D-pad matches facing direction?
and player_facing ; mask held direction with facing
beq player_ground_turning_anim ; no → turning animation
pha ; save direction on stack
jsr player_ground_slide_weapon ; slide/crouch check
pla ; restore direction from stack
jmp player_ground_move_horizontal ; move L/R based on direction
; --- turning: set turn animation, then move ---
player_ground_turning_anim: lda #$0D ; OAM $0D = turning animation
jsr reset_sprite_anim ; set turning animation
lda shoot_anim_timer ; check if shooting
beq player_ground_move_facing ; not shooting, move facing
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L125-L141player_ground_walk_handler: lda joy1_held ; D-pad L/R held?
and #$03 ; mask L/R bits
beq player_ground_idle ; no → idle
sta player_facing ; update facing direction
jsr player_ground_slide_weapon ; slide/crouch check
lda ent_anim_id ; check current animation ID
cmp #$0D ; OAM $0D = turning anim?
beq player_ground_shoot_walk_anim ; yes → use shoot-walk anim
cmp #$0E ; OAM $0E = special anim?
beq player_ground_shoot_walk_anim ; special anim variant match
cmp #$0F ; check OAM $0F
bne player_ground_turning_anim ; else → turning animation
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L163-L168player_ground_idle: lda shoot_anim_timer ; if shooting: skip idle anim reset
bne player_ground_slide_weapon ; shooting, skip idle check
lda #$01 ; OAM $01 = idle standing
cmp ent_anim_id ; compare with current anim
beq player_ground_slide_weapon ; already idle → skip reset
jsr reset_sprite_anim ; set OAM to $01 (idle standing)Checked by claude, 2026-10-26 — read player_ground_anim_dispatch through player_ground_idle at the pinned commit; Down is read on the ground only with a fresh A (slide) and by the ladder test (player_states.asm L394-L420), which needs a ladder tileSo a reversal from a walk moves him one frame the old way, pauses, then walks the new way, where Mega Man 2 turns at once at full speed (mega-man-2/turn-instant).
Measured in Mesen from a standstill in Top Man's stage start, Right held moves Mega Man 1.30 px on its first frame, not at all on the next 5, and 1.30 px on every frame after; let go after a walk, he moves 1.30 px on one more frame and then stands. mega-man-3/ground-measured
About: Mega Man 3 Running
Value
{
"stepFrames": 1,
"pauseFrames": 5,
"stopFrames": 1
}src/fixed/player_ground.asm#L115-L121 — the step: the turning animation, then a moveplayer_ground_turning_anim: lda #$0D ; OAM $0D = turning animation
jsr reset_sprite_anim ; set turning animation
lda shoot_anim_timer ; check if shooting
beq player_ground_move_facing ; not shooting, move facing
jsr shoot_oam_advance_variant ; update shoot animation variant
player_ground_move_facing: lda player_facing ; move in facing direction
jmp player_ground_move_horizontal ; move in facing directionMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/bank1A_1B_oam_sequences.asm#L199 — animation $0D: 2 frames of 6 ticks (turn-animation).byte $08,$19,$01,$05,$0C,$0C,$01,$05Checked by claude, 2026-10-26 — ran movement.mjs, whose walk case holds Right 30 frames and lets go 20 from Top Man's stage start in Mesen, and checked the per-frame X steps against the values; the 5 still frames are the 6 ticks of the turning animation's first frame less the one counted on the step frameProbe: probes/jump-physics/movement.mjsprobes/lab/conformance.mjs runs every lab preset and stress script, the slide scripts among them, through the model and the cartridge from the same start and finds them equal frame for frame.
A jump lifts Mega Man on its press frame: the ground state sets the jump speed and runs the airborne routine at once, which moves him 4.89 px up from where that frame's floor test left him (the test takes him 0.25 px down, or, when that crosses into the next pixel and the snap puts his feet back on the floor, 0.75 px up), takes gravity, and gives the frame the air move, 1.30 px/frame toward a held direction or none, in place of the ground move. A press in a walking pause still jumps, since the press is read before the walk. mega-man-3/takeoff
About: Mega Man 3 Jump
VerifiedMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L59-L65player_ground_jump_or_slide: lda joy1_press ; check new button presses
and #BTN_A ; A button pressed?
beq player_ground_anim_dispatch ; no → animate walk/idle
lda joy1_held ; check held buttons
and #BTN_DOWN ; holding down?
beq player_ground_normal_jump ; not holding down, jump
jmp slide_initiate ; initiate slideMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L71-L78player_ground_normal_jump: lda joy2_held ; P2 held buttons
and #$01 ; bit 0 = Right held?
bne player_ground_super_jump ; yes → debug super jump
lda #$E5 ; normal jump: Y vel = $04.E5
sta ent_yvel_sub,x ; store Y velocity sub-pixel
lda #$04 ; Y velocity whole = $04
sta ent_yvel,x ; store Y velocity whole
jmp player_airborne ; enter airborne stateChecked by claude, 2026-10-26 — read player_ground_jump_or_slide and player_ground_normal_jump at the pinned commit, and ran movement.mjs, whose jump case from Top Man's stage start in Mesen shows the first frame's rise equal to the jump speed less the 0.25 px dropProbe: probes/jump-physics/movement.mjsMega Man 2's press frame moves him at the ground speed and doesn't lift him (mega-man-2/takeoff-x, jump-start).
A landing frame moves Mega Man only vertically: the airborne routine snaps him onto the floor, sets the ground state and the turning animation already on its second frame, and returns before the air move. From the next frame he walks at once if a direction is held, with no pause. An A press on the landing frame is lost, since the airborne state reads no presses. mega-man-3/landing
About: Mega Man 3 Jump
VerifiedMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L322-L339player_airborne_landing_check: plp ; restore carry (landing status)
jsr ladder_check_entry_ground ; check if player should enter ladder
bcc player_airborne_state_dispatch ; carry clear = no ladder → continue airborne
; --- player landed on ground → transition to on_ground ---
lda player_state ; check current player state
beq player_airborne_landing_anim ; already on_ground → skip transition
lda #SFX_LAND ; play landing sound
jsr submit_sound_ID ; play landing sound effect
lda #$00 ; state = on_ground ($00)
sta player_state ; set to on_ground state
lda #$0D ; OAM $0D = landing animation
jsr reset_sprite_anim ; set landing animation
…Checked by claude, 2026-10-26 — read player_airborne_landing_check through player_airborne_landing_anim at the pinned commit, and ran movement.mjs: in Top Man's stage start in Mesen, the jumprun case's landing frame has no X step and the next a walking one, and the mash case, A pressed every other frame, lands on press frames and stays grounded the frame afterProbe: probes/jump-physics/movement.mjsMega Man 2 jumps on a press on the landing frame (mega-man-2/landing).
Starting a slide, Mega Man 3 first tests for a wall 8 and 22 px ahead of his center at its height, and a wall there cancels the press. A slide lowers him 2 px, tests the floor at x − 15, x, and x + 15, 10 px below his lowered center, and moves with a wall point 16 px ahead, at 5 px above and 7 px below that center. A turn, a wall, the timer's end, or a jump press leads to the standing test, the ceiling points 12 px over his lowered center. mega-man-3/slide-box
About: Mega Man 3 Ground slide
Value
{
"drop": 2,
"floor": {
"x": [
-15,
0,
15
],
"y": 10
},
"wall": {
"x": 16,
"y": [
-5,
7
]
},
"start": {
"x": [
8,
22
],
"y": 0
}
}src/fixed/player_states.asm#L269-L282slide_initiate: lda joy1_held ; read D-pad state
and #$03 ; left/right bits
beq slide_check_direction ; no direction → keep current
sta player_facing ; update facing from D-pad
slide_check_direction: ldy #$04 ; Y=4 (check right) or Y=5 (check left)
lda player_facing ; based on facing direction
and #$01 ; bit 0 = right
bne slide_wall_collision ; facing right → skip INY
iny ; facing left → Y=5
slide_wall_collision: jsr check_tile_horiz ; check for wall ahead at slide height
lda $10 ; collision result
and #$10 ; bit 4 = solid wall
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/collision.asm#L491-L501 — hitbox_h_* rows 2, 4, and 5: from index 9, 3 points at Y + $0A, X −15 then +15 and +15; from $12 and $16, 2 points at Y + 0, X +8 then +14, and X −22 then +14hitbox_h_start_idx: .byte $00,$05,$09,$0E,$12,$16,$1A,$1F
…
hitbox_h_x_offsets: .byte $F9,$07,$07,$01,$F4,$F9,$0E,$02
.byte $0A,$F1,$0F,$0F,$01,$00,$F9,$0E
.byte $01,$00,$08,$0E,$01,$00,$EA,$0EMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/collision.asm#L525-L533 — hitbox_v_* rows 2 and 3: from index $0A, 2 points at X + $10, Y −5 then +12; from $0E, the same at X − $10hitbox_v_start_idx: .byte $00,$05,$0A,$0E,$12,$17,$1C,$21
…
hitbox_v_y_offsets: .byte $F5,$0B,$0B,$02,$F8,$F5,$0B,$0B
.byte $01,$10,$FB,$0C,$01,$F0,$FB,$0CMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_states.asm#L324-L343 — the slide's floor test (row 2) and moves (rows 2 and 3)player_slide:
ldy #$02 ; apply $99 (Y=2 = slide hitbox)
jsr move_vertical_gravity ; apply gravity with slide hitbox
bcc slide_end_clear_state ; C=0 → no ground, fell off ledge
lda joy1_held ; read held D-pad
and #$03 ; mask left/right bits
beq slide_move_check_facing ; no direction → keep sliding
cmp player_facing ; same as current facing?
beq slide_move_check_facing ; yes → keep sliding
sta player_facing ; changed direction → end slide
bne slide_end_ground_check ; always taken → end slide
slide_move_check_facing: lda player_facing ; facing direction: bit 0 = right
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_states.asm#L361-L365 — the standing test, row 1, the ceiling pointsslide_end_ground_check: ldy #$01 ; check ground at standing height
jsr check_tile_horiz ; check tile at standing height
lda $10 ; solid ground under feet?
and #$10 ; bit 4 = solid
bne slide_end_on_ground_return ; on ground → return to standingChecked by claude, 2026-10-26 — read slide_initiate, player_slide, and slide_end_ground_check with the offset rows they pick at the pinned commit; the 2 px drop is slide_initiate_confirmed's (slide)A slide that ends on open ground leaves Mega Man in the ground state with the walking animation, still 2 px low; the next frame's floor test pushes him back up. That walking animation moves him on the next frame toward his facing, so with no direction held he steps once more at 1.30 px/frame and then pauses, and with one held he walks on at once. A slide whose floor test finds nothing ends without moving that frame, and he falls from the next. mega-man-3/slide-exit
About: Mega Man 3 Ground slide
VerifiedMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_states.asm#L366-L384sta $33 ; clear timer and state
sta player_state ; state = on_ground
lda joy1_press ; A button pressed?
and #BTN_A ; check A button
bne slide_jump_walk_speed_init ; yes → slide jump
beq slide_end_fall_anim ; no → fall from slide
slide_end_clear_state: lda #$00 ; clear timer
sta $33 ; clear slide timer
lda #$01 ; OAM anim $01 = airborne
bne slide_end_anim_set ; always taken → set anim
slide_end_fall_anim: lda #$04 ; OAM anim $04 = jump/fall
slide_end_anim_set: jsr reset_sprite_anim ; set fall/airborne animation
…Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_states.asm#L324-L327player_slide:
ldy #$02 ; apply $99 (Y=2 = slide hitbox)
jsr move_vertical_gravity ; apply gravity with slide hitbox
bcc slide_end_clear_state ; C=0 → no ground, fell off ledgeChecked by claude, 2026-10-26 — read slide_end_ground_check through slide_end_on_ground_return at the pinned commit (no Y adjustment on any exit), and ran movement.mjs: in Top Man's stage start in Mesen the slide case is 2 px low through its last frame, level the frame after, and moves 1.30 px on that frameProbe: probes/jump-physics/movement.mjsThe slide's floor points reach 15 px out, so a slide carries him further over a ledge than walking would before it ends.
Mega Man 3's ordinary-floor movement differs from Mega Man 2's from the first frame of a walk: Right held from a standstill moves him 1.30 px, then nothing for 5 frames, then 1.30 px a frame, where Mega Man 2 moves 0.125 px a frame for 7 frames, then 1.375. A jump differs from its press frame, which lifts him 4.89 px where Mega Man 2 lifts him 0. Letting go of A mid-rise starts the fall at once, where Mega Man 2 cuts a rise of 2 px/frame or more to 1, and falls cap at 7 px/frame instead of 12. He keeps Mega Man 2's gravity, floor and ceiling corners, and 16 px tiles, and adds the slide. mega-man-3/movement-differs-from-mega-man-2
About: Mega Man 3 Running
CodeMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) ·src/fixed/player_ground.asm#L115-L121 — Mega Man 3's stepplayer_ground_turning_anim: lda #$0D ; OAM $0D = turning animation
jsr reset_sprite_anim ; set turning animation
lda shoot_anim_timer ; check if shooting
beq player_ground_move_facing ; not shooting, move facing
jsr shoot_oam_advance_variant ; update shoot animation variant
player_ground_move_facing: lda player_facing ; move in facing direction
jmp player_ground_move_horizontal ; move in facing directionMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/game_loop.asm#L642-L645 — one speed for walking and the airprelude_reset_walk_speed: lda #$4C ; reset walk speed to $01.4C
sta ent_xvel_sub ; store walk sub-pixel speed
lda #$01 ; walk speed whole = $01
sta ent_xvel ; store walk whole speedmm2: Mega Man 2 disassembly (lsmmega/mm2) · engine/run_megaman.asm#L208-L219 — Mega Man 2's release capLDA z:zjoy1_pressed
AND #a_button
BNE @skip
LDA aobject_ycoord_speed
BMI @skip
CMP #$01
BCC @skip
BEQ @skip
LDA #$01
STA aobject_ycoord_speed
LDA #$00
STA aobject_ycoord_speed_fractionMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L71-L78 — Mega Man 3's press frame runs the airborne state at onceplayer_ground_normal_jump: lda joy2_held ; P2 held buttons
and #$01 ; bit 0 = Right held?
bne player_ground_super_jump ; yes → debug super jump
lda #$E5 ; normal jump: Y vel = $04.E5
sta ent_yvel_sub,x ; store Y velocity sub-pixel
lda #$04 ; Y velocity whole = $04
sta ent_yvel,x ; store Y velocity whole
jmp player_airborne ; enter airborne stateMega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/player_ground.asm#L260-L267 — Mega Man 3's release cutplayer_airborne: lda ent_yvel ; check Y velocity sign
bmi player_airborne_variable_jump ; negative (falling) → skip to clear flag
lda force_full_jump ; check force-full-jump flag
bne player_airborne_wall_check ; nonzero → skip variable jump
lda joy1_held ; check if A button held
and #BTN_A ; A button mask
bne player_airborne_variable_jump ; held → keep rising (variable jump)
jsr reset_gravity ; A released → clamp Y speed to 0 (variable jump)Mega Man 3 (U), annotated ca65 disassembly (plasticsmoke) · src/fixed/movement.asm#L393-L402 — Mega Man 3's fall capapply_gravity_player_check: lda ent_yvel_sub,x ; Y sub velocity -= gravity
sec ; prepare for subtraction
sbc gravity ; subtract gravity value
sta ent_yvel_sub,x ; store new Y sub velocity
lda ent_yvel,x ; Y velocity -= borrow
sbc #$00 ; propagate borrow to whole byte
sta ent_yvel,x ; store new Y velocity
bpl apply_gravity_exit ; still positive (rising)? done
cmp #$F9 ; check against terminal velocity $F9
bcs apply_gravity_exit ; within limit (-7 to -1)? donemm2: Mega Man 2 disassembly (lsmmega/mm2) · engine/run_megaman.asm#L84-L88 — Mega Man 2 moves on the ground first, then checks for a jump, so the press frame doesn't lift himJSR _megaman_status_parameter
JSR _check_object_horizontal_move
JSR _check_object_vertical_move
LDA z:z00
BNE _run_megaman_landing_common_1mm2: Mega Man 2 disassembly (lsmmega/mm2) · engine/check_object_vertical_move.asm#L81-L87 — Mega Man 2's fall capBPL @skip
CMP #$F4
BCS @skip
LDA #$00
STA aobject_ycoord_speed_fraction
LDA #$F4
STA aobject_ycoord_speedChecked by claude, 2026-10-26 — compared the two games'' claims (mega-man-3/ground-measured, takeoff, release-cut, max-fall, gravity, terrain-box; mega-man-2/state-speeds, ground-measured, jump-start, release-cap, max-fall), each checked against its cartridge by probes/lab/conformance.mjs, and re-read the lines quoted at their pinned commitsMega Man 2's 7-frame step and 1.375 px/frame run are measured in mega-man-2/ground-measured and mega-man-2/state-speeds. Their code differs too: Mega Man 2 sets each state's speed from a table, while Mega Man 3 runs one speed and gates the move by animation. Mega Man 3's lab model is its own (mega-man-3.model.ts); the two models first differ on frame 1 of a walk or a jump.
Cited by: Mega Man (classic series)