SLOT_FACTORY_ARCHITECTURE.md

Architecture du Slot Factory Engine — Cool Kids Objectif : produire N machines publiables sur Stake Engine en remplaçant assets, thème, layout, maths et features — sans toucher au moteur.


1. Décision structurante n°1 — la stack

Options évaluées

Option Description Pour Contre
A. Web-sdk Stake complet Svelte 5 + PixiJS 8 + TS (monorepo officiel) Ce que les reviewers voient le plus ; reels/état déjà modélisés Chaîne lourde ; notre expertise validée est ailleurs ; personnalisation profonde du rendu = se battre contre le framework ; notre fork n'a que le sample number-picker
B. Vanilla single-file étendu (TNF) Continuer le pattern 1 fichier Zéro build ; pipeline validé en review Inmaintenable à l'échelle factory (2 551 lignes pour UN jeu simple) ; pas de réutilisation propre ; strip par regex fragile
C. Hybride (RECOMMANDÉ) Vite + modules ES vanilla/TS · PixiJS 8 self-hosté pour la grille · nos modules TNF extraits en packages · schémas du web-sdk adoptés (bookEvents, handler map) Le meilleur des deux : notre code éprouvé en review + un vrai renderer de grille + build réel (strip dev propre, atlas, hashing) ; UI chrome en DOM (ce qui a passé la review TNF, et bien plus simple à thémer depuis le Factory) Introduit une étape de build (Vite) — assumé et documenté

Décision C — détails d'application

2. Décision structurante n°2 — le périmètre de généricité

Règle : un module n'entre dans le moteur que s'il a (ou aura immédiatement) DEUX consommateurs. Sinon il reste dans le dossier du jeu. C'est la parade anti-usine-à-gaz. Conséquence : v1 du moteur = modules consommés par (a) le template reels 5×3 et (b) TNF migré.

3. Vue d'ensemble — arborescence

SLOT FACTORY/
├── docs/                       # les 8 livrables .md + guides
├── engine/                     # LE MOTEUR (npm workspace)
│   └── packages/
│       ├── core/               # GameCore : machine à états, playBook, bus d'événements, session
│       ├── stake-rgs/          # Couche Stake (extraite de TNF, source-verified) — GELÉE et testée
│       ├── config/             # chargement + validation des game.config.json / math profiles
│       ├── theme/              # résolveur d'asset manifest, tokens CSS, fonts, préchargement
│       ├── layout/             # presets : fixed-stage-landscape, vw-layers-portrait, grid geometry
│       ├── reels/              # ReelStage PixiJS : strips, spin/stop/anticipation/turbo, win presentation
│       ├── features/           # modules de bonus (interface commune, § 6)
│       ├── ui/                 # bet bar, boutons, modales, autoplay, fitVal, toasts, fatal
│       ├── audio/              # AudioBus (Web Audio, résilience iOS, hold/release bgm)
│       ├── fx/                 # fx-text (annonceur or), particules, pluie de pièces, win tiers
│       ├── character-actor/    # personnages vidéo chroma-key (pattern TNF) — opt-in
│       ├── cinematics/         # vidéos plein écran (muted + piste Web Audio synchro)
│       ├── i18n/               # locales + couche compliance social (stake.us)
│       └── devtools/           # mock RGS, panneau de forçage, simulation — EXCLU du build prod
├── factory/                    # L'OUTIL INTERNE
│   ├── site/                   # panneau web : créer/dupliquer/importer assets/preview/valider/exporter
│   ├── qa/                     # harnais Playwright (smoke, viewports, diff visuel, pièges Safari)
│   ├── math-tools/             # générateurs + vérificateurs (RTP exact Fraction, book↔LUT, REVIEW-EVENTS)
│   └── deploy/                 # wrangler previews + packaging de soumission
├── templates/                  # points de départ clonables
│   ├── lines-5x3/              # paylines classique
│   ├── ways-5x3/               # 243 ways
│   ├── ultra-simple/           # sans rouleaux (ADN TNF : un geste + un personnage)
│   └── (v2 : cluster-6x5, cascading, hold-and-win)
└── games/                      # UN DOSSIER = UN JEU (aucun code moteur dedans)
    ├── _placeholder/           # thème neutre complet (fallbacks universels)
    ├── tails-never-fails/      # TNF migré (consomme le moteur ; la v2 soumise reste gelée ailleurs)
    └── <nouveau-jeu>/
        ├── game.config.json    # LA définition du jeu (cf. GAME_CONFIGURATION_SCHEMA.md)
        ├── theme/assets.json   # manifest d'assets (clé logique → fichier)
        ├── theme/audio.json
        ├── locales/en.json
        ├── math/               # profil math (généré/validé par math-tools)
        ├── assets/             # fichiers finaux (nomenclature du SLOT_ASSET_MANIFEST)
        └── art-src/            # sources de travail — jamais buildé

4. Les 7 couches (séparation stricte)

┌────────────────────────────────────────────────────────────┐
│  FACTORY SITE (création, validation, export)               │
└──────────────┬─────────────────────────────────────────────┘
               │ écrit/valide
┌──────────────▼─────────────────────────────────────────────┐
│  CONFIG D'UN JEU  = math profile + theme + layout + features│
└──────────────┬─────────────────────────────────────────────┘
               │ charge
