The Lost Levels keeps Super Mario Bros.'s movement tables for Mario byte for byte. Take-off speeds, held and falling gravities, frictions, and top speeds all match. smb2j/mario-tables
About: Super Mario Bros.: The Lost Levels Jump
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L4955-L4974JumpMForceData:
.byte $20, $20, $1e, $28, $28, $0d, $04
…
FallMForceData:
.byte $70, $70, $60, $90, $90, $0a, $09
…
FrictionData:
.byte $e4, $98, $d0
…
PlayerYSpdData:
.byte $fc, $fc, $fc, $fb, $fb, $fe, $ff
…
…smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L4858-L4878JumpMForceData:
.db $20, $20, $1e, $28, $28, $0d, $04
…
FallMForceData:
.db $70, $70, $60, $90, $90, $0a, $09
…
PlayerYSpdData:
.db $fc, $fc, $fc, $fb, $fb, $fe, $ff
…
InitMForceData:
.db $00, $00, $00, $00, $00, $80, $00
…
…Checked by claude, 2026-10-21 — compared the seven tables byte by byte in both disassemblies; LoadMarioPhysics copies the same Mario bytes back in for each gameCited by: Super Mario Bros.: The Lost Levels
Picking Luigi copies his own gravities and frictions over Mario's: held gravity $18–$22 for the five jump rows against Mario's $1E–$28, falling gravity $3E–$5D against $60–$90, and friction $B4, $68, $A0 against $E4, $98, $D0. It also writes an RTS over the ASL that doubles Mario's friction when he faces away from his motion. His take-off speeds are Mario's. Each area's setup loads the chosen player's set; the axe at the end of a castle loads Mario's set and ASL back for the walk that follows, whoever is playing, until the next area loads the player's own again. smb2j/luigi-physics
About: Super Mario Bros.: The Lost Levels Jump
Value
{
"heldGravity": [
24,
24,
24,
34,
34
],
"fallGravity": [
66,
66,
62,
93,
93
],
"friction": [
180,
104,
160
]
} CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14477-L14488;jump, fall and friction physics data used with mario and luigi
;note luigi's higher jump, but with lesser friction
JumpFrictionData:
.byte $20, $20, $1e, $28, $28, $0d, $04
.byte $70, $70, $60, $90, $90, $0a, $09
.byte $e4, $98, $d0
.byte $18, $18, $18, $22, $22, $0d, $04
.byte $42, $42, $3e, $5d, $5d, $0a, $09
.byte $b4, $68, $a0SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14490-L14507LoadPhysicsData:
ldx #$60 ;use luigi's offsets and RTS opcode
ldy #$21
lda SelectedPlayer ;if selected luigi, branch to continue
bne ModifyPhysics
LoadMarioPhysics:
ldx #$0e ;otherwise use mario's offsets and ASL opcode
ldy #$10
ModifyPhysics:
stx PhyOpcode ;load opcode into friction code to modify it
ldx #$10
MPhyLoop:
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L5124-L5129lda PlayerFacingDir
cmp Player_MovingDir ;check facing direction against moving direction
beq ExitPhy ;if the same, branch to leave
PhyOpcode: asl FrictionAdderLow ;otherwise multiply friction by 2
rol FrictionAdderHigh ;then leave (or just leave, if code was modified earlier
ExitPhy: rtsSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L2605-L2611DoneInitArea: lda #Silence ;silence music
sta AreaMusicQueue
lda #$01 ;disable screen output
sta DisableScreenFlag
jsr LoadPhysicsData
inc OperMode_Task ;increment task for this mode
rtsSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L11158-L11165HandleAxeMetatile:
lda #$00
sta OperMode_Task ;reset secondary mode
lda #$02
sta OperMode ;set primary mode to victory mode
jsr LoadMarioPhysics
lda #$18
sta Player_X_Speed ;set horizontal speed and continue to erase axe metatileChecked by claude, 2026-10-26 — read LoadPhysicsData; the loop copies 17 bytes (X = $10 down to 0) ending at JumpMForceData+$10, which spans JumpMForceData, FallMForceData, and FrictionData but not PlayerYSpdData; $60 is the RTS opcode and $0E is ASL absolute. InitializeArea calls LoadPhysicsData for every area, and HandleAxeMetatile calls LoadMarioPhysics (Y = $10, Mario's rows) on the axeCited by: Super Mario Bros. 2, Super Mario Bros.: The Lost Levels
ProcJumping starts a jump only while Player_State is 0, on the ground, or while the player is swimming, as Super Mario Bros.'s does. Walking off a ledge doesn't keep that state: PlayerBGCollision sets a player in the normal or climbing state to falling (or, swimming, to state 1) on each frame of play before it looks for ground, and only landing on a block sets the state back to 0, so out of water a jump can't start after walking off a ledge. The background check skips that step only during the game's scripted routines (GameEngineSubroutine 0 to 3 and $0B). smb2j/no-coyote-time
About: Super Mario Bros.: The Lost Levels Coyote time
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L5015-L5019ProcJumping:
lda Player_State ;check player state
beq InitJS ;if on the ground, branch
lda SwimmingFlag ;if swimming flag not set, jump to do something else
beq NoJump ;to prevent midair jumping, otherwise continueSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L10936-L10949lda GameEngineSubroutine
cmp #$0b ;if running routine #11 or $0b
beq ExPBGCol ;branch to leave
cmp #$04
bcc ExPBGCol ;if running routines $00-$03 branch to leave
lda #$01 ;load default player state for swimming
ldy SwimmingFlag ;if swimming flag set,
bne SetPSte ;branch ahead to set default state
lda Player_State ;if player in normal state,
beq SetFallS ;branch to set default state for falling
cmp #$03
bne ChkOnScr ;if in any other state besides climbing, skip to next part
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L11046-L11056LandPlyr: jsr ChkForLandJumpSpring ;do sub to check for jumpspring metatiles and deal with it
lda #$f0
and Player_Y_Position ;mask out lower nybble of player's vertical position
sta Player_Y_Position ;and store as new vertical position to land player properly
jsr HandlePipeEntry ;do sub to process potential pipe entry
lda #$00
sta Player_Y_Speed ;initialize vertical speed and fractional
sta Player_Y_MoveForce ;movement force to stop player's vertical movement
sta StompChainCounter ;initialize enemy stomp counter
InitSteP: lda #$00
sta Player_State ;set player's state to normalsmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L4921-L4925ProcJumping:
LDA Player_State ; check player state
BEQ InitJS ; if on the ground, branch
LDA SwimmingFlag ; if swimming flag not set, jump to do something else
BEQ NoJump ; to prevent midair jumping, otherwise continueChecked by claude, 2026-10-26 — read ProcJumping in both disassemblies, PlayerBGCollision's state set and LandPlyr's reset at the pinned commitsCited by: Coyote time, Super Mario Bros.: The Lost Levels
A hit on big Mario sets InjuryTimer to 8, as in Super Mario Bros., and the timer counts down once every 21 frames with the interval timers, through the same timer loop as Super Mario Bros.'s. smb2j/injury-timer
About: Super Mario Bros.: The Lost Levels Invincibility frames
Value
{
"ticks": 8,
"framesPerTick": 21
} CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L10424-L10433lda InjuryTimer ;check again to see if either of the two
ora StarInvincibleTimer ;invincibility timers have expired, branch if not
bne ExInjColRoutines
ForceInjury:
ldx PlayerStatus ;check player's status
beq KillPlayer ;branch if small
sta PlayerStatus ;otherwise set player's status to small
lda #$08
sta InjuryTimer ;set injured invincibility timerSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L928-L933ldx #$14 ;set offset to decrement only frame timers
dec IntervalTimerControl ;if interval timer control not expired, branch
bpl DecrTheTimers ;to skip and thus decrement only frame timers
lda #$14
sta IntervalTimerControl ;otherwise reset interval timer control to 20 frames
ldx #$23 ;and load offset to decrement frame and interval timersSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L81-L99Timers = $0780
…
InjuryTimer = $079esmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L10583-L10592InjurePlayer:
LDA InjuryTimer ; check again to see if injured invincibility timer is
BNE ExInjColRoutines ; at zero, and branch to leave if so
ForceInjury:
LDX PlayerStatus ; check player's status
BEQ KillPlayer ; branch if small
STA PlayerStatus ; otherwise set player's status to small
LDA #$08
STA InjuryTimer ; set injured invincibility timersmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L126-L139DecTimers:
LDX #$14 ; load end offset for end of frame timers
DEC IntervalTimerControl ; decrement interval timer control,
BPL DecTimersLoop ; if not expired, only frame timers will decrement
LDA #$14
STA IntervalTimerControl ; if control for interval timers expired,
LDX #$23 ; interval timers will decrement along with frame timers
DecTimersLoop:
LDA Timers,x ; check current timer
BEQ SkipExpTimer ; if current timer expired, branch to skip,
DEC Timers,x ; otherwise decrement the current timer
SkipExpTimer:
…Checked by claude, 2026-10-21 — read ForceInjury and the timer loop; InjuryTimer is Timers+$1E, an interval timer as in Super Mario Bros. (the comment's "20 frames" is the reload value; the period is 21)Cited by: Super Mario Bros.: The Lost Levels
Wind blows the player to the right. An area object turns it on (WindOn) and another off (WindOff), and the setup that runs at the start of every area clears it. While it is on, and only in ground-type areas (underground, water, and castle areas stay calm), it moves the player 1 px right on every fourth frame while the frame counter is below 128 and on every second frame from 128 to 255: 96 px every 256 frames, 0.375 px a frame on average. The push is added to the position after the player's own move, on the ground and in the air but not while climbing, so it adds to a rightward speed and takes from a leftward one whatever the speed, and the screen scrolls with it. Its leaves and sound play while it is on, in every type of area. None of it runs until the game has loaded a disk file beyond worlds 1 to 4 (worlds 5 to 8, the ending, or worlds A to D): before that, FileListNumber is 0 and both calls are skipped. smb2j/wind
About: Super Mario Bros.: The Lost Levels Wind
Value
{
"push": 1,
"everyFrames": [
4,
2
],
"per256Frames": 96,
"average": 0.375
} CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2data2.asm#L179-L202BlowPlayerAround:
lda WindFlag ;if wind is turned off, just exit
beq ExBlow
lda AreaType ;don't blow the player around unless
cmp #$01 ;the area is ground type
bne ExBlow
ldy #$01
lda FrameCounter ;branch to set d0 if on an odd frame
asl
bcs BThr ;otherwise wind will only blow
ldy #$03 ;one out of every four frames
BThr: sty $00
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2data4.asm#L322-L345BlowPlayerAround:
lda WindFlag ;if wind is turned off, just exit
beq ExBlow
lda AreaType ;don't blow the player around unless
cmp #$01 ;the area is ground type
bne ExBlow
ldy #$01
lda FrameCounter
asl
bcs BThr
ldy #$03
BThr: sty $00
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L4848-L4853JmpMove: jsr MovePlayerHorizontally ;do another sub to move player horizontally
sta Player_X_Scroll ;set returned value as player's movement speed for scroll
lda FileListNumber ;if in worlds 1-4 don't bother checking for wind
beq ExOGSS
jsr BlowPlayerAround
ExOGSS: rtsSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L4892-L4892JSMove: jsr JmpMoveSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L4830-L4835MoveSubs: jsr JumpEngine
.word OnGroundStateSub
.word JumpSwimSub
.word FallingSub
.word ClimbingSubSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L4273-L4275lda FileListNumber
beq NoWind ;if in worlds 1-4, skip ahead
jsr SimulateWind ;otherwise, simulate wind where neededSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2data2.asm#L217-L221SimulateWind:
lda WindFlag ;if no wind, branch to leave
beq ExSimW
lda #$04 ;play wind sfx
sta NoiseSoundQueueSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2data2.asm#L264-L270WindOn:
lda #$01 ;branch to turn the wind on
bne WOn
WindOff:
lda #$00 ;turn the wind off
WOn: sta WindFlag
rtsSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L4236-L4239.word InitializeArea
.word ScreenRoutines
.word SecondaryGameSetup
.word GameCoreRoutineSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L2615-L2618SecondaryGameSetup:
lda #$00
sta DisableScreenFlag ;reenable screen, reset some flags
sta WindFlagChecked by claude, 2026-10-26 — read BlowPlayerAround in both overlay files, the two callers (JmpMove, reached from OnGroundStateSub and from JumpSwimSub and FallingSub through LRAir; ClimbingSub never calls it) and the game core's SimulateWind call, at the pinned commit; AreaType 1 is ground, as in Super Mario Bros.ASL of FrameCounter puts its bit 7 in the carry, so the mask is 1 (every second frame) while the counter is 128-255 and 3 (every fourth) while it is 0-127; the disassembly's comment ("set d0 if on an odd frame") misreads it. SM2DATA2 (worlds 5 to 8) and SM2DATA4 (worlds A to D) each carry the routine (the ROM map's regions blow-player-around and blow-player-around-2). Which areas place WindOn is level data, not read here.
Cited by: Super Mario Bros.: The Lost Levels
A poison mushroom comes out of a block of its own kind, and stays a poison mushroom whatever the player's size, where an ordinary mushroom block gives a fire flower to a big or fiery player. It slides along like a mushroom. Touching it removes it and runs the injury routine: a small player dies, and a big or fiery one drops straight to small, with the blink that follows any hit. During that blink, or while a star's invincibility lasts, it is removed and does nothing. smb2j/poison-mushroom
About: Super Mario Bros.: The Lost Levels Power-up
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L6308-L6339BlockCode: jsr JumpEngine ;run appropriate subroutine depending on block number
.word MushFlowerBlock
.word PoisonMushBlock
.word CoinBlock
.word CoinBlock
.word ExtraLifeMushBlock
.word PoisonMushBlock
.word MushFlowerBlock
.word MushFlowerBlock
.word PoisonMushBlock
.word VineBlock
.word StarBlock
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L6123-L6130lda PowerUpType
cmp #$02 ;check currently loaded power-up type
bcs PutBehind ;if star or 1-up, branch ahead
lda PlayerStatus ;otherwise check player's current status
cmp #$02
bcc StrType ;if player not fiery, use status as power-up type
lsr ;otherwise shift right to force fire flower type
StrType: sta PowerUpType ;store type hereSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L6148-L6155lda PowerUpType ;check power-up type
beq ShroomM ;if normal mushroom, branch ahead to move it
cmp #$03
beq ShroomM ;if 1-up mushroom, branch ahead to move it
cmp #$04
beq ShroomM
cmp #$05
beq ShroomMSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L6161-L6161ShroomM: jsr MoveNormalEnemy ;do sub to make mushrooms moveSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L10253-L10258HandlePowerUpCollision:
jsr EraseEnemyObject ;erase the power-up object
lda PowerUpType
cmp #$04 ;check power-up type
bne Safe ;if not a poison shroom, branch
jmp InjurePlayer ;otherwise injure the player properlySMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L10423-L10436InjurePlayer:
lda InjuryTimer ;check again to see if either of the two
ora StarInvincibleTimer ;invincibility timers have expired, branch if not
bne ExInjColRoutines
ForceInjury:
ldx PlayerStatus ;check player's status
beq KillPlayer ;branch if small
sta PlayerStatus ;otherwise set player's status to small
lda #$08
sta InjuryTimer ;set injured invincibility timer
asl
sta Square1SoundQueue ;play pipedown/injury sound
…Checked by claude, 2026-10-26 — read BumpBlock's jump table, SetupPowerUp, PowerUpObjHandler, HandlePowerUpCollision, and InjurePlayer through ForceInjury at the pinned commitPower-up type 4. ForceInjury stores A, which is 0 after the timer check falls through, as the player's status, so fiery goes to small in one step. The metatile tables label poison-mushroom versions of the ? block, the hidden block, and two bricks (sm2main.asm lines 2149, 2155, 2162, 2197); which blocks hold one is level data. Super Mario Bros.'s InjurePlayer checks only the blink timer (super-mario-code-lineage/smb1-smb2j-collisions).
Cited by: Super Mario Bros.: The Lost Levels
The poison mushroom is drawn from the same four tiles as the mushroom and the 1-up mushroom ($D8, $DA, $DB, $FF) in sprite palette 3, where the mushroom uses palette 2 and the 1-up palette 1. Like them it keeps its colors, where the fire flower and the star cycle theirs. smb2j/poison-mushroom-look
About: Super Mario Bros.: The Lost Levels Power-up
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L12527-L12535PowerUpGfxTable:
.byte $d8, $da, $db, $ff ;regular mushroom
.byte $d6, $d6, $d9, $d9 ;fire flower
.byte $8d, $8d, $e4, $e4 ;star
.byte $d8, $da, $db, $ff ;1-up mushroom
.byte $d8, $da, $db, $ff ;poison mushroom
PowerUpAttributes:
.byte $02, $01, $02, $01, $03SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L12565-L12571ldy Enemy_SprDataOffset+5 ;get sprite data offset again
pla ;pull saved power-up type from the stack
beq PUpOfs ;if regular mushroom, 1-up mushroom
cmp #$03 ;or poison mushroom, branch
beq PUpOfs ;do not change colors or flip them
cmp #$04
beq PUpOfsChecked by claude, 2026-10-26 — read PowerUpGfxTable, PowerUpAttributes, and DrawPowerUp's color-cycling skip at the pinned commitWhich colors palette 3 holds in each kind of area isn't read here.
Cited by: Super Mario Bros.: The Lost Levels
Every area from World 4-4 on, and every area of worlds A to D, starts with the secondary hard mode flag set; the areas of worlds 1 to 3 and 4-1 to 4-3 start without it. The memory clear at the start of each area resets it first. smb2j/hard-mode-from-4-4
About: Super Mario Bros.: The Lost Levels Difficulty ramp
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L2557-L2559InitializeArea:
ldy #$4b ;clear all memory again, only as far as $074b
jsr InitializeMemory ;this is only necessary in game modeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L2591-L2600lda HardWorldFlag ;check to see if we're in worlds A-D
bne SetSecHard ;if so, activate the secondary no matter where we're at
lda WorldNumber ;otherwise check world number
cmp #World4 ;if less than 4, do not activate secondary
bcc CheckHalfway
bne SetSecHard ;if not equal to, then world > 4, thus activate
lda LevelNumber ;otherwise, world 4, so check level number
cmp #Level4 ;if not 4, do not set secondary hard mode flag
bcc CheckHalfway
SetSecHard: inc SecondaryHardMode ;set secondary hard mode flag for areas 4-4 and beyondChecked by claude, 2026-10-26 — read InitializeArea at the pinned commitSecondaryHardMode is $06CC, under the $074B limit of the clear. World numbers 4 and up (worlds 5 to 9) branch straight to the set.
Cited by: Super Mario Bros.: The Lost Levels
With the secondary hard mode flag set: enemies the level data marks for hard mode appear, where otherwise they are skipped; a Bullet Bill cannon is picked to fire when a pseudorandom number masked to three bits is below 6 (6 of 8 values, against 6 of 16 with four bits); up to four flying Cheep Cheeps at once instead of three; hammer bros throw on a shorter timer ($1C against $30), can take the longer of two jump timers ($37 against $20), and in worlds 1 to 6 wait a shorter time before walking left ($50 against $80; in worlds 7 to 9 they don't wait either way, and worlds A to D, which share world numbers 1 to 4 and always have the flag, take $50); Bloopers pick a new direction when two pseudorandom bits are clear rather than six, and then a Blooper in an even enemy slot turns toward the player, one in an odd slot takes the player's moving direction; Bowser's fire breath and the flames that fly in on their own come 16 timer ticks sooner, and the flames move faster ($60 subtracted from their fractional position each frame, against $40); and the large lifts are drawn four sprites wide instead of six with a smaller box, as they always are in castles, with balance platforms and their ropes placed for the shorter width. smb2j/hard-mode-effects
About: Super Mario Bros.: The Lost Levels Difficulty ramp
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L6968-L6973iny
lda (EnemyData),y ;get second byte of object
and #%01000000 ;check to see if hard mode bit is set
beq CheckForEnemyGroup ;if not, branch to check for group enemy objects
lda SecondaryHardMode ;if set, check to see if secondary hard mode flag
beq Inc2B ;is on, and if not, branch to skip this object completelySMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L5753-L5754CannonBitmasks:
.byte %00001111, %00000111SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L5763-L5767lda PseudoRandomBitReg+1,x ;otherwise get part of LSFR
ldy SecondaryHardMode ;get secondary hard mode flag, use as offset
and CannonBitmasks,y ;mask out bits of LSFR as decided by flag
cmp #$06 ;check to see if lower nybble is above certain value
bcs Chk_BB ;if so, branch to check enemySMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7408-L7414ldy #$03 ;load Y with default value
lda SecondaryHardMode
beq MaxCC ;if secondary hard mode flag not set, do not increment Y
iny ;otherwise, increment Y to allow as many as four onscreen
MaxCC: sty $00 ;store whatever pseudorandom bits are in Y
cpx $00 ;compare enemy object buffer offset with Y
bcs ChpChpEx ;if X => Y, branch to leaveSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L8207-L8208HammerThrowTmrData:
.byte $30, $1cSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L8227-L8231lda HammerThrowingTimer,x ;check hammer throwing timer
bne DecHT ;if not expired, skip ahead, do not throw hammer
ldy SecondaryHardMode ;otherwise get secondary hard mode flag
lda HammerThrowTmrData,y ;get timer data using flag as offset
sta HammerThrowingTimer,x ;set as new timerSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L8241-L8242HammerBroJumpLData:
.byte $20, $37SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L8267-L8273lda $00 ;load preset value here to use as bitmask
and PseudoRandomBitReg+2,x ;and do bit-wise comparison with part of LSFR
tay ;then use as offset
lda SecondaryHardMode ;check secondary hard mode flag
bne HJump
tay ;if secondary hard mode flag clear, set offset to 0
HJump: lda HammerBroJumpLData,y ;get jump length timer data using offset from beforeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7166-L7167HBroWalkingTimerData:
.byte $80, $50SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7173-L7178lda WorldNumber ;if on worlds 7-9, branch to skip the walk delay
cmp #World7
bcs NoHBI
ldy SecondaryHardMode ;get secondary hard mode flag
lda HBroWalkingTimerData,y
sta EnemyIntervalTimer,x ;set value as delay for hammer bro to walk leftSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L8469-L8470BlooberBitmasks:
.byte %00111111, %00000011SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L8476-L8488ldy SecondaryHardMode ;use secondary hard mode flag as offset
lda PseudoRandomBitReg+1,x ;get LSFR
and BlooberBitmasks,y ;mask out bits in LSFR using bitmask loaded with offset
bne BlooberSwim ;if any bits set, skip ahead to make swim
txa
lsr ;check to see if on second or fourth slot (1 or 3)
bcc FBLeft ;if not, branch to figure out moving direction
ldy Player_MovingDir ;otherwise, load player's moving direction and
bcs SBMDir ;do an unconditional branch to set
FBLeft: ldy #$02 ;set left moving direction by default
jsr PlayerEnemyDiff ;get horizontal difference between player and bloober
bpl SBMDir ;if enemy to the right of player, keep left
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L9257-L9262jsr SetFlameTimer ;get timing for bowser's flame
ldy SecondaryHardMode
beq SetFBTmr ;if secondary hard mode flag not set, skip this
sec
sbc #$10 ;otherwise subtract from value in A
SetFBTmr: sta BowserFireBreathTimer ;set value as timer hereSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7554-L7561jsr SetFlameTimer ;get timer data based on flame counter
clc
adc #$20 ;add 32 frames by default
ldy SecondaryHardMode
beq SetFrT ;if secondary mode flag not set, use as timer setting
sec
sbc #$10 ;otherwise subtract 16 frames for secondary hard mode
SetFrT: sta FrenzyEnemyTimer ;set timer accordinglySMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L9331-L9335lda #$40 ;load default movement force
ldy SecondaryHardMode
beq SFlmX ;if secondary hard mode flag not set, use default
lda #$60 ;otherwise load alternate movement force to go faster
SFlmX: sta $00 ;store value hereSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L12395-L12402ldy AreaType
cpy #$03 ;check for castle-type level
beq ShrinkPlatform
ldy SecondaryHardMode ;check for secondary hard mode flag set
beq SetLast2Platform ;branch if not set elsewhere
ShrinkPlatform:
lda #$f8 ;load offscreen coordinate if flag set or castle-type levelSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7935-L7942SPBBox: lda #$05 ;set default bounding box size control
ldy AreaType
cpy #$03 ;check for castle-type level
beq CasPBB ;use default value if found
ldy SecondaryHardMode ;otherwise check for secondary hard mode flag
bne CasPBB ;if set, use default value
lda #$06 ;use alternate value if not castle or secondary not set
CasPBB: sta Enemy_BoundBoxCtrl,x ;set bounding box size control here and leaveSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7885-L7888ldy SecondaryHardMode ;if secondary hard mode flag not set,
bne AlignP ;branch ahead
ldy #$02 ;otherwise set value here
jsr PosPlatform ;do a sub to add or subtract pixelsSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7985-L7986PlatPosDataLow:
.byte $08,$0c,$f8SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L9813-L9821SetupPlatformRope:
pha ;save second/third copy to stack
lda Enemy_X_Position,y ;get horizontal coordinate
clc
adc #$08 ;add eight pixels
ldx SecondaryHardMode ;if secondary hard mode flag set,
bne GetLRp ;use coordinate as-is
clc
adc #$10 ;otherwise add sixteen more pixelsChecked by claude, 2026-10-26 — read every reader of SecondaryHardMode at the pinned commit (14 sites, grep for the label in all four source files) and the tables they indexThe hammer bro's jump-timer pick is the pseudorandom bits masked with $00, which the code before it sets to 1 only while the hammer bro is above the middle of the screen (Y below $70), so the longer timer comes only up there. Without the flag, balance platforms move 8 px left (PlatPosDataLow's $F8) and their rope 16 px further right. Which enemies carry the hard-mode bit is level data, not read here.
Cited by: Super Mario Bros.: The Lost Levels
The code keeps the primary hard mode flag's checks, which turn Goombas into Buzzy Beetles, start walking enemies faster ($F4 against $F8), and revive stomped enemies sooner and send them off faster. Nothing in The Lost Levels' code sets the flag, and starting a game clears it, so none of these takes effect. Super Mario Bros. sets it from its world select flag when a game starts. smb2j/primary-hard-mode-unset
About: Super Mario Bros.: The Lost Levels Difficulty ramp
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L6983-L6989BuzzyBeetleMutate:
cmp #Goomba ;if below $37, check for goomba
bne StrID ;value ($3f or more always fails)
ldy PrimaryHardMode ;check if primary hard mode flag is set
beq StrID ;and if so, change goomba to buzzy beetle
lda #BuzzyBeetle
StrID: sta Enemy_ID,x ;store enemy object number into bufferSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7751-L7756pha ;save another copy to stack
ldy #Goomba ;load value for goomba enemy
lda PrimaryHardMode ;if primary hard mode flag not set,
beq PullID ;branch, otherwise change to value
ldy #BuzzyBeetle ;for buzzy beetle
PullID: pla ;get second copy from stackSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7148-L7157NormalXSpdData:
.byte $f8, $f4
InitNormalEnemy:
ldy #$01 ;load offset of 1 by default
lda PrimaryHardMode ;check for primary hard mode flag set
bne GetESpd
dey ;if not set, decrement offset
GetESpd: lda NormalXSpdData,y ;get appropriate horizontal speed
SetESpd: sta Enemy_X_Speed,x ;store as speed for enemy objectSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L8213-L8214RevivedXSpeed:
.byte $08, $f8, $0c, $f4SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L8351-L8356lda PrimaryHardMode ;check primary hard mode flag
beq SetRSpd ;if not set, use pointer as-is
iny
iny ;otherwise increment 2 bytes to next data
SetRSpd: lda RevivedXSpeed,y ;load and store new horizontal speed
sta Enemy_X_Speed,x ;and leaveSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L10518-L10519RevivalRateData:
.byte $10, $0bSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L10530-L10532ldy PrimaryHardMode ;check primary hard mode flag
lda RevivalRateData,y ;load timer setting according to flag
sta EnemyIntervalTimer,x ;set as enemy timer to revive stomped enemySMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14848-L14849ldy #$6f ;clear all memory as in initialization procedure,
jsr InitializeMemory ;but this time, clear only as far as $076fsmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L415-L416LDA WorldSelectEnableFlag ; if world select flag is on, then primary
STA PrimaryHardMode ; hard mode must be on as wellChecked by claude, 2026-10-26 — grep for PrimaryHardMode and $076a in every source file at the pinned commit (five reads, no writes); read InitializeGame and InitializeMemory; read Super Mario Bros.'s StartWorld1 at xkeeper-smb1's pinPrimaryHardMode is $076A. A search of sm2main.asm, sm2data2-4.asm and fdswrap.asm finds it only as a source (LDA, LDY) and no store to it by name or by its address; InitializeGame's InitializeMemory call clears $0000-$076F (the stack's $0100-$0108 and $0160-$01FF spared), and the cold boot clears more.
Cited by: Super Mario Bros.: The Lost Levels
A warp zone leads to one world, whichever of its pipes the player takes. In world 1 it leads to world 2 from the first underground area, to world 3 from a ground area, and to world 4 from any other; in world 3, back to world 1; in world 5, to world 6 from one ground area (the 12th in the area table, which the disassembly's comment places at 5-1), to world 8 from any other ground area, and to world 7 from any other area; in worlds 2, 4, and 6 to 9, to world 5, which from 6 to 8 is back. In worlds A to D it goes by level: from a second level to world B, a third to world C, a fourth to world D; the table holds no world for a first level. smb2j/warp-destinations
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L3425-L3468ScrollLockObject_Warp:
ldx #$80 ;use base number for warp to world 2
lda HardWorldFlag ;if on worlds A-D, skip ahead to next part
bne WarpWorldsAThruD ;note d7 is set in all entries to prevent zero condition
lda WorldNumber ;from happening in warp zone code elsewhere
bne WarpWorlds2Thru8 ;if not on world 1, branch to handle a different way
ldy AreaType ;check to see if on ground level type
dey ;branch if so to add one to the number
beq W1Warp2
lda AreaAddrsLOffset ;if on first underground level, branch to use base number
beq W1Warp1
inx ;otherwise add two to the number and use it
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L1689-L1690WarpZoneNumbers:
.byte $02, $03, $04, $01, $06, $07, $08, $05, $0b, $0c, $0dSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L11332-L11343lda WarpZoneControl ;check warp zone control
beq ExPipeE ;branch to leave if none found
and #%00001111 ;mask out all but lower nybble
tax ;save as offset, then use to load warp zone destination
lda WarpZoneNumbers,x
ldy HardWorldFlag ;if playing worlds A-D, branch to skip this part
beq SetWDest
sec
sbc #$09 ;otherwise subtract 9 to get correct world number
SetWDest: tay
dey ;decrement for use as world number
sty WorldNumber ;store as world number and offsetChecked by claude, 2026-10-26 — read ScrollLockObject_Warp's branches entry by entry against WarpZoneNumbers and HandlePipeEntry at the pinned commitThe low nybble of WarpZoneControl indexes WarpZoneNumbers ($02, $03, $04, $01, $06, $07, $08, $05, $0B, $0C, $0D); in worlds A to D the entry less 9 is the letter world's number, so $87 plus level 0 gives $05 - 9, no world. Which areas hold warp zones is level data, not read here.
Cited by: Super Mario Bros.: The Lost Levels
Clearing a world's castle sets that world's bit in a record of completed worlds. After the ending that follows 8-4, the game goes on to World 9 if all eight bits are set, and back to the title screen if any is missing; after D-4 it always goes back to the title. The record is cleared when a game starts and when the player picks Retry at a game over, and Continue keeps it, so a world skipped by a warp counts only if the player warps back and clears its castle. smb2j/world-9-condition
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L1185-L1195WorldBits:
.byte $01, $02, $04, $08, $10, $20, $40, $80
SetupVictoryMode:
ldx ScreenRight_PageLoc ;get page location of right side of screen
inx ;increment to next page
stx DestinationPageLoc
ldy WorldNumber
lda WorldBits,y
ora CompletedWorlds ;set bit according to the world the player was in
sta CompletedWorldsSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2data3.asm#L428-L451BackToNormal:
lda #>SoundEngine ;reset sound engine vector
sta SoundEngineJSRCode+2 ;to run the original one
lda #<SoundEngine
sta SoundEngineJSRCode+1
lda #$00
sta DiskIOTask ;erase task numbers
sta OperMode_Task
lda HardWorldFlag ;if in world D, branch to end the game
bne EndTheGame
lda CompletedWorlds ;if completed all worlds without skipping over any
cmp #$ff ;then branch elsewhere (note warping backwards may
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14692-L14698lda SavedJoypadBits ;check to see if the player pressed start
and #Start_Button
beq ChkSelect ;if not, branch to check other buttons
lda #$00
sta CompletedWorlds
sta DiskIOTask
sta HardWorldFlagSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14455-L14460ContinueOrRetry:
lda ContinueMenuSelect ;if player selected "continue"
beq Continue ;then branch to continue
lda #$00
sta CompletedWorlds ;otherwise init completed worlds flags
jmp TerminateGame ;and end the gameSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14462-L14473Continue:
ldy #$02
sty NumberofLives ;give three lives
sta LevelNumber
sta AreaNumber ;put at x-1 of the current world
sta CoinTally
ldy #$0b
ISCont: sta ScoreAndCoinDisplay,y ;reset score
dey
bpl ISCont
inc Hidden1UpFlag ;allow 1-up to be found again
jmp ContinueGameChecked by claude, 2026-10-26 — read SetupVictoryMode's bit set, BackToNormal's three ways out, and the clears in GameMenuRoutine and ContinueOrRetry (Continue leaves CompletedWorlds alone) at the pinned commitCited by: Super Mario Bros.: The Lost Levels
Each time the ending loads, after 8-4 or D-4, the count of games beaten goes up by one, to at most 24 (a short read of the ending's files resets it to 0 first). After 8-4 the game writes the count to the disk's save file (a disk error aside); after D-4 it doesn't, and the title screen, which reloads the disk's first files after a game in worlds A to D or from world 5 on, reads the saved count back, so D-4 adds nothing. The title screen shows a star for each game beaten, in two rows of 12. With 8 or more, holding A while pressing Start begins the game at World A, loading worlds A to D, with their own halfway points, from the disk. smb2j/letter-worlds
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14261-L14280LoadEnding:
lda #$02 ;set filelist number to load SM2DATA3, SM2CHAR2 and SM2SAVE
sta FileListNumber
jsr LoadFiles ;load them now
bne ReturnedError
jsr CheckFileCount ;check to see if loaded the correct number of files
beq AddBeatenGame ;if so, continue
lda #$00 ;otherwise reset count of games beaten
sta GamesBeatenCount
AddBeatenGame:
lda GamesBeatenCount ;get the new count of games beaten we loaded (or reset)
clc ;add one to it, to a maximum of 24/$18
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2data3.asm#L391-L426RunMushroomRetainers:
jsr MushroomRetainersForW8 ;draw and flash the seven mushroom retainers
lda AltMusicBuffer ;if still playing victory music, branch to leave
bne ExRMR
lda HardWorldFlag ;if on world D, branch elsewhere
bne BackToNormal
inc OperMode_Task ;otherwise just move onto the last task
ExRMR: rts
EndingDiskRoutines:
lda DiskIOTask
jsr JumpEngine
.word DiskScreen
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14180-L14193LoadWorlds1Thru4:
lda NotColdFlag ;if not set, just cold booted, thus no need to check world info
beq InitWorldPos
lda HardWorldFlag ;if player was playing worlds A-D, skip ahead
bne LW14Files ;otherwise check the world number
lda WorldNumber
cmp #World5 ;if world number was less than 5, files still in memory
bcc InitWorldPos ;thus skip to the end, no need to load them again
LW14Files: lda #$00 ;set filelist number to reload SM2MAIN, SM2CHAR1 and SM2SAVE
sta FileListNumber
jsr LoadFiles ;now reload those files
bne ReturnedError ;if returned with error number, branch
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14301-L14308World14List:
.byte $01, $05, $0f, $ff
World58List:
.byte $20, $ff
EndingList:
.byte $10, $30, $0f, $ff
WorldADList:
.byte $40, $ffSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14832-L14847ldy #$33 ;set up offset in the title screen tiles
lda #$0c ;set up counter to print up to 12 stars per row
sta $00
ldx #$00 ;init star counter
PrintStars: lda #$26 ;print blank by default
cpx GamesBeatenCount ;check star counter against games beaten
bcs PrintToTS ;if counted up to games beaten, print the blank
lda #$f1 ;otherwise print a star for a beaten game
PrintToTS: sta TitleScreenGfxData,y ;print to title screen
iny
dec $00 ;decrement until done printing a row
bne NextStarR
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14692-L14706lda SavedJoypadBits ;check to see if the player pressed start
and #Start_Button
beq ChkSelect ;if not, branch to check other buttons
lda #$00
sta CompletedWorlds
sta DiskIOTask
sta HardWorldFlag
lda GamesBeatenCount ;check to see if player has beaten
cmp #$08 ;the game at least 8 times
bcc StG ;if not, start the game as usual at world 1
lda SavedJoypadBits
and #A_Button ;check if the player pressed A + start
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14147-L14160LoadHardWorlds:
lda HardWorldFlag ;if this is not set, skip this
beq NoLoadHW
lda #$03
sta FileListNumber ;set filelist number to load SM2DATA4
jsr LoadFiles
bne ReturnedError ;if returned with error number, branch
jsr CheckFileCount ;check to see if loaded the correct number of files
bne BadFileCount ;if not, branch to set error code
NoLoadHW:
jsr LoadAreaPointer ;update area pointer to get world 1 or A
lda HardWorldFlag ;if this is not set, skip this
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L1196-L1202lda HardWorldFlag ;if not playing worlds A-D, branch to skip this
beq W1Thru8
lda WorldNumber ;otherwise, if not on world D, branch to skip this
cmp #World4 ;(note worlds A-D use values 0-3 in this variable)
bcc W1Thru8
lda #World8 ;if on world D, set world number to 8 to satisfy
sta WorldNumber ;end of game condition in later victory mode subsChecked by claude, 2026-10-26 — read LoadEnding, the ending's task lists, the title's star loop and Start handling, and the two disk loaders at the pinned commitThe count is GamesBeatenCount, the byte that the save file SM2SAVE loads over at $D29F (sm2main.asm line 15279: "this is overwritten by the contents of SM2SAVE"). D-4 reaches the ending because SetupVictoryMode sets the world number to 8 on world D. After D-4, RunMushroomRetainers skips the disk task list that writes the save, and LoadWorlds1Thru4, with HardWorldFlag still set, reloads file list 0 (SM2MAIN, SM2CHAR1, SM2SAVE). A disk error is handled by DiskErrorHandler, not read here.
Cited by: Super Mario Bros.: The Lost Levels
Landing on a springboard sets both the held and the falling gravity to $70 and a launch speed of −7 px a frame ($F9). Over the spring's four steps it carries the player down 2 px a step, then up 2 px a step, and on the last step it launches them. A press of A, not held from before, in the second step or later raises the launch to −12 px a frame ($F4), or to −32 px a frame ($E0) on the green springboards of world numbers 2, 3, and 7, which the letter worlds share as B and C, since the test reads the world number without the letter-world flag. With both gravities at $70, the bounce rises and falls under one pull whether A is held or not, for Mario and Luigi alike. While the game's timers are stopped the spring waits. smb2j/springboard
About: Super Mario Bros.: The Lost Levels Springboard
Value
{
"gravity": 112,
"launch": -7,
"pressed": -12,
"green": -32
} CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L11291-L11303ChkForLandJumpSpring:
jsr ChkJumpspringMetatiles ;do sub to check if player landed on jumpspring
bcc ExCJSp ;if carry not set, jumpspring not found, therefore leave
lda #$70
sta VerticalForce ;otherwise set vertical movement force for player
sta VerticalForceDown
lda #$f9
sta JumpspringForce ;set default jumpspring force
lda #$03
sta JumpspringTimer ;set jumpspring timer to be used later
lsr
sta JumpspringAnimCtrl ;set jumpspring animation control to start animating
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L5574-L5620JumpspringHandler:
jsr GetEnemyOffscreenBits ;get offscreen information
lda TimerControl ;check master timer control
bne DrawJSpr ;branch to last section if set
lda JumpspringAnimCtrl ;check jumpspring frame control
beq DrawJSpr ;branch to last section if not set
tay
dey ;subtract one from frame control in A,
tya ;the only way a poor NMOS 6502 can
and #%00000010 ;mask out all but d1, original value still in Y
bne DownJSpr ;if set, branch to move player up
inc Player_Y_Position
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L12672-L12683lda #$02
ldy WorldNumber ;if the world number is not 2, 3 or 7
cpy #$01 ;then use regular attributes for jumpsprings
beq GrnJS ;which will paint them red
cpy #$02
beq GrnJS ;otherwise use alternate attributes
cpy #$06 ;to get the green superhigh jumpsprings
bne RedJS
GrnJS: lsr
RedJS: sta EnemyAttributeData+$18 ;set jumpspring gfx attributes in the lookup table
sta EnemyAttributeData+$19
sta EnemyAttributeData+$1aSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L6575-L6584MovePlayerVertically:
ldx #$00 ;set X for player offset
lda TimerControl
bne NoJSChk ;if master timer control set, branch ahead
lda JumpspringAnimCtrl ;otherwise check to see if jumpspring is animating
bne ExXMove ;branch to leave if so
NoJSChk: lda VerticalForce ;dump vertical force
sta $00
lda #$04 ;set maximum vertical speed here
jmp ImposeGravitySprObj ;then jump to move player verticallyChecked by claude, 2026-10-26 — read ChkForLandJumpSpring, JumpspringHandler, the spring's color patch in EnemyGfxHandler, and MovePlayerVertically at the pinned commitThe step is JumpspringAnimCtrl, 1 to 4; the handler reads it less one (0 to 3). The player's vertical move is skipped while the spring animates, and its gravity code caps only the fall (ImposeGravitySprObj passes no upward limit). Worlds A to D use world numbers 0 to 3 (the comment at sm2main.asm line 1199).
Cited by: Super Mario Bros.: The Lost Levels
Piranha plants are green in worlds 1 to 3 and red in worlds 4 to 9 and A to D. A green one stays in its pipe while the player is less than 33 px from it across, and moves on every second frame; a red one stays in only while the player is less than 19 px away, and moves on every frame. smb2j/piranha-plants
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7805-L7821InitPiranhaPlant:
lda #$22 ;set default attribute and range data here
sta $00 ;range data is used to detect how close player is
lda #$13 ;to the piranha plant to keep it inside the pipe
sta $01 ;default data makes red piranha plants
lda HardWorldFlag
bne PatchPP ;use default data if in worlds A-D
lda WorldNumber
cmp #$03
bcs PatchPP ;use default data if in worlds 4-8
dec $00
lda #$21 ;otherwise make green piranha plant instead
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L9593-L9596ChkPlayerNearPipe:
lda $00 ;get saved horizontal difference
cmp #$21
bcc PutinPipe ;if player within a certain distance, branch to leaveSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L9612-L9619RiseFallPiranhaPlant:
sta $00 ;save vertical coordinate here
lda EnemyAttributeData+PiranhaPlant
cmp #$22 ;check for red piranha plants
beq RedPP ;if found, skip to next part to execute code on every frame
lda FrameCounter ;get frame counter
lsr
bcc PutinPipe ;branch to leave if d0 set (execute code every other frame)Checked by claude, 2026-10-26 — read InitPiranhaPlant and MovePiranhaPlant at the pinned commitInitPiranhaPlant patches the operand of the cmp #$21 at ChkPlayerNearPipe+3: $13 for red, $21 for green. The palette patch is in EnemyAttributeData, which RiseFallPiranhaPlant reads back to tell red from green.
Cited by: Super Mario Bros.: The Lost Levels
While Lakitu's stretch of a level runs, the game checks every 128 frames whether a Lakitu is on screen. When none is, it counts the check, and on the third it sends a new Lakitu if an enemy slot is free (if none is, it tries again at the next check); the count starts over when a Lakitu is set up. The new one flies high in worlds 1 to 6 ($20) and lower in worlds 7 to 9 and A to D ($60). When one is on screen, the same check leads to the Spiny throw, not covered here. smb2j/lakitu-return
About: Super Mario Bros.: The Lost Levels Enemy spawning
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7251-L7288LakituAndSpinyHandler:
lda FrenzyEnemyTimer ;if timer here not expired, leave
bne ExLSHand
cpx #$05 ;if we are on the special use slot, leave
bcs ExLSHand
lda #$80 ;set timer
sta FrenzyEnemyTimer
ldy #$04 ;start with the last enemy slot
ChkLak: lda Enemy_ID,y ;check all enemy slots to see
cmp #Lakitu ;if lakitu is on one of them
beq CreateSpiny ;if so, branch out of this loop
dey ;otherwise check another slot
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L7234-L7236SetupLakitu:
lda #$00 ;erase counter for lakitu's reappearance
sta LakituReappearTimerSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L928-L933ldx #$14 ;set offset to decrement only frame timers
dec IntervalTimerControl ;if interval timer control not expired, branch
bpl DecrTheTimers ;to skip and thus decrement only frame timers
lda #$14
sta IntervalTimerControl ;otherwise reset interval timer control to 20 frames
ldx #$23 ;and load offset to decrement frame and interval timersChecked by claude, 2026-10-26 — read LakituAndSpinyHandler and SetupLakitu at the pinned commitFrenzyEnemyTimer is $078F, one of the frame timers ($0780-$0794) that count down every frame while the game's timers run.
Cited by: Super Mario Bros.: The Lost Levels
At a game over outside World 9, Select switches between CONTINUE and RETRY and Start picks. Continue restarts the current world at its first level, small, with three lives, the score and coins at zero, and the hidden 1-up available again. Retry ends the game and clears the record of completed worlds. smb2j/continue-or-retry
About: Super Mario Bros.: The Lost Levels Continue
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14431-L14444GameOverMenu:
lda SavedJoypadBits ;if player pressed the start button
and #Start_Button ;then either continue or start over
bne ContinueOrRetry
lda SavedJoypadBits
and #Select_Button ;if player pressed the select button
beq ChgSel ;then branch to select "continue" or "retry"
ldx SelectTimer ;if select timer not expired while
bne ChgSel ;pressing select, skip this
lsr
sta SelectTimer ;otherwise set the select timer
lda ContinueMenuSelect
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L14455-L14473ContinueOrRetry:
lda ContinueMenuSelect ;if player selected "continue"
beq Continue ;then branch to continue
lda #$00
sta CompletedWorlds ;otherwise init completed worlds flags
jmp TerminateGame ;and end the game
Continue:
ldy #$02
sty NumberofLives ;give three lives
sta LevelNumber
sta AreaNumber ;put at x-1 of the current world
sta CoinTally
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L2869-L2881ContinueGame:
jsr LoadAreaPointer ;update level pointer with
lda #$01 ;actual world and area numbers, then
sta PlayerSize ;reset player's size, status, and
inc FetchNewGameTimerFlag ;set game timer flag to reload
lda #$00 ;game timer from header
sta TimerControl ;also set flag for timers to count again
sta PlayerStatus
sta GameEngineSubroutine ;reset task for game core
sta OperMode_Task ;set modes and leave
lda #$01 ;if in game over mode, switch back to
sta OperMode ;game mode, because game is still on
…Checked by claude, 2026-10-26 — read GameOverMenu, ContinueOrRetry, Continue, and ContinueGame at the pinned commitCited by: Super Mario Bros.: The Lost Levels
Grabbing the flagpole gives an extra life in place of the height's points when the coin count's two digits are the same and match the timer's last digit (11 coins with the timer ending in 1, for example). smb2j/flagpole-1up
About: Super Mario Bros.: The Lost Levels Extra life
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L11219-L11231ChkFlagpoleYPosLoop:
cmp FlagpoleYPosData,x ;compare with current vertical coordinate data
bcs MtchF ;if player's => current, branch to use current offset
dex ;otherwise decrement offset to use
bne ChkFlagpoleYPosLoop ;do this until all data is checked (use last one if all checked)
MtchF: stx FlagpoleScore ;store offset here to be used later
lda CoinDisplay
cmp CoinDisplay+1 ;check to see if coin tally digits are the same
bne RunFR ;if not, branch to use flagpole score data as-is
cmp GameTimerDisplay+2 ;check to see if the last digit of game timer matches
bne RunFR ;the two digits, if not, branch to use data as-is
lda #$05
…SMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L5551-L5559GiveFPScr: ldy FlagpoleScore ;get score offset from earlier (when player touched flagpole)
cpy #$05
bne NoEL4F ;if set to give player an extra life, do so now
inc NumberofLives
lda #$40
sta $fe
jmp NoSc4F
NoEL4F: lda FlagpoleScoreMods,y ;get amount to award player points
ldx FlagpoleScoreDigits,y ;get digit with which to award pointsChecked by claude, 2026-10-26 — read HandleClimbing's flagpole score and FlagpoleRoutine's award at the pinned commitCited by: Super Mario Bros.: The Lost Levels
Fireworks go off over the castle when the timer's last digit matches the coin count's last digit as the level ends: three when the digit is odd, six when it is even, and none when they differ. smb2j/fireworks
CodeSMB2J disassembly for ca65 (threecreepio/smb2j-disassembly) · sm2main.asm#L9470-L9485GameTimerFireworks:
lda GameTimerDisplay+2 ;check to see if last digit of timer matches
cmp CoinDisplay+1 ;the last digit in the coin tally
bne NoFWks ;if not, skip the fireworks
and #$01
beq EvenDgs ;if so, check to see if they are both odd or even
ldy #$03
lda #$03 ;if they are both odd, set state and counter
bne SetFWC ;for 3 fireworks to go off
EvenDgs: ldy #$00 ;if they are both even, set state and counter
lda #$06 ;for 6 fireworks to go off
bne SetFWC
…Checked by claude, 2026-10-26 — read GameTimerFireworks at the pinned commitCited by: Super Mario Bros.: The Lost Levels