Architecture de la quête

Il n'y a pas de scripts. Il y a de la logique : comment sont organisés les nœuds et les variables dans Romergo

Quand on parle de logique dans un éditeur de jeu, on en vient généralement rapidement aux scripts. Quelque part, l'auteur écrit JavaScript, quelque part Python, et quelque part il étudie son propre langage moteur. Cela offre une liberté presque illimitée, mais cela apporte aussi des erreurs de syntaxe, des boucles suspendues, des plugins incompatibles et du code que même l'auteur a peur de rouvrir après six mois.

Dans Romergo, j'ai suivi une autre voie. Il n'y a pas de scripts arbitraires à l'intérieur de la quête. Il est impossible d'insérer une fonction, de faire une requête réseau ou d’accéder directement au lecteur. À la place, l'histoire est construite à partir d'un ensemble limité, mais suffisamment expressif : des nœuds logiques, des variables, des conditions et des effets.

Cela ne signifie pas que dans Romergo on ne peut créer que de simples bifurcations. On peut mémoriser les décisions du joueur, compter des points, afficher et cacher des options, choisir différents parcours dans les chapitres suivants, créer des événements aléatoires et concevoir des interfaces interactives. Simplement, au lieu de la commande « exécute ce code », l’auteur décrit « dans cet état, ceci doit se produire ».

Le graphe est déjà un programme

La logique la plus basique Romergo se trouve directement sur la carte du chapitre. Les scènes sont reliées par des transitions, et des nœuds spéciaux déterminent où le parcours continuera.

Pour simplifier au maximum, une petite quête ressemble à ceci :

Début
  ↓
Conversation avec le garde
  ↓
Y a-t-il un pass ?
  ├─ oui → Archives fermées
  └─ non → Chemin détourné

Ceci est déjà une logique exécutable. Player entre dans le nœud de démarrage, affiche la scène, lit l'état de l'historique et choisit la transition appropriée. L'auteur voit le même parcours comme une carte compréhensible, et non comme un ensemble de if, goto et d'identifiants de service.

![Flow Romergo : Final Accusation passe par Switch et trois If / Else vers différentes fins, en bas le panneau Logic est ouvert](../assets/logic-without-scripts-flow-nodes.webp)

*Fragment d'un réel Flow dans Builder : la scène Final Accusation transmet la décision à Switch, puis plusieurs If / Else vérifient l'état et répartissent l'histoire vers différentes fins. Le panneau Logic ouvert en bas montre tout l'ensemble disponible : Dead End, Checkpoint, If / Else, Switch, Random et Teleport.*

Dans le panneau de logique, il y a maintenant six nœuds principaux :

Début et Fin encadrent le chapitre, les scènes habituelles montrent le contenu, et les nœuds logiques les transforment en itinéraire. Déjà à ce niveau, il est possible de créer une histoire linéaire, une bifurcation, plusieurs fins, un événement aléatoire et un point de retour sécurisé — sans une seule ligne de script.

Une variable est la mémoire de l'histoire

Un seul graphe ne suffit pas si l'histoire doit se souvenir de ce qui s'est passé auparavant. Pour cela, il y a dans la quête des variables de trois types simples :

Chaque variable a une clé, un label compréhensible par l'auteur et une valeur initiale. Une variable numérique peut en plus avoir un minimum et un maximum décrits, tandis qu'une variable de service peut être cachée du panneau d'état du joueur ou affichée uniquement lorsque certaines conditions sont remplies.

L'essentiel ici est que les variables appartiennent à l'ensemble de la quête, et non à une seule scène. Si dans le premier chapitre le joueur obtient un laissez-passer, dans le quatrième chapitre on peut vérifier le même drapeau. Lors du passage par la fin vers le chapitre suivant, l'état n'est pas réinitialisé. Il est conservé avec les options choisies, les effets appliqués, le point de contrôle et l'étape actuelle de l'aléatoire.

On obtient un simple cycle auteur :

le choix du joueur enregistre la valeur
              ↓
la variable conserve l'état
              ↓
la condition le lit plus tard
              ↓
le graphe, la scène ou l'interface réagit

