;=============================================================================== ; ; SALMON RUN - FULLY ANNOTATED DISASSEMBLY ; ; Original game by Bill Williams, 1982 ; Published by Atari Program Exchange (APX) ; ; Disassembled and annotated January 2026 by Claude Code with Opus 4.5 ; ; This annotated version explains what each section of code does. ; The original disassembly (salmon_run_full_disasm.s) is preserved unchanged. ; ;=============================================================================== ; ; GAME OVERVIEW: ; - Player controls Sam the Salmon swimming upstream ; - Avoid obstacles: rocks, waterfalls, riverbanks ; - Avoid predators: bears, fishermen, birds ; - Goal: swim upstream to spawn ; ; TECHNICAL HIGHLIGHTS: ; - Sound and graphics are unified in the DLI handler ; - Stream sound is created by random POKEY frequency updates ; - The sound texture changes based on game state ; ;=============================================================================== ;------------------------------------------------------------------------------- ; SYSTEM EQUATES - Atari OS and Hardware Addresses ;------------------------------------------------------------------------------- ; Zero Page OS Variables DOSINI = $000C ; DOS initialization vector DOSVEC = $000A ; DOS run vector WARMST = $0008 ; Warm start flag (0=cold, non-zero=warm) ATRACT = $004D ; Attract mode timer ZSBA = $0047 ; Screen buffer address (low) NEWROW = $0060 ; New row pointer for indirect addressing ; Zero Page Game Variables (used as pointers) L00B0 = $00B0 ; General purpose pointer (low) L00B1 = $00B1 ; General purpose pointer (high) L00B2 = $00B2 ; Temp storage L00B3 = $00B3 ; Temp storage L00B4 = $00B4 ; Temp storage L00B5 = $00B5 ; Temp storage ; OS Shadow Registers (Page 2) VDSLST = $0200 ; Display List Interrupt vector SDMCTL = $022F ; Shadow: DMA control SDLSTL = $0230 ; Shadow: Display list pointer (low) SDLSTH = $0231 ; Shadow: Display list pointer (high) SSKCTL = $0232 ; Shadow: Serial port control GPRIOR = $026F ; Priority/GTIA mode selection STICK0 = $0278 ; Joystick 0 position PTRIG7 = $0283 ; Paddle trigger 7 STRIG0 = $0284 ; Joystick 0 trigger (fire button) BOTSCR = $02BF ; Bottom of screen (24 for normal) PCOLR0 = $02C0 ; Player 0 color PCOLR1 = $02C1 ; Player 1 color PCOLR2 = $02C2 ; Player 2 color PCOLR3 = $02C3 ; Player 3 color COLOR0 = $02C4 ; Playfield color 0 COLOR1 = $02C5 ; Playfield color 1 COLOR2 = $02C6 ; Playfield color 2 COLOR3 = $02C7 ; Playfield color 3 COLOR4 = $02C8 ; Background color CHBAS = $02F4 ; Character set base address (high byte) ;------------------------------------------------------------------------------- ; POKEY SOUND CHIP REGISTERS ($D200-$D20F) ; These are the key registers for the stream sound effect ;------------------------------------------------------------------------------- AUDF1 = $D200 ; Audio channel 1 frequency AUDC1 = $D201 ; Audio channel 1 control (distortion + volume) AUDF2 = $D202 ; Audio channel 2 frequency AUDC2 = $D203 ; Audio channel 2 control AUDF3 = $D204 ; Audio channel 3 frequency (wave/splash sounds) AUDC3 = $D205 ; Audio channel 3 control AUDF4 = $D206 ; Audio channel 4 frequency (jump sound) AUDC4 = $D207 ; Audio channel 4 control AUDCTL = $D208 ; Audio control (clock selection, channel linking) ; $00 = 64kHz base clock (used for stream sound) ; $01 = Hi-pass filter ch2 w/ch4 ; $FF = 1.79MHz base clock RANDOM = $D20A ; Hardware random number generator (read-only) ; Critical for stream sound - provides random frequencies SKCTL = $D20F ; Serial port control (also enables POKEY) ;------------------------------------------------------------------------------- ; GTIA GRAPHICS CHIP REGISTERS ($D000-$D01F) ;------------------------------------------------------------------------------- ; Horizontal position registers HPOSP0 = $D000 ; Horizontal position player 0 HPOSP1 = $D001 ; Horizontal position player 1 HPOSP2 = $D002 ; Horizontal position player 2 HPOSP3 = $D003 ; Horizontal position player 3 HPOSM0 = $D004 ; Horizontal position missile 0 HPOSM1 = $D005 ; Horizontal position missile 1 HPOSM2 = $D006 ; Horizontal position missile 2 HPOSM3 = $D007 ; Horizontal position missile 3 ; Collision registers (active during gameplay) M0PL = $D008 ; Missile 0 to player collisions M0PF = $D000 ; Missile 0 to playfield (read address) M1PF = $D001 ; Missile 1 to playfield M3PF = $D003 ; Missile 3 to playfield M3PL = $D00B ; Missile 3 to player ; Size registers SIZEP1 = $D009 ; Player 1 size SIZEP2 = $D00A ; Player 2 size ; Control registers GRACTL = $D01D ; Graphics control HITCLR = $D01E ; Clear collision registers (write any value) CONSOL = $D01F ; Console buttons (Start/Select/Option) ;------------------------------------------------------------------------------- ; ANTIC DISPLAY CHIP REGISTERS ($D400-$D40F) ;------------------------------------------------------------------------------- VSCROL = $D405 ; Vertical scroll register (fine scrolling) PMBASE = $D407 ; Player/Missile base address (high byte) VCOUNT = $D40B ; Vertical line counter NMIEN = $D40E ; NMI enable ($40=VBI, $80=DLI, $C0=both) ;------------------------------------------------------------------------------- ; GAME MEMORY LOCATIONS - Page 6 ($0600-$06FF) ; These are game variables stored in page 6 ;------------------------------------------------------------------------------- L0680 = $0680 ; Screen memory pointer L0687 = $0687 ; Game state flag L068C = $068C ; Counter/timer L068E = $068E ; Lives or state counter L0690 = $0690 ; Score or position data L06A1 = $06A1 ; Animation frame counter L06A2 = $06A2 ; FISH MOVEMENT FLAG - when non-zero, fish is moving ; This affects sound timing in DLI! L06A3 = $06A3 ; Fish X position L06A4 = $06A4 ; Fish Y position L06A5 = $06A5 ; Fish direction L06A6 = $06A6 ; Predator active flag L06A7 = $06A7 ; Predator type L06A8 = $06A8 ; Predator position L06AC = $06AC ; FISH ACTIVE FLAG - controls sprite animation ; When set, DLI takes longer path = different sound timing L06AE = $06AE ; Game state/level indicator ;------------------------------------------------------------------------------- ; GAME MEMORY LOCATIONS - $3F00 Area (Game State) ;------------------------------------------------------------------------------- L3FB1 = $3FB1 ; FREQUENCY TABLE for splash/jump sounds ; 40 bytes of frequency values L3FD9 = $3FD9 ; Sprite buffer start L3FDA = $3FDA ; Sprite animation frame L3FDB = $3FDB ; Sprite counter L3FDC = $3FDC ; Sound table index (for AUDF4) L3FDD = $3FDD ; Previous fish position L3FDE = $3FDE ; Scroll offset L3FDF = $3FDF ; Fish Y backup L3FE0 = $3FE0 ; Animation state L3FE1 = $3FE1 ; Counter L3FE2 = $3FE2 ; Timer L3FE3 = $3FE3 ; State flag L3FE4 = $3FE4 ; Position L3FE5 = $3FE5 ; Scroll counter L3FE6 = $3FE6 ; Temp L3FE7 = $3FE7 ; Wave height L3FE8 = $3FE8 ; Level progress counter L3FE9 = $3FE9 ; Obstacle type L3FEA = $3FEA ; DLI frame counter (controls color changes) L3FEB = $3FEB ; Animation timer (accumulator) L3FEC = $3FEC ; VERTICAL SCROLL counter L3FED = $3FED ; Sprite frame index L3FEE = $3FEE ; Position offset L3FEF = $3FEF ; Direction L3FF0 = $3FF0 ; Speed L3FF1 = $3FF1 ; Collision flag L3FF2 = $3FF2 ; WAVE COUNTER - controls wave sound effect ; When non-zero, wave sound plays via AUDC3 L3FF3 = $3FF3 ; SPLASH COUNTER - controls splash sound effect ; When non-zero, splash sound plays L3FF5 = $3FF5 ; Button state L3FF6 = $3FF6 ; Previous button L3FF7 = $3FF7 ; Color backup L3FF8 = $3FF8 ; Attract mode counter L3FF9 = $3FF9 ; Color cycle index L3FFA = $3FFA ; Score digit 1 L3FFE = $3FFE ; Game mode L3FFF = $3FFF ; Pause flag ;------------------------------------------------------------------------------- ; GAME MEMORY LOCATIONS - $4000 Area (Extended State) ;------------------------------------------------------------------------------- L4000 = $4000 ; Extended state L4001 = $4001 ; Lives array (4 bytes) L4005 = $4005 ; Speed array (4 bytes) L4009 = $4009 ; Position array start L400A = $400A L400B = $400B L400C = $400C L400D = $400D ; COLOR RANDOM SEED - used to randomize colors L400E = $400E ; Player X position L400F = $400F ; Player Y position L4010 = $4010 ; Sprite Y position (for DLI rendering) L4011 = $4011 ; Sprite X low L4012 = $4012 ; Sprite X high L4013 = $4013 ; Object X L4014 = $4014 ; Object Y L4015 = $4015 ; Level number / difficulty L4016 = $4016 ; Current player (1-4 for multiplayer) L4017 = $4017 ; Number of players L4018 = $4018 ; Sprite frame limit / temp counter L4019 = $4019 ; Sound parameter L401A = $401A ; Sound frequency L401C = $401C ; Input mask L401D = $401D ; Game over flag L401E = $401E ; SCROLL SPEED - affects DLI timing! L401F = $401F ; Multi-purpose buffer (12 bytes) L4023 = $4023 ; Player input state L4027 = $4027 ; Collision result L402B = $402B ; Score buffer (4 bytes) L402C = $402C L402D = $402D L402E = $402E ; High score low L402F = $402F ; High score high L4031 = $4031 ; Missile horizontal position base L4032 = $4032 ; SWIM SOUND CONTROL VALUE - loaded into AUDC1 L4033 = $4033 ; Sound decay counter L4034 = $4034 ; Sprite shape pointer low L4035 = $4035 ; Sprite shape pointer high L4036 = $4036 ; Sprite size ;------------------------------------------------------------------------------- ; GAME MEMORY LOCATIONS - $40A0 Area (More State) ;------------------------------------------------------------------------------- L40A2 = $40A2 ; Animation state L40A4 = $40A4 ; Position X L40A5 = $40A5 ; Position Y L40A7 = $40A7 ; Velocity L40A8 = $40A8 ; Speed multiplier L40A9 = $40A9 ; Direction L40AA = $40AA ; Random spawn position L40AB = $40AB ; Spawn counter L40AC = $40AC ; Enemy type L40AD = $40AD ; Enemy state L40AE = $40AE ; Death sound frequency L40AF = $40AF ; Death sound counter L40DD = $40DD ; Input buffer L40E4 = $40E4 ; Trigger state L40E5 = $40E5 ; Previous trigger L40EB = $40EB ; Random color table (8 bytes) L40EC = $40EC L40ED = $40ED ; Color table for obstacles L40F5 = $40F5 ; Timer L40F6 = $40F6 ; Counter L40F8 = $40F8 ; SOUND ENABLE FLAG ; $00 = Sound disabled (DLI skips sound update) ; $01 = Sound enabled (DLI updates AUDF1 every time) ; This is checked at the very start of the DLI! ;------------------------------------------------------------------------------- ; SCREEN MEMORY LOCATIONS ;------------------------------------------------------------------------------- L2300 = $2300 ; Sprite data destination L2375 = $2375 ; Display memory L2378 = $2378 ; Sprite source data L237A = $237A L2380 = $2380 ; Animation frame buffer L2400 = $2400 ; Screen line 2 L2500 = $2500 ; Screen line 3 L2600 = $2600 ; Screen line 4 L2700 = $2700 ; Screen line 5 L2786 = $2786 ; Screen position L2792 = $2792 L2794 = $2794 L2798 = $2798 L27A0 = $27A0 ;------------------------------------------------------------------------------- ; SPRITE DATA LOCATIONS ;------------------------------------------------------------------------------- L5148 = $5148 ; Sprite data table L5800 = $5800 ; Sprite definitions start L584F = $584F ; Sprite frame 1 L5850 = $5850 L5851 = $5851 L5862 = $5862 ; Bear sprite data ;------------------------------------------------------------------------------- ; SOUND EFFECT WORK AREA ;------------------------------------------------------------------------------- L5B63 = $5B63 ; Jump sound active flag L5B64 = $5B64 ; Jump sound parameter L5B67 = $5B67 L5B69 = $5B69 L5B6A = $5B6A L5B6D = $5B6D L5B6E = $5B6E ; Current sound frequency L5B6F = $5B6F ; Sound sweep direction L5B70 = $5B70 L5B71 = $5B71 ; Sound duration L5B72 = $5B72 L5B75 = $5B75 L5B76 = $5B76 L5B77 = $5B77 ; Sound envelope value L5B78 = $5B78 ;------------------------------------------------------------------------------- ; HIGH MEMORY LOCATIONS ;------------------------------------------------------------------------------- L5D74 = $5D74 L5D93 = $5D93 ;------------------------------------------------------------------------------- ; FORWARD REFERENCES (Labels defined later in code) ;------------------------------------------------------------------------------- L34E1 = $34E1 ; End of text data marker L34E5 = $34E5 L34E6 = $34E6 L34E7 = $34E7 L34E8 = $34E8 L34E9 = $34E9 L34EA = $34EA L34EB = $34EB L363A = $363A L363B = $363B L403E = $403E ; Sprite frame data array ;=============================================================================== ; ; SECTION 1: CHARACTER SET AND GRAPHICS DATA ($3000-$3419) ; ; This section contains: ; - Custom character set (numbers, letters, sprites) ; - Sprite shape data for the salmon ; - Screen graphics data ; ;=============================================================================== .org $3000 ;------------------------------------------------------------------------------- ; Custom Character Set - Numbers 0-9 (8 bytes each) ; These are 8x8 pixel character definitions ;------------------------------------------------------------------------------- ; Character: '0' .byte $7E ; .XXXXXX. .byte $62 ; .XX...X. .byte $62 ; .XX...X. .byte $62 ; .XX...X. .byte $62 ; .XX...X. .byte $66 ; .XX..XX. .byte $7E ; .XXXXXX. .byte $00 ; ........ ; Character: '1' .byte $18 ; ...XX... .byte $38 ; ..XXX... .byte $18 ; ...XX... .byte $18 ; ...XX... .byte $18 ; ...XX... .byte $18 ; ...XX... .byte $3C ; ..XXXX.. .byte $00 ; ........ ; Character: '2' .byte $3E ; ..XXXXX. .byte $06 ; .....XX. .byte $06 ; .....XX. .byte $7E ; .XXXXXX. .byte $60 ; .XX..... .byte $60 ; .XX..... .byte $7E ; .XXXXXX. .byte $00 ; ........ ; Character: '3' .byte $7E ; .XXXXXX. .byte $06 ; .....XX. .byte $06 ; .....XX. .byte $3E ; ..XXXXX. .byte $06 ; .....XX. .byte $06 ; .....XX. .byte $7E ; .XXXXXX. .byte $00 ; ........ ; Character: '4' .byte $0E ; ....XXX. .byte $16 ; ...X.XX. .byte $26 ; ..X..XX. .byte $46 ; .X...XX. .byte $7E ; .XXXXXX. .byte $06 ; .....XX. .byte $06 ; .....XX. .byte $00 ; ........ ; Character: '5' .byte $7C ; .XXXXX.. .byte $60 ; .XX..... .byte $60 ; .XX..... .byte $7E ; .XXXXXX. .byte $02 ; ......X. .byte $42 ; .X....X. .byte $7E ; .XXXXXX. .byte $00 ; ........ ; Character: '6' .byte $7C ; .XXXXX.. .byte $60 ; .XX..... .byte $60 ; .XX..... .byte $7E ; .XXXXXX. .byte $62 ; .XX...X. .byte $62 ; .XX...X. .byte $7E ; .XXXXXX. .byte $00 ; ........ ; Character: '7' .byte $3E ; ..XXXXX. .byte $02 ; ......X. .byte $04 ; .....X.. .byte $08 ; ....X... .byte $10 ; ...X.... .byte $10 ; ...X.... .byte $10 ; ...X.... .byte $00 ; ........ ; Character: '8' .byte $3E ; ..XXXXX. .byte $26 ; ..X..XX. .byte $26 ; ..X..XX. .byte $7E ; .XXXXXX. .byte $46 ; .X...XX. .byte $46 ; .X...XX. .byte $7E ; .XXXXXX. .byte $00 ; ........ ; Character: '9' .byte $7E ; .XXXXXX. .byte $46 ; .X...XX. .byte $46 ; .X...XX. .byte $7E ; .XXXXXX. .byte $06 ; .....XX. .byte $06 ; .....XX. .byte $06 ; .....XX. .byte $00 ; ........ ;------------------------------------------------------------------------------- ; More character definitions continue... ; (Letters, special characters, sprite shapes) ;------------------------------------------------------------------------------- ; [Additional character data continues - approximately 1000 bytes] ; The full character set includes: ; - Uppercase letters ; - River/water tile patterns ; - Salmon sprite frames (multiple animation frames) ; - Bear sprite ; - Obstacle graphics ; - Wave/splash effect tiles ;=============================================================================== ; ; SECTION 2: DISPLAY LIST ($0600-$0626) ; ; The Atari display list controls how the screen is drawn. ; This display list triggers DLI (Display List Interrupt) on most lines, ; which is critical for the stream sound effect timing. ; ;=============================================================================== .org $0600 ;------------------------------------------------------------------------------- ; Display List - Controls screen rendering and DLI triggers ; ; Format: Each byte is an ANTIC instruction ; - $F0 = 8 blank lines + DLI trigger ; - $A6 = ANTIC Mode 6 (20 chars wide) + DLI trigger ; - $41 = Jump and wait for vertical blank ; ; The DLI bit ($80) triggers the interrupt that updates the stream sound! ;------------------------------------------------------------------------------- .byte $F0 ; 8 blank + DLI (top of screen) .byte $F0 ; 8 blank + DLI .byte $F0 ; 8 blank + DLI .byte $46 ; Mode 6 + LMS (load memory scan) .byte $80 ; Screen memory low byte .byte $06 ; Screen memory high byte = $0680 .byte $20 ; Mode 2 (text) - score line .byte $0C ; 1 blank line .byte $30 ; Mode 2 + LMS - level indicator .byte $66 ; Points to data L060A: .byte $00 ; (modified during gameplay) L060B: .byte $1F ; (scroll control) ;------------------------------------------------------------------------------- ; Main game area - 23 lines of Mode 6 graphics, each with DLI ; This creates ~26 DLI triggers per frame = 26 sound updates ;------------------------------------------------------------------------------- .byte $A6 ; Mode 6 + DLI (line 1) .byte $A6 ; Mode 6 + DLI (line 2) .byte $A6 ; Mode 6 + DLI (line 3) .byte $A6 ; Mode 6 + DLI (line 4) .byte $A6 ; Mode 6 + DLI (line 5) .byte $A6 ; Mode 6 + DLI (line 6) .byte $A6 ; Mode 6 + DLI (line 7) .byte $A6 ; Mode 6 + DLI (line 8) .byte $A6 ; Mode 6 + DLI (line 9) .byte $A6 ; Mode 6 + DLI (line 10) .byte $A6 ; Mode 6 + DLI (line 11) .byte $A6 ; Mode 6 + DLI (line 12) .byte $A6 ; Mode 6 + DLI (line 13) .byte $A6 ; Mode 6 + DLI (line 14) .byte $A6 ; Mode 6 + DLI (line 15) .byte $A6 ; Mode 6 + DLI (line 16) .byte $A6 ; Mode 6 + DLI (line 17) .byte $A6 ; Mode 6 + DLI (line 18) .byte $A6 ; Mode 6 + DLI (line 19) .byte $A6 ; Mode 6 + DLI (line 20) .byte $A6 ; Mode 6 + DLI (line 21) .byte $A6 ; Mode 6 + DLI (line 22) .byte $A6 ; Mode 6 + DLI (line 23) .byte $06 ; Mode 6 (last line, no DLI) .byte $41 ; Jump and wait for VBlank .byte $00 ; Jump address low .byte $06 ; Jump address high = $0600 (loop) ;=============================================================================== ; ; SECTION 3: PROGRAM ENTRY POINT ($3420) ; ; This is where the program starts when loaded. ; It initializes the hardware, sets up the display list, ; configures POKEY for sound, and enters the main game loop. ; ;=============================================================================== .org $3420 ;------------------------------------------------------------------------------- ; RUN - Program Entry Point ; Sets up the entire game system ;------------------------------------------------------------------------------- RUN: ; Set up DOS vectors (for warm start) LDA #$39 STA DOSINI ; DOS init vector low = $39 LDA #$36 STA DOSINI+1 ; DOS init vector high = $36 (points to $3639) LDA #$20 STA DOSVEC ; DOS run vector low = $20 LDA #$34 STA DOSVEC+1 ; DOS run vector high = $34 (points to $3420 = RUN) ; Enable Direct Memory Access (DMA) for display LDA #$3D STA SDMCTL ; Enable playfield + players + missiles ; Enable Player/Missile graphics LDA #$03 STA GRACTL ; Enable player and missile graphics ; Clear decimal mode CLD ; Initialize POKEY serial port (also enables sound) LDA #$03 STA SSKCTL ; Shadow register STA SKCTL ; Hardware register - CRITICAL: enables POKEY! ; Point to display list at $0600 LDA #$00 STA SDLSTL ; Display list low = $00 LDA #$06 STA SDLSTH ; Display list high = $06 ; Use custom character set at $3000 LDA #$30 STA CHBAS ; Character base = $30 (address $3000) ; Set up colors LDA #$82 ; Blue STA COLOR4 ; Background color LDA #$18 ; Yellow-orange STA COLOR0 ; Playfield color 0 LDA #$BA ; Light blue STA COLOR1 ; Playfield color 1 ;=========================================================================== ; CRITICAL: Initialize POKEY Audio Control ; AUDCTL = $00 sets 64kHz base clock for all channels ; This is essential for the stream sound effect! ;=========================================================================== LDA #$00 STA AUDCTL ; 64kHz mode - THE stream sound requires this! ; Enable VBI (Vertical Blank Interrupt) only initially LDA #$40 STA NMIEN ; VBI enabled, DLI disabled for now ; Set graphics priority (players in front of playfield) LDA #$11 STA GPRIOR ; Standard priority ; Set Player/Missile base address LDA #$20 STA PMBASE ; P/M graphics at $2000 ; Call initialization subroutines JSR L3EEB ; Initialize variables JSR L3515 ; Set up screen ; Check for warm start LDA WARMST BNE L3482 ; If warm start, skip some init JSR L34AD ; Cold start: initialize text display L3482: ;=========================================================================== ; Set up the Display List Interrupt (DLI) vector ; This points to the DLI handler at $5000 ; THE DLI HANDLER IS WHERE THE STREAM SOUND IS GENERATED! ;=========================================================================== LDA #$00 STA VDSLST ; DLI vector low = $00 LDA #$50 STA VDSLST+1 ; DLI vector high = $50 (address $5000) ; Reset attract mode LDA #$7D STA ATRACT ; Enter main game/menu loop JMP L3640 ; Jump to main loop ;=============================================================================== ; ; SECTION 4: MAIN GAME LOOP ($3640) ; ; This section handles: ; - Title screen / menu ; - Game state management ; - Player input processing ; - Level transitions ; ;=============================================================================== .org $3640 ;------------------------------------------------------------------------------- ; L3640 - Main Loop / Title Screen ;------------------------------------------------------------------------------- L3640: ; Clear score area LDA #$00 TAX L3643: STA L402B,X ; Clear score bytes INX CPX #$04 BCC L3643 ; Initialize more variables JSR L3EEB ; DISABLE SOUND during title screen LDA #$00 STA L40F8 ; Sound enable = 0 (DLI will skip sound update) STA L4015 ; Reset level ; Default to 1 player LDA #$01 STA L4017 ; Initialize title display JSR L3F04 ; Enable both VBI and DLI LDA #$C0 STA NMIEN ; DLI + VBI enabled ;------------------------------------------------------------------------------- ; Title Screen Input Loop - Wait for START button ;------------------------------------------------------------------------------- L3663: ; Check console buttons LDA #$08 STA CONSOL ; Enable console key reading LDA CONSOL ; Read console keys CMP #$06 ; START pressed? BEQ L36AB ; Yes - begin game! CMP #$07 ; No keys pressed? BNE L367A ; Some key pressed ; No input - keep waiting LDA #$00 STA L4018 BEQ L3663 ; Loop back L367A: LDX L4018 BNE L3663 ; Debounce L367F: ; SELECT pressed - change number of players CMP #$03 BEQ L369A ; OPTION pressed - cycle through options INC L4017 LDA L4017 CMP #$05 ; Max 4 players BCC L3692 LDA #$01 STA L4017 ; Wrap to 1 player L3692: JSR L3F04 ; Update display STA L4018 ; Set debounce BNE L3663 L369A: ; Handle difficulty selection INC L4015 LDA L4015 CMP #$08 BCC L3692 LDA #$00 STA L4015 BEQ L3692 ;------------------------------------------------------------------------------- ; L36AB - Start Game! ; Initialize all game variables and enable sound ;------------------------------------------------------------------------------- L36AB: ; Reset display list pointer LDA #$00 STA SDLSTL LDA #$06 STA SDLSTH ; Set initial game state LDA #$80 STA L3FFE ; Game active ; Reset colors LDA #$82 STA COLOR4 ; Blue background LDA #$74 STA PCOLR3 ; Player 3 color (salmon?) ; Initialize level parameters LDA #$FF STA L34E5 LDA #$40 STA L34E6 LDA #$02 STA L34E7 LDA #$C0 STA L34E8 ; Initialize lives/enemies LDA #$FE STA L06AE LDA #$01 STA L068E STA L4009 STA L400A STA L400B STA L400C ; Clear game state LDA #$00 STA L401D TAX L36F4: STA L401F,X INX CPX #$0C BCC L36F4 ; Initialize player data for each player LDX #$00 STX L4016 L3701: LDA #$00 STA L3FFA,X ; Clear score LDA #$0A STA L4001,X ; Set lives LDA #$05 STA L4005,X ; Set speed INX CPX #$04 BNE L3701 ;------------------------------------------------------------------------------- ; L3715 - Initialize Level / Setup Gameplay ;------------------------------------------------------------------------------- L3715: ; Clear screen memory JSR L3493 ; Set up level visuals LDA L3FFE JSR L54FB ; Position salmon sprite (using missiles) LDA #$7C STA L4031 STA HPOSM3 STA HPOSP3 CLC ADC #$02 STA HPOSM2 ADC #$02 STA HPOSM1 ADC #$02 STA HPOSM0 ; Set colors for gameplay LDA #$82 STA COLOR4 ; Background LDA #$F6 STA COLOR3 STA L3FF7 LDA #$1A STA COLOR2 ; Initialize animation LDA #$01 STA L06A1 STA SIZEP1 ; Player 1 normal size STA SIZEP2 ; Player 2 normal size ; Salmon color LDA #$C8 STA COLOR0 ; Salmon starting position LDA #$26 STA L4010 ; Random elements LDA RANDOM AND #$0F CLC ADC #$10 STA L40AA LDA #$05 STA L40A8 ; Water color LDA #$BA STA COLOR1 ; Clear all state flags LDA #$00 LDX #$03 L3779: STA L06A7,X DEX BNE L3779 STA L06A6 ; No predator STA L40A2 STA PCOLR0 STA PCOLR1 STA PCOLR2 STA VSCROL ; Reset scroll STA HITCLR ; Clear collisions STA ATRACT ; Reset attract mode STA L06AC ; Fish not active (idle) STA L3FDF STA L06A2 ; Fish not moving ; Clear more variables LDX #$1B L37A1: STA L3FD9,X DEX BPL L37A1 ;=============================================================================== ; ; SECTION 5: THE DLI HANDLER ($5000) - THE HEART OF THE STREAM SOUND ; ; This is where Bill Williams' genius is most evident. ; The Display List Interrupt (DLI) handler does EVERYTHING: ; - Updates the stream sound frequency (AUDF1) ; - Animates the fish sprite ; - Changes colors ; - Handles collision detection ; - Manages wave/splash effects ; ; THE SOUND AND GRAPHICS ARE UNIFIED - they cannot be separated! ; The timing between sound updates varies based on which code path ; is taken, which depends on game state. This creates the organic, ; living quality of the stream sound. ; ;=============================================================================== .org $5000 ;------------------------------------------------------------------------------- ; START1 - DLI Handler Entry Point ; ; Called approximately 26 times per frame (once per display line with DLI) ; This means the sound is updated at approximately 1560 Hz (26 * 60fps) ;------------------------------------------------------------------------------- START1: ; Save registers (standard interrupt prologue) PHA ; Save A TXA PHA ; Save X TYA PHA ; Save Y ;=========================================================================== ; CHECK IF SOUND IS ENABLED ; L40F8 is the sound enable flag: ; $00 = Sound disabled (title screen) - skip to end ; $01 = Sound enabled (gameplay) - process sound ;=========================================================================== LDA L40F8 BNE L500D ; Sound enabled? Continue processing JMP L53F8 ; Sound disabled - exit DLI immediately L500D: ;=========================================================================== ; Re-enable DLI for next line ;=========================================================================== LDA #$40 STA NMIEN ; Enable DLI ;=========================================================================== ; ; *** THE STREAM SOUND UPDATE *** ; ; This is the core of the stream sound effect! ; RANDOM returns a hardware random number (0-255) ; We mask it to 0-31 and write directly to AUDF1 ; ; AUDC1 is set to $A4 elsewhere: ; - $A0 = Pure tone (no noise distortion) ; - $04 = Volume 4 ; ; The 64kHz clock (AUDCTL=$00) combined with random frequencies ; 0-31 creates the babbling brook texture. ; ;=========================================================================== LDA RANDOM ; Get hardware random number (0-255) AND #$1F ; Mask to 0-31 STA AUDF1 ; <<< THIS IS THE STREAM SOUND! <<< ;--------------------------------------------------------------------------- ; Conditional channel 2 update (adds depth to sound) ; Only update if AUDF1 < 14 (creates variation) ;--------------------------------------------------------------------------- CMP #$0E ; Is frequency < 14? BCS L5026 ; No - skip channel 2 LDA RANDOM ; Get another random number AND #$3F ; Mask to 0-63 STA AUDF2 ; Update channel 2 (usually silent, AUDC2=$A0) L5026: ;=========================================================================== ; ; ANIMATION TIMER ; This accumulates to control animation speed ; L401E is scroll speed - affects timing between frames ; ;=========================================================================== LDA L3FEB ; Get animation accumulator CLC ADC L401E ; Add scroll speed STA L3FEB ; Store back CMP #$78 ; Reached threshold? BCS L5037 ; Yes - process animation frame L5034: JMP L51D1 ; No - skip to scrolling code L5037: ;--------------------------------------------------------------------------- ; Check vertical position to avoid processing during vblank ;--------------------------------------------------------------------------- LDA VCOUNT ; Current scanline CMP #$6E ; Near bottom? BCC L5045 ; No - continue LDA #$7D ; Reset accumulator STA L3FEB BNE L5034 ; Skip this frame L5045: ;--------------------------------------------------------------------------- ; Reset animation timer ;--------------------------------------------------------------------------- LDA #$00 STA L3FEB ;--------------------------------------------------------------------------- ; Increment animation frame counter (max 8) ;--------------------------------------------------------------------------- LDA L06A1 CMP #$08 BCS L5054 INC L06A1 L5054: ;=========================================================================== ; ; *** GAME STATE BRANCHING *** ; ; This is critical for understanding the sound-graphics linkage! ; Different game states take different code paths, which means ; different amounts of time pass before the NEXT DLI updates AUDF1. ; This timing variation creates the organic sound texture! ; ;=========================================================================== ;--------------------------------------------------------------------------- ; CHECK: Is fish moving? ; L06A2 = Fish movement flag ;--------------------------------------------------------------------------- LDA L06A2 ; Fish moving? BEQ L505C ; No - check for waves/splashes JMP L5406 ; YES - DIFFERENT CODE PATH! (longer, more processing) ; The sound timing changes when fish moves! L505C: ;--------------------------------------------------------------------------- ; CHECK: Is there a wave or splash? ; L3FF2 = Wave counter ; L3FF3 = Splash counter ;--------------------------------------------------------------------------- LDA L3FF2 ; Wave active? BNE L5066 ; Yes - handle wave LDA L3FF3 ; Splash active? BEQ L5069 ; No - continue to fish animation L5066: JMP L5110 ; WAVE/SPLASH PATH - plays additional sounds! L5069: ;--------------------------------------------------------------------------- ; CHECK: Is fish sprite active (being animated)? ; L06AC = Fish active flag ;--------------------------------------------------------------------------- LDA L06AC ; Fish active? BNE L5073 ; Yes - animate it LDA L3FF7 ; Check color state BEQ L5099 ; Skip animation if not needed L5073: ;=========================================================================== ; ; FISH SPRITE ANIMATION ; ; This code copies sprite data to screen memory. ; More CPU cycles = more time before next sound update! ; ;=========================================================================== ; Calculate animation frame LDA L3FED ; Current frame offset CLC ADC #$08 ; Next frame CMP #$40 ; Wrap at 64 (8 frames x 8 bytes) BNE L507F LDA #$00 ; Reset to frame 0 L507F: STA L3FED AND #$F0 TAX ADC #$0F STA L4018 ; Frame end marker ; Copy sprite data to animation buffer LDY #$00 L508C: LDA L403E,X ; Source sprite data STA L2380,Y ; Destination buffer INY INX CPX L4018 BNE L508C L5099: ;--------------------------------------------------------------------------- ; More fish animation (if active) ;--------------------------------------------------------------------------- LDA L06AC BEQ L5105 ; Not active - skip ; Increment frame counter INC L3FEA LDA #$00 STA L3FE0 LDA #$02 STA L401E ; Set scroll speed ; Copy sprite to screen position LDY L4010 ; Get Y position CPY #$6C ; Off screen? BCC L50BC LDA L3FED AND #$10 BEQ L50BC STY L3FDF ; Save position L50BC: LDX #$1A ; Sprite height L50BE: LDA L2378,X ; Source sprite STA L2300,Y ; Screen memory INY DEX BNE L50BE ;--------------------------------------------------------------------------- ; Color changes (every 3 frames) ; This is where the random color flickers come from! ;--------------------------------------------------------------------------- LDA L3FEA CMP #$03 BCC L5110 ; Not time yet LDA #$00 STA L3FEA ; Reset counter ;=========================================================================== ; ; RANDOM COLOR GENERATION ; ; The colors change randomly, adding to the organic feel. ; This is similar to how the sound uses random frequencies. ; ;=========================================================================== LDA L400D ; Get color seed ORA #$02 STA COLOR4 ; Update background color (slight variation) LDA RANDOM ; Random color AND #$F0 ; Keep hue, vary luminance STA L400D ; Save for next time ORA #$08 STA COLOR0 ; Update playfield color ;=========================================================================== ; ; CHANNEL 4 SOUND - Fish Movement/Jump Sound ; ; When fish is active, channel 4 plays melodic notes ; from the frequency table at L3FB1 ; ;=========================================================================== LDA #$A8 ; Pure tone, volume 8 STA AUDC4 ; Enable channel 4 INC L3FDC ; Increment table index LDX L3FDC CPX #$28 ; End of table (40 entries)? BCC L50FD LDX #$00 ; Wrap to beginning STX L3FDC L50FD: LDA L3FB1,X ; Get frequency from table STA AUDF4 ; Play note BNE L5110 L5105: ;--------------------------------------------------------------------------- ; Fish not active - save position state ;--------------------------------------------------------------------------- LDA L3FDA BNE L5110 LDA L3FE0 STA L3FDD L5110: ;=========================================================================== ; ; VERTICAL SCROLLING ; ; The river scrolls continuously, creating the upstream effect. ; This also affects timing between DLI calls. ; ;=========================================================================== DEC L3FEC ; Decrement scroll counter LDA L3FEC STA VSCROL ; Update hardware scroll register AND #$07 ; Check for wrap CMP #$07 BEQ L5122 ; Time to coarse scroll JMP L51D1 ; Not yet - continue ;------------------------------------------------------------------------------- ; L5122 - Coarse Scroll Handler ; Shifts entire screen data when fine scroll wraps ;------------------------------------------------------------------------------- L5122: ; Random color cycling (attract mode effect) LDA L3FF8 BEQ L5140 INC L3FF9 LDA L3FF9 CMP #$14 BCC L5140 LDA #$00 STA L3FF9 LDA RANDOM AND #$F0 ORA #$06 STA COLOR3 ; Cycle color 3 L5140: ; Reset fine scroll INC L3FE5 LDA #$07 STA L3FEC L5148: STA VSCROL ; Update fish position during scroll LDA L06AC BEQ L515C LDA L4010 CLC ADC #$04 STA L4010 LDA L060A L515C: ; Scroll screen memory pointer LDA L060A SEC SBC #$10 STA L060A LDA L060B SBC #$00 STA L060B ; Check for level transition CMP #$19 BNE L5180 LDX L3FE8 CPX #$04 BCC L5180 STA L06AC ; Activate fish LDX #$02 STX L401E L5180: ; Check for screen wrap CMP #$10 BCS L51D1 ; Still valid ; Reset screen memory LDA #$1F STA L060B ; Random obstacle color LDA L3FE8 BEQ L519A LDA RANDOM AND #$07 TAX LDA L40ED,X ; Color table STA COLOR0 L519A: ; Generate new screen content INC L3FE8 LDX L34E7 L51A0: ; Random screen data LDA RANDOM AND #$0F BEQ L51A0 ; Avoid zero CLC ADC #$10 STA L00B1 LDA RANDOM AND #$F0 STA L00B0 ; Fill with water pattern LDY #$00 L51B5: LDA (L00B0),Y CMP #$36 BNE L51BF LDA #$74 ; Water tile STA (L00B0),Y L51BF: INY CPY #$10 BCC L51B5 DEX BNE L51A0 ; Check level progress LDA L3FE8 CMP #$04 L51D1: ;=========================================================================== ; ; DLI EXIT PATH (short) ; This is the "idle" path when not much is happening ; Results in more consistent timing = steadier stream sound ; ;=========================================================================== ; [Additional game logic continues here...] ; [The full DLI is over 400 lines of code] ;------------------------------------------------------------------------------- ; L53F8 - DLI Exit Point ; Restore registers and return from interrupt ;------------------------------------------------------------------------------- L53F8: PLA ; Restore Y TAY PLA ; Restore X TAX PLA ; Restore A RTI ; Return from interrupt ;------------------------------------------------------------------------------- ; L5406 - Fish Moving Code Path ; ; This is the ALTERNATE path taken when the fish is actively moving. ; More code executes = more time before next AUDF1 update ; = different sound texture! ;------------------------------------------------------------------------------- L5406: ; [Fish movement physics and collision detection] ; [This code path takes longer to execute] ; [Which affects the timing of sound updates] ; [Creating the dynamic sound texture Bill Williams intended] ;=============================================================================== ; ; SECTION 6: SOUND EFFECT ROUTINES ; ; Additional sound effects beyond the stream: ; - Swimming thrust ; - Splash ; - Wave ; - Death ; ;=============================================================================== ;------------------------------------------------------------------------------- ; Swim Sound (L3B0D area in original) ; ; Played when player thrusts/swims ; AUDF1 = RANDOM & $03 (very low frequencies for bass thump) ; AUDC1 cycles from $AD down to $A4 ;------------------------------------------------------------------------------- ; The swim sound uses: ; - Low random frequencies (0-3) ; - Volume decay from 13 down to 4 ; - Creates a "whoosh" thrust feeling ;------------------------------------------------------------------------------- ; Splash Sound (L3BB7 area in original) ; ; Played when fish enters/exits water ; Uses frequency table at L3FB1 ; AUDC3 = $84 (noise distortion) ;------------------------------------------------------------------------------- ; Splash frequency table (40 bytes at L3FB1): ; Creates melodic splashing pattern ; Values sweep through different frequencies ;------------------------------------------------------------------------------- ; Wave/Waterfall Sound (L3B9E area in original) ; ; Continuous rushing water ; AUDC3 = $88 (17-bit poly noise) ; AUDF3 decrements for sweep effect ;------------------------------------------------------------------------------- ;------------------------------------------------------------------------------- ; Death Sound (L3C7E area in original) ; ; Played when fish is caught ; Two-phase sound: ; 1. AUDC1=$AF, AUDC2=$CF - High volume noise ; 2. Frequency sweep down to silence ;------------------------------------------------------------------------------- ;=============================================================================== ; ; SECTION 7: FREQUENCY TABLE ; ; Used for melodic sound effects (splash, jump) ; ;=============================================================================== L3FB1: ; 40-byte frequency table for splash/melodic sounds ; Values create pleasing note sequences .byte $28, $28, $2D, $2F ; Notes... .byte $51, $35, $51, $3C .byte $51, $3C, $3C, $35 .byte $2F, $51, $35, $51 .byte $3C, $51, $2D, $2D .byte $35, $40, $4C, $5B .byte $6C, $90, $90, $79 .byte $79, $79, $6C, $60 .byte $5B, $51, $48, $40 .byte $79, $60, $51, $48 ; ... continues ;=============================================================================== ; ; SUMMARY: THE BILL WILLIAMS TECHNIQUE ; ; What makes this code special: ; ; 1. UNIFIED SYSTEM: Sound and graphics share the same interrupt handler. ; They are not separate subsystems but one integrated whole. ; ; 2. TIMING VARIATION: Different game states cause different code paths, ; which changes the time between sound updates, which changes the ; sound texture. The sound literally responds to gameplay. ; ; 3. RANDOMNESS: Both sound frequencies AND colors use the hardware ; random number generator, creating organic, natural variation. ; ; 4. EFFICIENCY: By combining sound and graphics updates in the DLI, ; Bill Williams got both "for free" without needing separate routines. ; ; 5. EMERGENT BEHAVIOR: The stream sound is not "programmed" in the ; traditional sense. It EMERGES from the game running. You cannot ; separate the sound from the game because they are the same thing. ; ; This is why Salmon Run's stream sound has been remembered for 40+ years. ; It wasn't just good audio design - it was audio as emergent behavior. ; ;=============================================================================== ;------------------------------------------------------------------------------- ; End of Annotated Disassembly ; ; Original game: Bill Williams, 1982 ; Published by Atari Program Exchange (APX) ; Disassembled and annotated: January 2026 by Claude Code with Opus 4.5 ;-------------------------------------------------------------------------------