Der Musikclub — Client architecture

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.


1. Layers

          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.


2. Domain vs view

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

3. Players and seats

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.


4. Events

The engine emits architecture events, not a second rulebook. Examples:

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


5. Music boundary

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.


6. Where mutability lives

Mutable: UI, animation, timers, audio nodes, caches.

Disciplined / value-oriented: card definitions, game state, commands, music event lists.


7. Client stack (MVP)

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.


8. Native / extra runtime

Do not add a native audio or rules path until something is measured as hot (scheduler jitter, voices, browser audio). Correctness first.