<!-- deft:deposit-link-rewrite v=1 source="content/platforms/2600.md" -->
# Atari 2600 (VCS) Standards

Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.

**⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)

## Hardware Reference

- **CPU**: MOS 6507 (6502 variant), ~1.19 MHz (NTSC), ~1.18 MHz (PAL)
- **RAM**: 128 bytes ($80–$FF) via RIOT (6532)
- **ROM**: 4 KB standard cartridge (2 KB–32 KB+ via bankswitching)
- **Video**: TIA (Television Interface Adapter) — no framebuffer
- **TIA clock**: 3× CPU clock (228 color clocks/scanline, 76 CPU cycles/scanline)
- **Display**: 160×192 visible pixels (NTSC), 160×242 (PAL)
- **Sprites**: 2 players (8 px), 2 missiles (1 px), 1 ball (1 px)
- **Playfield**: 20 bits wide, mirrored or copied to fill 40 bits (160 px)
- **Audio**: 2 channels, 4-bit volume, 4-bit frequency, 4-bit waveform

## Toolchain

### Assembler
- ! Use [DASM](https://dasm-assembler.github.io/) macro assembler
- ! Include `vcs.h` and `macro.h` in all source files
- ! Assemble with `-f3` (raw binary, no header)
- ? Use VS Code with the `dasm-macro-assembler` extension

### Emulator
- ! Use [Stella](https://stella-emu.github.io/) as the primary development emulator
- ! Enable Stella's developer settings (jitter/roll simulation) during testing
- ~ Use Stella's built-in debugger for cycle counting and scanline verification

### Alternative Languages
- ? Use [batari Basic](https://www.randomterrain.com/atari-2600-memories-batari-basic-commands.html) for rapid prototyping
- ? Use DPC+ / CDFJ coprocessor via Harmony/UnoCart for advanced projects

## Standards

### Initialization
- ! Clear all RAM and TIA registers at startup (use `CLEAN_START` macro from `macro.h`)
- ! Set the stack pointer explicitly (`LDX #$FF; TXS`)
- ⊗ Assume any known state of RAM, TIA, or CPU registers at power-on

### Frame Structure (NTSC)
- ! Produce exactly 262 scanlines per frame (3 VSYNC + 37 VBLANK + 192 visible + 30 overscan)
- ! Begin each frame with 3 scanlines of VSYNC (`STA VSYNC` with bit 1 set)
- ! Maintain a consistent scanline count across all code paths in every frame
- ≉ Deviate from 262 lines — variable counts cause visible rolling/jitter on real hardware

### Frame Structure (PAL)
- ! Produce exactly 312 scanlines per frame (3 VSYNC + 45 VBLANK + 242 visible + 22 overscan)
- ! Always produce an **even** number of total scanlines (odd counts lose color on PAL TVs)
- ~ Provide separate NTSC and PAL builds with appropriate color palettes

### TV Compatibility
- ! Target 262 scanlines (NTSC) or 312 scanlines (PAL) exactly
- ~ Keep visible content within the safe area (avoid first/last ~8 scanlines of the picture region)
- ~ Test with Stella's jitter simulation enabled to catch timing issues before hardware testing
- ~ Test on real hardware (CRT television) before release

### Kernel (Display Loop)
- ! Account for exactly 76 CPU cycles per scanline in all kernel code
- ! Use `STA WSYNC` to synchronize to scanline boundaries
- ! Document cycle counts inline for every instruction in tight kernel sections
- ~ Use the Y register as the scanline counter (required for indirect indexed addressing `LDA (zp),Y`)
- ~ Store sprite data bottom-up in ROM for natural kernel scanning order
- ≉ Perform game logic during the visible kernel — use VBLANK and overscan periods instead
- ⊗ Exceed 76 cycles between WSYNC strobes without accounting for the extra scanline(s)

### Cycle Counting
- ! Count and annotate CPU cycles in all time-critical code (kernel, positioning routines)
- ! Account for the extra cycle on page-boundary crossings with `(Indirect),Y` addressing
- ~ Align data tables to avoid page-boundary penalties in kernel loops
- ~ Use constant-cycle-count code paths (pad shorter branches with NOPs or `SLEEP` macro)

### Horizontal Positioning
- ! Issue `STA HMOVE` immediately after `STA WSYNC` (within HBLANK)
- ! Wait at least 24 CPU cycles after HMOVE before modifying any motion registers (HMPx/HMMx/HMBL)
- ! Clear motion registers with `STA HMCLR` before setting new values
- ~ Use the divide-by-15 loop technique for coarse positioning with HMOVE fine adjustment
- ? Use `STA RESP0,X` timing loop for multi-object positioning

### Vertical Positioning
- ~ Use the "skipdraw" technique: compare scanline counter to sprite Y position in kernel
- ~ Pre-calculate sprite pointers during VBLANK to minimize kernel overhead

### Memory Management
- ! Treat all 128 bytes of RAM as precious — plan allocation carefully
- ~ Use zero-page addressing ($80–$FF) for all variables (saves 1 byte and 1 cycle vs. absolute)
- ~ Reuse RAM locations across non-overlapping game states
- ⊗ Use the stack for deep call chains — the stack shares the 128-byte RAM space

### ROM Management
- ! Place reset vector at $FFFC–$FFFD and interrupt vector at $FFFE–$FFFF
- ! For ROMs > 4 KB, use well-known bankswitching schemes (F8, F6, F4, etc.)
- ~ Keep the most time-critical code (kernel) in a single bank to avoid mid-scanline bank switches
- ~ Place lookup tables in the same bank as the code that references them
- ? Use Superchip (128 bytes extra RAM) for games needing more working memory

### Code Quality
- ! Comment all TIA register writes with the purpose and expected timing
- ~ Label all major sections: VSYNC, VBLANK, Kernel, Overscan
- ~ Use DASM macros and `REPEAT`/`REPEND` to reduce repetitive code
- ~ Use meaningful labels and constants from `vcs.h` (never write to magic addresses directly)
- ≉ Use undocumented 6502 opcodes unless targeting specific hardware and documenting the risk

### Sound
- ! Turn off sound by setting `AUDV0`/`AUDV1` to 0 (not `AUDC0`/`AUDC1` — the latter causes artifacts on SECAM)
- ~ Update audio registers during VBLANK or overscan, not mid-kernel
- ? Use a music sequencer driver for multi-voice music

### Testing & Debugging
- ! Verify scanline count in Stella (Developer → Video → Frame Stats overlay)
- ! Test both player difficulty switch positions (A/B)
- ! Test color/BW switch behavior
- ~ Test with Stella's phosphor mode for games using sprite flicker
- ~ Test on real hardware with Harmony/UnoCart cartridge before physical release
- ~ Disassemble and study classic games for proven techniques (use DiStella)
- ? Test on multiple TIA revisions if targeting broad hardware compatibility

### Anti-Patterns
- ⊗ Write to TIA registers at unpredictable or variable cycle positions within a scanline
- ⊗ Assume the electron beam position without explicit WSYNC synchronization
- ⊗ Ignore HMOVE timing constraints (causes visual corruption / "HMOVE bars")
- ⊗ Use absolute addressing for variables in the $80–$FF range (wastes ROM and cycles)
- ⊗ Produce ROMs with sizes that don't match standard cartridge formats (2K, 4K, 8K, 16K, 32K)
- ⊗ Hard-code color values without considering NTSC/PAL/SECAM palette differences

## Resources

- [Stella Programmer's Guide](https://alienbill.com/2600/101/docs/stella.html) — official TIA/RIOT register reference
- [6502.org](http://www.6502.org/) — 6502 instruction set, addressing modes, and tools
- [AtariAge 2600 Programming Forum](https://forums.atariage.com/forum/50-atari-2600-programming/) — community support
- [Nick Bensema's Guide to Cycle Counting](https://www.randomterrain.com/atari-2600-memories-guide-to-cycle-counting.html)
- [Andrew Davie's 2600 Programming for Newbies](https://www.randomterrain.com/atari-2600-memories-tutorial-andrew-davie-01.html)
- [2600 Advanced Programming Guide](https://www.qotile.net/minidig/docs/2600_advanced_prog_guide.txt) — bankswitching, skipdraw, HMOVE tricks
- [TIA Color Charts](https://www.randomterrain.com/atari-2600-memories-tia-color-charts.html) — NTSC/PAL/SECAM palettes
- [Cart Sizes and Bankswitching Methods](https://www.randomterrain.com/atari-2600-memories-tutorial-andrew-davie-25.html) — Kevin Horton's reference
