GAME_CONFIGURATION_SCHEMA.md
Format de configuration d'un jeu — Slot Factory Engine.
Un jeu = un dossier games/<gameId>/ contenant 4 fichiers de config + les assets. Créer un jeu « aliens » = dupliquer un dossier de thème et éditer ces fichiers. Aucune modification du moteur.
1. Vue d'ensemble des fichiers
games/space-raiders/
├── game.config.json # LE fichier maître (ci-dessous)
├── theme/
│ ├── assets.json # clé logique → fichier (+ variantes/états)
│ ├── audio.json # clé logique → fichier son
│ └── tokens.json # couleurs, fonts, style (→ CSS custom properties)
├── locales/
│ ├── en.json # référence obligatoire
│ └── fr.json …
├── math/
│ ├── publish_files/ # index.json + books + lookup (format ACP)
│ └── math.meta.json # GÉNÉRÉ par math-tools (jamais édité à la main)
├── assets/ # fichiers finaux (nomenclature SLOT_ASSET_MANIFEST)
└── art-src/ # sources de travail (non buildé)
2. game.config.json — schéma maître
{
"$schema": "../../engine/schemas/game.config.schema.json",
"gameId": "space_raiders", // [a-z0-9_], = identifiant ACP Stake
"displayName": "Space Raiders",
"studio": "coolkids",
"version": "1.0.0",
"template": "lines-5x3", // template d'origine (traçabilité)
"theme": "./theme", // dossier thème
"layout": {
"preset": "standard", // preset moteur : standard | tall | wide | custom
"grid": { "reels": 5, "rows": 3 },
"winSystem": "lines", // lines | ways | scatterPays (cluster/megaways : v2)
"paylineSetId": "lines20-classic", // réf. à une définition du moteur OU "./math/paylines.json"
"symbolSize": 1.0, // facteur vs cellule
"symbolGap": 0.06,
"landscape": { "gridAnchor": [0.5, 0.44], "gridWidth": 0.52 }, // fractions du stage 1920×1080
"portrait": { "gridAnchor": [0.5, 0.38], "gridWidth": 0.92 },
"spin": {
"duration": 2.4, // s, borné [2.0 .. 3.5] par le lint (turbo à part)
"stopStagger": 0.22,
"turbo": true, // sera ignoré si flag RGS disabledTurbo
"anticipation": true
}
},
"mathProfile": "./math", // le front NE porte AUCUNE constante math :
// costs/modes/wincap/paytable lus depuis math.meta.json
"features": { // un module absent = pas chargé (tree-shaken)
"freeSpins": { "introScreen": true, "summaryScreen": true, "retrigger": true },
"bonusBuy": { "modes": ["bonusbuy"] }, // les coûts viennent du math (index.json)
"multiplier": { "style": "orbs", "collectTo": "header" },
"holdAndWin": false,
"autoplay": { "counts": [10, 25, 50, 100], "stopConditions": true },
"turbo": true
},
"symbols": [ // mapping id math ↔ clé de thème ↔ type
{ "id": "L1", "themeKey": "symbol.low.01", "kind": "low" },
{ "id": "H1", "themeKey": "symbol.high.01", "kind": "high" },
{ "id": "W", "themeKey": "symbol.wild", "kind": "wild" },
{ "id": "S", "themeKey": "symbol.scatter", "kind": "scatter", "anticipation": true }
],
"winTiers": { // multiples de mise (conventions marché)
"big": 15, "mega": 30, "epic": 100,
"rollupSeconds": { "base": 1.0, "big": 3.0, "mega": 5.0, "epic": 8.0 }
},
"localization": { "default": "en", "supported": ["en", "fr", "de", "es", "pt", "ja"] },
"characterActor": false, // OU config du module chroma-key (cf. FEATURE_MODULE_SPEC)
"cinematics": {}, // clés vidéo éventuelles
"compliance": {
"maxWinX": null, // null = lu depuis math.meta.json (source de vérité)
"malfunctionClause": true, // toujours true — le lint refuse false
"celebrateWinsBelowStake": false
}
}
3. theme/assets.json — manifest d'assets
{
"themeId": "space",
"fallbackTheme": "placeholder", // TOUJOURS présent — un asset manquant → placeholder + warning
"entries": {
"brand.logo": { "src": "space_brand_logo.webp" },
"bg.base": { "landscape": "space_bg_base_landscape.webp",
"portrait": "space_bg_base_portrait.webp", "preload": "P0" },
"bg.bonus": { "landscape": "space_bg_bonus_landscape.webp",
"portrait": "space_bg_bonus_portrait.webp", "preload": "P2" },
"frame.reels": { "src": "space_frame_reels.webp", "slice": [120, 120, 120, 120] },
"frame.betbar": { "landscape": "space_frame_betbar_landscape.webp",
"portrait": "space_frame_betbar_portrait.webp" },
"reels.bg": { "src": "space_reels_bg.webp" },
"symbol.low.01": { "states": { "idle": "space_symbol_low_01_idle.webp",
"win": "space_symbol_low_01_win.webp" } },
"symbol.wild": { "states": { "idle": "space_symbol_wild_idle.webp",
"win": "space_symbol_wild_win.webp" } },
"ui.spin": { "states": { "default": "space_ui_spin_default.webp",
"pressed": "space_ui_spin_pressed.webp" } },
"ui.plaque": { "src": "space_ui_plaque.webp", "slice": [40, 60, 40, 60] },
"bonus.freespins.intro": { "src": "space_bonus_freespins_intro.webp", "preload": "P2" },
"video.bonus.intro": { "src": "space_video_bonus_intro.mp4", "preload": "P2" }
}
}
Règles : preload = P0 (avant premier écran) / P1 (avant premier spin) / P2 (lazy). État manquant → fallback procédural du moteur. Clé manquante → placeholder + warning Factory.
4. theme/tokens.json — style
{
"colors": { "primary": "#f4c95d", "panel": "#170f29", "text": "#ece6ff",
"win": "#3ddc84", "accent": "#a64dff", "danger": "#ff5a6e" },
"fonts": { "display": "space_font_display.woff2", "ui": null }, // null = fallback moteur (Cinzel/system)
"style": { "buttonShape": "round", "borderRadius": 14, "glowIntensity": 0.8,
"shadowDepth": 0.6, "gradientAngle": 180 }
}
→ injecté en CSS custom properties (--ck-primary, --ck-font-display…). Aucune couleur codée en dur dans les composants moteur.
5. math/math.meta.json — GÉNÉRÉ (source de vérité front↔math)
Produit par factory/math-tools à partir de publish_files/ — jamais édité à la main (leçon TNF : BUY_COST dupliqué).
{
"generatedFrom": "publish_files/index.json (sha256:…)",
"rtp": 0.96,
"wincapX": 10000,
"modes": [
{ "name": "base", "cost": 1.0, "label": "strings:mode.base" },
{ "name": "bonusbuy", "cost": 100.0, "label": "strings:mode.bonusbuy", "isBuy": true }
],
"paytable": [ { "symbol": "H1", "of": [3,4,5], "pays": [5,15,50] } ],
"hitRates": { "base": 0.281, "freeSpins": 0.00124 },
"reviewEvents": "REVIEW-EVENTS.md",
"verification": { "books": 57819, "lutMismatches": 0, "rtpExactPerMode": true }
}
6. Ce qu'un créateur de jeu NE PEUT PAS faire
- Mettre une constante math dans la config front (le schéma n'a pas de champ pour ça).
- Contourner un flag RGS (turbo/fullscreen/social gérés au niveau moteur).
- Retirer la clause malfunction, l'affichage RTP ou le max win des règles.
- Référencer un fichier hors de
games/<id>/assets/. - Casser le jeu avec un asset manquant (fallback placeholder systématique).
7. Validation
factory validate <gameId> exécute : JSON Schema du config → cohérence symbols↔math (ids du paytable) → manifest complet (checklist SLOT_ASSET_MANIFEST) → dimensions/poids → budgets perf → lint layout (touch targets, safe areas) → maths (RTP par mode, book↔LUT) → build test + smoke Playwright. Rapport bloquant/warnings. C'est la même passe que l'onglet « Validation » du site Factory.