┌──────────────▼───────────────┐   ┌────────────────────────┐
│  GAME CORE (machine à états) │◄──┤ STAKE INTEGRATION      │
│  IDLE→BET→PLAY→BOOK→SETTLE   │   │ (rgs client, replay,   │
│  playBook → bus d'événements │   │  erreurs, resume, demo)│
└──────┬───────────┬───────────┘   └────────────────────────┘
       │ events    │ hooks
┌──────▼─────┐ ┌───▼──────────┐
│ FEATURES   │ │ PRESENTATION │  ← ne décide JAMAIS d'un résultat
│ (modules)  │ │ reels · ui · │     (le book fait foi, pattern TNF/web-sdk)
└────────────┘ │ fx · audio · │
               │ char · ciné  │
               └──────────────┘

5. Flux d'un spin (runtime)

tap SPIN
→ core.bet()            # vérifie solde/mise (affichage seulement)
→ stake-rgs.play(amount, mode)          # ou mock en dev
→ reçoit book { events[], payoutMultiplier }
→ reels.spinStart()     # la grille tourne pendant l'aller-retour réseau
→ core.playBook(events) # séquence : chaque event dispatché
     ├─ 'reveal'    → reels.stopOn(board)         # positions du book
     ├─ 'winInfo'   → fx.presentWins(lines/ways)  # + audio tiers
     ├─ 'freeSpinTrigger' → features.freespins.enter()
     ├─ '<custom>'  → handler déclaré par le jeu
     └─ 'finalWin'  → ui.updateWin()
→ stake-rgs.endRound()  # si gain
→ core.settle() → IDLE

L'anticipation, le turbo, l'ordre d'arrêt des rouleaux = chorégraphie de présentation paramétrée par le layout/thème — le résultat est déjà connu.

6. Interface d'un Feature Module

interface FeatureModule {
  id: 'freeSpins' | 'bonusBuy' | 'multiplier' | 'respins' | 'holdAndWin'
    | 'pickBonus' | 'mysterySymbol' | 'symbolTransform' | 'collectionMeter' | string;
  paramsSchema: JSONSchema;          // paramètres autorisés (validés au chargement)
  requiredAssets(params): AssetKey[];// ce que le manifest doit fournir (checklist Factory)
  requiredSounds(params): SoundKey[];
  bookEvents: string[];              // events du book qu'il consomme
  mathDeps: string[];                // ce que le package math doit déclarer (ex. mode bonusbuy)
  hooks: {
    onRegister(ctx)                  // câblage (bus, ui, assets)
    onSpinStart?(ctx)
    onBookEvent?(ctx, ev)            // cœur du module
    onSettle?(ctx)
  };
  overlay?: () => Screen;            // écran/overlay plein écran éventuel (intro FS, wheel…)
  devForces?: ForceSpec[];           // entrées du panneau de simulation (dev-only)
}

Activation par config : features: { freeSpins: {...params}, bonusBuy: {...} } — un module absent de la config n'est pas chargé (tree-shaken du bundle). Détail par module : FEATURE_MODULE_SPECIFICATION.md.

7. Thème & assets — résolution

  1. theme/assets.json mappe clé logique → fichier (+ variantes landscape/portrait, états).
  2. Le loader précharge par priorité (P0 : premier écran ; P1 : spin ; P2 : bonus — lazy).
  3. Clé manquante → placeholder du thème neutre + warning remonté au Factory (jamais de crash).
  4. Couleurs/fonts → CSS custom properties injectées (--gold, --panel, --font-display…).
  5. Les pièges Safari connus (filter+transform, background-clip+scale, clip-path+filter, drop-shadow sur PNG) sont encapsulés dans les composants du moteur — un thème ne peut pas les réintroduire.

8. Migration de TNF (preuve de généricité n°1)

  1. La v2 soumise reste gelée (TAILS NEVER FAILS/ intouché, prod).
  2. games/tails-never-fails/ : copie restructurée qui importe stake-rgs, audio, fx, character-actor, cinematics, ui.modals, i18n, devtools — sa présentation coin-flip reste spécifique (template ultra-simple).
  3. Critère de réussite : diff visuel Playwright ≈ 0 sur les parcours clés (landing, spin, gain, bonus, replay, portrait+landscape) entre la v2 gelée et la version migrée.
  4. Bénéfice immédiat : toute correction RGS/compliance future se fait UNE fois dans le moteur.

9. Second jeu démo (preuve n°2)

games/demo-frontier/ (western) : template lines-5x3, thème placeholder habillé cowboy minimal, maths générées par math-tools (profil médium), free spins + bonus buy. Même moteur, zéro ligne de code spécifique hors config/thème → c'est le test d'acceptation du Factory.

10. Critères de fin de phase (définition of done)

Phase Fini quand
Docs Les 8 .md livrés et validés par toi
Moteur core Template lines-5x3 joue en mock : spin, gains, free spins, bonus buy, autoplay, turbo, portrait+landscape
Placeholder Un jeu 100 % placeholder tourne sans un seul asset custom
Factory site Create → import assets → preview → validate → export produit un zip conforme
Migration TNF Diff visuel ≈ 0 + tests verts
Démo 2e thème Jeu western jouable, généré sans toucher au moteur
Tests Suite verte (unit + Playwright) ; simulation dev-only inerte en prod