A persistent turtle racing simulation with real-time web multiplayer.
TurboShells uses a Hexagonal Architecture to decouple simulation from rendering:
┌─────────────────────────────────────────────────────────────┐
│ Frontend (React) │
│ ┌─────────────┐ ┌───────────────┐ ┌───────────────────┐ │
│ │ useRaceSocket│ │ Paper Doll │ │ RaceStage (PixiJS)│ │
│ │ (30Hz) │ │ Assembler │ │ Interpolation │ │
│ └──────┬──────┘ └───────────────┘ └───────────────────┘ │
└─────────┼───────────────────────────────────────────────────┘
│ WebSocket
┌─────────┴───────────────────────────────────────────────────┐
│ Backend (FastAPI) │
│ ┌─────────────────┐ ┌────────────────┐ ┌──────────────┐ │
│ │ RaceOrchestrator│ │ConnectionManager│ │ REST /api/* │ │
│ │ 60Hz→30Hz │ │ Zombie Cleanup │ │ Roster │ │
│ └────────┬────────┘ └────────────────┘ └──────────────┘ │
└───────────┼─────────────────────────────────────────────────┘
│
┌───────────┴─────────────────────────────────────────────────┐
│ Core Engine (Python) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ RaceEngine │ │ TurtleState │ │ GenomeCodec │ │
│ │ (60Hz) │ │ (Pydantic) │ │ B1-S2-P0-CFF0000 │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ │ │
│ ┌────────────────────────┴────────────────────────────────┐│
│ │ SQLite (turboshells.db) ││
│ │ TurtleDB │ RaceResultDB ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
- Python 3.12+
- Node.js 18+
# Install dependencies
pip install -e ".[server]"
# Start server
uvicorn src.server.app:app --port 8765cd web
npm install
npm run devOpen http://localhost:5173 and click Start Race!
src/
├── engine/ # Headless simulation core
│ ├── race_engine.py # 60Hz tick-based physics
│ ├── models.py # Pydantic: TurtleState, RaceSnapshot
│ ├── genome_codec.py # Paper Doll encoding
│ └── persistence.py # SQLModel: TurtleDB, RaceResultDB
├── server/ # FastAPI WebSocket bridge
│ ├── app.py # CORS, lifespan, routes
│ ├── websocket_manager.py
│ ├── race_orchestrator.py
│ └── routes/
│ ├── race.py # /ws/race WebSocket
│ └── roster.py # /api/turtles REST
└── game/ # Original game entities
web/
├── src/
│ ├── hooks/ # useRaceSocket
│ ├── lib/ # paperDoll, interpolation
│ ├── components/ # RaceStage (PixiJS)
│ └── types/ # TypeScript interfaces
└── vite.config.ts
// Server → Client (30Hz)
{"tick": 150, "turtles": [...], "finished": false}
// Client → Server
{"action": "start"}
{"action": "stop"}| Endpoint | Method | Description |
|---|---|---|
/api/turtles |
GET | List all turtles |
/api/turtles |
POST | Create turtle |
/api/history |
GET | Race history |
/api/stats/{id} |
GET | Turtle statistics |
Turtles are rendered from a compact genome string:
B1-S2-P0-CFF0000
│ │ │ └─ Color (hex)
│ │ └──── Pattern type
│ └─────── Shell type
└────────── Body type
The frontend parses this into layered sprites with dynamic tinting.
| Metric | Value |
|---|---|
| Physics tick rate | 60 Hz |
| Network broadcast | 30 Hz |
| JSON per turtle | ~172 bytes |
| Interpolation | Linear lerp |
# Backend tests
pytest tests/ -v
# Frontend build verification
cd web && npm run buildMIT