Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8d146a0
Wire core gameplay loop: attack pacing, MIRV, smart bombs, fliers, me…
CAG07 Jul 3, 2026
3b77f0f
Add rendering pipeline: native 256x231 surface, upscaling, HUD, controls
CAG07 Jul 3, 2026
0a67718
Add scoring multiplier and fix permanent city loss; count-up tally sc…
CAG07 Jul 3, 2026
bac8f4a
Add procedural POKEY-style audio synthesis (numpy)
CAG07 Jul 3, 2026
1052e05
Split application controller out of main.py into src/app.py
CAG07 Jul 3, 2026
6887d6a
Replace approximated wave tables with real wave-guide data
CAG07 Jul 3, 2026
43ebcd1
Rework silo/city terrain to match reference arcade screenshots
CAG07 Jul 4, 2026
a23202c
Wire attract-mode autoplay demo and high-score table into the app
CAG07 Jul 4, 2026
be80414
Move attract-mode high-score table up to mid-screen
CAG07 Jul 4, 2026
9755df2
Add headless 20-wave simulation test (SPEC.md acceptance criteria)
CAG07 Jul 4, 2026
59b0a59
Close SPEC.md gaps: operator options, wave intro, non-blocking initia…
CAG07 Jul 4, 2026
93a16f1
Add edge-case coverage for city/defense/missile managers; remove dead…
CAG07 Jul 4, 2026
787a2fd
Fix wave 1+ difficulty: ICBM "speed" was a velocity, should be a delay
CAG07 Jul 4, 2026
e7031b1
Redesign flier sprite as a jet silhouette instead of a plain triangle
CAG07 Jul 4, 2026
d826729
Widen native playfield to 410x231 (16:9-ish) for modern monitors
CAG07 Jul 4, 2026
bdb3af9
Fix invisible destroyed-city/silo rendering; mouse buttons -> 3 silos
CAG07 Jul 5, 2026
37ab189
Fix "destroyed with no missile around" and MIRVs on wave 1
CAG07 Jul 5, 2026
2d21ceb
Add persistent ground cratering from any explosion touching the ground
CAG07 Jul 5, 2026
71627b8
Fix the real cause of "damage out of nowhere": intercepted ICBMs
CAG07 Jul 5, 2026
6a5af21
Add keyboard input for high-score initials entry
CAG07 Jul 5, 2026
3e5d9dd
Slow down and smooth out the wave difficulty ramp per playtest feedback
CAG07 Jul 5, 2026
0f86d44
Fix keyboard input for high-score initials (it silently did nothing)
CAG07 Jul 5, 2026
996d628
Add sounds
CAG07 Jul 6, 2026
57957d7
Merge branch 'main' into fable-completion
CAG07 Jul 6, 2026
f9a86ff
Potential fix for pull request finding
CAG07 Jul 6, 2026
e96cee8
Potential fix for pull request finding
CAG07 Jul 6, 2026
d2a2e5f
Potential fix for pull request finding
CAG07 Jul 6, 2026
fccb7d9
Potential fix for pull request finding
CAG07 Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
__pycache__/
*.pyc
.pytest_cache/
scores.json
.coverage
scores.json

# Claude
SPEC.md
.claude
7 changes: 5 additions & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ verify_ssl = true
name = "pypi"

[packages]
pygame = "==2.0.1"
pygame = ">=2.5.0,<3.0.0"
numpy = ">=1.24.0"

[dev-packages]
pytest = ">=7.0.0"
pytest-cov = ">=4.0.0"

[requires]
python_version = "3.10"
python_version = "3.11"
146 changes: 81 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
# Missile Command

A faithful recreation of the 1980 Atari arcade game **Missile Command**.
A faithful recreation of the 1980 Atari arcade game **Missile Command**, built
against the annotated 6502 disassembly of the rev 3 ROMs.

![Game Screenshot](missile-command-arcade.gif)
![Missile Command](missile-command-arcade.gif)

## Overview

Defend cities from nuclear attack by firing counter-missiles (ABMs) from three silos. The game faithfully replicates the original arcade mechanics including:
Defend six cities from nuclear attack by firing counter-missiles (ABMs) from
three silos. The game reproduces the original arcade's core mechanics:

- 60Hz game loop with IRQ-driven architecture
- Slot-based missile system (8 ABM, 8 ICBM, 1 flier, 20 explosions)
- Fixed-point 8.8 math for missile movement
- Octagonal explosions with 3/8 slope
- Smart bombs with evasive movement
- MIRV splitting logic
- Attack wave pacing and limitations
- Authentic scoring and bonus city system
- 60Hz game loop with simulated 240Hz IRQ timing
- Slot-based object tables (8 ABM, 8 ICBM, 1 flier, 20 explosions in 5 groups of 4)
- Fixed-point 8.8 missile movement (not Bresenham lines)
- Octagonal explosions (corner-chamfered square), color-cycled at 30Hz
- Smart bombs with evasive movement, MIRV splitting, bomber/satellite fliers
- Attack pacing, mercy rule, and scoring multiplier matching the wave guide
Comment thread
Copilot marked this conversation as resolved.
- Procedural POKEY-style audio synthesis (no ROM samples)
- Attract-mode autoplay demo and mouse-driven high-score initials entry

