SP slotplate
All the diagrams, in one place

Architecture

A tour of how slotplate is put together — one diagram per concept, with the rationale on the side.

1. Who owns what: client vs server

This is the first diagram to read. slotplate is a client — it does not own the math. Confusing this split is how slot codebases end up with parallel evaluators (one on the server, one on the client) that drift until certification fails.

CLIENT · slotplate presentation + flow rendering reels animations, timing, skip HUD, bet input, sound FSM phase orchestration optimistic balance display request spin from server paytable logic RNG / stop positions win evaluation RTP / math certification balance truth SERVER · RGS math + truth paytable + payouts RNG + reelstrip lookup grid selection per spin win evaluation anticipation trigger rule bonus trigger decision balance authoritative state session lifecycle RTP certification rendering / animation sound / HUD / speed modes SpinRequest { bet } SpinResponse grid, winlines, totalWin, teasingReels
Client animates. Server computes. Between them flows exactly one pair of messages per round.

2. The layer stack

Inside the client, layers import downward only. Biome enforces the boundaries — you can't import pixi-reels from a store, you can't import MobX from domain types, you can't reach from view/ back into flow/.

SLOTPLATE · CLIENT UI plain DOM · optional React/Vue scenes Pixi application lifecycle · mounts pixi-reels flow · FSM + phase handlers idle → spin → stopSpin → winShow → idle owns GAME TIME · only writer to stores presenters ReelsPresenter · HUDPresenter state → view infrastructure Ticker · Network · AssetLoader · Analytics I/O boundary state · MobX RootStore = Balance + Data + UI domain · WIRE TYPES ONLY SpinRequest · SpinResponse · Grid · Winline config columns · rows · symbolIds (client-side only) ↓ import direction (enforced by Biome) SERVER (RGS) not slotplate paytable reelstrips RNG + spin eval RTP / math balance (truth) SpinResponse The client never computes these. It renders what the server said happened.
The orange block (FSM) is privileged — it's the only writer to the stores. Everything else reads.

3. Unidirectional data flow

Data moves one way around the loop. Input triggers an FSM transition. The transition mutates state through a store action. Presenters observe state changes and drive the view. The view runs, and when it finishes (reels land, animations complete) that resolution feeds back to the FSM — which transitions again.

input HUD click FSM owns time state MobX stores presenters observe → drive view pixi-reels resolution allLanded, onComplete transition action autorun render done next transition ONE DIRECTION no back-channels

4. The finite state machine

A slot round is a finite state machine. slotplate makes the states explicit. Each named phase is a file in src/flow/phases/. The FSM runs exactly one phase at a time. Transitions are explicit calls — never flag-based.

idle waits for click spin debitBet + request stopSpin reels land on grid winShow spotlight + credit click response received allLanded win hold elapsed (or skip) no winlines → idle immediately skip() = forceStop skip() = forceStop skip() = cancel hold idle / resting phase active phase normal transition conditional shortcut
idle → spin → stopSpin → winShow → idle. Bonus games nest as sub-FSMs inside their own phase.

5. The spin lifecycle, in sequence

The same flow as a sequence diagram — who calls whom, when. Time flows top to bottom. The par block (marked with a dashed rectangle) is the critical bit: reels start rolling and the network request goes out in parallel. Whichever finishes first, the FSM is ready for it.

HUD presenter FSM flow Balance store Reels presenter Network infrastructure Data store user clicks Spin transition('spin') debitBet() par [in parallel] startSpin() spin({ bet }) SpinResponse transition('stopSpin') stopWithResult(grid) allLanded transition('winShow') showWin(winlines) credit(totalWin) transition('idle') time →

6. Timing: why no setTimeout

The quiet architectural detail that bites every slot codebase eventually. setTimeout runs on the platform clock and keeps firing in backgrounded tabs — even while Pixi's ticker pauses. slotplate uses gsap.delayedCall on a GSAP ticker that's synced to app.ticker. When Pixi pauses, game timing pauses with it.

BROKEN · setTimeout setTimeout runs on the platform clock. TAB VISIBLE pixi ticker · animation runs setTimeout · fires on schedule TAB HIDDEN (paused) pixi ticker · paused hidden CORRECT · Ticker.schedule gsap.delayedCall on app.ticker. TAB VISIBLE pixi ticker GSAP synced to pixi.ticker TAB HIDDEN (paused) pixi ticker · paused (also paused — in sync)
Left: the common bug. Right: the fix. All scheduled game-time calls go through the Ticker abstraction in src/infrastructure/timing.ts.

Where to next