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.
1. En bref
Section intitulée « 1. En bref »ventes * prix // arithmétiquemin(capacite, demande) // fonctionsif(tresorerie < 0, tresorerie * 0.04, 0) // conditionfournisseur == 'asie' // comparer une option (apostrophes)fournisseur.facteurCout // propriété de l'option choisiesum(ca) // somme sur les éléments d'une listeprix[ddr5] // valeur d'un élément précisprev(ventes) // valeur au tour précédentcumul(resultatNet) // somme depuis le début de la partieshare(attractivite) // part du total de toutes les entreprisescurve(effetFormation, formationParEmploye) // lire une courbenormal(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".
2. Valeurs et types
Section intitulée « 2. Valeurs et types »| 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 %.
3. Identifiants
Section intitulée « 3. Identifiants »Un identifiant désigne, par ordre de recherche :
- une grandeur intégrée :
round,rounds,isLastRound,isPractice,mode,difficulty,companyCount,isBot,leaderboardRank,item(voir la référence §22) ; - 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 |
4. Opérateurs
Section intitulée « 4. Opérateurs »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.
5. Listes et grandeurs par élément
Section intitulée « 5. Listes et grandeurs par élément »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).
5.2 Indice explicite et vecteur complet
Section intitulée « 5.2 Indice explicite et vecteur complet »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.
5.3 Vecteurs et agrégations
Section intitulée « 5.3 Vecteurs et agrégations »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 totalesavg(prix) // prix moyen (non pondéré)max(stock) // plus gros stocksum(ventes * prix) // calcul élément par élément, puis sommesum(ventes * prix) / max(1, sum(ventes)) // prix moyen pondéré par les ventescountIf(stock == 0) // nombre de produits en ruptureargmax(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.
6. Options de décisions
Section intitulée « 6. Options de décisions »| 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.
7. Catalogue des fonctions
Section intitulée « 7. Catalogue des fonctions »7.1 Mathématiques
Section intitulée « 7.1 Mathématiques »| 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 |
7.2 Logique
Section intitulée « 7.2 Logique »| 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 |
7.3 Listes (vecteurs)
Section intitulée « 7.3 Listes (vecteurs) »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é) |
7.4 Concurrence entre entreprises
Section intitulée « 7.4 Concurrence entre entreprises »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).
7.5 Temps et historique
Section intitulée « 7.5 Temps et historique »| 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 :
epeut ê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 valeurinitial, tout le reste vaut0. - 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 simplementtresorerie. prevne 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,stdevLastetgrowthne 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 valeurinitial; pour détecter une hausse dès le tour 1, écrireprev(prix)sans défaut (une décision vaut alors soninitial).
7.6 Hasard
Section intitulée « 7.6 Hasard »| 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 (probabilityetwhensont déjà des tirages gérés par le moteur), le score, les textes et les enseignements.
7.7 Courbes
Section intitulée « 7.7 Courbes »| Fonction | Résultat |
|---|---|
curve(idCourbe, x) |
Valeur de la courbe idCourbe (section curves) au point x |
7.8 Événements
Section intitulée « 7.8 Événements »| 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.
8. Où chaque construction est permise
Section intitulée « 8. Où chaque construction est permise »| 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).
9. Vérification des types
Section intitulée « 9. Vérification des types »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.
10. Grammaire (EBNF)
Section intitulée « 10. Grammaire (EBNF) »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.
11. Recettes
Section intitulée « 11. Recettes »Formules d’usage courant, à adapter.
Marge unitaire prix - coutUnitaireTaux 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 produitAmortissement linéaire valeurInvestie / dureeAmortissementIntérêts d'un emprunt emprunt * tauxAnnuel / 4 // tour = trimestreAgios 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 variableConvergence progressive qualite + 0.5 * (qualiteCible - qualite) // dans le nextSaisonnalité 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)12. « Comprendre ce chiffre »
Section intitulée « 12. « Comprendre ce chiffre » »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.