## Installation

### Requirements
- Python 3.8 or higher
- pygame 2.5.0 or higher
- Python 3.11 or higher
- pygame 2.5.0+ and numpy 1.24+ (see `requirements.txt`)

### Setup
```bash
Expand All @@ -32,57 +34,91 @@ pip install -r requirements.txt

### Run
```bash
python missile-defense.py
python main.py
```

## Controls

### Keyboard
- **1**: Start 1-player game
- **P**: Pause/Unpause
- **ESC**: Exit game
- **A / S / D** or **1 / 2 / 3**: fire from left / center / right silo
- **1** (from the attract screen): start a game
- **F11**: toggle fullscreen
- **ESC**: quit
- On the high-score initials screen: **Left / Right** cycles the highlighted
character, **Return** or **Space** confirms it and advances to the next slot

### Mouse
- **Mouse Movement**: Move crosshairs
- **Left Button**: Fire from left silo
- **Middle Button**: Fire from center silo
- **Right Button**: Fire from right silo
- **Relative motion**: move the crosshair (trackball emulation)
- **Left / Middle / Right click**: fire from the left / center / right silo,
matching the arcade cabinet's 3 dedicated fire buttons (not a
proximity/nearest-silo auto-select)
- On the high-score initials screen: move the mouse to scrub the highlighted
letter, left-click to confirm each of the 3 initials

## Gameplay

### Objective
Defend your cities from incoming ICBMs, bombers, satellites, and smart bombs. The game ends when all cities are destroyed.
Defend your cities from incoming ICBMs, smart bombs, bombers, and satellites.
The game ends when every city (and any banked bonus cities) are gone.

### Missile Silos
- **3 silos**: Left, Center, Right
- **10 ABMs per silo** (restored each wave)
- **Center silo**: Faster missiles (7 units/frame)
- **Side silos**: Slower missiles (3 units/frame)
- **Maximum 8 ABMs** in flight at once
- **3 silos**: Alpha (left), Delta (center), Omega (right)
- **10 ABMs per silo**, restored at the start of each wave
- **Delta (center)** fires at 7 units/frame; **Alpha/Omega (side)** fire at 3
units/frame, making the center silo best for last-second intercepts
- **Maximum 8 ABMs** in flight at once across all silos
- An empty silo plays a distinct "can't fire" sound instead of launching
- The HUD shows a "LOW" banner once any silo drops to 3 or fewer ABMs, and
"OUT" once one is empty

### Scoring
| Target | Points |
|--------|--------|
| ICBM | 25 |
| Bomber/Satellite | 100 |
| ICBM / warhead | 25 |
| Bomber / Satellite | 100 |
| Smart Bomb | 125 |
| Unfired ABM (wave end) | 5 |
| Surviving City (wave end) | 100 |
| Surviving city (wave end) | 100 |

**Scoring Multiplier**: Increases every other wave (1x → 2x → 3x → 4x → 5x → 6x at wave 11+)
All points are multiplied by the current scoring multiplier: 1x (waves 1–2),
2x (3–4), 3x (5–6), 4x (7–8), 5x (9–10), 6x (wave 11+). The wave-end tally
screen counts up the bonus tick by tick before the next wave begins.

### Bonus Cities
- Awarded every 10,000 points (default, configurable)
- Can accumulate up to 255 bonus cities
- Randomly placed when cities are destroyed

### Game Modes
- **Marathon Mode**: 6 initial cities, bonus every 10K points
- **Tournament Mode**: 6 initial cities, no bonus cities
- Awarded every 10,000 points by default (configurable via `--tournament` to
disable them entirely)
- Destroyed cities stay destroyed between waves — only silos are restored
each wave — until a banked bonus city repairs a random crater
- The banked bonus-city count is an 8-bit value that wraps at 256, matching
the original hardware

### Attack Waves
- ICBM counts, speeds, smart-bomb introduction (wave 6), and flier cooldown /
altitude tables are taken from the disassembly's wave guide, not
approximated
- New attacks are paced by the highest in-flight missile's altitude
(`202 - 2 * wave_number`, floor 180) and launch in batches
- ICBMs can MIRV-split mid-flight per the altitude-scan conditions at
`$5379`/`$56d1`: an eligible missile must be in the 128–159 altitude band
with nothing already seen above 159, and slots/wave-budget must remain
- Smart bombs move like ordinary missiles until an explosion is nearby, then
evade without changing target; capped at 2 on screen at once
- Fliers (bomber or satellite) appear from wave 2, fly at a per-wave altitude
band, and periodically release ICBMs of their own
- **Mercy rule**: the player never loses more than 3 cities in a single wave;
if that cap is hit and no ABMs remain, the wave ends immediately

### Explosions
- Octagons (not circles) — max radius 13
- Expand, hold, then contract; 20 slots in 5 groups of 4, one group updated
per frame to spread the per-frame cost
- Collision against ICBMs/smart bombs is checked only when a group updates
(every 5 frames); ABMs pass through explosions unharmed; no collision below
screen line 33

## Command Line Options
```bash
python missile-defense.py [OPTIONS]
python main.py [OPTIONS]

