DevilsDozen / docs /ARCHITECTURE.md
legomaheggo's picture
docs: Update architecture/UI docs and add new game mode template
b24ad7d
# Devil's Dozen - Technical Architecture
## 1. System Overview
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CLIENT (Browser) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Streamlit Frontend β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ Dice Tray β”‚ β”‚ Scoreboard β”‚ β”‚ Turn Controls β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Game Engine β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ PeasantsGamble β”‚ β”‚ AlchemistsAscent β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ Engine β”‚ β”‚ Engine β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SUPABASE β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Lobbies β”‚ β”‚ Players β”‚ β”‚ Game State β”‚ β”‚
β”‚ β”‚ Table β”‚ β”‚ Table β”‚ β”‚ Table β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Realtime Channels β”‚ β”‚
β”‚ β”‚ (WebSocket Push Notifications) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
---
## 2. Module Dependency Graph
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ config β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ β”‚
β–Ό β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ engine β”‚ β”‚ database β”‚ β”‚ realtime β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
β”‚ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ui β”‚
β”‚ (imports engine, database, realtime)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
**Dependency Rules**:
- `engine` has ZERO external dependencies (pure Python)
- `database` depends only on `config` for settings
- `realtime` depends on `database` for client access
- `ui` imports from all other modules
- No circular dependencies allowed
---
## 3. Data Flow
### 3.1 Turn Flow (Happy Path)
```
Player A Supabase Player B
β”‚ β”‚ β”‚
│──── Click "Roll" ─────────► β”‚
β”‚ β”‚ β”‚
β”‚ [Engine calculates β”‚ β”‚
β”‚ scoring locally] β”‚ β”‚
β”‚ β”‚ β”‚
│──── UPDATE game_state ────► β”‚
β”‚ (dice, turn_score) β”‚ β”‚
β”‚ │── Realtime broadcast ────►│
β”‚ β”‚ β”‚
│◄─── Confirmation ─────────│ β”‚
β”‚ β”‚ β”‚
│──── Click "Bank" ─────────► β”‚
β”‚ β”‚ β”‚
│──── UPDATE players ───────► β”‚
β”‚ (add turn_score) β”‚ β”‚
β”‚ β”‚ β”‚
│──── UPDATE lobbies ───────► β”‚
β”‚ (next turn_index) β”‚ β”‚
β”‚ │── Realtime broadcast ────►│
```
### 3.2 Bust Flow
```
Player A Supabase Player B
β”‚ β”‚ β”‚
│──── Click "Roll" ─────────► β”‚
β”‚ β”‚ β”‚
β”‚ [Engine: no scoring β”‚ β”‚
β”‚ dice = BUST] β”‚ β”‚
β”‚ β”‚ β”‚
│──── UPDATE game_state ────► β”‚
β”‚ (bust: true) β”‚ β”‚
β”‚ │── Realtime broadcast ────►│
β”‚ β”‚ (bust event) β”‚
β”‚ β”‚ β”‚
│──── UPDATE lobbies ───────► β”‚
β”‚ (next turn_index) β”‚ β”‚
β”‚ β”‚ β”‚
│◄─── Now it's Player B ────│◄── Player B's turn now ──│
```
---
## 4. Database Schema
### 4.1 Tables
```sql
-- Lobby: Game room containing players
CREATE TABLE lobbies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(6) UNIQUE NOT NULL, -- Shareable join code
game_mode VARCHAR(20) NOT NULL, -- 'peasants_gamble' | 'alchemists_ascent'
win_condition INT NOT NULL, -- Target score to win
current_turn_index INT DEFAULT 0, -- Index into player turn order
status VARCHAR(20) DEFAULT 'waiting', -- waiting | active | finished
winner_id UUID REFERENCES players(id), -- Set when game ends
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Player: Participant in a game
CREATE TABLE players (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
lobby_id UUID REFERENCES lobbies(id) ON DELETE CASCADE,
username VARCHAR(30) NOT NULL,
total_score INT DEFAULT 0,
turn_order INT NOT NULL, -- 0, 1, 2, or 3
is_connected BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(lobby_id, username), -- No duplicate names in lobby
UNIQUE(lobby_id, turn_order) -- No duplicate turn orders
);
-- Game State: Current turn state (transient)
CREATE TABLE game_state (
lobby_id UUID PRIMARY KEY REFERENCES lobbies(id) ON DELETE CASCADE,
active_dice JSONB DEFAULT '[]', -- Current dice values [1,4,5,2,3,6]
held_indices JSONB DEFAULT '[]', -- Indices of held dice [0, 2]
turn_score INT DEFAULT 0, -- Points accumulated this turn
is_bust BOOLEAN DEFAULT false, -- Did player bust?
roll_count INT DEFAULT 0, -- Number of rolls this turn
tier INT DEFAULT 1, -- For Alchemist's Ascent
previous_dice JSONB DEFAULT '[]', -- For Tier 2 reroll comparison
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX idx_players_lobby ON players(lobby_id);
CREATE INDEX idx_lobbies_code ON lobbies(code);
CREATE INDEX idx_lobbies_status ON lobbies(status);
```
### 4.2 Row Level Security
```sql
-- Lobbies: Anyone can read, only creator can update
ALTER TABLE lobbies ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Lobbies are publicly readable"
ON lobbies FOR SELECT
USING (true);
CREATE POLICY "Anyone can create lobbies"
ON lobbies FOR INSERT
WITH CHECK (true);
CREATE POLICY "Lobby members can update"
ON lobbies FOR UPDATE
USING (true); -- Simplified for MVP
-- Similar policies for players and game_state tables
```
---
## 5. State Management
### 5.1 Streamlit Session State
```python
# Session state structure
st.session_state = {
# Identity
"player_id": "uuid",
"player_name": "string",
# Lobby
"lobby_id": "uuid",
"lobby_code": "ABC123",
"game_mode": "peasants_gamble",
# Turn state
"is_my_turn": bool,
"current_dice": [1, 4, 5, 2, 3, 6],
"held_indices": {0, 2},
"turn_score": 0,
# UI state
"sound_enabled": True,
"show_rules": False,
}
```
### 5.2 Optimistic Updates
To reduce perceived latency:
1. Update local state immediately on user action
2. Send update to Supabase
3. If Supabase update fails, rollback local state
4. Other players receive update via Realtime subscription
---
## 6. Error Handling Strategy
| Error Type | Response |
|------------|----------|
| Network failure | Retry 3x with backoff, then show error |
| Supabase error | Log, show user-friendly message |
| Invalid game state | Reset to last known good state |
| Concurrent modification | Last write wins (simplified) |
| Player disconnect | Mark as disconnected, allow rejoin |
---
## 7. Deployment Architecture
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ GitHub (source) β”‚ β”‚ Hugging Face Spaces β”‚
β”‚ origin/master β”‚ β”‚ hf/main (live app) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ git push origin master β”‚ git push hf master:main
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Streamlit Application β”‚
β”‚ - Serves UI + game engine β”‚
β”‚ - JS-cached audio system β”‚
β”‚ - Connects to Supabase (HTTPS) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Supabase β”‚
β”‚ - PostgreSQL database β”‚
β”‚ - Realtime WebSocket channels β”‚
β”‚ - Row Level Security β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
### Remotes
| Remote | URL | Branch |
|--------|-----|--------|
| `origin` | `https://github.com/legomaheggoz-source/DevilsDozen.git` | `master` |
| `hf` | `https://huggingface.co/spaces/legomaheggo/DevilsDozen` | push `master:main` |
### Environment Variables (HF Spaces Secrets / `.env` locally)
```
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=sb_publishable_xxx...
```
---
## 8. Performance Considerations
### Bottlenecks & Mitigations
| Bottleneck | Mitigation |
|------------|------------|
| Streamlit rerun on state change | Use `st.fragment` for isolated updates |
| Database round-trips | Batch updates where possible |
| Realtime subscription limits | One channel per lobby |
| Animation performance | CSS animations over JS |
| Audio data re-send on rerun | JS Audio caching in `window.parent` β€” base64 sent once, then tiny control commands |
| Widget prefs lost on `st.rerun()` | Non-widget session state keys (`_sfx_pref`, `_music_pref`, etc.) |
### Scaling Limits (Free Tier)
- **Supabase**: 500MB database, 2GB bandwidth/month
- **HF Spaces**: 2 vCPU, 16GB RAM
- **Concurrent games**: ~50 lobbies estimated