This document is client architecture, not the table game. Match rules are rules.md. Engine rulings are comprehensive-rules.md. Do not duplicate them here.
The product is Der Musikclub. It is a PvP night for 2–4 DJs. Each human DJ is a standalone session (own client, own connection). A bot may fill a seat for practice. It is not the product. Tournaments are a later session layer on the same rules engine. There is no hotseat.
SESSION matchmaking, seats, later brackets
│
▼
MATCH HOST authoritative GameState + rules engine
│
┌─────────┼─────────┐
▼ ▼ ▼
CLIENT A CLIENT B (bot)
(1 seat) (1 seat) optional
│ │
▼ ▼
VIEW VIEW each session sees public table + own private zones
The view does not own the match. The bot does not own the match. One client never occupies two human seats. Seats submit actions; the host returns state and events.
Domain: crate, hand, bin, dice, lines, mix, turn, hold, result. Plain data. Deterministic transitions.
View: DOM, layout, animation, audio playback, input.
Rules code must not walk the UI tree. UI must not decide legality.
Input (this session) → Action → match host / rules engine → GameState' → Events → this view / music
GameState has N DJs (2–4), not “player vs opponent.”
Each human seat is one standalone client session, a source of ActionRequested. A bot, if present, uses the same path (legalActions → ActionRequested) on the host.
A client must not submit actions for another seat and must not receive another DJ’s private hand.
Remote humans and bots use the same engine path. Tournaments assign seats to matches; they do not fork the mix rules.
The engine emits architecture events, not a second rulebook. Examples:
ActionRequestedStateChangedTurnEndedMatchEndedMusicRequestedMatchEnded carries whatever the rules engine concluded (including a shared win or a stalemate). The shell displays that. It does not invent Victory/Defeat as the only outcomes.
Music is a consequence of match events, not the rules.
GameState / Events
→ MusicRequested
→ music service (optional)
→ event list (e.g. MIDI-Lite: time, pitch, velocity, duration)
→ local samples / Web Audio
The rules engine does not decode MIDI or trigger voices. The music module does not compute the mix.
MIDI-Lite, if used, is data, versioned, not audio. One instrument is enough to prove playback.
Mutable: UI, animation, timers, audio nodes, caches.
Disciplined / value-oriented: card definitions, game state, commands, music event lists.
The first client is a browser HTML app: one DJ per session. The rules engine runs on the match host, not as a shared local hotseat. Keep the engine portable (plain modules, no DOM) so the host and tests can load it.
A later native/Godot shell may bind the same engine as another client. Do not start from a scene tree as the database for the match.
Do not add a native audio or rules path until something is measured as hot (scheduler jitter, voices, browser audio). Correctness first.