DevilsDozen / docs /ARCHITECTURE.md
legomaheggo's picture
docs: Update architecture/UI docs and add new game mode template
b24ad7d

A newer version of the Streamlit SDK is available: 1.54.0

Upgrade

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

-- 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

-- 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

# 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