data/claims/contra.yamlContra (vermiceli's annotated disassembly of the US NES release)
Game: Contra.
While 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. contra/konami-code
About: Contra Cheat code
Value: 30 lives
CodeContra (US), annotated disassembly (vermiceli) ·src/bank7.asm#L749-L752game_routine_01:
jsr konami_input_check ; check if current input is part of Konami code (30-lives code)Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L930-L955konami_input_check:
ldy KONAMI_CODE_NUM_CORRECT ; load the number of successful inputs of Konami code
bmi konami_code_exit ; if #$ff (invalid Konami input), exit
lda CONTROLLER_STATE_DIFF ; buttons pressed (only care on input change so held button doesn't affect code)
and #$cf ; only care about input from d-pad and A/B buttons (not select nor start)
beq konami_code_exit ; if no input detected, exit
cmp konami_code_lookup_table,y ; compare with Konami code sequence at index y
beq konami_input_index_correct ; on success, goto konami_input_index_correct
lda #$ff ; incorrect sequence for Konami code
sta KONAMI_CODE_NUM_CORRECT ; since incorrect set KONAMI_CODE_NUM_CORRECT (number of successful inputs) to $ff
…
cpy #$0a ; Konami code is 10 inputs, compare against how many successfully entered
…Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L716-L720game_routine_00:
…
sty KONAMI_CODE_NUM_CORRECT ; initialize konami check to #$00 (see konami_input_check)Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L1165-L1174init_player_lives:
lda #$02 ; start off with #$02 lives
ldy KONAMI_CODE_STATUS ; 30-lives code switch ($01 = code activated)
beq init_player_num_lives ; if KONAMI_CODE_STATUS is not set, then just set 2 lives
lda #$1d ; KONAMI_CODE_STATUS active so set lives to #$1d (29 decimal)
…
sta P1_NUM_LIVES,x ; if X is 1, then set P2_NUM_LIVES to accumulator (a is either #$02 or #$1d)Checked by claude, 2026-10-21 — read konami_input_check, its table, the reset in game_routine_00, the call in game_routine_01, and init_player_lives at the pinned commitThe table's bit values are the disassembly's controller layout: Up $08, Down $04, Left $02, Right $01, B $40, A $80. The lives byte counts reserve lives: a death with it at 0 is game over (contra/lives).
Cited by: Cheat code, Contra, Gradius
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. contra/lives
Value
{
"lives": 3,
"continues": 3
}src/bank7.asm#L1143-L1147init_score_player_lives:
…
lda #$03 ; a = #$03
sta NUM_CONTINUES ; continues remainingContra (US), annotated disassembly (vermiceli) · src/bank7.asm#L1165-L1166init_player_lives:
lda #$02 ; start off with #$02 livesContra (US), annotated disassembly (vermiceli) · src/bank7.asm#L5150-L5161init_player_dec_num_lives:
…
lda P1_NUM_LIVES,x ; load player number of lives
beq @set_game_over ; branch if no more lives
dec P1_NUM_LIVES,x ; decrement player number of lives
…
@set_game_over:
lda #$01 ; a = #$01
sta P1_GAME_OVER_STATUS,x ; game over state of player (1 = game over)Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L3289-L3307show_game_over_screen:
…
dec NUM_CONTINUES ; subtract from number of continues remaining
bmi @no_continues_remaining ; if run out of continues, jump
lda #$0e ; a = #$0e (continue end)Checked by claude, 2026-10-21 — read init_score_player_lives, init_player_lives, init_player_dec_num_lives, and show_game_over_screen at the pinned commitThe count starts at 3 and is decremented before the test, so game overs 1 to 3 offer CONTINUE and the fourth doesn't.
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. contra/extra-life
About: Contra Extra life
Value
{
"first": 20000,
"every": 30000,
"max_lives": 99
}src/bank7.asm#L1177-L1178lda #$c8 ; set default high score. #$c8 is 200 decimal
sta EXTRA_LIFE_SCORE_LOW ; starting score for extra life (20,000)Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L1283-L1294; 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)
…
lda #$2c ; a = #$2c (12c = 300 decimal) (high byte will be set to #$01 a few lines down)
…
adc EXTRA_LIFE_SCORE_LOW,y ; add 300 decimal to low byte of score required to get extra lifeContra (US), annotated disassembly (vermiceli) · src/bank7.asm#L1310-L1314inc P1_NUM_LIVES,x ; if X is $01, then set P2 number of lives
lda P1_NUM_LIVES,x ; load incremented number into memory
cmp #$63 ; compare to #$63 (99 decimal)
bcc @set_num_lives ; can't have more than 99 lives
lda #$63 ; a = #$63 (63 = 99 decimal)Checked by claude, 2026-10-21 — read the extra-life setup in init_player_num_lives and @extra_life_logic in add_player_score at the pinned commitScores are kept in hundreds, so $C8 (200) is 20,000 and $12C (300) is 30,000. The routine stops awarding once the threshold's high byte reaches $75 (L1288-L1290), past 2.9 million points.
Cited by: Contra, Extra life
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 ($90 on level 7), and the falcon destroys the enemies on screen. A death resets the gun to the default. contra/weapon-pickup
About: Contra Weapon pickup
CodeContra (US), annotated disassembly (vermiceli) ·src/bank7.asm#L6884-L6955pick_up_weapon_item:
…
lda #$0a ; a = #$0a
sta $00 ; set score to add to player as #$0a (1,000 points)
…
lda ENEMY_ATTRIBUTES,y ; get weapon item attributes
and #$07 ; keep bits 0-2 (attributes)
beq @set_rapid_flag ; set rapid flag for weapon if attribute is #$00
cmp #$05 ; check for b weapon (barrier). Gives invincibility
bcc @compare_and_set_weapon ; branch if less than #$05 (MFSL)
beq @set_invincibility_timer ; branch for barrier weapon
jsr destroy_all_enemies ; falcon weapon effect - destroy all enemies
…Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L5150-L5151init_player_dec_num_lives:
jsr init_player_and_weapon ; initialize player variables to 0Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L5338-L5340init_player_and_weapon:
lda #$00 ; a = #$00
sta P1_CURRENT_WEAPON,x ; reset current player's weaponChecked by claude, 2026-10-21 — read pick_up_weapon_item through remove_current_enemy, init_player_dec_num_lives, and init_player_and_weapon at the pinned commitItem types 1 to 4 are M, F, S, and L (the weapon byte's low nibble, ram.asm L727-L735). The disassembly notes that the barrier timer counts down every 8 frames, about 17 seconds at $80; the atlas hasn't read that decrement.
Cited by: Contra, Weapon pickup
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). contra/rank
Value
{
"per_loop": 40,
"max_loops": 3,
"per_strength": 5,
"strength": {
"default": 0,
"M": 2,
"F": 1,
"S": 3,
"L": 2
},
"level_1_start": 144
}src/bank2.asm#L1736-L1779adjust_generation_timer:
jsr get_soldier_gen_timer_for_lvl
sta SOLDIER_GENERATION_TIMER ; soldier generation timer
ldy GAME_COMPLETION_COUNT ; load the number of times the game has been completed
beq @adjust_timer_for_weapon ; branch if haven't yet beat the game any times
tya
cmp #$04 ; compare GAME_COMPLETION_COUNT to #$04
bcc @continue ; branch if GAME_COMPLETION_COUNT < #$04
ldy #$03 ; maximum multiplier of increase chance for generating soldier is #$03
…
lda #$28 ; lower soldier generation timer
…
…Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L3270inc GAME_COMPLETION_COUNT ; increment game completion count, used mainly to increase enemy difficulty every play-throughContra (US), annotated disassembly (vermiceli) · src/bank7.asm#L3920-L3926set_frame_scroll_weapon_strength:
…
sta PLAYER_WEAPON_STRENGTH ; clear player weapon strengthContra (US), annotated disassembly (vermiceli) · src/bank7.asm#L4096-L4116set_player_weapon_strength:
…
lda weapon_strength,y ; load how strong the weapon is
cmp PLAYER_WEAPON_STRENGTH ; compare against current weapon strength
bcc @exit ; exit and do not lower player's current weapon strength
sta PLAYER_WEAPON_STRENGTH ; store current weapon strength code (#$00-#$03)
…
weapon_strength:
.byte $00 ; Regular = Weak
.byte $02 ; M = Strong
.byte $01 ; F = Medium
.byte $03 ; S = Very Strong
…Checked by claude, 2026-10-21 — read adjust_generation_timer and its table, the loop counter's increment, and the per-frame clear and maximum of PLAYER_WEAPON_STRENGTH at the pinned commitEach frame clears the strength and then raises it to each living player's gun (run_player_invincibility_and_weapon_strength, L3940-L3958), so it tracks the stronger of the two. The same two numbers raise enemy hit points and fire rates elsewhere (vermiceli's docs/Game Completion Modifier.md lists them); this claim covers only the soldier timer.
A 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 $23 to it every airborne frame, 0.137 px/frame², without reading a button: the jump's height doesn't depend on holding A. contra/jump
Value: 1040 1/256 px/frame
CodeContra (US), annotated disassembly (vermiceli) ·src/bank7.asm#L4533-L4557set_jump_status_and_y_velocity:
…
lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor
lsr
lda #$fb ; a = #$fb (-5)
ldy #$f0 ; y = #$f0 (.94)
bcc @set_y_velocity ; branch if outdoor level (use Y velocity -5.94)
lda #$fc ; indoor level, (Y velocity -4.56), set fast velocity to #$fc (-4)
ldy #$90 ; set fractional Y velocity to #$90 (.56)
…
sta PLAYER_Y_FAST_VELOCITY,x ; set Y fast velocity to a
tya ; transfer fractional velocity to a
…The atlas reads this differently: The fraction byte is unsigned and carries into the whole byte, so $FB.$F0 is −5 + 240/256 = −4.0625 px/frame and $FC.$90 is −3.4375, not −5.94 and −4.56.Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L4708-L4757handle_jump:
lda PLAYER_Y_FAST_VELOCITY,x ; load player Y fast velocity
bmi @check_collision_above ; branch if player is still ascending
…
@apply_gravity:
jsr apply_gravity ; increments Y fractional velocity by #$23 (applying gravity)
…
@set_y_pos:
jsr player_jumping_set_y_pos ; set player Y position based on PLAYER_JUMP_COEFFICIENT and velocityContra (US), annotated disassembly (vermiceli) · src/bank7.asm#L5113-L5130player_jumping_set_y_pos:
…
lda PLAYER_JUMP_COEFFICIENT,x ; load player's jump modifier (alters height of jump)
clc ; clear carry in preparation for addition
adc PLAYER_Y_FRACT_VELOCITY,x ; add to player's fractional Y velocity
sta PLAYER_JUMP_COEFFICIENT,x ; update player's jump modifier (alters height of jump)
lda SPRITE_Y_POS,x ; load player's Y position
adc PLAYER_Y_FAST_VELOCITY,x ; add (subtract) the fast Y velocity (with jump coefficient overflow)Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L5139-L5147apply_gravity:
clc
lda PLAYER_Y_FRACT_VELOCITY,x ; load player's Y fractional velocity
adc #$23 ; add #$23 to Y fractional velocity (.1367 decimal)Checked by claude, 2026-10-21 — read set_jump_status_and_y_velocity, handle_jump (every path but landing reaches @apply_gravity, and none reads the controller's A bit), player_jumping_set_y_pos, and apply_gravity at the pinned commitThe position adds the fraction with carry and then the whole byte, so $FB.$F0 is −5 + 240/256 = −4.0625 px/frame and $FC.$90 is −3.4375. The disassembly's comments read them as −5.94 and −4.56. Screen Y grows downward, so negative is up. PLAYER_JUMP_COEFFICIENT is the position's fraction, not a modifier.
Cited by: Contra
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. contra/scroll
About: Contra Auto-scroll
CodeContra (US), annotated disassembly (vermiceli) ·src/bank7.asm#L5164-L5208set_frame_scroll_if_appropriate:
…
lda SPRITE_X_POS,x ; load player X position
cmp horizontal_scroll_point_tbl,y ; load horizontal scroll point position
bcc @exit ; if not yet reached point to initiate horizontal scroll, exit
cpy #$02 ; see if both players are active
bne @set_horizontal_level_frame_scroll ; if both players aren't active, begin setting scroll
…
lda SPRITE_X_POS,y ; load other player's X position
cmp #$21 ; compare other player to left edge of screen
bcc @stop_player_x_velocity ; don't scroll if other player is preventing it by being too far to the left
…
…Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L5228-L5229horizontal_scroll_point_tbl:
.byte $80,$80,$b0Contra (US), annotated disassembly (vermiceli) · src/bank7.asm#L6552-L6566set_boss_auto_scroll:
…
cmp LEVEL_SCREEN_NUMBER ; screen number
bne @exit ; exit if not on the appropriate screen to start auto scroll
…
lda auto_scroll_timer_tbl,y ; load the appropriate auto scroll timer for the level
sta AUTO_SCROLL_TIMER_00 ; start auto scroll to reveal the bossChecked by claude, 2026-10-21 — read set_frame_scroll_if_appropriate, horizontal_scroll_point_tbl, and set_boss_auto_scroll at the pinned commitThe table is indexed by PLAYER_GAME_OVER_BIT_FIELD, where 2 means both players are active. Vertical levels (the waterfall) use their own scroll point (L5231 on).
Cited by: Auto-scroll, Contra