# Salmon Run Reverse Engineering Report **Project:** Extraction and Analysis of Bill Williams' Salmon Run (1982) **Platform:** Atari 800 / XL / XE **Date:** January 16-17, 2025 **Analyst:** Claude Code (Anthropic) --- ## Executive Summary This report documents the complete reverse engineering of Bill Williams' Salmon Run (1982), an Atari Program Exchange (APX) game renowned for its naturalistic water/stream sound effects. The project successfully: 1. Fully disassembled the 6,953-byte XEX executable (4,443 lines of 6502 assembly) 2. Identified the exact POKEY sound chip parameters creating the stream sound 3. Discovered Bill Williams' innovative technique of linking sound generation to graphics rendering 4. Verified the disassembly by reassembling it to a byte-identical executable 5. Created a tribute website with working sound demonstrations --- ## Table of Contents 1. [Project Goals](#1-project-goals) 2. [Tools and Methods](#2-tools-and-methods) 3. [Disassembly Process](#3-disassembly-process) 4. [Sound System Analysis](#4-sound-system-analysis) 5. [The Sound-Graphics Linkage Discovery](#5-the-sound-graphics-linkage-discovery) 6. [POKEY Configuration Details](#6-pokey-configuration-details) 7. [Additional Sound Effects](#7-additional-sound-effects) 8. [Verification and Reassembly](#8-verification-and-reassembly) 9. [Website and Documentation](#9-website-and-documentation) 10. [Conclusions](#10-conclusions) --- ## 1. Project Goals ### Primary Objective Extract and recreate the distinctive "stream" water sound effect from Salmon Run - a sound that has been remembered by Atari enthusiasts for over 40 years as one of the most realistic water simulations on 8-bit hardware. ### Secondary Objectives - Understand how the sound was technically achieved - Document Bill Williams' programming techniques - Create playable sound demonstrations - Preserve this knowledge for future researchers --- ## 2. Tools and Methods ### Disassembly Tools - **atari_8bit_utils/disasm** - 6502 disassembler by pcrow (GitHub) - **ca65** - cc65 suite assembler for verification - **ld65** - cc65 suite linker ### Analysis Tools - **Python 3** - For binary analysis and XEX parsing - **xxd** - Hex dump utility for byte comparison - **grep/ripgrep** - Pattern searching in disassembly ### Emulation - **atari800** - Atari 800 emulator for testing - **POKEY.js** - JavaScript POKEY emulator for web demos ### Reference Materials - Atari Hardware Manual (POKEY chip documentation) - De Re Atari (Atari programming reference) - Mapping the Atari (memory map reference) --- ## 3. Disassembly Process ### XEX File Structure Analysis The original APX_Salmon_Run.XEX file is 6,953 bytes containing 44 load segments: ``` Segment 1: $3000-$3081 (130 bytes) - Character set / graphics data Segment 2: $3089-$30BF (55 bytes) - Additional graphics ... Segment 24: $0600-$0626 (39 bytes) - Page 6 variables ... Segment 34: $5000-$579C (1949 bytes) - Main DLI handler (CRITICAL) ... Segment 44: $02E0-$02E1 (2 bytes) - Run address ($3420) ``` ### Disassembly Command ```bash disasm -a 3000 -o salmon_run_full_disasm.s APX_Salmon_Run.XEX ``` ### Output Statistics - **Total lines:** 4,443 - **Code segments:** 44 - **Labels generated:** ~500 - **Undocumented opcodes found:** 3 - Line 2422: NOP $2F35,X (opcode $3C) - Line 2424: NOP $3C2F,X (opcode $3C) - Line 2431: RLA $3C35 (opcode $2F) ### Undocumented Opcode Handling The disassembler correctly identified undocumented 6502 opcodes. For reassembly compatibility with ca65, these were converted to `.byte` directives: ```assembly ; Before (ca65 incompatible): RLA L3C34+1 ; (undocumented opcode) ; After (ca65 compatible): .byte $2F,$35,$3C ; (undocumented opcode) - RLA $3C35 ``` --- ## 4. Sound System Analysis ### Initial Assumptions (Incorrect) Early attempts assumed: - Tight loop updating sound frequency - Standard noise generation - Separate sound routine ### POKEY Register Extraction All POKEY writes were extracted from the disassembly: ``` === AUDF1 ($D200) WRITES === Line 2617: LDA RANDOM / AND #$1F / STA AUDF1 <- PRIMARY STREAM SOUND Line 1898: STA AUDF1 (swim sound) Line 2233: STA AUDF1 (death sound) ... === AUDC1 ($D201) WRITES === Line 2112: LDA #$A6 / STA AUDC1 <- STREAM CONTROL ... === AUDCTL ($D208) WRITES === Line 1152: LDA #$00 / STA AUDCTL <- STANDARD 64kHz MODE ``` ### Key Discovery: Write-Only Registers **Critical insight:** POKEY registers are WRITE-ONLY. Reading them returns pot counter values, not the last written value. Early debugging attempts that read AUDCTL showed $FF, but the actual written value was $00. --- ## 5. The Sound-Graphics Linkage Discovery ### The Revelation The stream sound cannot be perfectly isolated because **it is inseparable from the game itself**. Bill Williams created something extraordinary: the sound texture is not just "played" - it is a **byproduct of the game running**. The Display List Interrupt (DLI) that updates the sound frequency ALSO handles: - Fish sprite animation - Color changes - Collision detection - Wave/splash effects - Player positioning ### DLI Handler Analysis (Lines 2604-3034) ```assembly ; Located at $5000 - Main DLI Handler L5000: PHA TXA PHA TYA PHA ; Check if sound enabled LDA L40F8 ; Sound enable flag BNE L500D ; If enabled, continue JMP L53F8 ; If disabled, exit DLI L500D: LDA #$40 STA NMIEN ; Reset NMI ; === SOUND UPDATE (THE STREAM!) === LDA RANDOM ; Hardware random register ($D20A) AND #$1F ; Mask to 0-31 STA AUDF1 ; Update channel 1 frequency CMP #$0E ; If frequency < 14... BCS L5026 LDA RANDOM ; ...also update channel 2 AND #$3F ; Mask to 0-63 STA AUDF2 L5026: ; === GAME STATE CHECKS === LDA L06A2 ; Fish movement flag BEQ L505C ; If zero, skip fish movement code JMP L5406 ; DIFFERENT CODE PATH = DIFFERENT TIMING! L505C: LDA L3FF2 ; Wave counter BNE L5066 ; If wave active, handle wave LDA L3FF3 ; Splash counter BEQ L5069 ; If no splash, continue JMP L5110 ; SPLASH CODE = SPLASH SOUND! ; ... 400+ more lines of game logic ... ``` ### Why The Sound Changes During Gameplay The code path through the DLI varies based on game state: | Game State | Flags Set | Code Path | Sound Character | |------------|-----------|-----------|-----------------| | Idle | All zero | Shortest | Pure stream | | Fish moving | L06A2=1 | Longer | Slightly different texture | | Wave active | L3FF2>0 | Medium | Wave overlay | | Splash | L3FF3>0 | Longest | Splash sound added | **The timing between AUDF1 updates changes based on how much code executes after the sound update.** This creates subtle texture variations that make the stream sound "alive." ### Display List Structure The original Salmon Run display list triggers DLI approximately 26 times per frame: - 3x $F0 instructions (8 blank lines + DLI each) - 23x $A6 instructions (ANTIC Mode 6 + DLI each) This creates a sound update rate of approximately 26 × 60 = 1,560 Hz. --- ## 6. POKEY Configuration Details ### Stream Sound Recipe | Register | Address | Value | Description | |----------|---------|-------|-------------| | AUDCTL | $D208 | $00 | Standard 64kHz polynomial clock | | SKCTL | $D20F | $03 | Enable POKEY | | AUDC1 | $D201 | $A4 | Pure tone (distortion $A), volume 4 | | AUDC2 | $D203 | $A0 | Silent (volume 0, used conditionally) | | AUDF1 | $D200 | RANDOM & $1F | Random 0-31, updated via DLI | | AUDF2 | $D202 | RANDOM & $3F | Random 0-63, conditional | ### AUDC Control Byte Format ``` Bit 7-4: Distortion (poly counter selection) $A = Pure tone (use 17-bit poly for frequency only) $C = Pure noise $8 = Noise Bit 3-0: Volume (0-15) $4 = Volume 4 (moderate) $6 = Volume 6 (louder) ``` ### Minimal Working Code ```assembly ; Initialize POKEY for stream sound LDA #$00 STA AUDCTL ; 64kHz mode LDA #$03 STA SKCTL ; Enable POKEY LDA #$A4 ; Pure tone, volume 4 STA AUDC1 ; DLI Handler (called ~26 times per frame) dli_handler: PHA LDA RANDOM ; $D20A - hardware random AND #$1F ; 0-31 STA AUDF1 ; Update frequency PLA RTI ``` --- ## 7. Additional Sound Effects ### Sounds Identified in Disassembly | Sound | Trigger | POKEY Config | Location | |-------|---------|--------------|----------| | Stream | Always (idle) | AUDC1=$A4, AUDF1=RND&$1F | $500D | | Swim | Fish movement | AUDC1=$A6, varies | $3B45 | | Splash | Water entry | AUDC3=$84, AUDF3=counter | $3BB7 | | Wave | Wave animation | AUDC3=$88, AUDF3=L3FF2 | $3B9E | | Death | Fish caught | AUDC1=$A8, sweep | $3ECD | | Jump | Fish jumping | AUDC4=$A8, AUDF4=table | $50FD | ### Frequency Tables Two frequency tables were found at $3FB1 and $3F8D, used for melodic sound effects (jump, death sequences). --- ## 8. Verification and Reassembly ### Assembly Process ```bash # Assemble ca65 -t atari -o salmon_run_rebuild.o salmon_run_full_disasm.s # Link (custom config for multi-segment output) ld65 -C salmon_rebuild.cfg -o salmon_run_rebuild.xex salmon_run_rebuild.o ``` ### Verification Results Segment-by-segment comparison of reassembled code with original: ``` $3000-$3081: MATCH (130 bytes) $3089-$30BF: MATCH (55 bytes) $30CA-$311E: MATCH (85 bytes) ... [all 44 segments] ... $02E0-$02E1: MATCH (2 bytes) Total: 44/44 segments match completely ``` ### Final Verification ```python # Binary comparison if rebuilt_xex == original_xex: print("SUCCESS! Rebuilt XEX is IDENTICAL to original!") # Result: # Created salmon_run_from_disasm.xex (6953 bytes) # SUCCESS! Rebuilt XEX is IDENTICAL to original! ``` --- ## 9. Website and Documentation ### Tribute Website A comprehensive tribute website was created at `bxfoundry.com/salmonrun/` featuring: - Technical analysis of Bill Williams' sound techniques - Biography of Bill Williams (1960-1998) - Interactive sound demonstrations using POKEY.js emulator - Downloadable files (XEX, disassembly, documentation) - Screenshots from the original game ### Sound Demonstration Implementation ```javascript // POKEY.js AudioWorklet integration async function initAudio() { audioContext = new AudioContext({ sampleRate: 48000 }); await audioContext.audioWorklet.addModule('pokey.js'); pokeyNode = new AudioWorkletNode(audioContext, 'POKEY'); // Configure for stream sound pokeyNode.port.postMessage({ type: 'poke', address: 0xD208, // AUDCTL value: 0x00 // 64kHz mode }); // ... additional configuration } ``` ### Files Produced | File | Description | |------|-------------| | salmon_run_full_disasm.s | Complete 4,443-line disassembly | | salmon_run_from_disasm.xex | Verified reassembled executable | | pokey_writes_analysis.txt | All POKEY register writes extracted | | SALMON_STREAM_SOUND_FINAL.md | Technical recipe document | | REVERSE_ENGINEERING_REPORT.md | This report | --- ## 10. Conclusions ### Technical Achievements 1. **Complete Disassembly:** Successfully disassembled all 6,953 bytes into readable, reassemblable 6502 assembly code. 2. **Sound Recipe Extraction:** Identified exact POKEY parameters: - AUDCTL = $00 (64kHz clock) - AUDC1 = $A4 (pure tone, volume 4) - AUDF1 = RANDOM & $1F (updated via DLI) 3. **Timing Discovery:** The stream sound requires DLI-rate updates (~1,560 Hz), not tight-loop updates. 4. **Sound-Graphics Linkage:** Documented Bill Williams' innovative technique of unifying audio and visual processing in a single interrupt handler. 5. **Verification:** Reassembled code is byte-identical to original, proving disassembly accuracy. ### Historical Significance Bill Williams (1960-1998) was a visionary who understood that the Atari 800 was an instrument, not just a computer. His games featured unified audiovisual experiences where sound emerged from the game's heartbeat rather than being layered on top. The stream sound in Salmon Run is not a "water sound effect" - it is the mathematical byproduct of the game running. The audio and visuals are intertwined at the hardware level, making them inseparable. This explains why the sound has been remembered for 40+ years: it wasn't just good audio design - it was audio as emergent behavior. ### Why This Technique is Brilliant 1. **Memory Efficiency:** No separate sound driver needed 2. **CPU Efficiency:** Sound updates happen "for free" during display processing 3. **Organic Feel:** Sound naturally varies with gameplay 4. **Unreplicable:** You cannot "rip" just the sound - you'd have to rip the entire game ### Lessons for Modern Development Modern game audio is typically a separate subsystem - music and sound effects layered on top of gameplay. Bill Williams demonstrated that audio could be **intrinsic** to game logic itself, creating experiences that feel more alive and organic than the sum of their parts. --- ## Appendix A: Key Memory Locations | Address | Label | Description | |---------|-------|-------------| | $0600-$0626 | Page 6 | Game variables | | $3000-$30FF | | Character set data | | $3420 | | Program entry point | | $3FB1 | | Frequency table 1 | | $3F8D | | Frequency table 2 | | $3FF2 | L3FF2 | Wave counter | | $3FF3 | L3FF3 | Splash counter | | $40F8 | L40F8 | Sound enable flag | | $5000 | | DLI handler entry | | $06A2 | L06A2 | Fish movement flag | | $06AC | L06AC | Fish active flag | ## Appendix B: Build Instructions ### Requirements - cc65 toolchain (ca65, ld65) - atari800 emulator ### Build Commands ```bash # Assemble ca65 -t atari -o salmon_run_rebuild.o salmon_run_full_disasm.s # Link (creates raw binary with fill) ld65 -C salmon_rebuild.cfg -o salmon_run_rebuild.xex salmon_run_rebuild.o # Create proper multi-segment XEX (use Python script) python3 create_xex.py # Test atari800 -xl -run salmon_run_from_disasm.xex ``` --- **Report prepared by Claude Code** **January 17, 2025**