Options:
--fullscreen Launch in fullscreen mode
Expand Down Expand Up @@ -184,54 +220,34 @@ mypy src/

## References

This implementation is based on the official **Missile Command Disassembly** (revision 3 ROMs):
This implementation is based on the official **Missile Command Disassembly**
(revision 3 ROMs):
- [6502 Disassembly Project](https://6502disassembly.com/va-missile-command/)
- [Attack Wave Guide](https://6502disassembly.com/va-missile-command/wave-guide.html)

### Original Game
- **Title**: Missile Command
- **Developer**: Atari, Inc.
- **Year**: 1980
- **Platform**: Arcade
- **CPU**: 6502
- **Designer**: Dave Theurer

### Key Disassembly References
- Attack wave logic: `$5791`
- MIRV conditions: `$5379/$56d1`
- MIRV conditions: `$5379`/`$56d1`
- Wave end check: `$59fa`
- Score deferral: `$50ff`
- Bonus city table: `$6082`
- Initial city table: `$5b08`

### Additional Resources
- [Original Source Code](https://github.com/historicalsource/missile-command) (rev 1)
- [MAME Emulator](https://www.mamedev.org/)
- [Atari Service Manuals](http://arcarc.xmission.com/)

## Known Differences from Original

This recreation targets **revision 3** behavior. Notable differences from revision 2:
- ✓ Fixed "810 bug" (176 bonus cities at 810K)
- ✓ Fixed wave 255/256 multiplier overflow
- ✓ Fixed attack pacing at wave 102+

## License

This is a recreation for educational purposes. The original Missile Command is copyright 1980 Atari, Inc.

## Contributing

Contributions welcome! Please:
1. Reference the disassembly documentation
2. Add tests for new features
3. Maintain 60Hz timing accuracy
4. Follow existing code style
This is a recreation for educational purposes. The original Missile Command
is copyright 1980 Atari, Inc.

## Acknowledgments

- Dave Theurer — Original game designer
- Andy McFadden — Disassembly documentation
- MAME Team — Emulation and preservation
- Atari — Original game
- Based on initial work by [BekBrace](https://github.com/BekBrace)

Expand Down
28 changes: 0 additions & 28 deletions city.py

This file was deleted.

50 changes: 0 additions & 50 deletions config.py

This file was deleted.

13 changes: 13 additions & 0 deletions data/sfx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Sound effect assets for POKEY chip audio simulation.

**None of these files are required.** `AudioManager` procedurally
synthesizes every sound with numpy (`src/ui/synth.py`) at startup, so
the game is fully playable with this directory empty. Drop a real
`.wav` file in here (matching a name below) to override the
synthesized version for that specific sound -- loaded files always
take priority over synthesis.

## Channel Assignments

The original arcade uses 4 independent sound channels (POKEY chip).
Expand Down Expand Up @@ -33,6 +40,12 @@ The original arcade uses 4 independent sound channels (POKEY chip).
| `flier.wav` | Continuous sound when flier active |
| `smart_bomb.wav` | Continuous sound when smart bomb active |

### Not in the original channel table
| File | Description |
|------|-------------|
| `roll_up_2.wav` | One tick per surviving city counted up on the tally screen (played first) |
| `roll_up_1.wav` | One tick per unfired ABM counted up on the tally screen (played after cities) |

## Audio Format

- WAV format (pygame native support)
Expand Down
Binary file added data/sfx/bonus_city.wav
Binary file not shown.
Binary file added data/sfx/explosion.wav
Binary file not shown.
Binary file added data/sfx/game_over.wav
Binary file not shown.
Binary file added data/sfx/roll_up_1.wav
Binary file not shown.
Binary file added data/sfx/roll_up_2.wav
Binary file not shown.
Binary file added data/sfx/start_wave.wav
Binary file not shown.
Loading
Loading