Par exemple, dans la première scène, le choix « Afficher la photo trouvée » peut définir portrait_revealed = true. Plus tard, le nœud **Si / Sinon** vérifiera ce drapeau et ouvrira une nouvelle scène de conversation. Encore plus tard, l'interface du terminal affichera une entrée supplémentaire uniquement aux joueurs qui ont révélé la photo.

Une variable relie trois parties différentes de la quête. Il n'est pas nécessaire de transmettre manuellement la valeur entre les scènes ou les chapitres : elles lisent un état de progression commun.

![La page de la variable route montre la clé, le nom, le type et la visibilité de l'état](../assets/logic-without-scripts-variables.webp)

*Pour la variable route, une carte contient la clé stable, le nom compréhensible, le type de valeur et la visibilité de l'état. Les liens avec les choix et les branches se trouvent plus bas sur la même page.*

Les choix écrivent, les branches lisent

Dans une scène normale, le moyen le plus compréhensible de changer l'état consiste à ajouter un effet à l'option de choix. Dans l'éditeur visuel, cela ressemble à une affectation :

« Faire confiance à Marie » → ally = "mary"
« Partir seul » → ally = "none"

Après cela, Switch peut diriger le joueur vers la scène souhaitée selon la valeur de ally. La page des variables montre séparément quels choix enregistrent la variable et quelles branches la lisent. C'est un petit détail, mais dans une grande quête, cela remplace la recherche pénible à travers des dizaines de scènes.

![Paramètres Switch avec les branches Laboratory et Habitat, qui comparent la variable de l'itinéraire sélectionné](../assets/logic-without-scripts-switch-rules.webp)

*Les branches Switch sont vérifiées de haut en bas. Dans cet exemple, l'itinéraire Laboratory se déclenche pour route = lab, et Habitat pour route = habitat.*

Les conditions prennent en charge les comparaisons ordinaires :

Dans le cas simple, l'auteur choisit directement la variable, l'opérateur et la valeur dans les paramètres de If / Else ou Switch. Pour une logique plus complexe, le runtime général comprend des groupes **toutes les conditions**, **n'importe quelle condition** et la négation.

Une telle règle peut être décrite structurellement :

{
  "op": "all",
  "conditions": [
    { "op": "gte", "key": "evidence", "value": 3 },
    { "op": "eq", "key": "alarm_active", "value": false }
  ]
}

Cela ressemble à un petit script, mais ce n'en est pas fondamentalement un. Ici, il n'est pas possible d'appeler une fonction ou de modifier quelque chose de manière aléatoire. Romergo connaît à l'avance toutes les opérations autorisées, vérifie la structure et l'exécute de la même manière dans l'aperçu de Builder et dans le Player publié.

Effets : petites commandes sans langage de programmation

La condition lit l'état, tandis que l'effet le modifie. Runtime Romergo prend en charge plusieurs opérations atomiques :

Dans l'inspecteur de sélection standard, le scénario principal est délibérément simple : choisir une variable et enregistrer une nouvelle valeur via set. Dans la logique avancée des scènes d'interface, les conditions et les effets peuvent être définis comme des structures vérifiables. Par exemple, le passage entre les états du terminal peut simultanément mémoriser la décision, ajouter un indice et déclencher l'alarme :

![Éditeur de scène Romergo : trois options de sélection enregistrent lab, habitat et server dans la variable route](../assets/logic-without-scripts-choice-effects.webp)

*L'effet est attaché directement à l'option de réponse : trois choix enregistrent dans route les valeurs lab, habitat et server. Le champ de la variable et la nouvelle valeur restent séparés même dans l'inspecteur réduit.*

[
  { "op": "set", "key": "terminal_decision", "value": "cut-power" },
  { "op": "inc", "key": "evidence", "amount": 1 },
  { "op": "toggle", "key": "alarm_active" }
]

Cette approche a une limite importante : ce n'est pas une calculatrice de formules arbitraires. On ne peut pas écrire evidence * trust / 2, lancer une boucle ou créer sa propre fonction. Un comportement complexe est construit à partir de plusieurs étapes explicites, et l'extension de la logique se fait via de nouvelles opérations vérifiables runtime, et non par l'exécution d'un code inconnu.

Pour l'auteur, c'est un peu moins illimité, mais l'histoire reste prévisible. Les mêmes effets peuvent être vérifiés avant publication, sauvegardés, synchronisés entre les joueurs et reproduits après le chargement de la progression.

Pourquoi à l'intérieur du nœud logique il n'y a pas encore un autre Flow

Malgré toute la prévisibilité, l'approche actuelle a un inconvénient honnête : pour une personne sans expérience en développement, la forme « variable — opérateur — valeur » peut déjà sembler être de la programmation. Et lorsque des groupes de conditions et plusieurs effets apparaissent à côté, la structure déclarative reste sûre pour Player, mais ne devient pas nécessairement simple pour l'auteur.

Il semble naturel de venir un jour aux nœuds visuels et à l'intérieur de la logique elle-même. Au lieu d'une liste de conditions, on pourrait connecter des blocs **ET**, **OU**, **NON**, comparer une variable et modifier une valeur. Sur papier, cela semble plus convivial que du texte ou du JSON.

Mais ensuite, j'imagine un scénario réel : l'auteur ouvre un nœud logique sur la carte du chapitre, et à l'intérieur, il découvre encore un autre Flow avec ses propres nœuds et connexions. Cela fait un graphe dans un graphe. Il faut comprendre où vous vous trouvez actuellement, comment revenir au niveau de l'histoire et dans lequel des deux Flow chercher l'erreur. Pour un débutant, une telle imbrication peut sembler plus effrayante que la forme actuelle.

C'est pourquoi un compromis est utilisé pour l'instant. Le parcours de l'histoire reste visuel, tandis que les conditions et les effets à l'intérieur des nœuds sont affichés dans des champs explicites et compacts. Cela fonctionne, mais je ne considère pas l'interface comme définitivement résolue. Il est possible que plus tard apparaisse une combinaison plus compréhensible de modèles prêts à l'emploi, de réglages étape par étape et de blocs visuels — quelque chose qui réduira vraiment la complexité, au lieu de simplement la déplacer vers un autre éditeur.

La logique ne vit pas uniquement sur la carte

Les scènes d'interface utilisent le même état. Un widget, un message, une option de réponse, une tâche, une notification ou un flux média ne peut apparaître que si le showWhen correspond. La valeur de la métrique peut être liée à une variable, et la transition entre les états du widget peut être lancée après un délai, selon une condition ou après une action du joueur.

Par exemple, l'écran de l'ordinateur de bord peut se comporter ainsi :

1. Au départ, seul le terminal verrouillé est visible.

2. Après le choix du joueur, l'effet définit le terminal_unlocked = true.

3. La condition ouvre le schéma du vaisseau et de nouveaux boutons.

4. Cliquer sur le bouton change l'état du widget et augmente evidence.

5. Lorsque evidence >= 3, une notification cachée apparaît et une nouvelle branche de quête devient disponible.

La carte du chapitre, les choix ordinaires et l'interface interactive ne créent pas trois systèmes séparés. Ils lisent et modifient un seul objet d'état. Ainsi, l'indice trouvé dans le dialogue peut ouvrir un élément de l'interface, et une action dans l'interface peut modifier la scène suivante.

C'est ici que la logique déclarative commence à se ressentir comme une véritable programmation. Mais au lieu de fichiers, de fonctions et de bus d'événements, l'auteur travaille avec des entités d'histoire compréhensibles : des faits, des choix, des conditions et des transitions.

Pourquoi je ne fais pas exprès d'ajouter du JavaScript arbitraire

La possibilité d'insérer un script semble être le chemin le plus court vers toute nouvelle mécanique. Mais dès qu'une quête publiée obtient le droit d'exécuter du code arbitraire, tout le modèle du produit change.

Il faut décider quels API de navigateur sont accessibles à ce code, comment limiter le réseau et le stockage, que faire avec les boucles infinies, comment rendre la quête disponible hors ligne, comment la synchroniser dans un jeu collaboratif et comment garantir que l'ancien projet continuera de fonctionner après la mise à jour Player.

Une règle déclarative est beaucoup plus ennuyeuse qu'une fonction arbitraire — et c'est justement pour cela qu'elle est plus fiable. Elle peut être validée avant l'exécution. On peut comprendre quelles variables elle lit et écrit. On peut sauvegarder le progrès en toute sécurité au milieu de la chaîne. On peut exécuter la quête de la même manière dans le navigateur, dans une version autonome et intégrée à Player.

Un vocabulaire limité ici ne gêne pas la logique, mais en définit les limites. Si une opération vraiment utile apparaît, il vaut mieux l'ajouter dans le runtime général et donner à tous les auteurs un comportement vérifié identique, plutôt que de forcer chaque projet à inventer son propre moteur.

En dessert — son propre HTML et CSS

En même temps, Romergo laisse encore de la place pour un vrai code là où il est le plus sûr : dans la mise en scène d'une scène ordinaire.

Dans le projet, il est possible de créer son propre thème d'apparence sur HTML et CSS et de l'utiliser dans différents chapitres. HTML définit la disposition des zones préparées — la fenêtre de réplique, le portrait, le nom du personnage, le texte, la question et les boutons de sélection. CSS permet de modifier considérablement ces zones : les cadres, le fond, la forme des cartes, les marges, la typographie et la réactivité à la taille de l'écran.

Les thèmes prêts **Standard**, **Minimalism**, **Brutalism**, **Romance** et **Neon** montrent une gamme confirmée sans mise en page manuelle. Lors de la création d'un thème personnalisé, Romergo copie le modèle sélectionné : après cela, son HTML et CSS peuvent être modifiés et vérifiés immédiatement dans l'aperçu réel Player.

![Éditeur de mise en page réel Romergo : CSS des thèmes Brutalism et aperçu mobile du dialogue](../assets/logic-without-scripts-custom-html-css.webp)

*Il n'y a pas de concept art ici : à gauche est montré le vrai CSS du thème intégré Brutalism, à droite — le résultat du même code dans l'aperçu téléphonique. Le sélecteur Desktop / Mobile permet de vérifier immédiatement les règles adaptatives, et le thème personnalisé commence son fonctionnement à partir d'une copie du modèle choisi.*

Mais la bordure reste la même. Dans le HTML personnalisé il n'y a pas de <script>, de gestionnaires d'événements, de formulaires, d'iframe ni de ressources externes. CSS ne peut pas charger de url() ou @import externes. Les zones obligatoires de la scène doivent rester en place ; si le modèle les a perdues, Player revient à une mise en page standard sûre.

C'est-à-dire que HTML et CSS sont responsables de **l'apparence de l'histoire**, tandis que les nœuds, variables, conditions et effets sont responsables de **son fonctionnement**.

J'aime précisément cette séparation. L'auteur peut rendre la quête visuellement différente des autres, mais son accomplissement reste néanmoins une partie du runtime globalement vérifiable. La couche externe peut être radicalement recolorée et reconstruite, sans transformer chaque thème en un programme séparé.

Un code sans code n'est pas une absence de logique

Romergo n'a pas pour objectif de remplacer le langage de programmation par des images. Le graphique est mal adapté aux mathématiques complexes, et un ensemble d'effets atomiques ne deviendra pas un moteur universel d'automatisation.

En revanche, pour une histoire interactive, on a souvent besoin d'autres choses : mémoriser un fait, modifier un compteur, ouvrir une option, choisir un itinéraire, montrer l'état de l'interface, sauvegarder un point de retour et transmettre toutes ces décisions au chapitre suivant.

Pour cela, les nœuds et les variables ne sont pas une version simplifiée des scripts, mais un langage plus direct de l'histoire elle-même. L'auteur ne décrit pas des commandes pour l'ordinateur, mais les relations de cause à effet du monde : le joueur a fait un choix, l'état a changé, l'histoire a réagi.

Et si vous avez envie d'un peu de vrai code, HTML et CSS vous attendent quand même pour le dessert.

Retour au blog technique