# Technical Plan — Skirmish (C64 6502 Assembly) --- ## 1. Architecture Overview ``` ┌─────────────────────────────────────┐ │ Main Loop (Interrupt) │ ├─────────────────────────────────────┤ │ 1. Read Input │ │ 2. Update Game State │ │ 3. Update Sprites/Screen │ │ 4. Check Win/Lose │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ Game State Machine │ ├─────────────────────────────────────┤ │ WAITING_FOR_INPUT (player turn) │ │ EXECUTING_MOVE (animate & check) │ │ ENEMY_TURN (AI + animation) │ │ CHECK_WIN (end condition) │ └─────────────────────────────────────┘ ``` --- ## 2. Memory Layout (64 KB) ``` $0000–$00FF: Zero page (save $F0–$FF for OS) $0100–$01FF: Stack $0200–$1FFF: BASIC ROM area (will be replaced by our code) $2000–$2FFF: Screen RAM (1000 bytes @ $2000) $3000–$3FFF: Color RAM (1000 bytes @ $3000) $4000–$5FFF: Game code + data $6000–$CFFF: Free (more game code, sprites, etc.) $D000–$DFFF: I/O (VIC-II, SID, CIA) — READ-ONLY, memory-mapped $E000–$FFFF: Kernal ROM (on-chip, can be banked out for RAM) Key allocation: – Code (main + subroutines): $4000–$5FFF (~8 KB) – Sprite data: $5800–$5FFF (shared with code, or higher if we're careful) – Game state: $0200–$02FF (zero page + 256 bytes) – Lookup tables: $6000–$7FFF (grids, distances, etc.) ``` --- ## 3. Zero-Page Variables (Critical Path) ```asm $F0–$FE: Temporary registers (reusable per subroutine) Game State Variables: $30 INPUT_KEY ; Last key pressed ($00 = none) $31 GAME_STATE ; 0=INPUT, 1=MOVE, 2=ENEMY, 3=CHECK $32 SELECTED_UNIT ; 0–2 (player unit index) or 255 (none) $33 TURN_COUNTER ; Turn number (0–255+) $34 PLAYER_UNITS ; Count of alive player units (0–3) $35 ENEMY_UNITS ; Count of alive enemy units (0–3) Unit State Array (6 units × 4 bytes each = 24 bytes, $40–$57): Each unit: +0 X position (0–7) +1 Y position (0–7) +2 HP (0–3, or 0 = dead) +3 Team (0 = player, 1 = enemy) Layout: $40–$43: Unit 0 (player) $44–$47: Unit 1 (player) $48–$4B: Unit 2 (player) $4C–$4F: Unit 3 (enemy) $50–$53: Unit 4 (enemy) $54–$57: Unit 5 (enemy) $58 MOVE_DEST_X ; Target X for current move $59 MOVE_DEST_Y ; Target Y for current move $5A MOVE_VALID ; Flag: move is valid (0/1) ``` --- ## 4. Grid Representation **Physical Grid:** 8×8 = 64 cells **Occupancy Array:** $6000–$603F (one byte per cell, index = Y*8 + X) - $FF = empty - 0–5 = unit index (0–5) On each frame, rebuild occupancy from unit state array. **Sprite Rendering:** - Each unit is a single C64 sprite (13×21 pixels, or 12×21) - Position calculation: `X_pixel = (unit_x * 40) + offset`, `Y_pixel = (unit_y * 25) + offset` - Adjust offsets to center sprite in grid cell --- ## 5. Main Game Loop ```asm ; Interrupt handler (triggered 60x/sec on NTSC, 50x on PAL) IRQ_HANDLER: Save registers (A, X, Y) ; 1. Read input JSR INPUT_READ ; 2. Update game state CASE GAME_STATE 0 => Player input → select/move/attack 1 => Animate move, resolve collision/damage 2 => Enemy AI, animate enemy moves 3 => Check win/lose, advance state ; 3. Render sprites & text JSR SPRITE_UPDATE JSR TEXT_UPDATE Restore registers RTI ``` --- ## 6. Subroutine Checklist ### Input & Selection | Name | In | Out | Cost | |------|----|----|------| | `INPUT_READ` | — | `$30` (key code) | 10 cyc | | `UNIT_SELECT` | `$30` (key) | `$32` (unit) | 20 cyc | | `UNIT_MOVE` | `$32`, `$58`, `$59` | `$5A` (valid) | 40 cyc | | `UNIT_ATTACK` | `$32`, target unit | HP update | 30 cyc | ### Game Logic | Name | In | Out | Cost | |------|----|----|------| | `FIND_CLOSEST_ENEMY` | `$32` (player unit) | `$F0` (enemy idx) | 50 cyc | | `ENEMY_MOVE` | `$F0` (enemy idx) | Update unit pos | 80 cyc | | `ENEMY_AI` | — | Execute all enemies | 300 cyc | | `CHECK_WIN` | — | Set `GAME_STATE` | 20 cyc | ### Rendering | Name | In | Out | Cost | |------|----|----|------| | `SPRITE_UPDATE` | Unit state | VIC-II regs | 100 cyc | | `TEXT_UPDATE` | Game state | Screen RAM | 50 cyc | | `REBUILD_OCCUPANCY` | Unit state | Occupancy array | 60 cyc | --- ## 7. Key Algorithms ### Manhattan Distance ```asm ; Input: X in $F0, Y in $F1 (target); A (own X), $F2 (own Y) ; Output: A (distance) MANHATTAN: SEC SBC $F0 ; A = own_x - target_x BPL + ; If positive, skip EOR #$FF ADC #$01 ; Negate (two's complement) + STA $F3 ; Save |dx| LDA $F2 SEC SBC $F1 ; A = own_y - target_y BPL + ; If positive, skip EOR #$FF ADC #$01 ; Negate + CLC ADC $F3 ; A = |dx| + |dy| RTS ``` ### Grid Occupancy Check ```asm ; Input: X in $F0, Y in $F1 ; Output: A = unit index (or $FF if empty) GRID_OCCUPIED: LDA $F1 ASL ASL ASL ; A = Y * 8 CLC ADC $F0 ; A = Y*8 + X TAX LDA $6000,X ; Lookup in occupancy array RTS ``` --- ## 8. State Transitions ``` WAITING_FOR_INPUT ↓ [Player selects unit] ↓ [Player moves/attacks] EXECUTING_MOVE ↓ [Move resolves, damage applied] ENEMY_TURN ↓ [Each enemy acts] ↓ [Wait for animations] CHECK_WIN ↓ [If enemy count = 0 or player count = 0] GAME_OVER (win/lose) ↓ [Display result, wait for restart key] WAITING_FOR_INPUT [loop] ``` --- ## 9. Build & Run **Development tools:** - **cc65** (C-to-6502 compiler, includes assembler `ca65` and linker `ld65`) - **VICE** (Commodore 64 emulator, with debugger) - **Makefile** or shell script to assemble & link **Command sequence:** ```bash ca65 main.asm -o main.o # Assemble ld65 main.o -C c64.cfg -o game.prg # Link x64sc game.prg # Run in VICE ``` --- ## 10. Implementation Order (MVP Priority) 1. **Kernel & Interrupt Handler** — Get IRQ handler ticking 2. **Sprite Rendering** — Display 6 units on screen 3. **Grid & Occupancy** — Manage unit positions 4. **Input & Unit Selection** — Player can select a unit 5. **Movement & Collision** — Player can move units (no combat yet) 6. **Combat & Damage** — Attack resolves HP 7. **Enemy AI** — Simple greedy pathfinding 8. **Win/Lose Detection** — Game ends correctly 9. **Polish** — Text output, feedback, cleanup --- ## 11. Known Pitfalls & Mitigations | Risk | Mitigation | |------|-----------| | Interrupt handler exceeds raster line | Use raster-stable jitter removal; keep subroutines cycle-predictable | | Sprite overlap (>8 on screen) | Multiplex later; for now, 6 sprites fit without overlap | | Occupancy array corruption | Rebuild every frame from unit state; don't write directly | | AI hangs/slowdown | Limit enemy AI to 1 move each; use lookup tables for distance | | Collision bugs | Test grid occupancy before allowing move; confirm unit dies at 0 HP | --- ## 12. File Structure ``` skirmish-game/ src/ main.asm # Entry point, IRQ handler, main loop input.asm # Input polling, key handling game-logic.asm # Unit selection, movement, combat enemy-ai.asm # Pathfinding, enemy turn render.asm # Sprite updates, text output data.asm # Lookup tables, sprite/tile data assets/ sprites.chr # C64 sprite design (binary) tiles.chr # Tile data (if used) docs/ GDD.md # Game design (this file) TECHNICAL-PLAN.md # Technical details (this file) C64-CHEATSHEET.md # Quick register/memory reference Makefile # Build script c64.cfg # cc65 linker config (if using cc65) README.md # Build & run instructions ``` --- ## 13. Next Steps 1. **Choose assembler:** cc65 + ca65/ld65 (recommended) or ACME 2. **Set up build environment** on your machine 3. **Write skeleton main.asm** (entry point + IRQ stub) 4. **Create sprite test** (render 6 colored squares) 5. **Implement zero-page variables** 6. **Add input handler** (keyboard or joystick) 7. **Iterate gameplay logic**