← Home

Docs & ER

# Will DM — ER / star schema

**Principle:** all product state lives in **Postgres**. Binary media = URLs on rows later.

**Physical world principle:** Non-creature board objects are *real*. Almost everything can be **damaged, breached, or reduced to rubble**. Almost nothing is an immortal video-game blocker. **Earth / ground plane** is the exception (immovable bedrock unless DM fiat). **Movable** is separate from **destructible** (crates shove; walls usually don’t move but still fall).

## Hubs

```
                         Campaign
                    ┌────────┼────────┐
                    ▼        ▼        ▼
                 Seats    Maps    Sessions
                    │        │        │
                 Player   Floors   Events / Initiative
                    │        │
              weapons…   tiles / obstacles / lights
                         monsters / rewards / familiars
                              │
                         Engagement
```

### Table identity (Pass A)
- **`TableSeat`** — person at the table (`dm` | `player` | `spectator`), `seatToken`, optional bound `Player`, `sessionId`
- **`GameSession`** — `status` prep|live|paused|ended, `activeMapId`, `activeFloorId`, `round`
- **`SessionEvent`** — append-only log for sync/replay later
- **`InitiativeEntry`** — optional sticky-note turn order

### Player star
- **Hub:** `Player` (character sheet + token on a map)
- Token: `posX/Y`, `gridX/Y`, `rotation`, `locked`, `controlledBySeatId`
- Hanging: weapons, abilities, magic, items; bound seats; familiars

### Map star
- **Hub:** `Map` — size, grid, `status`, `defaultFloorId`, wizard brief
- **`MapFloor`** — basement / ground / upper / courtyard / balcony / pathway
- **`MapTile`** — geometry + **structure contract** + materials  
  - `isEarth`, `destructible`, `movable`, `anchor`  
  - `sturdiness`, `fragility`, `structureHp`, `damageState` (`intact`|`damaged`|`breached`|`rubble`)  
  - `blocksLos` / `blocksMove` update with damage state (breached = hole)
- **`MapObstacle`** — furniture, cover, doors, stairs  
  - same structure contract  
  - **portal:** `portalKind`, `portalOpen`, `portalLocked`, `targetFloorId`, `targetX/Y`, `requiresKeyTag`
- **`MapLight`** — torch/sconce; movable when carried; smashable
- **`MapMonster` / `MapReward` / `MapFamiliar`** — tokens with grid + `controlledBySeatId`
- Catalogs: `MonsterCatalog`, `PrizeCatalog`, `FamiliarCatalog`

### Engagement star
- Actors, recommendations (never auto-resolve), DM resolutions
- May include terrain-break tips from structure scoring

## Damage states (structures)

| State | Meaning | Typical LOS | Typical move |
|-------|---------|-------------|--------------|
| intact | Full | as placed | as placed |
| damaged | Cracked / scorched | still blocks | still blocks |
| breached | Hole blasted | open | open (maybe difficult) |
| rubble | Crumbled | open | open (difficult terrain) |

Earth tiles: `isEarth=true`, `destructible=false` by default.

## APIs (thin)

| Endpoint | Purpose |
|----------|---------|
| `POST/GET /api/campaigns/:id/seats` | Claim / list seats |
| `POST/GET /api/campaigns/:id/sessions` | Start / list sessions |
| `PATCH /api/campaigns/:id/sessions/:sessionId` | Live/paused, active floor |
| `POST /api/maps/:id/structure/hit` | Hit tile \| obstacle \| light |

## Local vs prod

| Env | Database |
|-----|----------|
| Local | `DATABASE_URL` Postgres (or file if switched) |
| Prod | Supabase Postgres on Vercel |

## Mermaid (core)

```mermaid
erDiagram
  Campaign ||--o{ TableSeat : seats
  Campaign ||--o{ GameSession : sessions
  Campaign ||--o{ Map : maps
  Campaign ||--o{ Player : characters
  GameSession ||--o{ TableSeat : has
  GameSession ||--o{ SessionEvent : logs
  GameSession ||--o{ InitiativeEntry : order
  TableSeat ||--o{ Player : controls
  Map ||--o{ MapFloor : levels
  Map ||--o{ MapTile : geometry
  Map ||--o{ MapObstacle : props_portals
  Map ||--o{ MapMonster : tokens
  Map ||--o{ MapReward : loot
  Map ||--o{ MapFamiliar : familiars
  MapMonster }o--o| MonsterCatalog : from
  MapReward }o--o| PrizeCatalog : from
  MapFamiliar }o--o| FamiliarCatalog : from
```