Référence du format de scénario Stratilead — version 1.0
Ce document décrit intégralement le format d’un scénario Stratilead. Il fait foi pour :
- les auteurs de scénarios (éditeur du jeu, intervenants) ;
- les IA à qui l’on demande de générer un scénario (voir aussi le kit IA, plus condensé) ;
- les développeurs du moteur, de l’éditeur et du validateur.
Le langage des formules est décrit à part, dans langage-formules.md. La façon dont le moteur exécute un tour est détaillée dans ../04-moteur-simulation.md.
Conventions de rédaction : « DOIT » = obligatoire (le validateur refuse sinon) ; « DEVRAIT » = recommandé (le validateur avertit) ; « PEUT » = facultatif.
1. Le modèle mental : Entrées → Règles → Sorties → État
Section intitulée « 1. Le modèle mental : Entrées → Règles → Sorties → État »Un scénario décrit une ou plusieurs entreprises qui jouent plusieurs tours (un trimestre, un mois, une saison… le nom est libre). À chaque tour :
┌──────────────────────────────────────────────────────────┐ │ UN TOUR │ ÉTAT au │ ENTRÉES RÈGLES SORTIES │ ÉTAT au début du ──┼─►(décisions des ──► (formules, ──► (indicateurs, ─┼─► tour suivant tour │ joueurs) marché, résultats, │ (variables (variables)│ événements) classement) │ mises à jour) └──────────────────────────────────────────────────────────┘| Brique | Rôle | Exemple (usine de RAM) |
|---|---|---|
| Constantes | Paramètres fixes du scénario | Coût matière d’une barrette, salaire trimestriel |
| Variables d’état | Ce qui se reporte d’un tour au suivant (les « stocks » au sens dynamique) | Trésorerie, stock, effectif, qualité |
| Décisions | Les entrées des joueurs à chaque tour | Prix de vente, volume de production, embauches |
| Formules | Les règles de calcul du tour | Coût de production = volume × coût unitaire |
| Marché | Répartit la demande entre entreprises concurrentes | Parts de marché selon prix, qualité, notoriété |
| Événements | Ce qui arrive en cours de partie | Pénurie de silicium, grève, commande géante |
| Score | Ce qui classe les entreprises | Bénéfice cumulé |
Tout le reste (thème, images, rapports, pédagogie, bots, difficultés) habille et paramètre ces briques.
Règle d’or : tout ce qui se calcule est une « grandeur » nommée. Constantes, variables, décisions, formules et sorties du marché partagent un même espace de noms. Une formule peut utiliser n’importe quelle grandeur par son identifiant, et le moteur calcule tout dans le bon ordre automatiquement.
2. Fichier et paquet
Section intitulée « 2. Fichier et paquet »2.1 Fichier scenario.json
Section intitulée « 2.1 Fichier scenario.json »- Encodage UTF-8, JSON strict (pas de commentaires, pas de virgule finale).
- Taille maximale : 2 Mo.
- La première clé DEVRAIT être
$schema, qui pointe vers le JSON Schema du format, pour l’autocomplétion dans les éditeurs de code :"$schema": "https://stratilead.fr/schema/scenario-1.0.json"(domaine définitif à confirmer, voir la spec technique).
2.2 Paquet .stratilead
Section intitulée « 2.2 Paquet .stratilead »Pour embarquer des images, un scénario s’exporte et s’importe sous forme d’archive ZIP portant l’extension .stratilead :
mon-scenario.stratilead├── scenario.json└── assets/ ├── cover.webp ├── fond-usine.jpg └── evt-penurie.png| Contrainte | Valeur |
|---|---|
| Taille maximale du paquet | 25 Mo |
| Taille maximale d’une image | 5 Mo |
| Formats d’image acceptés | PNG, JPEG, WebP, AVIF (le SVG est refusé pour des raisons de sécurité) |
| Dimensions maximales | 4096 × 4096 px |
| Nombre maximal d’images | 100 |
À l’import, la plateforme recompresse les images (WebP), supprime les métadonnées EXIF et les héberge elle-même.
Un scenario.json seul (sans images) est toujours importable : les images manquantes sont remplacées par des illustrations par défaut du secteur, et l’éditeur signale ce qui manque.
3. Conventions communes
Section intitulée « 3. Conventions communes »3.1 Identifiants
Section intitulée « 3.1 Identifiants »| Élément | Règle | Exemple |
|---|---|---|
Identifiant de scénario (id) |
minuscules, chiffres, tirets : ^[a-z0-9]+(-[a-z0-9]+)*$, 3 à 64 caractères |
usine-ram |
| Tous les autres identifiants (grandeurs, listes, éléments de liste, options, événements, courbes, groupes, rôles, niveaux…) | camelCase ASCII : ^[a-z][a-zA-Z0-9_]{0,47}$ |
tresorerie, prixVente, ddr5 |
- Les identifiants de grandeurs (constantes, variables, décisions, formules, sorties de marché) DOIVENT être uniques entre eux.
- Les autres familles (listes, éléments d’une même liste, options d’une même décision, événements, courbes, groupes, rôles, niveaux, prix, bots) ont chacune leur propre espace de noms.
- Les identifiants sont en français sans accents de préférence (le public est francophone), en camelCase :
coutMatiere, pascoût_matière. - Mots réservés, interdits comme identifiants de grandeur : les noms de fonctions du langage de formules, les grandeurs intégrées (§22), et
true,false,and,or,not,item,previous.
3.2 Textes localisés (LocalizedText)
Section intitulée « 3.2 Textes localisés (LocalizedText) »Partout où un texte est affiché, on peut écrire :
- soit une chaîne simple, dans la langue principale du scénario (
meta.language) :"label": "Trésorerie"; - soit un objet par langue :
"label": { "fr": "Trésorerie", "en": "Cash" }.
Un scénario multilingue déclare ses langues dans meta.languages (la langue principale en premier) ; chaque texte affiché doit alors exister dans chacune d’elles (avertissement P720). Les noms propres qui ne sont pas des textes (meta.authors[].name, meta.tags, assets.*.credit, unit) restent des chaînes simples. La langue d’une session est choisie à sa création parmi meta.languages (I18N-03) : tous les textes rendus (vues, gabarits, événements, rapports, débrief, PDF, CSV) le sont dans cette langue, et un texte absent de cette langue retombe sur la langue principale. Exemple : le scénario officiel limonade est bilingue (["fr", "en"]).
Les champs « markdown » acceptent du Markdown simple : gras, italique, listes, liens, tableaux. Le HTML est interdit (il est retiré à l’affichage).
3.3 Gabarits {{ … }} dans les textes
Section intitulée « 3.3 Gabarits {{ … }} dans les textes »Les textes d’événements, de conseils, d’enseignements et de débrief peuvent afficher des valeurs calculées :
"Votre stock de {{ stockTotal }} barrettes vaut {{ valeurStock | money }}."- Entre
{{et}}: n’importe quelle expression du langage de formules, évaluée dans le contexte de l’entreprise concernée (ou du monde). - Filtre de format facultatif après
|:money,percent,integer,number,number(2)(2 décimales). - Sans filtre, une grandeur nommée s’affiche avec son propre
format; une expression calculée s’affiche commenumber. - Un gabarit est évalué au moment où le texte s’affiche, et suit les règles de ce moment (§3.11) : annonce pour les textes d’événements, conseils, aides des décisions et briefing ; saisie pour les messages de contraintes ; résolution pour les issues de dilemmes ; fin de partie pour les enseignements.
3.4 Formats, unités, pourcentages
Section intitulée « 3.4 Formats, unités, pourcentages »Chaque grandeur PEUT déclarer un format d’affichage :
format |
Affichage | Décimales par défaut |
|---|---|---|
money |
12 500 € (devise de settings.currency) |
0 |
number |
1 234,5 |
1 |
integer |
1 235 |
0 |
percent |
15,0 % |
1 |
boolean |
Oui / Non | — |
decimals PEUT surcharger le nombre de décimales affichées. unit PEUT ajouter une unité libre ("unités", "pers.", "pts").
Les exemples ci-dessus sont en français. Dans une session en anglais (lot L26), les mêmes valeurs s’écrivent €12,500, 1,234.5, 15.0%, Yes / No ; toute autre langue garde les conventions françaises.
Important — les pourcentages sont stockés en ratio. Une valeur au format
percentvaut0.15pour 15 %. C’est vrai partout : constantes, décisions (une décisionpercentbornée de0à0.3s’affiche de 0 à 30 %), formules. Ne jamais écrire15pour 15 %.
3.5 Valeurs, expressions et le mot-clé previous
Section intitulée « 3.5 Valeurs, expressions et le mot-clé previous »Certains champs acceptent soit une valeur littérale, soit une expression du langage de formules. La règle est uniforme :
- un nombre ou un booléen JSON est une valeur littérale ;
- une chaîne JSON est une expression (
"max": "tresorerie / 2") ; - un objet JSON est une valeur par élément de liste (§3.7) ;
- seule exception : les champs
initialetdefaultdes décisionschoice/multiChoicecontiennent des identifiants d’options (etdefaultaccepte le mot-clé"previous"), voir §13.
Dans une expression, une chaîne littérale s’écrit entre apostrophes : fournisseur == 'europe'.
3.6 Portée : entreprise ou monde (scope)
Section intitulée « 3.6 Portée : entreprise ou monde (scope) »scope |
Signification | Exemple |
|---|---|---|
company (défaut) |
La grandeur existe en un exemplaire par entreprise | Trésorerie, prix de vente |
world |
La grandeur existe en un seul exemplaire pour tout le marché | Prix du silicium, taux d’intérêt, taille du marché |
Une formule de monde peut agréger les entreprises (market_sum(marketing)) ; une formule d’entreprise peut lire les grandeurs du monde. Les décisions sont toujours de portée company.
En marché parallèle, chaque entreprise joue dans son propre monde, identique pour tous (§15.4).
3.7 Listes et grandeurs « par élément » (per)
Section intitulée « 3.7 Listes et grandeurs « par élément » (per) »Une liste énumère des éléments (produits, gammes de chambres, lignes aériennes, services…). Une grandeur déclarée avec "per": "produit" possède une valeur par élément de la liste produit.
Valeur par élément, dans les champs value / initial / default :
"value": { "ddr4": 15, "ddr5": 26 }"value": { "*": 10, "ddr5": 16 } // "*" = valeur pour tous les éléments non cités"value": 10 // même valeur pour tous les élémentsDans une formule elle-même déclarée per la même liste, une grandeur par élément s’utilise sans indice : elle désigne l’élément courant. Les détails (indice explicite prix[ddr5], agrégations sum(ventes), élément courant item) sont dans le langage de formules.
Le format 1.0 gère une seule dimension par grandeur. Pour croiser deux dimensions (ligne aérienne × classe), on crée une liste d’éléments combinés (parisNiceEco, parisNiceBusiness…).
3.8 Visibilité (visibility)
Section intitulée « 3.8 Visibilité (visibility) »Qui voit la valeur d’une grandeur pendant la partie :
| Valeur | Qui voit | Défaut pour |
|---|---|---|
private |
L’entreprise elle-même et l’intervenant | grandeurs company (y compris décisions) |
public |
Tout le monde : les joueurs voient la valeur de chaque concurrent après la résolution du tour | grandeurs world |
facilitator |
Seulement l’intervenant | — |
hidden |
Calcul intermédiaire : jamais affiché comme grandeur dans les rapports, mais détaillé dans « Comprendre ce chiffre » quand il entre dans le calcul d’une grandeur que le spectateur a le droit de voir ; visible dans l’éditeur, le banc d’essai et la vue de débogage de l’intervenant. Pour un secret, utiliser facilitator |
— |
revealInDebrief: true PEUT rendre la grandeur visible de tous au débrief final (ex. : une qualité cachée révélée à la fin).
Les décisions des concurrents marquées public (typiquement le prix) ne sont révélées qu’après la résolution du tour, jamais pendant la saisie.
3.9 Filtre de difficulté (levels)
Section intitulée « 3.9 Filtre de difficulté (levels) »La plupart des éléments (décisions, formules, événements, contraintes, conseils, éléments de liste, bots…) acceptent "levels": ["normal", "difficile"] : l’élément n’existe que dans ces niveaux de difficulté. Sans levels, il existe dans tous les niveaux. Voir §18.
3.10 Icônes et couleurs
Section intitulée « 3.10 Icônes et couleurs »icon: un nom d’icône de la bibliothèque Lucide ("factory","users","euro","megaphone"…).- Couleurs : hexadécimal
#RRGGBB.
3.11 Moments d’évaluation
Section intitulée « 3.11 Moments d’évaluation »Une expression n’est pas toujours évaluée au même moment du tour, et elle ne peut utiliser que ce qui est déjà connu à ce moment-là :
| Moment | Expressions concernées | Peut utiliser |
|---|---|---|
| Création (début de partie) | initial des variables |
Constantes, décisions de setup, grandeurs intégrées |
| Annonce (début du tour, avant la saisie) | trigger.probability, trigger.when, select.by, enabledIf, auto, tips[].when, décisions des bots, value des effets d’un événement effect, gabarits des textes d’annonce (text des événements, help, briefing, conseils) |
Constantes, variables (valeur de début de tour), décisions de setup, grandeurs intégrées, prev(…) de n’importe quoi, et les formules qui ne dépendent que de ces éléments |
| Saisie (pendant que les joueurs décident) | min, max, step des décisions, constraints, preview |
Tout ce qui précède, plus les décisions du tour et les formules qui en dépendent, sauf le marché et le hasard |
| Résolution (fin du tour) | expr des formules, next des variables, marché, effets des dilemmes et quiz |
Tout ; les variables valent leur valeur de début de tour |
| Clôture (après la mise à jour des variables) | scoring.score, scoring.tiebreak, bankruptcy.when, valeurs des effets de faillite |
Tout ; les variables valent leur valeur de fin de tour (résultat de next), les formules leur valeur du tour |
| Fin de partie (clôture du dernier tour) | insights, awards[].by, objectives[].check |
Comme la clôture, sur toute l’historique (cumul, prev) |
Le validateur signale toute expression qui utilise une grandeur pas encore disponible à son moment (code T340). Pour utiliser un résultat du tour précédent à l’annonce, écrivez prev(resultatNet).
Dans les rapports, une variable affichée pour un tour donné montre sa valeur de fin de ce tour (la trésorerie « au 31 mars »), une formule sa valeur du tour.
4. Structure racine
Section intitulée « 4. Structure racine »{ "$schema": "https://stratilead.fr/schema/scenario-1.0.json", "formatVersion": "1.0", "id": "usine-ram", "version": "1.0.0", "meta": { }, "settings": { }, "theme": { }, "assets": { }, "groups": [ ], "roles": [ ], "lists": { }, "constants": [ ], "curves": { }, "variables": [ ], "decisions": [ ], "constraints": [ ], "formulas": [ ], "market": { }, "events": [ ], "bots": { }, "difficulties": { }, "scoring": { }, "reports": { }, "pedagogy": { }}| Clé | Requis | Section |
|---|---|---|
$schema |
non | §2.1 |
formatVersion |
oui — "1.0" |
§24 |
id |
oui | §3.1 |
version |
oui — semver "1.0.0" |
§24 |
meta |
oui | §5 |
settings |
oui | §6 |
theme |
non | §7 |
assets |
non | §7 |
groups |
non (recommandé dès 5 décisions) | §8 |
roles |
non | §8 |
lists |
non | §9 |
constants |
non | §10 |
curves |
non | §11 |
variables |
oui (au moins une) | §12 |
decisions |
oui (au moins une) | §13 |
constraints |
non | §13.6 |
formulas |
non | §14 |
market |
non (sans marché, la demande est entièrement définie par les formules) | §15 |
events |
non | §16 |
bots |
non | §17 |
difficulties |
non (un niveau unique normal est alors créé) |
§18 |
scoring |
oui | §19 |
reports |
non (un tableau de bord par défaut est généré) | §20 |
pedagogy |
non (fortement recommandé) | §21 |
5. meta — présentation du scénario
Section intitulée « 5. meta — présentation du scénario »"meta": { "title": "Mémoire Vive", "subtitle": "Dirigez une usine de barrettes de RAM", "summary": "Production, prix, stocks et RH : survivez au marché de la mémoire.", "description": "## L'histoire\nVous reprenez une usine…", "sector": "industrie", "tags": ["production", "stocks", "prix", "rh"], "language": "fr", "languages": ["fr"], "audience": "Post-bac, écoles de commerce et d'ingénieurs", "estimatedDuration": { "min": 60, "max": 120 }, "authors": [{ "name": "Équipe Stratilead" }], "license": "CC-BY-4.0", "cover": "cover"}| Champ | Type | Requis | Description |
|---|---|---|---|
title |
LocalizedText (≤ 60 car.) | oui | Nom du scénario |
subtitle |
LocalizedText (≤ 100) | non | Accroche |
summary |
LocalizedText (≤ 200) | oui | Résumé pour la marketplace |
description |
LocalizedText markdown | non | Description longue, histoire, contexte |
sector |
enum | oui | industrie, hotellerie, restauration, transport, commerce, services, tech, agriculture, sante, finance, energie, culture, autre |
tags |
string[] (≤ 10) | non | Mots-clés libres de recherche |
language |
code langue (fr, en…) |
oui | Langue principale des textes |
languages |
code langue[] | non | Langues disponibles (défaut : [language]) |
audience |
LocalizedText | non | Public conseillé |
estimatedDuration |
{ min, max } minutes |
non | Durée indicative d’une session |
authors |
{ name, url? }[] |
non | Auteurs (l’auteur du compte est ajouté automatiquement) |
license |
string | non | CC-BY-4.0, CC-BY-SA-4.0, CC-BY-NC-4.0, proprietary (défaut) |
cover |
clé d’asset | non | Image de couverture (marketplace, lobby) |
Les objectifs pédagogiques sont dans pedagogy (§21).
6. settings — paramètres de jeu
Section intitulée « 6. settings — paramètres de jeu »"settings": { "currency": "EUR", "roundLabel": { "singular": "trimestre", "plural": "trimestres" }, "companyLabel": { "singular": "usine", "plural": "usines" }, "rounds": { "default": 8, "min": 4, "max": 12 }, "practiceRound": true, "marketModes": ["shared", "parallel"], "playModes": ["teams", "individual"], "sharedModeLuck": "independent", "limits": { "maxPlayers": 256, "maxCompanies": 64, "teamSize": { "min": 1, "max": 5 } }, "decisionTime": { "default": 420, "min": 120, "max": 1800 }}| Champ | Type | Défaut | Description |
|---|---|---|---|
currency |
code ISO 4217 | "EUR" |
Devise affichée |
roundLabel |
{ singular, plural } LocalizedText |
« tour / tours » | Nom d’un tour (« mois », « saison », « trimestre »…) |
companyLabel |
{ singular, plural } |
« entreprise / entreprises » | Nom d’une entreprise (« hôtel », « compagnie »…) |
rounds |
{ default, min, max } |
{8, 3, 20} |
Nombre de tours ; l’intervenant choisit dans [min, max]. Maximum absolu : 40 |
practiceRound |
booléen | false |
Propose un tour d’essai, rejoué ensuite depuis l’état initial (l’intervenant peut le désactiver) |
marketModes |
("shared" | "parallel")[] |
["parallel"] |
Modes de marché supportés ; le premier est le défaut. Voir §15 |
playModes |
("teams" | "individual")[] |
["teams", "individual"] |
Équipes, ou une entreprise par joueur |
sharedModeLuck |
"independent" | "identical" |
"independent" |
En marché partagé : les tirages aléatoires propres aux entreprises (fonctions aléatoires des formules et événements company à sélection each, issues des dilemmes) sont-ils indépendants, ou identiques pour toutes (équité maximale : un événement aléatoire touche alors toutes les entreprises ou aucune) ? En marché parallèle, ils sont toujours identiques |
limits.maxPlayers |
entier 1–256 | 256 | Joueurs maximum par session pour ce scénario (la plateforme plafonne à 256) |
limits.maxCompanies |
entier 1–64 | 64 | Entreprises maximum (équipes + bots) dans un marché partagé. maxPlayers DOIT rester atteignable : au plus maxCompanies × teamSize.max joueurs (256 joueurs = 64 équipes de 4) ; sinon avertissement P740. Un marché partagé à beaucoup d’entreprises garde son équilibre si sa taille dépend de companyCount |
limits.teamSize |
{ min, max } |
{1, 6} |
Taille des équipes |
decisionTime |
{ default, min, max } secondes |
{300, 60, 3600} |
Temps de décision conseillé par tour |
7. theme et assets — personnalisation visuelle
Section intitulée « 7. theme et assets — personnalisation visuelle »7.1 assets
Section intitulée « 7.1 assets »Dictionnaire clé → image. Les autres sections référencent les images par leur clé.
"assets": { "cover": { "src": "assets/cover.webp", "alt": "Une salle blanche de production de RAM" }, "fondUsine": { "src": "assets/fond-usine.jpg", "alt": "Ligne de production", "credit": "Photo : X" }, "evtPenurie": { "alt": "Wafers de silicium" }}| Champ | Type | Requis | Description |
|---|---|---|---|
src |
chemin relatif dans le paquet (assets/…) |
non | Absent = image à fournir plus tard (l’éditeur le signale, une illustration par défaut est utilisée) |
alt |
LocalizedText | oui | Texte alternatif (accessibilité), et description pour qui devra fournir l’image |
credit |
string | non | Crédit / licence de l’image |
Les URL externes (https://…) ne sont pas acceptées dans src : la plateforme héberge toutes les images.
7.2 theme
Section intitulée « 7.2 theme »"theme": { "primaryColor": "#2563EB", "accentColor": "#F59E0B", "background": "fondUsine", "logo": "logoScenario", "companyAvatars": ["avatarUsine1", "avatarUsine2"]}| Champ | Type | Description |
|---|---|---|
primaryColor |
couleur | Couleur principale de l’interface pendant la partie |
accentColor |
couleur | Couleur secondaire |
background |
clé d’asset | Image de fond (lobby, écran de projection) |
logo |
clé d’asset | Logo du scénario |
companyAvatars |
clés d’assets | Avatars proposés aux équipes pour leur entreprise (sinon : avatars génériques) |
Le nom, la couleur et l’avatar de chaque entreprise sont choisis par les joueurs en début de partie : ce n’est pas défini par le scénario.
8. groups et roles — organisation des décisions
Section intitulée « 8. groups et roles — organisation des décisions »8.1 groups
Section intitulée « 8.1 groups »Les groupes rangent les décisions (onglets de l’écran de décision) et peuvent servir de sections dans les rapports.
"groups": [ { "id": "production", "label": "Production", "icon": "factory", "description": "Volumes, lignes, maintenance, achats." }, { "id": "rh", "label": "Ressources humaines", "icon": "users" }, { "id": "commercial", "label": "Commercial", "icon": "megaphone" }, { "id": "finance", "label": "Finance", "icon": "landmark" }]L’ordre du tableau est l’ordre d’affichage. Une décision sans group va dans un groupe « Général ».
8.2 roles
Section intitulée « 8.2 roles »En mode équipe, les rôles répartissent les décisions entre les membres. Facultatif : sans rôles, tous les membres éditent toutes les décisions et le capitaine valide.
"roles": [ { "id": "pdg", "label": "PDG", "description": "Arbitre et valide les décisions.", "groups": ["strategie"], "captain": true }, { "id": "prod", "label": "Directeur de production", "groups": ["production"] }, { "id": "drh", "label": "DRH", "groups": ["rh"] }, { "id": "mkt", "label": "Directeur commercial", "groups": ["commercial"] }, { "id": "daf", "label": "Directeur financier", "groups": ["finance"] }]| Champ | Description |
|---|---|
groups |
Groupes de décisions que ce rôle peut modifier (tous les rôles voient tout) |
captain |
Exactement un rôle DOIT être captain: true si roles est défini : il valide l’envoi des décisions |
Si l’équipe compte moins de membres que de rôles, la plateforme cumule les rôles (le capitaine récupère les rôles non attribués). L’intervenant PEUT désactiver les rôles pour une session.
9. lists — listes d’éléments
Section intitulée « 9. lists — listes d’éléments »"lists": { "produit": { "label": "Produits", "items": [ { "id": "ddr4", "label": "DDR4 16 Go", "description": "Technologie mature, marché en déclin.", "image": "imgDdr4" }, { "id": "ddr5", "label": "DDR5 32 Go", "description": "Technologie récente, marché en croissance." } ] }}| Champ | Type | Description |
|---|---|---|
label |
LocalizedText | Nom de la liste |
items |
tableau, 1 à 50 éléments | Éléments de la liste |
items[].id |
identifiant | Unique dans la liste |
items[].label |
LocalizedText | Nom affiché |
items[].description |
LocalizedText | Description |
items[].image / icon |
clé d’asset / icône | Illustration |
items[].levels |
niveaux | L’élément n’existe que dans ces niveaux de difficulté |
Les caractéristiques chiffrées des éléments (coût, capacité…) se déclarent comme constantes par élément (§10), pas dans la liste.
10. constants — paramètres fixes
Section intitulée « 10. constants — paramètres fixes »"constants": [ { "id": "salaireTrimestriel", "label": "Salaire chargé par employé", "value": 10000, "format": "money", "group": "rh" }, { "id": "coutMatiere", "label": "Coût matière unitaire", "per": "produit", "value": { "ddr4": 15, "ddr5": 26 }, "format": "money" }, { "id": "tresorerieInitiale", "label": "Trésorerie de départ", "value": 250000, "format": "money", "adjustable": { "min": 100000, "max": 500000, "step": 10000 } }]| Champ | Type | Requis | Description |
|---|---|---|---|
id |
identifiant | oui | |
label |
LocalizedText | oui | |
value |
nombre, booléen ou valeurs par élément | oui | Littéral uniquement (pas d’expression) |
per |
id de liste | non | Une valeur par élément |
scope |
— | — | Sans objet : une constante est la même pour tous |
format, decimals, unit, description, concept, group, icon |
non | Affichage (§3.4, §21) | |
visibility |
non | Défaut public (les règles du jeu sont connues) ; facilitator ou hidden pour garder un paramètre secret |
|
adjustable |
{ min, max, step? } |
non | L’intervenant peut modifier cette valeur en créant sa session (« paramètres avancés ») |
levels |
niveaux | non |
Les constantes peuvent être modifiées temporairement par des événements (§16.6) et par niveau de difficulté (§18).
11. curves — courbes et barèmes
Section intitulée « 11. curves — courbes et barèmes »Une courbe transforme une valeur en une autre par interpolation entre des points. C’est le moyen le plus simple d’exprimer un rendement décroissant, un seuil ou un barème sans mathématiques.
"curves": { "effetFormation": { "label": "Effet du budget de formation sur la compétence", "points": [[0, -2.5], [250, 0], [500, 2.5], [1000, 5], [2000, 7]], "interpolation": "linear" }, "baremeRemise": { "label": "Remise selon le volume commandé", "points": [[0, 0], [1000, 0.05], [5000, 0.1]], "interpolation": "step" }}| Champ | Valeurs | Description |
|---|---|---|
points |
[x, y][], 2 à 50 points, x strictement croissants |
Points de la courbe |
interpolation |
linear (défaut), step, smooth |
step = palier : la valeur du dernier point dont x ≤ entrée. smooth = interpolation monotone (Fritsch–Carlson) |
extrapolation |
clamp (défaut), linear, ou { "left": …, "right": … } |
Hors des bornes : on garde la valeur du premier / dernier point, ou on prolonge la pente ; la forme objet règle chaque côté séparément |
Usage dans une formule : curve(effetFormation, formationParEmploye).
12. variables — l’état qui se reporte d’un tour à l’autre
Section intitulée « 12. variables — l’état qui se reporte d’un tour à l’autre »"variables": [ { "id": "tresorerie", "label": "Trésorerie", "format": "money", "initial": "tresorerieInitiale", "next": "tresorerie + fluxTresorerie", "visibility": "private" }, { "id": "stock", "label": "Stock", "per": "produit", "format": "integer", "unit": "barrettes", "initial": { "ddr4": 1500, "ddr5": 500 }, "next": "(stock + productionReelle - ventesTotales) * (1 - obsolescence)", "min": 0 }, { "id": "qualite", "label": "Qualité", "format": "number", "decimals": 0, "unit": "/100", "initial": "if(positionnement == 'premium', 66, 60)", "next": "qualite + 0.5 * (qualiteCible - qualite)", "min": 0, "max": 100, "visibility": "public" }, { "id": "prixSilicium", "label": "Indice du prix du silicium", "scope": "world", "format": "number", "decimals": 2, "initial": 1, "next": "prixSilicium + 0.3 * (1 - prixSilicium) + normal(0, 0.04)", "min": 0.6, "max": 2 }]| Champ | Type | Requis | Description |
|---|---|---|---|
id, label |
oui | ||
initial |
valeur, valeurs par élément, ou expression | oui | Valeur au début du tour 1. Une expression ne PEUT utiliser que des constantes, des décisions de setup (§13.4) et des grandeurs intégrées |
next |
expression | non | Valeur au début du tour suivant, calculée en fin de résolution. Toutes les grandeurs du tour sont disponibles. Sans next, la variable ne change que par les événements |
per, scope |
non | §3.6, §3.7 | |
min, max |
nombre | non | Bornes appliquées à la valeur après chaque mise à jour (next et événements) |
format, decimals, unit, description, concept, group, icon, visibility, revealInDebrief, levels |
non |
Sémantique essentielle : pendant le tour t, écrire tresorerie dans une formule donne la valeur au début du tour t. La valeur de fin de tour est le résultat de next, qui devient la valeur de début du tour t + 1.
Arrondis à l’enregistrement : à la fin de chaque tour, une variable integer est arrondie à l’entier le plus proche, une variable money au centime. Les formules, elles, ne sont jamais arrondies (seulement à l’affichage).
13. decisions — les entrées des joueurs
Section intitulée « 13. decisions — les entrées des joueurs »13.1 Champs communs
Section intitulée « 13.1 Champs communs »{ "id": "prix", "label": "Prix de vente", "per": "produit", "type": "money", "min": 10, "max": 150, "step": 0.5, "initial": { "ddr4": 38, "ddr5": 65 }, "default": "previous", "group": "commercial", "widget": "slider", "help": "Un prix bas attire les clients mais réduit votre marge.", "concept": "elasticitePrix", "visibility": "public", "preview": ["margeUnitaire"]}| Champ | Type | Requis | Description |
|---|---|---|---|
id, label |
oui | ||
type |
voir §13.2 | oui | |
per |
id de liste | non | Une saisie par élément (ex. un prix par produit) |
phase |
round (défaut) | setup |
non | setup = décision prise une seule fois avant le tour 1 (§13.4) |
initial |
valeur / valeurs par élément | non | Valeur proposée au premier tour (défaut : min pour les nombres, false, première option) |
default |
valeur, "previous", ou expression |
non | Valeur proposée aux tours suivants et utilisée si l’entreprise n’envoie rien. Défaut : "previous" (on reprend la décision du tour précédent). Pour un flux ponctuel (embauches, investissement), mettre 0 |
auto |
expression | non | Valeur utilisée quand la décision n’est pas proposée aux joueurs (niveau de difficulté qui l’exclut, enabledIf faux), évaluée au moment « annonce » (donc sans les autres décisions du tour). Permet d’automatiser une décision en mode facile. Défaut : la valeur de default |
enabledIf |
expression booléenne | non | Décision proposée seulement si vrai (ex. round >= 3, lignes > 0). Évaluée au début du tour |
levels |
niveaux | non | Niveaux où la décision est proposée. Hors de ces niveaux, auto s’applique |
group |
id de groupe | non | Onglet de l’écran de décision |
widget |
voir §13.2 | non | Contrôle d’interface |
help |
LocalizedText markdown | DEVRAIT | Aide affichée à côté du champ : à quoi sert la décision, quel arbitrage |
concept |
id de concept | non | Lien vers le glossaire (§21) |
visibility |
non | Défaut private. public = les concurrents voient la décision après la résolution |
|
preview |
ids de formules | non | Formules calculées en direct pendant la saisie, affichées comme estimation (ex. coût estimé). Elles ne DOIVENT pas dépendre du marché ni du hasard (§13.6) |
format, decimals, unit, icon, description |
non |
13.2 Types de décision
Section intitulée « 13.2 Types de décision »type |
Valeur | Champs propres | Widgets possibles (premier = défaut) |
|---|---|---|---|
number |
nombre réel | min, max, step |
slider, input, stepper |
integer |
entier | min, max, step |
stepper, slider, input |
money |
nombre (format money) |
min, max, step |
input, slider, stepper |
percent |
ratio 0–1 | min, max, step (en ratio) |
slider, input |
boolean |
booléen | labels: { true, false } |
toggle, cards |
choice |
id d’une option | options |
cards, select, radio |
multiChoice |
liste d’ids d’options | options, minSelected, maxSelected |
cards, checkboxes |
allocation |
une valeur par option | options, total, allocationMode |
allocation |
min, max et step acceptent un nombre, une expression (ex. "max": "employes" pour les licenciements) ou, pour une décision per, des valeurs par élément ("max": { "toulouse": 400, "*": 250 }). Une expression de borne est évaluée pour chaque élément ; elle PEUT dépendre d’autres décisions du même tour (l’interface recalcule en direct), mais ni du marché ni du hasard.
13.3 Options (choice, multiChoice, allocation)
Section intitulée « 13.3 Options (choice, multiChoice, allocation) »{ "id": "fournisseur", "label": "Fournisseur de wafers", "type": "choice", "group": "production", "initial": "europe", "options": [ { "id": "asie", "label": "Asie (low cost)", "description": "Moins cher, qualité moyenne.", "icon": "ship", "props": { "facteurCout": 0.85, "facteurQualite": 0.6 } }, { "id": "europe", "label": "Europe", "props": { "facteurCout": 1.0, "facteurQualite": 0.8 } }, { "id": "premium", "label": "Premium (Japon)", "props": { "facteurCout": 1.2, "facteurQualite": 1.0 }, "enabledIf": "round >= 2" } ]}| Champ d’option | Description |
|---|---|
id, label |
requis |
description, image, icon |
présentation (le widget cards affiche des cartes illustrées) |
props |
caractéristiques numériques ou booléennes de l’option, lues dans les formules par fournisseur.facteurCout |
enabledIf |
option proposée seulement si vrai |
levels |
niveaux où l’option existe |
Dans les formules :
choice:fournisseurvaut l’id choisi (fournisseur == 'asie') ;fournisseur.facteurCoutvaut la propriété de l’option choisie.multiChoice:has(certifications, 'iso9001');count(certifications);certifications.cout= somme de la propriété sur les options cochées.allocation:canaux.web= montant alloué à l’optionweb.
initial / default d’un choice : un id d’option (ou "previous" pour default). Pour un multiChoice : un tableau d’ids. Pour une allocation : un objet { idOption: valeur }.
Allocation — répartir un total entre des options (budget marketing entre canaux, temps de travail entre activités) :
{ "id": "canaux", "label": "Répartition du budget marketing", "type": "allocation", "format": "percent", "total": 1, "allocationMode": "exact", "options": [ { "id": "web", "label": "Web" }, { "id": "salons", "label": "Salons" }, { "id": "presse", "label": "Presse spécialisée" } ], "initial": { "web": 0.5, "salons": 0.3, "presse": 0.2 }}total : nombre ou expression. allocationMode : exact (la somme DOIT égaler le total ; défaut) ou upTo (somme ≤ total). Une allocation ne peut pas être déclarée per.
13.4 Décisions de mise en place (phase: "setup")
Section intitulée « 13.4 Décisions de mise en place (phase: "setup") »Prises une fois, pendant la phase de création des entreprises (avec le nom et l’avatar), avant le tour 1. Elles servent aux choix stratégiques fondateurs : positionnement, implantation, statut juridique…
{ "id": "positionnement", "phase": "setup", "type": "choice", "label": "Positionnement de votre usine", "options": [ { "id": "volume", "label": "Volume", "description": "Coûts réduits, qualité de départ plus faible.", "props": { "bonusCout": -0.05, "bonusQualite": -5 } }, { "id": "equilibre", "label": "Équilibre", "props": { "bonusCout": 0, "bonusQualite": 0 } }, { "id": "premium", "label": "Premium", "description": "Meilleure qualité, coûts fixes plus élevés.", "props": { "bonusCout": 0.05, "bonusQualite": 6 } } ]}Leur valeur reste ensuite constante toute la partie ; elles sont utilisables dans les initial des variables et partout ailleurs.
13.5 Valeur effective d’une décision
Section intitulée « 13.5 Valeur effective d’une décision »Pour chaque entreprise et chaque tour, la valeur utilisée par le moteur est, dans l’ordre :
- si la décision n’est pas proposée (niveau,
enabledIffaux) →autosi défini, sinon la valeur par défaut ci-dessous ; - sinon, la valeur envoyée par l’entreprise ;
- si l’entreprise n’a rien envoyé : le dernier brouillon valide, sinon
default(previous= valeur du tour précédent ; au tour 1,initial) ; - la valeur est enfin bornée à
[min, max], arrondie austepet à l’entier pourinteger.
13.6 constraints — contraintes entre décisions
Section intitulée « 13.6 constraints — contraintes entre décisions »"constraints": [ { "id": "budgetLignes", "check": "nouvellesLignes * coutLigne <= tresorerie + nouvelEmprunt", "message": "Vous n'avez pas la trésorerie pour financer ces nouvelles lignes.", "severity": "error", "group": "production" }, { "id": "surproduction", "check": "chargeDemandee <= capacite * 1.0001", "message": "Votre production dépasse votre capacité : elle sera réduite de {{ (1 - tauxCharge) | percent }}.", "severity": "warning" }]| Champ | Description |
|---|---|
check |
expression booléenne qui DOIT être vraie |
message |
texte affiché quand elle est fausse (gabarits {{ }} permis) |
severity |
error : bloque l’envoi des décisions ; warning : simple alerte |
group |
onglet où afficher l’alerte |
levels |
niveaux concernés |
Les contraintes, les preview et les bornes min/max sont évaluées pendant la saisie, avant que le marché ne soit connu. Elles ne DOIVENT donc dépendre ni du marché (sorties de market, fonctions market_*, share, rank), ni du hasard. Le validateur le vérifie (code T340).
Robustesse. Une contrainte
errorempêche l’envoi manuel, mais si le temps est écoulé le moteur résout quand même le tour avec la dernière saisie valide ou les valeurs par défaut. Les formules DOIVENT donc rester cohérentes même avec des décisions absurdes : utilisezmin()/max()dans les formules plutôt que de compter sur les contraintes.
14. formulas — les règles de calcul
Section intitulée « 14. formulas — les règles de calcul »"formulas": [ { "id": "ca", "label": "Chiffre d'affaires", "per": "produit", "expr": "ventes * prix", "format": "money", "description": "Prix de vente × quantités vendues." }, { "id": "caTotal", "label": "Chiffre d'affaires total", "expr": "sum(ca)", "format": "money", "concept": "chiffreAffaires" }, { "id": "tailleMarche", "label": "Demande potentielle", "scope": "world", "per": "produit", "expr": "demandeBase * pow(tendance, round - 1) * saisonnalite", "format": "integer" }]| Champ | Type | Requis | Description |
|---|---|---|---|
id, label |
oui | ||
expr |
expression | oui | Voir le langage de formules |
per, scope |
non | §3.6, §3.7 | |
description |
LocalizedText | DEVRAIT | Explication en français de la règle, affichée dans « Comprendre ce chiffre » |
format, decimals, unit, concept, group, icon, visibility, revealInDebrief, levels |
non |
- L’ordre des formules dans le fichier n’a aucune importance : le moteur construit le graphe des dépendances et calcule dans le bon ordre.
- Une dépendance circulaire entre formules est une erreur (
D400). Pour boucler, passer par une variable d’état (sa valeur est celle du début du tour) ou parprev(). - Une formule exclue par
levelsvaut0(oufalse) ; le validateur avertit si une formule active en dépend. - Chaque formule s’affiche avec un bouton « Comprendre ce chiffre » qui décompose le calcul avec les valeurs réelles (ex.
CA = ventes (9 120) × prix (38 €) = 346 560 €).
15. market — le marché et la concurrence
Section intitulée « 15. market — le marché et la concurrence »Le bloc market répartit une demande entre des entreprises selon leur attractivité. Il gère les ruptures de stock et le report des clients non servis. C’est lui qui rend possible la concurrence en marché partagé.
15.1 Exemple
Section intitulée « 15.1 Exemple »"market": { "segments": [ { "id": "oem", "label": "Fabricants de PC et serveurs", "per": "produit", "size": "tailleMarche * if(mode == 'shared', companyCount * 1.2, 4.2)", "attractiveness": "pow(prixReference / prix, elasticite) * pow(qualite / 60, 1.2) * (0.8 + 0.5 * notoriete / 100)", "supply": "disponibleMarche", "outsideOption": "0.2 * if(mode == 'shared', companyCount, 1)", "spillover": 0.6, "integerUnits": true, "virtualCompetitors": [ { "id": "importateurs", "label": "Importateurs établis", "modes": ["parallel"], "attractiveness": "3 * pow(qualiteConcurrence / 60, 1.2)" } ], "outputs": { "demand": "demande", "sales": "ventes", "share": "partDeMarche", "lost": "ventesPerdues" } } ]}15.2 Champs d’un segment
Section intitulée « 15.2 Champs d’un segment »| Champ | Portée de l’expression | Requis | Description |
|---|---|---|---|
id, label |
oui | ||
per |
non | Le segment est dupliqué pour chaque élément de la liste (un marché par produit) | |
size |
monde | oui | Demande potentielle totale du segment, en unités, pour le tour |
attractiveness |
entreprise | oui | Attractivité d’une entreprise (≥ 0) : plus elle est grande, plus l’entreprise capte de clients |
supply |
entreprise | non | Quantité que l’entreprise peut vendre ce tour (stock disponible). Absent = illimité |
outsideOption |
monde | non | Attractivité de « ne rien acheter ». Défaut 0 : toute la demande achète. Plus elle est grande, plus des prix élevés font fuir les clients du marché |
spillover |
nombre 0–1 | non | Part des clients non servis (rupture de stock) qui se reportent sur les concurrents ayant du stock. Défaut 0.5 |
integerUnits |
booléen | non | Ventes arrondies à l’unité inférieure. Défaut true |
virtualCompetitors |
tableau | non | Concurrents fictifs pilotés par le scénario (§15.5) |
outputs |
objet | oui | Noms des grandeurs produites (§15.3) |
15.3 Sorties
Section intitulée « 15.3 Sorties »Chaque sortie déclarée dans outputs devient une grandeur d’entreprise utilisable partout (par élément si le segment est per) :
| Clé | Signification |
|---|---|
demand |
Demande adressée à l’entreprise (part de la demande + clients reportés depuis les concurrents en rupture) |
sales |
Quantité effectivement vendue (≤ supply) |
share |
Part de marché : ventes de l’entreprise ÷ ventes totales du segment (concurrents virtuels inclus) |
lost |
Clients perdus faute de stock : demand − sales |
Seule sales est obligatoire. On PEUT ajouter les champs d’affichage communs (label, description, format, decimals, unit, visibility, concept, group, icon, revealInDebrief) à une sortie sous la forme d’un objet : "share": { "id": "partDeMarche", "label": "Part de marché", "format": "percent", "visibility": "public" }.
Plusieurs segments PEUVENT viser les mêmes produits (ex. grand public et professionnels, ou nuits de pointe et nuits ordinaires) : additionnez alors leurs ventes dans une formule. Règles :
- Les segments sont indépendants : chacun répartit sa propre demande, et le report (
spillover) ne passe jamais d’un segment à un autre ni vers l’option extérieure (ces clients sont perdus). - Chaque segment consomme sa propre
supply: si deux segments puisent dans le même stock, c’est à l’auteur de le découper (ex.supply= capacité × part des nuits de pointe) ; le moteur ne partage pas une capacité entre segments. - Les identifiants de concurrents virtuels sont uniques au sein d’un segment.
demandinclut les clients reçus par report depuis les concurrents en rupture ;lost=demand − sales.- Valeur de
spillover: 0,3 pour des clients peu mobiles (hôtel réservé à l’avance), 0,5 par défaut, 0,7 à 0,9 pour un produit banalisé où le client va voir ailleurs sans hésiter.
15.4 Modes de marché
Section intitulée « 15.4 Modes de marché »L’intervenant choisit le mode à la création de la session, parmi settings.marketModes.
Marché partagé (shared) |
Marché parallèle (parallel) |
|
|---|---|---|
| Principe | Toutes les entreprises (équipes et bots) se disputent les mêmes clients | Chaque entreprise joue seule dans sa copie du monde ; on compare les résultats à la fin |
| Concurrents dans le calcul du marché | Toutes les entreprises + concurrents virtuels dont modes contient shared |
L’entreprise seule + concurrents virtuels dont modes contient parallel |
Fonctions market_*, share, rank |
Portent sur toutes les entreprises | Portent sur la seule entreprise |
| Hasard | Tirages du monde communs ; tirages des entreprises selon sharedModeLuck |
Tous les tirages identiques pour toutes les entreprises : même météo, mêmes événements, même chance |
companyCount |
Nombre d’entreprises du marché | 1 |
| Intérêt pédagogique | Concurrence, guerre des prix, réaction aux autres | Comparer des stratégies à conditions identiques ; grands groupes |
Pour qu’un scénario fonctionne dans les deux modes, la taille du marché et l’option extérieure doivent tenir compte du mode (cf. l’exemple : companyCount, mode), et des concurrents virtuels doivent créer la concurrence en mode parallèle.
15.5 Concurrents virtuels
Section intitulée « 15.5 Concurrents virtuels »Entreprises fictives sans décisions ni comptabilité : elles ont seulement une attractivité (et éventuellement une capacité). Elles représentent « le reste du marché ».
| Champ | Description |
|---|---|
id, label |
|
attractiveness |
expression monde, évaluée par élément si le segment est per (elle peut donc différer selon le produit ou la ligne). Une attractivité nulle rend le concurrent absent ce tour-là : c’est ainsi qu’on fait entrer un concurrent en cours de partie (formule monde valant 0, puis modifiée par un événement) |
supply |
expression monde ; absent = illimité |
modes |
modes où ce concurrent existe (défaut : les deux) |
logo |
clé d’asset |
À ne pas confondre avec les bots (§17), qui sont de vraies entreprises simulées, avec décisions et comptes.
15.6 Algorithme de répartition (résumé)
Section intitulée « 15.6 Algorithme de répartition (résumé) »Pour chaque segment (et chaque élément si per) :
D=size; pour chaque participant k :A_k= attractivité (≥ 0),S_k= offre ;A_0=outsideOption.- Demande initiale :
d_k = D × A_k / (A_0 + Σ A). - Ventes :
s_k = min(d_k, S_k). Clients non servis :U = Σ (d_k − s_k). - Report :
spillover × Uclients sont redistribués aux participants qui ont encore du stock, au prorata de leur attractivité, l’option extérieure prenant sa part (ces clients quittent le marché), en au plus 5 passes. Le reste est perdu. Ainsi, quand tous les concurrents sont complets, un vendeur au prix absurde ne récupère pas tout l’excédent. - Sorties :
demand=d_k+ clients reçus par report ;sales= ventes finales ;share=sales_k / Σ sales;lost=demand − sales.
Détails, cas limites et arrondis : moteur de simulation §6.
Sans bloc
market, la demande est calculée par les formules seules (ex.ventes = min(stock, demandeClient)). C’est suffisant pour un scénario purement parallèle ou pédagogique simple.
16. events — événements
Section intitulée « 16. events — événements »16.1 Les quatre types
Section intitulée « 16.1 Les quatre types »type |
Ce que voient les joueurs | Effet |
|---|---|---|
info |
Une actualité (flash info, rumeur, indice sur le marché) | Aucun ; peut être une rumeur fausse, révélée au débrief |
effect |
Une annonce : « Pénurie de silicium : coût matière +30 % pendant 2 trimestres » | Modifie des grandeurs, immédiatement ou pendant plusieurs tours |
dilemma |
Une situation à trancher, avec des options | Les effets dépendent de l’option choisie, avec parfois une part de hasard |
quiz |
Une question à choix multiple, minutée, façon Kahoot | Récompense (ou pénalité) selon les bonnes réponses |
16.2 Champs communs
Section intitulée « 16.2 Champs communs »{ "id": "penurieSilicium", "type": "effect", "title": "Pénurie de silicium", "text": "Un séisme a touché les fonderies de Taïwan. Le prix du silicium s'envole.", "image": "evtPenurie", "tone": "negative", "scope": "world", "trigger": { "type": "random", "probability": 0.15, "from": 2, "to": 7 }, "maxOccurrences": 1, "effects": [ { "target": "prixSilicium", "op": "multiply", "value": 1.35 } ], "teaching": "Dépendre d'un seul approvisionnement expose aux chocs. Un stock de sécurité ou un fournisseur alternatif amortit le choc.", "concept": "chaineApprovisionnement"}| Champ | Type | Requis | Description |
|---|---|---|---|
id, type, title |
oui | ||
text |
LocalizedText markdown, gabarits permis | oui (facultatif pour quiz, qui a question) |
Texte de l’annonce, rendu après l’application des effets de l’annonce (on peut donc afficher la nouvelle valeur d’une variable modifiée) |
image |
clé d’asset | non | Illustration |
tone |
positive | negative | neutral |
non | Couleur et son de l’annonce. Défaut neutral |
display |
popup (défaut) | news |
non | Fenêtre bloquante à lire, ou simple bandeau d’actualité |
scope |
world | company |
non | world (défaut) : un seul tirage pour tout le marché. company : chaque entreprise est concernée séparément (§16.4) |
select |
objet | non | Ciblage des entreprises pour un événement company (§16.4) |
trigger |
objet | oui | Déclenchement (§16.3) |
maxOccurrences |
entier | non | Nombre maximal de déclenchements par partie (par entreprise si company). Défaut : illimité |
cooldown |
entier | non | Tours minimum entre deux déclenchements. Défaut 0 |
manual |
booléen | non | L’intervenant peut le déclencher à la main. Défaut true |
effects |
effets | non | Effets à l’annonce (§16.6) |
teaching |
LocalizedText markdown | DEVRAIT | Leçon à retenir, affichée dans les résultats du tour et au débrief |
concept, levels |
non | ||
onScreen |
booléen | non | Afficher l’annonce sur l’écran de projection. Défaut true pour world, false pour company |
16.3 Déclenchement (trigger)
Section intitulée « 16.3 Déclenchement (trigger) »trigger.type |
Champs | Signification |
|---|---|---|
scheduled |
round (entier) ou rounds (entiers) |
À ce(s) tour(s) précis |
random |
probability (nombre ou expression), from, to, when |
À chaque tour entre from et to (inclus), avec cette probabilité, si when est vrai |
condition |
when (expression), probability (défaut 1), from, to |
À chaque tour où la condition est vraie (sous réserve de maxOccurrences et cooldown) ; pour un déclenchement unique, mettre maxOccurrences: 1 |
manual |
— | Uniquement à la main par l’intervenant (bibliothèque d’événements) |
probabilityetwhensont évalués au début du tour, sur l’état de début de tour et les résultats du tour précédent (prev(...)). Dans un événementcompany, ils sont évalués pour chaque entreprise :"probability": "clamp(0.3 - prev(maintenanceParLigne, 1, 3000) / 12000, 0.03, 0.3)".- Les événements sont tirés au début du tour, pour que les joueurs puissent réagir pendant leur saisie.
- Pendant le tour d’essai, seuls les événements
scheduledau tour 1 se déclenchent.
16.4 Portée et ciblage
Section intitulée « 16.4 Portée et ciblage »scope: "world": un seul tirage ; l’événement concerne le monde, et ses effets sur des grandeurs d’entreprise s’appliquent à toutes les entreprises.scope: "company": l’événement touche des entreprises précises, selonselect:
select.type |
Effet |
|---|---|
each (défaut) |
Chaque entreprise fait son propre tirage (probability, when) |
random |
Un tirage unique ; si l’événement a lieu, count entreprises au hasard (défaut 1) parmi celles qui vérifient when |
top / bottom |
Un tirage unique ; si l’événement a lieu, les count entreprises qui ont la plus grande / la plus petite valeur de l’expression by |
"scope": "company","select": { "type": "top", "by": "prev(resultatNet)", "count": 1 }16.5 Dilemmes
Section intitulée « 16.5 Dilemmes »{ "id": "commandeGeante", "type": "dilemma", "scope": "company", "title": "Une commande géante", "text": "Un fabricant de serveurs vous propose 3 000 barrettes DDR5 à 52 € pièce, livrables **ce trimestre**. Chaque barrette non livrée coûtera 20 € de pénalité.", "trigger": { "type": "random", "probability": 0.12, "from": 3, "when": "lignes >= 3" }, "maxOccurrences": 1, "options": [ { "id": "accepter", "label": "Accepter la commande", "effects": [ { "target": "commandeB2B", "op": "set", "value": 3000, "items": ["ddr5"] } ] }, { "id": "refuser", "label": "Refuser poliment" } ], "default": "refuser", "teaching": "Une grosse commande sécurise du chiffre d'affaires, mais engage votre capacité : vérifiez-la avant de signer."}| Champ | Description |
|---|---|
options |
2 à 5 options : id, label, description, image, enabledIf, effects, outcomes |
options[].outcomes |
Issues aléatoires possibles, tirées à la résolution : [{ "probability": 0.7, "text": "…", "effects": [ … ] }, …] ; les probabilités DOIVENT sommer à 1 |
default |
Option retenue si l’entreprise ne répond pas (requis) |
deadline |
decision (défaut : jusqu’à la fin de la saisie du tour) |
- Le dilemme apparaît pendant la saisie des décisions ; en équipe, c’est le capitaine qui tranche (les membres peuvent voter).
- Les effets de l’option choisie et de son issue s’appliquent à la résolution du tour, avant le calcul des formules (§16.6).
- Le texte de l’issue est affiché dans les résultats du tour.
- Dans les formules,
dilemma(commandeGeante)donne l’option choisie ce tour-ci (''si le dilemme n’a pas eu lieu).
16.6 Effets et modificateurs
Section intitulée « 16.6 Effets et modificateurs »{ "target": "coutMatiere", "op": "multiply", "value": 1.3, "items": ["ddr5"], "duration": 2, "delay": 0 }| Champ | Description |
|---|---|
target |
Identifiant d’une variable, constante ou formule (pas d’une décision, ni d’une sortie de marché) |
op |
add, multiply ou set |
value |
nombre ou expression, évaluée une fois, au moment où l’effet s’applique (annonce pour un événement effect, résolution pour un dilemme ou un quiz, clôture pour une faillite), dans le contexte de la cible (entreprise ou monde). Ex. "-0.1 * tresorerie" |
items |
pour une cible per : éléments concernés (défaut : tous) |
duration |
pour une constante ou une formule : nombre de tours d’effet (défaut 1), ou "permanent" |
delay |
tours avant que l’effet commence (défaut 0 = tour courant) |
Deux mécanismes selon la cible :
- Cible = variable d’état → modification ponctuelle et définitive de sa valeur courante (
tresorerie add -20000).durationest interdit. Pour un événementeffect, elle s’applique à l’annonce (les joueurs la voient avant de décider) ; pour un dilemme ou un quiz, à la résolution. - Cible = constante ou formule → modificateur temporaire : pendant les tours
t + delayàt + delay + duration − 1, la valeur calculée de la grandeur est transformée. Plusieurs modificateurs se cumulent : on applique d’abord tous lesmultiply, puis tous lesadd; unsetremplace le résultat (le dernier déclenché l’emporte).
Le modificateur porte le contexte de l’événement : un événement de portée company (ou l’option d’un dilemme, toujours choisie par une entreprise, même pour un dilemme world) qui modifie une constante ou une formule ne la modifie que pour l’entreprise concernée — la constante prend alors une valeur effective différente selon l’entreprise. Un événement effect de portée world la modifie pour tout le monde ; s’il cible une grandeur d’entreprise, sa value est évaluée une fois par entreprise (elle peut donc dépendre de l’entreprise : -0.1 * tresorerie). Les effets d’un dilemme, d’un quiz ou d’une faillite ne peuvent pas cibler une grandeur world (E602).
Pour un effet qui dure et se voit dans les comptes, préférez une formule dédiée (commandeB2B, valant 0 par défaut) que l’événement modifie.
Dans les formules, active('penurieSilicium') vaut vrai tant qu’un modificateur de cet événement est actif, et occurred('penurieSilicium') donne le nombre de déclenchements depuis le début.
16.7 Événements info et rumeurs
Section intitulée « 16.7 Événements info et rumeurs »{ "id": "rumeurRachat", "type": "info", "display": "news", "title": "Rumeur", "text": "Selon un blog spécialisé, un géant américain s'apprêterait à racheter un fabricant européen.", "trigger": { "type": "random", "probability": 0.2, "from": 2 }, "maxOccurrences": 1, "rumor": true, "revealText": "C'était une fausse information : il fallait recouper les sources avant de réagir." }rumor: true signale une information non vérifiée ; revealText est affiché au débrief.
16.8 Quiz
Section intitulée « 16.8 Quiz »{ "id": "quizMargeBrute", "type": "quiz", "title": "Quiz : la marge brute", "trigger": { "type": "scheduled", "round": 2 }, "question": "Votre chiffre d'affaires est de 600 000 € et votre coût des ventes de 240 000 €. Quelle est votre marge brute ?", "answers": [ { "id": "a", "label": "360 000 €", "correct": true }, { "id": "b", "label": "840 000 €" }, { "id": "c", "label": "240 000 €" }, { "id": "d", "label": "40 %" } ], "timeLimit": 30, "explanation": "Marge brute = chiffre d'affaires − coût des ventes = 600 000 − 240 000 = 360 000 €, soit 60 % du CA.", "reward": { "threshold": 0.5, "effects": [ { "target": "tresorerie", "op": "add", "value": 5000 } ] }, "concept": "margeBrute"}| Champ | Description |
|---|---|
question |
LocalizedText |
answers |
2 à 6 réponses, au moins une correct: true (plusieurs = plusieurs bonnes réponses acceptées) |
timeLimit |
secondes (10 à 120, défaut 30) |
explanation |
affichée après la réponse |
reward.threshold |
part minimale de bonnes réponses dans l’équipe pour obtenir la récompense (défaut 0.5) |
reward.effects |
effets appliqués à l’entreprise récompensée (à la résolution du tour) |
penalty.effects |
effets si le seuil n’est pas atteint (facultatif) |
Chaque joueur répond individuellement sur son appareil ; l’écran de projection affiche le décompte et les points « quiz » (rapidité + justesse), classement ludique distinct du score de l’entreprise. Un quiz est toujours de portée world (tous les joueurs).
16.9 Événements et intervenant
Section intitulée « 16.9 Événements et intervenant »- Avant de lancer la session, l’intervenant voit la liste des événements du scénario et peut en désactiver, modifier la probabilité, ou créer des événements propres à sa session (mêmes champs, via un formulaire).
- Pendant la partie, il peut déclencher à la main tout événement
manual: true, sur tout le marché ou sur une entreprise choisie, et choisir le tour d’effet (immédiat ou prochain tour).
17. bots — entreprises pilotées par l’ordinateur
Section intitulée « 17. bots — entreprises pilotées par l’ordinateur »"bots": { "default": 2, "max": 6, "profiles": [ { "id": "prudent", "name": "Prudentia Memory", "logo": "logoBot1", "description": "Prix légèrement au-dessus du marché, production calée sur les ventes passées.", "decisions": { "prix": "prixReference * 1.05", "production": "max(0, prev(ventes, 1, demandeBase) * 1.05 - stock)", "embauches": "if(prev(tauxCharge, 1, 0) > 0.95, 2, 0)", "fournisseur": "'europe'", "marketing": "20000" }, "noise": 0.05, "levels": ["facile", "normal", "difficile"] } ]}| Champ | Description |
|---|---|
default |
Nombre de bots proposé par défaut à la création de session (0 = désactivés par défaut) |
max |
Nombre maximum |
profiles[] |
Profils de stratégie ; les bots d’une session reçoivent les profils dans l’ordre, puis en boucle |
profiles[].decisions |
Pour chaque décision : une expression évaluée dans le contexte du bot, au début du tour (état de début de tour, résultats précédents via prev). Décision absente = valeur par défaut. Pour un choice, l’expression renvoie un id d’option ('europe') ; pour une décision per, elle est évaluée pour chaque élément |
profiles[].noise |
Bruit aléatoire multiplicatif sur les décisions numériques (0.05 = ±5 %), pour qu’ils ne soient pas prévisibles |
profiles[].dilemmas |
{ idDilemme: "expression renvoyant un id d'option" } ; sinon l’option default |
Les bots respectent les bornes des décisions, ne répondent pas aux quiz et sont signalés comme bots dans les classements. En marché parallèle, chaque bot joue dans sa propre copie du monde, comme une équipe.
decisionsPEUT contenir des décisions desetup: l’expression est alors évaluée au moment « création » (constantes et grandeurs intégrées seulement) ; sinon le bot prend la valeurinitial.- Les décisions d’un profil sont évaluées dans l’ordre où elles sont écrites : une décision PEUT lire celles déjà calculées du même tour (ex. planifier les vols en connaissant les heures affrétées).
helpersPEUT définir des expressions intermédiaires propres au bot, évaluées avant ses décisions et dans l’ordre d’écriture :"helpers": { "volsCibles": "prev(passagers, 1, demandeBase) / sieges" }, utilisables ensuite dansdecisionsetdilemmaspar leur nom. Leurs noms ne doivent pas entrer en conflit avec une grandeur.- Une décision non proposée dans le niveau joué (
levels) suitautopour tout le monde, bots compris : l’expression du bot est ignorée. - Pour une décision
per, l’expression est évaluée par élément ;itemdésigne l’élément courant. - Limite du format 1.0 : une décision
allocationne peut être pilotée ni par un bot ni parauto(une expression ne renvoie pas d’objet) ; les bots gardent sa valeurinitial/default. Une allocation exclue par le niveau prend aussiinitial/default.
Évolution prévue (non incluse en 1.0) :
"strategy": { "type": "llm" }pour des bots pilotés par un modèle d’IA. Les champs inconnus sont refusés en 1.0.
18. difficulties — niveaux de difficulté
Section intitulée « 18. difficulties — niveaux de difficulté »"difficulties": { "default": "normal", "levels": [ { "id": "facile", "label": "Facile", "description": "Moins de décisions, plus de trésorerie, événements cléments.", "set": { "constants.tresorerieInitiale.value": 350000, "events.penurieSilicium.trigger.probability": 0.05, "settings.rounds.default": 6, "bots.default": 1 } }, { "id": "normal", "label": "Normal" }, { "id": "difficile", "label": "Difficile", "description": "Toutes les décisions, marché plus sensible aux prix.", "set": { "constants.elasticite.value": { "ddr4": 3.4, "ddr5": 2.6 }, "constants.tresorerieInitiale.value": 180000 } } ]}Deux mécanismes complémentaires :
- Filtrer : tout élément portant
levelsn’existe que dans ces niveaux. C’est ainsi qu’on masque des décisions en mode facile (avec unautopour les automatiser), qu’on ajoute des événements en mode difficile, etc. - Surcharger :
setmodifie des champs. Chaque clé est un cheminsection.id.champ[.sous-champ…], oùiddésigne l’élément de la section par son identifiant ; poursettings,bots,scoringle chemin part directement du champ.
Chemins autorisés :
| Section | Exemples de chemins |
|---|---|
constants |
constants.X.value |
variables |
variables.X.initial, variables.X.min, variables.X.max |
decisions |
decisions.X.min, .max, .step, .initial, .default, .auto |
formulas |
formulas.X.expr, formulas.X.visibility |
events |
events.X.trigger.probability, events.X.trigger.from, events.X.maxOccurrences, events.X.manual |
market |
market.segments.X.spillover, market.segments.X.outsideOption |
settings |
settings.rounds.default, settings.rounds.min, settings.rounds.max, settings.decisionTime.default |
bots |
bots.default, bots.profiles.X.decisions.Y, bots.profiles.X.noise |
scoring |
scoring.bankruptcy.when |
L’intervenant choisit le niveau à la création de la session. Un scénario sans difficulties a un unique niveau normal.
Paramètres ajustables par l’intervenant : les constantes déclarées adjustable (§10) sont proposées dans les paramètres avancés de la session, après application du niveau de difficulté.
19. scoring — score, classement, faillite, trophées
Section intitulée « 19. scoring — score, classement, faillite, trophées »"scoring": { "score": "cumul(resultatNet) - penalites", "label": "Bénéfice cumulé", "format": "money", "order": "desc", "tiebreak": ["tresorerie"], "showDuringGame": true, "bankruptcy": { "when": "tresorerie < -150000", "action": "rescue", "message": "Votre banque a dû intervenir en urgence : apport de 200 000 € et pénalité de 300 000 € sur votre score.", "effects": [ { "target": "tresorerie", "op": "add", "value": 200000 }, { "target": "penalites", "op": "add", "value": 300000 } ], "maxRescues": 2 }, "awards": [ { "id": "qualite", "label": "Trophée de la qualité", "icon": "gem", "by": "qualite", "order": "desc" }, { "id": "social", "label": "Employeur de l'année", "icon": "heart-handshake", "by": "climatSocial", "order": "desc" } ], "objectives": [ { "id": "zeroRupture", "label": "Jamais en rupture", "description": "Aucune vente perdue sur toute la partie.", "check": "cumul(sum(ventesPerdues)) == 0", "icon": "package-check" } ]}| Champ | Description |
|---|---|
score |
Expression d’entreprise, recalculée à chaque tour. Par défaut, un scénario DEVRAIT classer au profit cumulé. Tout autre critère est permis (score composite, part de marché, satisfaction…) |
label, format |
Affichage du score |
order |
desc (défaut : le plus grand gagne) ou asc |
tiebreak |
Expressions départageant les ex æquo, dans l’ordre |
showDuringGame |
Classement visible pendant la partie (défaut true) ; l’intervenant peut le masquer |
bankruptcy.when |
Condition de faillite, évaluée en fin de tour |
bankruptcy.action |
continue : l’entreprise est signalée en difficulté mais continue ; rescue : les effects s’appliquent (renflouement, pénalité) et l’entreprise continue, au plus maxRescues fois, puis eliminate ; eliminate : l’entreprise est éliminée, ses joueurs deviennent spectateurs. Défaut continue |
awards |
Trophées remis au débrief : l’entreprise qui a la meilleure valeur de by en fin de partie (by peut être une expression, ex. cumul(caTotal)). Champs : id, label, icon, by, order, levels |
objectives |
Badges obtenus si check est vrai en fin de partie. Champs : id, label, description, icon, check, levels |
En classe, rescue ou continue sont préférables à eliminate : personne ne reste sans jouer.
20. reports — ce que voient les joueurs
Section intitulée « 20. reports — ce que voient les joueurs »Sans reports, la plateforme génère un tableau de bord par défaut : score, trésorerie, et toutes les grandeurs private/public regroupées par group.
"reports": { "dashboard": { "kpis": ["tresorerie", "resultatNet", "caTotal", "tauxMargeBrute", "qualite"] }, "pages": [ { "id": "resultat", "label": "Compte de résultat", "icon": "file-spreadsheet", "widgets": [ { "type": "statement", "title": "Compte de résultat", "period": "rounds", "rows": [ { "ref": "caTotal", "style": "total" }, { "ref": "coutDesVentes", "sign": -1 }, { "ref": "margeBrute", "style": "subtotal" }, { "ref": "masseSalariale", "sign": -1 }, { "label": "Autres charges", "expr": "fraisFixes + marketing + maintenance + formation", "sign": -1 }, { "ref": "resultatNet", "style": "total" } ] } ] }, { "id": "marche", "label": "Marché", "icon": "chart-pie", "widgets": [ { "type": "chart", "chart": "line", "title": "Prix pratiqués", "compare": "companies", "series": ["prix"], "items": ["ddr5"] }, { "type": "chart", "chart": "pie", "title": "Parts de marché DDR5", "compare": "companies", "series": ["partDeMarche"], "items": ["ddr5"] }, { "type": "table", "title": "Concurrents", "compare": "companies", "columns": ["prix", "qualite", "notoriete"] }, { "type": "text", "text": "Les prix des concurrents sont publiés après chaque trimestre." } ] } ], "facilitator": { "columns": ["tresorerie", "resultatNet", "caTotal", "qualite", "effectif", "lignes"] }, "screen": { "kpis": ["prixSilicium"], "charts": [ { "chart": "bar", "compare": "companies", "series": ["qualite"] } ] }}20.1 Widgets
Section intitulée « 20.1 Widgets »type |
Champs | Affichage |
|---|---|---|
kpi |
refs |
Cartes de chiffres clés avec évolution par rapport au tour précédent |
statement |
rows, period (rounds = un tour par colonne, défaut ; last = dernier tour seul) |
État financier : lignes avec ref (grandeur ; label facultatif pour renommer la ligne) ou label + expr (évaluée au moment « clôture » de chaque tour, dans le contexte de l’entreprise, puis enregistrée avec le tour ; la ligne n’est montrée qu’à un spectateur qui peut voir toutes les grandeurs lues par l’expression, et une ligne calculée sans aucune valeur visible est masquée), sign (−1 pour afficher en charge), style (normal, subtotal, total, header) |
chart |
chart (line, bar, area, pie, stackedBar), series (grandeurs), compare (rounds défaut : évolution dans le temps ; companies : comparaison des entreprises), items (pour une grandeur per) |
Graphique |
table |
columns (grandeurs), compare (rounds | companies | items), items (pour les grandeurs per) |
Tableau |
text |
text (markdown, gabarits) |
Texte libre |
image |
image, caption |
Image |
Règles :
- un widget
compare: "companies"vu par un joueur ne montre que des grandeurspublic(le validateur le vérifie,P710). L’intervenant voit tout ; - toute grandeur (constante, variable, décision, formule, sortie de marché) peut être affichée dans un widget ;
- une grandeur
peraffichée sansitemsmontre tous ses éléments (une série, une colonne ou une ligne par élément, libellés par lelabelde l’élément) ; avecitems, seulement ceux listés.
20.2 Vues spéciales
Section intitulée « 20.2 Vues spéciales »dashboard.kpis: chiffres clés en tête de l’écran joueur.facilitator.columns: colonnes du tableau de suivi de l’intervenant (une ligne par entreprise).screen: ce que l’écran de projection affiche entre les tours, en plus du classement et des actualités :kpis(grandeurs de monde ou publiques) etcharts(widgetschartsans le champtype). Seules des grandeurspublicou de portéeworldy sont permises.
21. pedagogy — apprendre en jouant
Section intitulée « 21. pedagogy — apprendre en jouant »"pedagogy": { "learningObjectives": [ "Arbitrer entre prix, volume et marge", "Piloter un stock : coût de stockage contre ventes perdues", "Comprendre la différence entre résultat et trésorerie" ], "briefing": "## Bienvenue chez votre usine\nVous prenez la direction d'une usine de 15 salariés…", "concepts": [ { "id": "margeBrute", "term": "Marge brute", "definition": "Chiffre d'affaires moins le coût des produits vendus.", "formula": "CA − coût des ventes" }, { "id": "pointMort", "term": "Point mort", "definition": "Niveau de chiffre d'affaires à partir duquel l'entreprise couvre toutes ses charges." } ], "tips": [ { "round": 1, "text": "Commencez par vérifier votre capacité de production avant de fixer vos volumes." }, { "when": "prev(sum(ventesPerdues)) > 0.1 * prev(sum(demande))", "text": "Vous avez perdu {{ prev(sum(ventesPerdues)) }} ventes faute de stock au dernier trimestre." } ], "insights": [ { "id": "rupture", "when": "cumul(sum(ventesPerdues)) > 0.1 * cumul(sum(demande))", "tone": "warning", "text": "Vous avez perdu {{ cumul(sum(ventesPerdues)) }} ventes faute de stock, soit environ {{ cumul(sum(ventesPerdues) * avg(prix)) | money }} de chiffre d'affaires." }, { "id": "tresoNegative", "when": "occurred('decouvert') > 0", "tone": "warning", "text": "Votre trésorerie est passée dans le rouge : le résultat n'est pas la trésorerie !" } ], "debriefQuestions": [ "Quelle équipe a vendu le plus cher ? A-t-elle gagné ? Pourquoi ?", "Qu'auriez-vous fait différemment au trimestre 3 ?" ], "facilitatorNotes": "## Déroulé conseillé\n- Tour d'essai : 10 minutes, expliquez l'écran…"}| Champ | Description |
|---|---|
learningObjectives |
Objectifs pédagogiques (marketplace, débrief) |
briefing |
Histoire et consignes affichées au lancement. Ses gabarits sont évalués au moment « annonce » du tour 1 ; dans la salle d’attente, avant la mise en place, ils sont rendus avec une mise en place par défaut (valeurs initial des décisions de setup) |
concepts |
Glossaire : chaque grandeur, décision ou événement peut s’y relier par concept ; le terme s’affiche alors avec une info-bulle |
tips |
Conseils affichés pendant la saisie : à un tour précis (round) ou quand une condition est vraie (when, évaluée au début du tour pour chaque entreprise) |
insights |
Enseignements personnalisés du débrief : pour chaque entreprise, ceux dont when est vrai en fin de partie sont affichés. tone : positive, warning, neutral |
debriefQuestions |
Questions pour animer la discussion finale |
facilitatorNotes |
Guide de l’animateur (markdown) : déroulé, pièges, corrigé ; visible seulement des intervenants |
22. Grandeurs intégrées
Section intitulée « 22. Grandeurs intégrées »Fournies par le moteur, utilisables dans toutes les expressions, non modifiables :
| Nom | Type | Valeur |
|---|---|---|
round |
entier | Numéro du tour en cours (1 au premier tour ; 0 pendant la mise en place) |
rounds |
entier | Nombre total de tours de la session |
isLastRound |
booléen | round == rounds |
isPractice |
booléen | Vrai pendant le tour d’essai |
mode |
texte | 'shared' ou 'parallel' |
difficulty |
texte | Id du niveau de difficulté |
companyCount |
entier | Nombre d’entreprises dans le marché (1 en mode parallèle) |
isBot |
booléen | L’entreprise est un bot (portée entreprise uniquement) |
leaderboardRank |
entier | Rang de l’entreprise au classement général après le dernier tour clôturé (toutes instances confondues, donc aussi en marché parallèle) ; 0 avant la fin du premier tour. Portée entreprise uniquement |
item |
texte | Id de l’élément courant, dans une grandeur per |
23. Validation
Section intitulée « 23. Validation »L’import, l’éditeur et l’outil en ligne de commande (stratilead validate) vérifient un scénario en trois niveaux :
- Structure : conformité au JSON Schema.
- Sémantique : références, types, graphe de dépendances, portées, cohérence.
- Simulation (banc d’essai,
stratilead bench) : des centaines de parties jouées par des stratégies automatiques pour détecter les déséquilibres.
Un scénario avec au moins une erreur ne peut être ni joué ni publié. Les avertissements n’empêchent pas de jouer ; la publication sur la marketplace exige qu’ils soient corrigés ou explicitement justifiés.
Chaque problème a un code stable, un chemin vers l’élément concerné et un message. Liste de référence :
Structure (S1xx)
Section intitulée « Structure (S1xx) »| Code | Gravité | Problème |
|---|---|---|
| S100 | erreur | JSON invalide |
| S101 | erreur | Champ requis manquant |
| S102 | erreur | Champ inconnu (souvent une faute de frappe) |
| S103 | erreur | Type de valeur incorrect |
| S104 | erreur | Valeur hors des bornes autorisées (ex. probability > 1) |
| S105 | erreur | formatVersion non supporté |
| S110 | erreur | Limite dépassée (taille, nombre d’éléments, longueur d’expression) |
Références et noms (R2xx)
Section intitulée « Références et noms (R2xx) »| Code | Gravité | Problème |
|---|---|---|
| R200 | erreur | Identifiant mal formé |
| R201 | erreur | Identifiant en double |
| R202 | erreur | Identifiant réservé |
| R210 | erreur | Référence inconnue (grandeur, liste, élément, option, événement, courbe, asset, groupe, rôle, niveau, concept) |
| R211 | erreur | set de difficulté : chemin invalide ou non autorisé |
| R220 | avertissement | Grandeur jamais utilisée ni affichée |
| R221 | avertissement | Asset sans fichier (src absent) |
| R222 | avertissement | Concept défini mais jamais référencé |
Types et expressions (T3xx)
Section intitulée « Types et expressions (T3xx) »| Code | Gravité | Problème |
|---|---|---|
| T300 | erreur | Erreur de syntaxe dans une expression |
| T301 | erreur | Fonction inconnue ou mauvais nombre d’arguments |
| T310 | erreur | Types incompatibles (ex. nombre comparé à un id d’option) |
| T311 | erreur | Expression qui doit être booléenne et ne l’est pas (when, check, enabledIf) |
| T312 | erreur | Id d’option comparé à une option qui n’existe pas dans la décision |
| T320 | erreur | Grandeur par élément utilisée sans agrégation hors d’un contexte de même liste |
| T321 | erreur | Indice [x] qui n’est pas un élément de la liste |
| T330 | erreur | Portée invalide (ex. grandeur d’entreprise lue dans une expression de monde sans agrégation market_*) |
| T340 | erreur | Contrainte, preview, borne, enabledIf ou initial qui dépend du marché, du hasard ou d’une grandeur non encore disponible |
| T350 | erreur | initial de variable qui utilise autre chose que des constantes, des décisions de setup et des grandeurs intégrées |
| T360 | avertissement | Division dont le dénominateur peut valoir zéro (le résultat sera 0) |
Graphe de calcul (D4xx)
Section intitulée « Graphe de calcul (D4xx) »| Code | Gravité | Problème |
|---|---|---|
| D400 | erreur | Dépendance circulaire (le cycle est indiqué) |
| D410 | avertissement | Formule active qui dépend d’une formule exclue par levels |
Marché (M5xx)
Section intitulée « Marché (M5xx) »| Code | Gravité | Problème |
|---|---|---|
| M500 | erreur | size ou outsideOption de portée entreprise |
| M501 | erreur | attractiveness ou supply de portée monde seule (doit pouvoir différer selon l’entreprise) |
| M510 | erreur | Sortie de marché en conflit avec une grandeur existante |
| M520 | avertissement | Mode parallel supporté sans concurrent virtuel ni outsideOption : les prix n’ont alors aucun effet sur les ventes |
Événements (E6xx)
Section intitulée « Événements (E6xx) »| Code | Gravité | Problème |
|---|---|---|
| E600 | erreur | Effet qui cible une décision ou une sortie de marché |
| E601 | erreur | duration sur un effet qui cible une variable |
| E602 | erreur | Événement company qui cible une grandeur de portée world |
| E610 | erreur | Probabilités des outcomes qui ne somment pas à 1 |
| E611 | erreur | Dilemme sans default, ou default inconnu |
| E612 | erreur | Quiz sans bonne réponse |
| E620 | avertissement | Événement qui ne peut jamais se déclencher (bornes from/to, manual: false sans déclencheur) |
Présentation et pédagogie (P7xx)
Section intitulée « Présentation et pédagogie (P7xx) »| Code | Gravité | Problème |
|---|---|---|
| P700 | avertissement | Décision sans help |
| P701 | avertissement | Formule affichée sans description |
| P702 | avertissement | Événement sans teaching (exemptés : les quiz qui ont une explanation, et les rumeurs, c’est-à-dire les événements info qui portent un revealText : la leçon est donnée par la révélation au débrief) |
| P710 | erreur | Widget compare: "companies" d’un rapport joueur qui affiche une grandeur non public |
| P720 | avertissement | Texte manquant dans une langue déclarée dans meta.languages |
| P730 | avertissement | Aucun objectif pédagogique |
| P740 | avertissement | limits.maxPlayers inaccessible : supérieur à maxCompanies × teamSize.max |
Banc d’essai (B8xx)
Section intitulée « Banc d’essai (B8xx) »| Code | Gravité | Problème |
|---|---|---|
| B800 | erreur | Erreur d’exécution (valeur non numérique, NaN, infini) apparue pendant les simulations |
| B810 | avertissement | Taux de faillite supérieur à 40 % des parties avec des stratégies raisonnables |
| B811 | avertissement | Taux de faillite nul même avec des stratégies aléatoires (le jeu ne punit rien) |
| B820 | avertissement | Stratégie dominante : une valeur extrême d’une décision gagne dans plus de 80 % des cas |
| B821 | avertissement | Décision sans influence mesurable sur le score |
| B830 | avertissement | Écart de score trop faible entre stratégies (le jeu ne récompense pas les bonnes décisions) |
| B840 | avertissement | Événement dont l’impact moyen dépasse 50 % du résultat moyen d’un tour |
| B850 | avertissement | Division par zéro survenue pendant les simulations |
24. Versions
Section intitulée « 24. Versions »24.1 Version du format (formatVersion)
Section intitulée « 24.1 Version du format (formatVersion) »"1.0"pour ce document. Le moteur accepte toutes les versions1.xantérieures ou égales à la sienne.- Une version mineure (
1.1) n’ajoute que des champs facultatifs : un scénario 1.0 reste valide. - Une version majeure (
2.0) peut casser la compatibilité : la plateforme fournit alors une migration automatique des scénarios existants.
24.2 Version du scénario (version)
Section intitulée « 24.2 Version du scénario (version) »- Versionnage sémantique choisi par l’auteur :
1.0.0,1.1.0,2.0.0. - Une version publiée est immuable : toute modification crée une nouvelle version.
- Une session reste liée à la version avec laquelle elle a été créée.
- Changez la version majeure si l’équilibre du jeu change nettement (les statistiques et notes de la marketplace sont affichées par version majeure).
25. Exemples
Section intitulée « 25. Exemples »- Scénario minimal commenté pas à pas : guide de création (« Le stand de limonade »), fichier
scenarios/officiels/limonade/scenario.json. - Scénario complet utilisant toutes les fonctionnalités :
scenarios/officiels/usine-ram/scenario.json. - Autres scénarios officiels :
scenarios/officiels/.