SP slotplate
Rules

The 10 Principles

Hard rules. Break them and the diff should not pass review.

These aren't suggestions. They're the load-bearing rules the architecture rests on. Every layer, every file, every lint rule in slotplate points back to one of these. Where possible we encode the rule in Biome so the codebase itself rejects violations.

1. The client does not evaluate

The paytable lives on the server. The win evaluator lives on the server. The RNG and the reelstrips live on the server. The client sends a SpinRequest and receives a SpinResponse with grid, winlines, and totalWin already resolved. The client renders. That is the entire loop.

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
No parallel math, no client-side fallback evaluator, no offline win recomputation. The server is authoritative.

Why: two evaluators mean two sources of truth. One of them is wrong. Finding out which in production, at 3am, with real money moving, is the bug you do not want.

Enforced by: the domain/ folder has no evaluateWin. The template has no paytable JSON. If an agent tries to generate one, it has no place to wire it.

2. No setTimeout in game code

All scheduled work goes through Ticker.schedule(ms, fn) from src/infrastructure/timing.ts. Under the hood it's gsap.delayedCall on a GSAP ticker that's synced to Pixi's app.ticker.

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)
setTimeout runs on the platform clock. When Pixi pauses in a hidden tab, setTimeout keeps firing. That's a bug factory.

Enforced by: Biome's no-restricted-globals rejects setTimeout and setInterval in game code, with a message pointing at the Ticker service.

3. Presenters don't decide when

A presenter observes state, translates it into view calls, and exposes a narrow API for the FSM to drive. It does not hold timers. It does not sequence animations across multiple frames of game state. That's the phase's job.

Why: presenters are dumb — easy to test, easy to replace. The moment a presenter owns timing, it becomes a miniature state machine, and you have two of them. Two state machines are four times harder to reason about than one.

4. State mutates via actions only

MobX @action-wrapped methods are the only mutators. Views read; presenters read; phases call actions. A view or presenter that writes directly to a store is a bug.

5. Disposable everything

Any class that allocates a ticker handle, event listener, or Pixi resource implements Disposable and is torn down by its parent scene. Scene teardown cascades.

Why: slots run for hours. Tiny leaks compound. Resource-leak bugs are usually invisible for the first week and lethal by the second.

6. Fail loud

No silent catch . No as any without a one-line comment explaining why this is the least-bad option. No "fallback to a hidden default" that masks a missing config. Throw with a message that names what's missing and who should have provided it.

7. One composition root

src/composition.ts is the only file that instantiates services. Everything else receives dependencies. If you find yourself writing new SomeService(...) anywhere else, stop — pass it as a parameter instead.

8. pixi-reels lives behind one presenter

Only files in src/view/ and src/presenters/ may import pixi-reels or pixi.js. The rest of the app talks to the view through ReelsPresenter.

Enforced by: Biome's no-restricted-imports with a pattern that catches any import of pixi-anything outside those two folders.

9. Every phase is a file

New FSM states go in src/flow/phases/ and register in src/composition.ts. Phases implement Phase with enter, optional skip, optional exit. They're testable without a canvas — see tests/flow/SpinPhase.test.ts for the shape.

10. Docs are for agents too

When you add a concept, add a docs page. /llms.txt and /llms-full.txt regenerate on build. Agents read docs the same way humans do — make sure yours exist.