Claim set · data/claims/smb1-av.yaml

Super Mario Bros. graphics and sound (doppelganger's SMBDIS via Xkeeper0/smb1)

Game: Super Mario Bros..

Each frame the NMI handler waits for the sprite 0 flag to clear, spins until the sprite 0 hit, waits a short loop for horizontal blank, and only then writes the level's scroll, so the status bar above the hit stays still. smb1-av/sprite-0-split

About: Super Mario Bros. Status bar split

Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L159-L183LDA Sprite0HitDetectFlag ; check for flag here … LDA PPU_STATUS ; wait for sprite 0 flag to clear, which will AND #%01000000 ; not happen until vblank has ended … Sprite0Hit: LDA PPU_STATUS ; do sprite #0 hit detection AND #%01000000 BEQ Sprite0Hit LDY #$14 ; small delay, to wait until we hit horizontal blank time … LDA HorizontalScroll ; set scroll registers from variables …Checked by claude, 2026-10-21 — read the NMI handler's sprite 0 wait at the pinned commit

Cited by: Status bar split

The game sets sprite 0 to OAM bytes $18, $FF, $23, $58, so tile $FF sits at Y $18 and X $58 behind the background in sprite palette 3. smb1-av/sprite-0-placement

About: Super Mario Bros. Status bar split

Value
{
 "y": 24,
 "tile": 255,
 "attributes": 35,
 "x": 88
}
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L2118-L2119Sprite0Data: .db $18, $ff, $23, $58smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L2236-L2245LDY #$03 ; set up sprite #0 ISpr0Loop: LDA Sprite0Data,y STA Sprite_Data,y … INC Sprite0HitDetectFlag ; set sprite #0 check flagChecked by claude, 2026-10-21 — read Sprite0Data and the loop that copies it into OAM

The OAM bytes are Y, tile, attributes, X (nesdev-ppu-oam). Attribute $23 sets bit 5 (behind the background) and palette bits 3. Which status-bar tile the hit lands on was not checked; a probe should name it.

Cited by: Status bar split

Every unpaused frame SpriteShuffler adds one of three amounts, $58, $48, or $38 in turn, to each object's OAM offset at or above $28, wrapping within $28-$FF, so the objects' sprites change OAM slots each frame and a ninth sprite on a line drops out on different frames. smb1-av/sprite-shuffle

About: Super Mario Bros. Sprite flicker

Value
{
 "amounts": [
  88,
  72,
  56
 ],
 "floor": 40,
 "phases": 3
}
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L164-L169LDA GamePauseStatus ; if in pause mode, do not bother with sprites at all … JSR MoveSpritesOffscreen JSR SpriteShufflersmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L240-L266SpriteShuffler: … LDA #$28 ; load preset value which will put it at STA $00 ; sprite #10 LDX #$0e ; start at the end of OAM data offsets ShuffleLoop: LDA SprDataOffset,x ; check for offset value against CMP $00 ; the preset value BCC NextSprOffset ; if less, skip this part LDY SprShuffleAmtOffset ; get current offset to preset value we want to add CLC ADC SprShuffleAmt,y ; get shuffle amount, add to current sprite offset …smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L2225-L2230LDA #$38 ; load sprite shuffle amounts to be used later STA SprShuffleAmt+2 LDA #$48 STA SprShuffleAmt+1 LDA #$58 STA SprShuffleAmtChecked by claude, 2026-10-21 — read SpriteShuffler, its call in the NMI handler, and the amounts set at area start

The claim that a dropped sprite moves between frames follows from the PPU's in-order search (nesdev-ppu-oam); the atlas has not measured which sprites flicker in play.

Cited by: Sprite flicker

Every 8th frame ColorRotation rewrites background palette 3 at $3F0C, putting the next of six colors, $27, $27, $27, $17, $07, $17, in its second entry, so the question blocks and coins shimmer on a 48-frame loop. smb1-av/color-rotation

About: Super Mario Bros. Palette cycling

Value
{
 "palette": "$3F0C",
 "entry": 1,
 "colors": [
  39,
  39,
  39,
  23,
  7,
  23
 ],
 "framesPerStep": 8,
 "loopFrames": 48
}
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L1386-L1390ColorRotatePalette: .db $27, $27, $27, $17, $07, $17 … .db $3f, $0c, $04, $ff, $ff, $ff, $ff, $00smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L1399-L1402ColorRotation: LDA FrameCounter ; get frame counter AND #$07 ; mask out all but three LSB BNE ExitColorRot ; branch if not set to zero to do this every eighth framesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L1429-L1441LDY ColorRotateOffset ; get color cycling offset LDA ColorRotatePalette,y STA VRAM_Buffer1+4,x ; get and store current color in second slot of palette … CMP #$06 ; check to see if it's still in rangeChecked by claude, 2026-10-21 — read ColorRotation, its tables, and its call from the area's per-frame routines

BlankPalette is a VRAM buffer header (address $3F0C, 4 bytes); the area-type row of Palette3Data fills it and the cycled color overwrites entry 1. Which tiles use palette 3 is a T0 reading from play, not checked in the level data.

Cited by: CHR bank switching, Palette cycling

The cartridge header declares two 16 KB PRG ROM pages and one 8 KB CHR ROM page, so the game's tile set never changes. smb1-av/chr-fixed

About: Super Mario Bros. CHR bank switching

Value
{
 "prgPages": 2,
 "chrPages": 1,
 "chrKB": 8
}
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · smb1.asm#L4-L10.db "NES", $1a ; identification of the iNES header … .db 2 ; number of 16KB PRG-ROM pages .db 1 ; number of 8KB CHR-ROM pages … .db 1 ; mapper/mirroring/whateverChecked by claude, 2026-10-21 — read the header bytes at the pinned commit

Byte 6 = 1 with byte 7 = 0 is mapper 0 (NROM) with vertical mirroring in the iNES format; the atlas has no pinned iNES reference yet, so the mapper number rests on that reading.

Cited by: Super Mario Bros.

The NMI handler calls the sound engine once a frame; its music handler steps square 2, square 1, the triangle, and the noise channel in turn, and each channel counts its note length down by 1 per frame and reads its next byte at 0. smb1-av/music-loop

About: Super Mario Bros. Music driver

Value
{
 "order": [
  "square2",
  "square1",
  "triangle",
  "noise"
 ],
 "tick": "frame"
}
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/prg.asm#L115JSR SoundEngine ; play soundsmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L691-L698HandleSquare2Music: DEC Squ2_NoteLenCounter ; decrement square 2 note length BNE MiscSqu2MusicTasks ; is it time for more data? if not, branch to end tasks LDY MusicOffset_Square2 ; increment square 2 music offset and fetch data INC MusicOffset_Square2 LDA (MusicData),y BEQ EndOfMusicData ; if zero, the data is a null terminator BPL Squ2NoteHandler ; if non-negative, data is a notesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L763-L766HandleSquare1Music: LDY MusicOffset_Square1 ; is there a nonzero offset here? BEQ HandleTriangleMusic ; if not, skip ahead to the triangle channel DEC Squ1_NoteLenCounter ; decrement square 1 note lengthChecked by claude, 2026-10-21 — read MusicHandler and each channel's handler at the pinned commit

Cited by: Music driver

A song's header picks an 8-entry row of MusicLengthLookupTbl, and a length code of 0-7 indexes it in frames; the overworld theme's row is 54, 3, 9, 6, 18, 27, 36, 12, a quarter note of 18 frames, and the hurry-up version adds 8 to the row offset for 36, 2, 6, 4, 12, 18, 24, 8. smb1-av/note-lengths

About: Super Mario Bros. Music driver

Value
{
 "overworld": [
  54,
  3,
  9,
  6,
  18,
  27,
  36,
  12
 ],
 "hurry": [
  36,
  2,
  6,
  4,
  12,
  18,
  24,
  8
 ],
 "table": [
  5,
  10,
  20,
  40,
  80,
  30,
  60,
  2,
  4,
  8,
  16,
  32,
  64,
  24,
  48,
  12,
  3,
  6,
  12,
  24,
  48,
  18,
  36,
  8,
  54,
  3,
  9,
  6,
  18,
  27,
  36,
  12,
  36,
  2,
  6,
  4,
  12,
  18,
  24,
  8,
  18,
  1,
  3,
  2,
  6,
  9,
  12,
  4
 ]
}
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-data.asm#L34-L39; header format is as follows: ; 1 byte - length byte offsetsmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L662-L666LoadHeader: LDA MusicHeaderOffsetData,y ; load offset for header TAY LDA MusicHeaderData,y ; now load the header STA NoteLenLookupTblOfssmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/ram.asm#L521NoteLenLookupTblOfs = $f0smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-data.asm#L63-L64GroundLevelPart1Hdr: .db $18, <GroundM_P1Data, >GroundM_P1Data, $2d, $1c, $b8smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-data.asm#L377-L383MusicLengthLookupTbl: .db $05, $0a, $14, $28, $50, $1e, $3c, $02 .db $04, $08, $10, $20, $40, $18, $30, $0c .db $03, $06, $0c, $18, $30, $12, $24, $08 .db $36, $03, $09, $06, $12, $1b, $24, $0c .db $24, $02, $06, $04, $0c, $12, $18, $08 .db $12, $01, $03, $02, $06, $09, $0c, $04smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L925-L933ProcessLengthData: AND #%00000111 ; clear all but the three LSBs CLC ADC $f0 ; add offset loaded from first header byte ADC NoteLengthTblAdder ; add extra if time running out musicsmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L625-L627CMP #TimeRunningOutMusic ; is it time running out music? BNE FindEventMusicHeader LDX #$08 ; load offset to be added to length byte of headerChecked by claude, 2026-10-25 — read the header format, the ground headers, the length table, and ProcessLengthData; LoadHeader (L662-L666) stores the header's first byte in NoteLenLookupTblOfs, $f0

Every overworld part's header starts with $18. Reading 18 frames as a quarter note gives about 200 beats a minute at 60.1 frames a second; the note-value reading is the atlas's, not the disassembly's.

Cited by: Music driver

A note byte is an offset into FreqRegLookupTbl, a table of 51 two-byte timer periods that the driver writes to the channel's timer registers; a recreation turns period t into fCPU/(16(t+1)) Hz on a square channel and half that on the triangle. smb1-av/period-table

About: Super Mario Bros. Music driver

Value
[
 136,
 47,
 0,
 678,
 640,
 604,
 570,
 538,
 479,
 452,
 427,
 403,
 380,
 359,
 339,
 320,
 302,
 285,
 269,
 254,
 239,
 226,
 213,
 201,
 190,
 179,
 169,
 160,
 151,
 142,
 134,
 119,
 126,
 113,
 84,
 100,
 95,
 89,
 80,
 71,
 67,
 59,
 53,
 42,
 35,
 1141,
 855,
 761,
 719,
 508,
 106
]
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L107-L115Dump_Freq_Regs: TAY LDA FreqRegLookupTbl+1,y ; use previous contents of A for sound reg offset BEQ NoTone ; if zero, then do not load STA SND_REGISTER+2,x ; first byte goes into LSB of frequency divider LDA FreqRegLookupTbl,y ; second byte goes into 3 MSB plus extra bit forsmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-data.asm#L362-L375FreqRegLookupTbl: .db $00, $88, $00, $2f, $00, $00 .db $02, $a6, $02, $80, $02, $5c, $02, $3a … .db $02, $f9, $02, $cf, $01, $fc, $00, $6aAPU Pulse · Pulse channel output to mixerfpulse = fCPU/(16*(t+1))APU Triangle · leadf = fCPU ÷ (32 × (tval + 1))Checked by claude, 2026-10-21 — read Dump_Freq_Regs and decoded the table's high/low byte pairs at the pinned commit

Pairs are stored high byte first. Period 254 gives 438.7 Hz on a square channel and 508 gives 219.8 Hz, so the table sits close to A = 440 Hz equal temperament.

Cited by: Music driver

A new area-music square note sets duty and volume byte $82 and an envelope index of 8; each frame the driver writes AreaMusicEnvData[index] to the channel and counts the index down, so the note plays at 50% duty with volumes 0, 8, 7, 6, 5, 5, 4, 4 and then 0 until the next note. smb1-av/square-envelope

About: Super Mario Bros. Music driver

Value
{
 "duty": 0.5,
 "volumes": [
  0,
  8,
  7,
  6,
  5,
  5,
  4,
  4,
  0
 ],
 "table": [
  144,
  148,
  148,
  149,
  149,
  150,
  151,
  152
 ]
}
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L934-L952LoadControlRegs: … LDA #$08 ; this is the default value for all other music … LDX #$82 ; load contents of other sound regs for square 2 LDY #$7fsmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L754-L759LDY Squ2_EnvelopeDataCtrl ; check for contents saved from LoadControlRegs BEQ NoDecEnv1 DEC Squ2_EnvelopeDataCtrl ; decrement unless already zero NoDecEnv1: JSR LoadEnvelopeData ; do a load of envelope data to replace default STA SND_SQUARE2_REG ; based on offset set by first load unless playingsmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-data.asm#L388-L392AreaMusicEnvData: .db $90, $94, $94, $95, $95, $96, $97, $98 … .db $90, $91, $92, $92, $93, $93, $93, $94Checked by claude, 2026-10-21 — traced Squ2NoteHandler into MiscSqu2MusicTasks and LoadEnvelopeData at the pinned commit

On the note's first frame the index is still 8 when it is read (the DEC is to memory, after LDY), so it reads one byte past the 8-byte table, the first byte of WaterEventMusEnvData, $90: volume 0 for one frame. Bits 7-6 of $9x are duty 2 (50%) and bit 4 is constant volume (nesdev-apu-pulse). Water music uses WaterEventMusEnvData and an index of $28 instead. A Mesen probe of $4004 would confirm the per-frame trace.

Cited by: Music driver

The noise channel plays three drum sounds, all at constant volume 12 ($400C = $1C), a short beat at period index 3, a strong beat at period index 12, and a long beat at period index 3 with a longer length-counter load ($58 instead of $18). smb1-av/noise-beats

About: Super Mario Bros. Music driver

Value
{
 "short": [
  28,
  3,
  24
 ],
 "strong": [
  28,
  12,
  24
 ],
 "long": [
  28,
  3,
  88
 ],
 "silent": [
  16
 ]
}
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L888-L913LDA #$1c ; short beat data LDX #$03 LDY #$18 BNE PlayBeat StrongBeat: LDA #$1c ; strong beat data LDX #$0c LDY #$18 BNE PlayBeat LongBeat: LDA #$1c ; long beat data LDX #$03 …Checked by claude, 2026-10-21 — read NoiseBeatHandler at the pinned commit

Period indexes 3 and 12 are 32 and 762 CPU cycles on NTSC (nes-hardware/noise). The underground and castle songs skip the noise routine.

Cited by: Music driver

During the overworld and underwater songs the sound engine raises a counter by 1 a frame up to $30 and writes it to the DMC's output level at $4011; other songs count it back down to 0, and the game plays no samples. smb1-av/dac-level

About: Super Mario Bros. DPCM samples

Value
{
 "max": 48,
 "step": 1
}
Codesmb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1) · src/music-engine.asm#L79-L91LDY DAC_Counter ; load some sort of counter LDA AreaMusicBuffer AND #%00000011 ; check for specific music BEQ NoIncDAC INC DAC_Counter ; increment and check counter CPY #$30 BCC StrWave ; if not there yet, just store it NoIncDAC: TYA BEQ StrWave ; if we are at zero, do not decrement DEC DAC_Counter ; decrement counter StrWave: …Checked by claude, 2026-10-21 — read the end of SoundEngine at the pinned commit

Area music bits 0 and 1 are the ground and water songs (AreaMusicBuffer). The audible effect of the raised level on the other channels is not sourced here; the NESdev APU Mixer page would be the anchor. "No samples": the disassembly never writes the sample address or length registers ($4012, $4013); its only other DMC write resets $4011 at power-on (src/prg.asm line 31).

Cited by: DPCM samples