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