Skip to content
FrançaisCreate a game

Langage de formules Stratilead — version 1.0

This content is not available in your language yet.

Les formules d’un scénario s’écrivent dans un petit langage proche des formules d’un tableur. Il est volontairement limité : pas de boucles, pas de variables temporaires, pas d’accès au système. Tout calcul est déterministe : mêmes entrées, même graine aléatoire, mêmes résultats.

Ce document fait foi pour les auteurs, les IA génératrices et les développeurs du moteur. Il complète la référence du format.


ventes * prix // arithmétique
min(capacite, demande) // fonctions
if(tresorerie < 0, tresorerie * 0.04, 0) // condition
fournisseur == 'asie' // comparer une option (apostrophes)
fournisseur.facteurCout // propriété de l'option choisie
sum(ca) // somme sur les éléments d'une liste
prix[ddr5] // valeur d'un élément précis
prev(ventes) // valeur au tour précédent
cumul(resultatNet) // somme depuis le début de la partie
share(attractivite) // part du total de toutes les entreprises
curve(effetFormation, formationParEmploye) // lire une courbe
normal(0, 0.05) // tirage aléatoire (reproductible)

Où s’écrivent les expressions : expr des formules, next et initial des variables, bornes et auto des décisions, check des contraintes, déclencheurs et effets des événements, décisions des bots, score, gabarits {{ }} des textes… Dans le JSON, une expression est une chaîne de caractères : "expr": "ventes * prix".


