Contra
Konami's 1987 run-and-gun, read here in its US NES release through vermiceli's annotated disassembly.
On this page
The atlas reads Contra’s US NES release through vermiceli’s annotated ca65 disassembly, which grew from Trax’s earlier one. Built with the ROM’s assets, it matches the ROM’s PRG byte for byte. Labels and comments quoted here are theirs. Nothing has been measured in the ROM yet.
Each falcon is a weapon pickup. M, F, S, and L replace the gun, R adds rapid fire, B makes the player invincible for a while, and the falcon destroys the enemies on screen; a death returns the default gun1. The Konami code, entered while the title scrolls in, raises the starting lives from 3 to 30 lives (29 in reserve), against 32,2. A game has 3 continues3, and an extra life comes at 20,000 points and every 30,000 after4.
Contra keeps a rank. Random soldiers come faster with each loop of the game cleared, counting up to 3, and with the strongest gun either player holds5. The screen moves only when a player pushes it; on the boss’s screen it scrolls on its own to reveal the boss6.
A jump rises at 4.06 px/frame ($FB.$F0)7 and gravity takes 0.137 px/frame² off on every frame, whatever the buttons do7.
Cartridge
Mechanics
| Mechanic | Kind | How |
|---|---|---|
| Weapon pickup | Replace | falcons carry M, F, S, L, R, B, or a screen-clearing falcon; a new gun drops rapid fire unless it's the one held |
| Cheat code | On the title | Up Up Down Down Left Right Left Right B A on the title screen: 30 lives |
| Continue | Limited | 3 lives and 3 continues a game |
| Extra life | Repeating | at 20,000 points and every 30,000 after, up to 99 lives |
| Rank | Loops and guns | random soldiers come faster with each loop cleared, up to 3, and with the strongest gun held |
| Auto-scroll | Player-pushed | players push the screen past X 128; it scrolls on its own only to reveal a boss |
| Jump | One speed | 4.06 px/frame outdoors, 3.44 in the base |
| Gravity | One gravity | 0.137 px/frame² on every airborne frame |
| Variable jump height | None | the jump routine never reads A |
Every claim about this game, by concept (8)
References
- ^Touching a weapon item in the US NES Contra adds 1,000 points and dispatches on the item's type: R sets the rapid-fire bit, M, F, S, and L replace the gun (keeping rapid fire only when the new gun is the one held), B starts an invincibility timer at
$80($90on level 7), and the falcon destroys the enemies on screen. A death resets the gun to the default.Codevermiceli/nes-contra-usHow we know
6884pick_up_weapon_item: ⋮ 6886 lda #$0a ; a = #$0a 6887 sta $00 ; set score to add to player as #$0a (1,000 points) ⋮ 6895 lda ENEMY_ATTRIBUTES,y ; get weapon item attributes 6896 and #$07 ; keep bits 0-2 (attributes) 6897 beq @set_rapid_flag ; set rapid flag for weapon if attribute is #$00 6898 cmp #$05 ; check for b weapon (barrier). Gives invincibility 6899 bcc @compare_and_set_weapon ; branch if less than #$05 (MFSL) 6900 beq @set_invincibility_timer ; branch for barrier weapon 6901 jsr destroy_all_enemies ; falcon weapon effect - destroy all enemies ⋮ 6912@set_invincibility_timer: 6913 lda #$80 ; set duration of the b weapon effect ⋮
src/bank7.asm lines 6884–6955 at 687d651 · upstream ↗ 5150init_player_dec_num_lives: 5151 jsr init_player_and_weapon ; initialize player variables to 0
src/bank7.asm lines 5150–5151 at 687d651 · upstream ↗ 5338init_player_and_weapon: 5339 lda #$00 ; a = #$00 5340 sta P1_CURRENT_WEAPON,x ; reset current player's weapon
src/bank7.asm lines 5338–5340 at 687d651 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abWhile the title screen scrolls in, the US NES Contra compares each new d-pad, A, or B press with a ten-entry table, Up Up Down Down Left Right Left Right B A. A wrong press sets the count to
$FF, which stops the check until the title starts over. The full sequence sets a flag, and a new game then gives each player 29 lives in reserve instead of 2: 30 lives instead of 3.Codevermiceli/nes-contra-usHow we know
750game_routine_01: 751 jsr konami_input_check ; check if current input is part of Konami code (30-lives code)
src/bank7.asm lines 749–752 at 687d651 · upstream ↗ 930konami_input_check: 931 ldy KONAMI_CODE_NUM_CORRECT ; load the number of successful inputs of Konami code 932 bmi konami_code_exit ; if #$ff (invalid Konami input), exit 933 lda CONTROLLER_STATE_DIFF ; buttons pressed (only care on input change so held button doesn't affect code) 934 and #$cf ; only care about input from d-pad and A/B buttons (not select nor start) 935 beq konami_code_exit ; if no input detected, exit 936 cmp konami_code_lookup_table,y ; compare with Konami code sequence at index y 937 beq konami_input_index_correct ; on success, goto konami_input_index_correct 938 lda #$ff ; incorrect sequence for Konami code 939 sta KONAMI_CODE_NUM_CORRECT ; since incorrect set KONAMI_CODE_NUM_CORRECT (number of successful inputs) to $ff ⋮ 945 cpy #$0a ; Konami code is 10 inputs, compare against how many successfully entered 946 bcc konami_code_exit ; Konami code not yet fully entered, exit ⋮
src/bank7.asm lines 930–955 at 687d651 · upstream ↗ 716game_routine_00: ⋮ 720 sty KONAMI_CODE_NUM_CORRECT ; initialize konami check to #$00 (see konami_input_check)
src/bank7.asm lines 716–720 at 687d651 · upstream ↗ 1165init_player_lives: 1166 lda #$02 ; start off with #$02 lives 1167 ldy KONAMI_CODE_STATUS ; 30-lives code switch ($01 = code activated) 1168 beq init_player_num_lives ; if KONAMI_CODE_STATUS is not set, then just set 2 lives 1169 lda #$1d ; KONAMI_CODE_STATUS active so set lives to #$1d (29 decimal) ⋮ 1174 sta P1_NUM_LIVES,x ; if X is 1, then set P2_NUM_LIVES to accumulator (a is either #$02 or #$1d)
src/bank7.asm lines 1165–1174 at 687d651 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^A new game of the US NES Contra gives each player 2 lives in reserve (3 in all) and the game 3 continues. A death with none in reserve puts that player in game over, and each game over spends a continue; with none left, the CONTINUE/END choice is skipped.Codevermiceli/nes-contra-us
How we know
1143init_score_player_lives: ⋮ 1146 lda #$03 ; a = #$03 1147 sta NUM_CONTINUES ; continues remaining
src/bank7.asm lines 1143–1147 at 687d651 · upstream ↗ 1165init_player_lives: 1166 lda #$02 ; start off with #$02 lives
src/bank7.asm lines 1165–1166 at 687d651 · upstream ↗ 5150init_player_dec_num_lives: ⋮ 5153 lda P1_NUM_LIVES,x ; load player number of lives 5154 beq @set_game_over ; branch if no more lives 5155 dec P1_NUM_LIVES,x ; decrement player number of lives ⋮ 5158@set_game_over: 5159 lda #$01 ; a = #$01 5160 sta P1_GAME_OVER_STATUS,x ; game over state of player (1 = game over)
src/bank7.asm lines 5150–5161 at 687d651 · upstream ↗ 3289show_game_over_screen: ⋮ 3301 dec NUM_CONTINUES ; subtract from number of continues remaining 3302 bmi @no_continues_remaining ; if run out of continues, jump 3303 lda #$0e ; a = #$0e (continue end)
src/bank7.asm lines 3289–3307 at 687d651 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^The US NES Contra awards its first extra life at 20,000 points and each later one 30,000 points past the last threshold, and caps lives at 99.Codevermiceli/nes-contra-us
How we know
1177 lda #$c8 ; set default high score. #$c8 is 200 decimal 1178 sta EXTRA_LIFE_SCORE_LOW ; starting score for extra life (20,000)
src/bank7.asm lines 1177–1178 at 687d651 · upstream ↗ 1283; every time an extra life is obtained, #$12c is added to points required to get next extra life (300 decimal, 30,000 in game score) ⋮ 1291 lda #$2c ; a = #$2c (12c = 300 decimal) (high byte will be set to #$01 a few lines down) ⋮ 1294 adc EXTRA_LIFE_SCORE_LOW,y ; add 300 decimal to low byte of score required to get extra life
src/bank7.asm lines 1283–1294 at 687d651 · upstream ↗ 1310 inc P1_NUM_LIVES,x ; if X is $01, then set P2 number of lives 1311 lda P1_NUM_LIVES,x ; load incremented number into memory 1312 cmp #$63 ; compare to #$63 (99 decimal) 1313 bcc @set_num_lives ; can't have more than 99 lives 1314 lda #$63 ; a = #$63 (63 = 99 decimal)
src/bank7.asm lines 1310–1314 at 687d651 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^Each completed loop of the US NES Contra and the strongest gun either player holds both shorten the timer between random soldiers: from the level's start value (144 on level 1), 40 comes off per loop, counting up to 3, and 5 per point of weapon strength (S 3, M and L 2, F 1, the default 0).Codevermiceli/nes-contra-us
How we know
1736adjust_generation_timer: 1737 jsr get_soldier_gen_timer_for_lvl 1738 sta SOLDIER_GENERATION_TIMER ; soldier generation timer 1739 ldy GAME_COMPLETION_COUNT ; load the number of times the game has been completed 1740 beq @adjust_timer_for_weapon ; branch if haven't yet beat the game any times 1741 tya 1742 cmp #$04 ; compare GAME_COMPLETION_COUNT to #$04 1743 bcc @continue ; branch if GAME_COMPLETION_COUNT < #$04 1744 ldy #$03 ; maximum multiplier of increase chance for generating soldier is #$03 ⋮ 1747 lda #$28 ; lower soldier generation timer ⋮ 1752 ldy PLAYER_WEAPON_STRENGTH ; load player weapon strength (damage strength) 1753 beq @exit ; don't generate if player's weapon strength is #$00 ⋮
src/bank2.asm lines 1736–1779 at 687d651 · upstream ↗ 3270 inc GAME_COMPLETION_COUNT ; increment game completion count, used mainly to increase enemy difficulty every play-throughsrc/bank7.asm line 3270 at 687d651 · upstream ↗ 3920set_frame_scroll_weapon_strength: ⋮ 3926 sta PLAYER_WEAPON_STRENGTH ; clear player weapon strength
src/bank7.asm lines 3920–3926 at 687d651 · upstream ↗ 4096set_player_weapon_strength: ⋮ 4102 lda weapon_strength,y ; load how strong the weapon is 4103 cmp PLAYER_WEAPON_STRENGTH ; compare against current weapon strength 4104 bcc @exit ; exit and do not lower player's current weapon strength 4105 sta PLAYER_WEAPON_STRENGTH ; store current weapon strength code (#$00-#$03) ⋮ 4111weapon_strength: 4112 .byte $00 ; Regular = Weak 4113 .byte $02 ; M = Strong 4114 .byte $01 ; F = Medium 4115 .byte $03 ; S = Very Strong 4116 .byte $02 ; L = Strong
src/bank7.asm lines 4096–4116 at 687d651 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^On the side-scrolling levels of the US NES Contra the screen moves only while a player passes X 128 (176 when two are playing), by that player's speed, and not while the other player is left of X 33; on the boss's screen it takes over and scrolls on its own to reveal the boss.Codevermiceli/nes-contra-us
How we know
5164set_frame_scroll_if_appropriate: ⋮ 5178 lda SPRITE_X_POS,x ; load player X position 5179 cmp horizontal_scroll_point_tbl,y ; load horizontal scroll point position 5180 bcc @exit ; if not yet reached point to initiate horizontal scroll, exit 5181 cpy #$02 ; see if both players are active 5182 bne @set_horizontal_level_frame_scroll ; if both players aren't active, begin setting scroll ⋮ 5186 lda SPRITE_X_POS,y ; load other player's X position 5187 cmp #$21 ; compare other player to left edge of screen 5188 bcc @stop_player_x_velocity ; don't scroll if other player is preventing it by being too far to the left ⋮ 5196 jsr set_boss_auto_scroll ; starts the auto scroll to reveal boss if at right screen, otherwise do nothing ⋮ 5202 lda PLAYER_X_VELOCITY,x ; load player X velocity 5203 adc #$00 ; incorporate any velocity overflow from INDOOR_TRANSITION_X_ACCUM ⋮
src/bank7.asm lines 5164–5208 at 687d651 · upstream ↗ 5228horizontal_scroll_point_tbl: 5229 .byte $80,$80,$b0
src/bank7.asm lines 5228–5229 at 687d651 · upstream ↗ 6552set_boss_auto_scroll: ⋮ 6557 cmp LEVEL_SCREEN_NUMBER ; screen number 6558 bne @exit ; exit if not on the appropriate screen to start auto scroll ⋮ 6563 lda auto_scroll_timer_tbl,y ; load the appropriate auto scroll timer for the level 6564 sta AUTO_SCROLL_TIMER_00 ; start auto scroll to reveal the boss
src/bank7.asm lines 6552–6566 at 687d651 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abA jump in the US NES Contra sets the 16-bit vertical speed to
$FB.$F0, a rise of 4.06 px/frame ($FC.$90, 3.44 px/frame, in the indoor base levels), and the jump routine adds$23to it every airborne frame, 0.137 px/frame², without reading a button: the jump's height doesn't depend on holding A.Codevermiceli/nes-contra-usHow we know
4533set_jump_status_and_y_velocity: ⋮ 4545 lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor 4546 lsr 4547 lda #$fb ; a = #$fb (-5) 4548 ldy #$f0 ; y = #$f0 (.94) 4549 bcc @set_y_velocity ; branch if outdoor level (use Y velocity -5.94) 4550 lda #$fc ; indoor level, (Y velocity -4.56), set fast velocity to #$fc (-4) 4551 ldy #$90 ; set fractional Y velocity to #$90 (.56) ⋮ 4554 sta PLAYER_Y_FAST_VELOCITY,x ; set Y fast velocity to a 4555 tya ; transfer fractional velocity to a 4556 sta PLAYER_Y_FRACT_VELOCITY,x ; set Y fractional velocity to a
src/bank7.asm lines 4533–4557 at 687d651 · upstream ↗ 4708handle_jump: 4709 lda PLAYER_Y_FAST_VELOCITY,x ; load player Y fast velocity 4710 bmi @check_collision_above ; branch if player is still ascending ⋮ 4747@apply_gravity: 4748 jsr apply_gravity ; increments Y fractional velocity by #$23 (applying gravity) ⋮ 4756@set_y_pos: 4757 jsr player_jumping_set_y_pos ; set player Y position based on PLAYER_JUMP_COEFFICIENT and velocity
src/bank7.asm lines 4708–4757 at 687d651 · upstream ↗ 5114player_jumping_set_y_pos: ⋮ 5123 lda PLAYER_JUMP_COEFFICIENT,x ; load player's jump modifier (alters height of jump) 5124 clc ; clear carry in preparation for addition 5125 adc PLAYER_Y_FRACT_VELOCITY,x ; add to player's fractional Y velocity 5126 sta PLAYER_JUMP_COEFFICIENT,x ; update player's jump modifier (alters height of jump) 5127 lda SPRITE_Y_POS,x ; load player's Y position 5128 adc PLAYER_Y_FAST_VELOCITY,x ; add (subtract) the fast Y velocity (with jump coefficient overflow)
src/bank7.asm lines 5113–5130 at 687d651 · upstream ↗ 5139apply_gravity: 5140 clc 5141 lda PLAYER_Y_FRACT_VELOCITY,x ; load player's Y fractional velocity 5142 adc #$23 ; add #$23 to Y fractional velocity (.1367 decimal)
src/bank7.asm lines 5139–5147 at 687d651 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
Sources
Code
- (vermiceli), Michael Miceli. 2026. “Contra (US), annotated disassembly (vermiceli).” Commit 687d651. vermiceli/nes-contra-us @ 687d651 · licence, all uses