Type Exemples Remarques
nombre 42, 3.14, 0.15, 1e6, 150_000 Nombres à virgule flottante 64 bits. _ est un séparateur de milliers facultatif. La virgule décimale est le point
booléen true, false
texte 'europe', 'ddr5' Uniquement pour comparer des ids d’options, d’éléments, mode ou difficulty. Les apostrophes sont recommandées (les guillemets " sont acceptés mais doivent être échappés dans le JSON)
vecteur ventes (grandeur per hors de son contexte) Une valeur par élément d’une liste ; doit être agrégé (§5)

Il n’y a aucune conversion implicite : true + 1 est une erreur (T310). Pour compter des booléens, utilisez num(condition) (1 si vrai, 0 sinon).

Les pourcentages sont des ratios : 0.15 signifie 15 %.


Un identifiant désigne, par ordre de recherche :

  1. une grandeur intégrée : round, rounds, isLastRound, isPractice, mode, difficulty, companyCount, isBot, leaderboardRank, item (voir la référence §22) ;
  2. une grandeur du scénario : constante, variable, décision, formule ou sortie de marché.

Attention : round sans parenthèses est le numéro du tour, round(x) avec parenthèses est la fonction d’arrondi. Le parseur les distingue par la parenthèse ouvrante.

Dans certains arguments de fonction, un identifiant désigne autre chose (une courbe, un événement, une option, un élément de liste) : c’est précisé pour chaque fonction. Ces arguments peuvent s’écrire avec ou sans apostrophes : curve(effetFormation, x) et curve('effetFormation', x) sont équivalents.

Valeur d’un identifiant selon la nature de la grandeur, pendant le tour t :

Grandeur Valeur
constante sa valeur (éventuellement modifiée par un événement actif)
variable sa valeur au début du tour t ; à la clôture (score, faillite, enseignements), sa valeur de fin de tour
décision la valeur décidée pour le tour t
formule, sortie de marché la valeur calculée au tour t

Du plus prioritaire au moins prioritaire :

Priorité Opérateurs Associativité Remarques
1 f(…), x[…], x.prop gauche Appel, indice, propriété d’option
2 ^ droite Puissance : 2 ^ 3 ^ 2 = 2 ^ 9
3 -x, not x, !x — -2 ^ 2 vaut -4
4 *, /, % gauche % est le reste de la division (modulo), pas un pourcentage
5 +, - gauche
6 <, <=, >, >= — Non chaînables : écrire between(x, 1, 5) ou x >= 1 and x <= 5
7 ==, != — Nombres, booléens, textes
8 and, && gauche Évaluation paresseuse
9 or, || gauche Évaluation paresseuse
10 c ? a : b droite Équivalent à if(c, a, b)

Division par zéro : a / 0 vaut 0 (et non une erreur), pour qu’une partie ne s’arrête jamais. Le validateur avertit si un dénominateur peut être nul (T360) et le banc d’essai compte les occurrences (B850). Pour une autre valeur de repli, utilisez safeDiv(a, b, repli).

Le résultat d’un calcul non fini (NaN, infini — par exemple sqrt(-1)) est remplacé par 0 et signalé comme erreur d’exécution (B800) : un scénario publié ne doit jamais en produire.


Soit une liste produit = [ddr4, ddr5] et des grandeurs déclarées "per": "produit" : prix, ventes, stock.

5.1 Dans une grandeur de la même liste : l’élément courant

Section intitulée « 5.1 Dans une grandeur de la même liste : l’élément courant »

Dans une formule "per": "produit", prix désigne le prix de l’élément en cours de calcul. La formule est calculée une fois par produit.

{ "id": "ca", "per": "produit", "expr": "ventes * prix" }

item vaut l’id de l’élément courant : if(item == 'ddr5', 1.4, 1).

prix[ddr5] vaut le prix du produit ddr5, quel que soit le contexte. L’indice peut être un id d’élément (avec ou sans apostrophes) ou une expression texte : prix[argmax(ventes)].

ventes[*] désigne le vecteur complet (toutes les valeurs), y compris dans une grandeur de la même liste où ventes seul désigne l’élément courant. C’est ainsi qu’on rapporte un élément au total :

{ "id": "partDuProduit", "per": "produit", "expr": "ventes / max(1, sum(ventes[*]))" }

Les bornes min/max/step et la valeur auto d’une décision per sont des expressions évaluées pour chaque élément (contexte de l’élément) : "max": "capaciteParType" donne une borne différente par type de chambre.

Hors d’une grandeur de la même liste, ventes est un vecteur (une valeur par produit). Un vecteur doit être réduit à un nombre par une fonction d’agrégation :

sum(ventes) // ventes totales
avg(prix) // prix moyen (non pondéré)
max(stock) // plus gros stock
sum(ventes * prix) // calcul élément par élément, puis somme
sum(ventes * prix) / max(1, sum(ventes)) // prix moyen pondéré par les ventes
countIf(stock == 0) // nombre de produits en rupture
argmax(ventes) // id du produit le plus vendu

À l’intérieur d’une agrégation, les opérations entre vecteurs de la même liste se font élément par élément, et un nombre se combine avec chaque élément ; les fonctions à plusieurs arguments (min(a, b), if(c, a, b), pow…) s’appliquent aussi élément par élément : sum(min(stock, demande)). Combiner deux listes différentes est une erreur (T320). Un vecteur qui n’est pas réduit par une agrégation là où un nombre est attendu est aussi une erreur (T320).

Le format 1.0 ne gère qu’une dimension : pas de grandeur « par produit et par usine ». Créez une liste d’éléments combinés si nécessaire.


Expression Décision Valeur
fournisseur choice Id de l’option choisie (texte)
fournisseur == 'asie' choice Vrai si cette option est choisie. Une option inconnue est une erreur (T312)
fournisseur.facteurCout choice Propriété props.facteurCout de l’option choisie
has(certifications, iso9001) multiChoice Vrai si l’option est cochée
count(certifications) multiChoice Nombre d’options cochées
certifications.cout multiChoice Somme de la propriété cout sur les options cochées (0 si aucune)
canaux.web allocation Montant alloué à l’option web

Toutes les options d’une décision DEVRAIENT déclarer les mêmes propriétés ; une propriété absente vaut 0 (ou false). Sur un multiChoice, une propriété booléenne donne le nombre d’options cochées où elle est vraie.

Tolérance : quand l’autre côté d’une comparaison (ou la clé d’un switch) est une décision choice ou un élément de liste, un id d’option écrit sans apostrophes est accepté s’il ne désigne aucune grandeur (fournisseur == asie). Les apostrophes restent la forme recommandée.


Fonction Résultat Exemple
abs(x) Valeur absolue abs(ecart)
min(a, b, …) Plus petite valeur (2 arguments ou plus) min(stock, demande)
max(a, b, …) Plus grande valeur max(0, resultat)
clamp(x, bas, haut) x borné à [bas, haut] clamp(qualite, 0, 100)
round(x, d) Arrondi à d décimales (défaut 0), demi vers l’extérieur round(prix, 2)
floor(x) / ceil(x) Arrondi inférieur / supérieur floor(capacite / chargeUnitaire)
sqrt(x) Racine carrée
pow(x, y) Puissance (équivalent à x ^ y) pow(prixRef / prix, elasticite)
exp(x), ln(x), log10(x) Exponentielle, logarithmes ln(1 + marketing / 20000)
sign(x) -1, 0 ou 1
safeDiv(a, b, repli) a / b, ou repli si b vaut 0 safeDiv(resultat, ca, 0)
normalCdf(x, moyenne, écartType) Probabilité qu’une loi normale soit ≤ x normalCdf(capacite, presentsAttendus, ecart) : probabilité de ne refuser personne
normalLoss(x, moyenne, écartType) Espérance de max(X − x, 0) pour X normale : dépassement attendu au-delà de x normalLoss(sieges, presentsAttendus, ecart) : passagers refusés attendus par vol
Fonction Résultat Exemple
if(c, a, b) a si c est vrai, sinon b (seule la branche utile est calculée) if(tresorerie < 0, -tresorerie * 0.04, 0)
switch(v, k1, r1, k2, r2, …, défaut) Le résultat associé à la première clé égale à v, sinon défaut switch(positionnement, 'volume', 0.95, 'premium', 1.1, 1)
between(x, bas, haut) bas <= x and x <= haut between(round, 3, 6)
num(b) 1 si b est vrai, 0 sinon num(stock == 0) * penalite

Un seul argument, un vecteur (§5.3).

Fonction Résultat
sum(v) Somme
avg(v) Moyenne
min(v) / max(v) Minimum / maximum
count(v) Nombre d’éléments de la liste
countIf(v) Nombre d’éléments où le vecteur booléen v est vrai
argmax(v) / argmin(v) Id de l’élément de valeur maximale / minimale (le premier en cas d’égalité)

Ces fonctions lisent la valeur d’une grandeur d’entreprise chez toutes les entreprises du marché (équipes et bots, pas les concurrents virtuels). En marché parallèle, le « marché » ne contient que l’entreprise elle-même : share(x) vaut 1 et rank(x) vaut 1. Pour une vraie concurrence dans les deux modes, utilisez le bloc market (référence §15).

Fonction Résultat Utilisable dans
market_sum(x) Somme de x sur toutes les entreprises entreprise et monde
market_avg(x) Moyenne entreprise et monde
market_min(x) / market_max(x) Minimum / maximum entreprise et monde
market_count() Nombre d’entreprises (= companyCount) entreprise et monde
share(x) x / market_sum(x) pour cette entreprise (0 si la somme est nulle) entreprise
rank(x) Rang de l’entreprise, 1 = plus grande valeur (ex æquo : même rang) entreprise
rank_asc(x) Rang, 1 = plus petite valeur entreprise

x doit être une grandeur d’entreprise (ou une expression d’entreprise). Ces fonctions dépendent du résultat de toutes les entreprises : elles ne sont utilisables qu’à la résolution (pas dans les contraintes ni les aperçus).

Fonction Résultat
prev(e) Valeur de l’expression e au tour précédent
prev(e, n) Valeur de e il y a n tours
prev(e, n, défaut) Idem, avec défaut si ce tour n’existe pas (avant le tour 1)
cumul(e) Somme de e du tour 1 au tour courant inclus
avgLast(e, n) Moyenne de e sur les n derniers tours, tour courant inclus (moins s’il n’y en a pas autant)
minLast(e, n) / maxLast(e, n) Minimum / maximum de e sur les n derniers tours
stdevLast(e, n) Écart-type de e sur les n derniers tours (0 s’il n’y a qu’un tour)
growth(e) Évolution relative par rapport au tour précédent : (e − prev(e)) / abs(prev(e)), 0 si prev(e) vaut 0 ou n’existe pas

Précisions :

  • e peut être une expression quelconque : prev(sum(ventes) * avg(prix)).
  • Sans défaut, quand le tour demandé n’existe pas : une variable vaut sa valeur initiale, une décision sa valeur initial, tout le reste vaut 0.
  • Pour une variable, prev(tresorerie) est la valeur au début du tour précédent ; la valeur au début du tour courant s’écrit simplement tresorerie.
  • prev ne crée pas de dépendance dans le tour courant : c’est le moyen de faire réagir une formule à son propre passé sans dépendance circulaire.
  • Le tour d’essai ne fait pas partie de l’historique.
  • prev, cumul, avgLast, minLast, maxLast, stdevLast et growth ne peuvent pas contenir textuellement de fonction aléatoire ; référencer une formule qui en utilise une est permis (cumul(ventesRefusees)), ses valeurs passées étant enregistrées.
  • Piège : prev(prix, 1, prix) au tour 1 renvoie la valeur courante (le défaut), pas la valeur initial ; pour détecter une hausse dès le tour 1, écrire prev(prix) sans défaut (une décision vaut alors son initial).
Fonction Résultat
random() Nombre uniforme dans [0, 1[
uniform(a, b) Nombre uniforme dans [a, b[
randomInt(a, b) Entier uniforme entre a et b inclus
normal(moyenne, écartType) Tirage selon une loi normale
chance(p) Vrai avec la probabilité p

Le hasard est reproductible : chaque appel est identifié par la partie (graine), le tour, l’entreprise, la grandeur, l’élément de liste et la position de l’appel dans l’expression. Rejouer une partie avec la même graine et les mêmes décisions redonne exactement les mêmes résultats.

  • En marché parallèle, toutes les entreprises reçoivent les mêmes tirages.
  • En marché partagé, les tirages du monde sont communs ; ceux des entreprises sont indépendants ou identiques selon settings.sharedModeLuck.
  • Le hasard est interdit dans les contraintes, les aperçus, les bornes de décisions, enabledIf, les déclencheurs d’événements (probability et when sont déjà des tirages gérés par le moteur), le score, les textes et les enseignements.
Fonction Résultat
curve(idCourbe, x) Valeur de la courbe idCourbe (section curves) au point x
Fonction Résultat
active(idEvenement) Booléen : vrai si un modificateur de cet événement est actif ce tour pour cette entreprise (ou pour le monde)
occurred(idEvenement) Nombre (entier) de fois où l’événement a touché cette entreprise (ou le monde) depuis le début de la partie, tour courant inclus
dilemma(idDilemme) Texte : id de l’option choisie ce tour pour ce dilemme, '' s’il n’a pas eu lieu ce tour. Pour un choix passé : prev(dilemma(x))

Les tirages aléatoires sont indépendants par entreprise (sauf réglage contraire, §7.6), par élément de liste dans une grandeur per, et par appel dans l’expression : normal(0, 1) + normal(0, 1) fait deux tirages distincts.


Construction Annonce (déclencheurs, bots, enabledIf, conseils) Saisie (bornes, contraintes, aperçus) Résolution (formules, next) Clôture et fin de partie (score, faillite, enseignements, trophées)
Constantes, variables, setup, intégrées oui oui oui oui
Décisions du tour non oui oui oui
Formules celles qui ne dépendent ni des décisions du tour, ni du marché, ni du hasard celles qui ne dépendent ni du marché ni du hasard oui oui
prev, cumul, avgLast, growth oui oui oui oui
Sorties de marché, market_*, share, rank via prev uniquement non oui oui
Hasard décisions des bots uniquement non oui non

Le validateur vérifie ces règles (code T340).


Le validateur vérifie chaque expression avant toute partie :

  • identifiants et fonctions connus (R210, T301) ;
  • types cohérents : on n’additionne pas un booléen, on ne compare pas un nombre à un texte (T310) ;
  • conditions booléennes là où elles sont attendues (T311) ;
  • options et éléments de liste existants (T312, T321) ;
  • vecteurs agrégés (T320) ;
  • portées : une expression de monde ne lit une grandeur d’entreprise que via market_* (T330) ;
  • moment d’évaluation (T340).

Limites : 2 000 caractères par expression, 64 niveaux d’imbrication.


expression = ternary ;
ternary = or_expr , [ "?" , expression , ":" , expression ] ;
or_expr = and_expr , { ( "or" | "||" ) , and_expr } ;
and_expr = equality , { ( "and" | "&&" ) , equality } ;
equality = comparison , [ ( "==" | "!=" ) , comparison ] ;
comparison = additive , [ ( "<" | "<=" | ">" | ">=" ) , additive ] ;
additive = multiplicative , { ( "+" | "-" ) , multiplicative } ;
multiplicative = unary , { ( "*" | "/" | "%" ) , unary } ;
unary = ( "-" | "not" | "!" ) , unary | power ;
power = postfix , [ "^" , unary ] ;
postfix = primary , { "[" , expression , "]" | "." , identifier } ;
primary = number | string | "true" | "false"
| identifier , [ "(" , [ arguments ] , ")" ]
| "(" , expression , ")" ;
arguments = expression , { "," , expression } ;
number = digit , { digit | "_" } , [ "." , digit , { digit } ] , [ ( "e" | "E" ) , [ "+" | "-" ] , digit , { digit } ] ;
string = "'" , { any_char - "'" } , "'" | '"' , { any_char - '"' } , '"' ;
identifier = letter , { letter | digit | "_" } ;

Espaces et retours à la ligne sont libres. Il n’y a pas de commentaires dans les expressions : documentez avec description.


Formules d’usage courant, à adapter.

Marge unitaire prix - coutUnitaire
Taux de marge safeDiv(margeBrute, caTotal, 0)
Ventes limitées au stock min(stock + production, demande)
Stock de fin de tour max(0, stock + production - ventes)
Stock qui se périme (stock + production - ventes) * (1 - tauxPerte)
Capacité de production min(lignes * capaciteParLigne, effectif * productivite)
Production réalisable production * min(1, safeDiv(capacite, sum(production[*] * chargeUnitaire[*]), 1)) // dans une formule per produit
Amortissement linéaire valeurInvestie / dureeAmortissement
Intérêts d'un emprunt emprunt * tauxAnnuel / 4 // tour = trimestre
Agios de découvert if(tresorerie < 0, -tresorerie * tauxDecouvert, 0)
Impôt sur les bénéfices max(0, resultatAvantImpot) * tauxImpot
Élasticité-prix pow(prixReference / prix, elasticite)
Rendement décroissant ln(1 + budget / echelle)
Saturation (0 → max) max_ * (1 - exp(-budget / echelle))
Effet retardé d'un tour prev(marketing, 1, 0)
Moyenne mobile avgLast(ventes, 3)
Mémoire (notoriété) notoriete * 0.9 + gain // dans le next d'une variable
Convergence progressive qualite + 0.5 * (qualiteCible - qualite) // dans le next
Saisonnalité trimestrielle switch(round % 4, 0, 1.15, 1, 0.92, 1)
Marche aléatoire bornée clamp(prix * (1 + normal(0, 0.03)), 0.5, 2)
Retour à la moyenne x + 0.3 * (1 - x) + normal(0, 0.04)
Surbooking (par vol/nuit) refuses = normalLoss(capacite, reservations * (1 - tauxNoShow), ecartPresents)
// la demande d'un tour est agrégée : raisonner par vol ou par nuit (voir le kit IA, recette « Surbooking »)
Prime au leader if(rank(caTotal) == 1, 5000, 0)

Pour chaque formule affichée, le joueur peut demander une explication. Le moteur réévalue l’expression en conservant les valeurs intermédiaires et l’affiche sous forme d’arbre lisible :

Résultat net = résultat avant impôt (82 000 €) − impôt (20 500 €) = 61 500 €
└ résultat avant impôt = EBE (97 000 €) − amortissements (12 000 €) − intérêts (3 000 €)
└ impôt = max(0 ; 82 000 €) × 25 % = 20 500 €

Les libellés viennent des label des grandeurs, et l’explication en français de leur description. D’où l’importance de soigner label et description : ce sont eux qui font apprendre.