Files
Forge-Engine/screens/flow/constants.py
T
williamandClaude Sonnet 5 efa7d1d9b0
Build and deploy / test-python (push) Successful in 1m41s
Build and deploy / test-js (push) Successful in 7s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Fondations multi-éditeurs (1/3) : game_type + schéma/CRUD des objets de scène
Première brique du plan "Fondations d'une plateforme multi-éditeurs" —
l'utilisateur veut un éditeur dédié aux jeux 2D/serious games (scène à
coordonnées pixel fixes, objets en couches, collision, personnages
animés), distinct de l'éditeur générique actuel, sans dupliquer ce qui
peut être partagé (comptes, objets de données, moteur de logique de
flow, hébergement/publication).

- db/games/get_game_type.py (nouveau) : "document" (défaut, éditeur
  actuel) ou "jeu_2d" (nouveau), stocké dans _meta comme
  is_public_played — aucune migration pour les jeux déjà créés
  (retombent sur "document"). Choisi obligatoirement à la création
  (templates/index.html), jamais modifiable ensuite.
- screens/scenes/ (nouveau sous-module) : table _scene_objects (une
  scène = des objets en pixels fixes, pas les % fluides de
  _screen_elements — indispensable pour la collision/l'animation),
  CRUD complet, rendu HTML. kind="personnage" réutilise TELLE QUELLE la
  structure _personnage_data et les fonctions resolve_personnage_* déjà
  écrites pour le widget "personnage" de l'éditeur document (Phase 8) —
  même bibliothèque Forge, même moteur d'animation, juste une autre
  table de stockage.
- screens/flow/ensure_flow_schema.py : += trigger_object_id/
  target_object_id (ALTER TABLE sans contrainte FK, même patron que
  cond_element_a) — le moteur de logique de flow (_flow_nodes/_flow_edges,
  flow-engine.js) reste EXACTEMENT le même pour les deux éditeurs, seule
  la palette de nœuds change (screens/scenes/flow_palette.py, nouveau :
  sous-ensemble direct des triggers/actions existants, déjà génériques).
- screens/screens_repo/ensure_schema.py : += scene_width/scene_height
  sur _screens (taille de scène fixe en pixels, sans effet sur un écran
  "document").

Reste à faire (prochains commits) : route + template de l'éditeur de
scène, puis le rendu en mode jouable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 09:42:38 +02:00

131 lines
7.5 KiB
Python

# ---------- Logique visuelle (éditeur de flow à nœuds) ----------
#
# Remplace l'ancien système "Actions" attaché à chaque élément : toute la
# logique d'une scène (déclencheurs, conditions, actions) vit maintenant dans
# un graphe de nœuds propre à l'écran, visible dans le panneau "Logique de
# la scène" de l'éditeur (voir screen_edit.html) — un nœud Déclencheur
# ("Au clic sur X") relié à des nœuds Condition (deux sorties Vrai/Faux) et
# Action (les mêmes effets qu'avant : changer d'écran, modifier un élément,
# modifier une donnée). L'ancien système _actions reste en base pour ne rien
# casser sur les jeux déjà créés, mais n'est plus exposé dans l'éditeur ni
# utilisé par le mode jouable.
# Valeur sentinelle pour cond_row_id/target_row_id (nœud Condition/action
# "Modifier une donnée") : "la ligne de Répéteur sur laquelle on vient de
# cliquer", résolue au moment de l'exécution (window.lastClickedRowId côté
# client — voir play.html) plutôt que figée à la création du nœud. Utile
# quand le déclencheur est "Au clic" sur un Répéteur : la ligne cliquée
# n'est jamais connue à l'avance (chaque ligne du Répéteur exécute le MÊME
# graphe), donc choisir une ligne précise dans l'éditeur n'a pas de sens
# ici — -1 ne collisionne jamais avec un vrai id de ligne (toujours >= 1,
# AUTOINCREMENT SQLite).
CLICKED_ROW_ID = -1
# Phase 4 (moteur) — "la ligne qu'un nœud Action 'Ajouter une ligne'
# vient de créer" : un id de ligne n'existe qu'APRÈS l'insertion, donc
# jamais connu à la création du nœud, exactement comme CLICKED_ROW_ID
# ci-dessus. Résolue au moment de l'exécution
# (window.lastInsertedRowId, voir static/js/play/actions.js) — permet
# d'enchaîner "Ajouter une ligne" -> "Modifier une donnée"/condition
# ciblant cette même ligne fraîchement créée, sans avoir à inventer un
# nouveau format de payload multi-champs pour le nœud "Ajouter une
# ligne" lui-même (qui ne crée qu'une ligne VIDE).
LAST_INSERTED_ROW_ID = -3
TRIGGER_EVENTS = [
("clic", "Au clic"),
("soumission", "À la soumission"),
# 3.1 (Confort) — interactions au survol, reconstruit comme déclencheur
# de flow (au lieu d'un réglage statique dans le panneau de propriétés,
# voir universal_controls.py) : "survol" et "fin_survol" sont deux
# déclencheurs distincts et explicites (comme "Au clic"), sans effet
# implicite — un créateur qui veut qu'un texte affiché au survol
# disparaisse ensuite doit poser l'action inverse sur "Fin du survol"
# lui-même, plutôt que de compter sur un retour automatique.
("survol", "Au survol"),
("fin_survol", "Fin du survol"),
# Pas de "trigger_element_id" pour celui-ci : il concerne l'ÉCRAN entier,
# pas un élément précis (voir 1.2 dans claude/forge-engine-lacunes-boitemail.md
# côté projet Forge — "affichage piloté par la donnée"). Se déclenche
# côté jouable à chaque fois que l'écran est montré (premier affichage,
# retour en arrière, changement d'écran...) ET après toute donnée
# modifiée pendant qu'on est déjà sur cet écran — ce qui permet de
# cacher/montrer un élément selon l'état de la partie SANS qu'un clic
# explicite soit nécessaire pour le réévaluer.
("affichage", "À l'affichage de l'écran"),
# Écoute un événement personnalisé (voir screens/custom_events/),
# déclenché depuis N'IMPORTE QUEL autre graphe (une autre scène, un
# autre modèle) via l'action "declencher_evenement" — ni élément ni
# écran précis : trigger_custom_event_id (quel événement) est le seul
# réglage propre à ce nœud, retrouvé par un scan global de
# gameData.flows côté client (voir dispatchGameEvent() dans
# templates/play.html), au même titre que findTriggerNode().
("evenement", "Sur un événement personnalisé"),
# Phase 3 (moteur) — ni élément ni écran précis : trigger_key (quelle
# touche, ev.key du clavier) est le seul réglage propre à ce nœud,
# écouté UNE SEULE FOIS pour tout le jeu (voir bindKeyboardTriggers()
# dans static/js/play/triggers.js), même patron de scan global que
# "evenement" ci-dessus.
("clavier", "À l'appui sur une touche"),
# Contrairement aux autres déclencheurs (réagissent à quelque chose),
# celui-ci fait AVANCER le jeu tout seul, à intervalle régulier —
# trigger_interval_ms (en millisecondes) est son seul réglage. Géré
# PAR ÉCRAN (voir showScreen() dans static/js/play/screens.js) : ne
# tourne que tant que l'écran qui le porte est affiché, jamais en
# arrière-plan sur un écran quitté.
("minuteur", "Toutes les X millisecondes"),
]
CONDITION_OPERATORS = [
("egal", "est égal à"),
("different", "est différent de"),
("superieur", "est supérieur à"),
("inferieur", "est inférieur à"),
("superieur_egal", "est supérieur ou égal à"),
("inferieur_egal", "est inférieur ou égal à"),
]
CONDITION_OPERATOR_LABELS = dict(CONDITION_OPERATORS)
FLOW_NODE_FIELDS = {
"trigger_element_id", "trigger_event",
"cond_definition_id", "cond_row_id", "cond_field", "cond_field_type", "cond_operator", "cond_value",
# 2.4 — conditions combinées (ET/OU) : cond_clauses est une liste JSON de
# clauses supplémentaires (en plus de la clause "historique" ci-dessus,
# qui reste la première clause) + cond_combinator ('et'/'ou') pour savoir
# comment les combiner. Absents => comportement legacy (une seule clause).
"cond_clauses", "cond_combinator",
# Condition sur une VARIABLE GLOBALE plutôt qu'un champ d'objet — voir
# ensure_flow_schema.py pour le détail des 3 clés (cond_source vaut
# "objet" ou "variable" ; absent => "objet", comportement historique).
"cond_source", "cond_variable", "cond_variable_chemin",
"action_type", "target_screen_id", "target_element_id", "element_property", "element_value",
"target_definition_id", "target_row_id", "target_field", "data_operation", "data_value",
"target_variable",
# Événements personnalisés (voir screens/custom_events/) : quel
# événement un nœud Déclencheur écoute (trigger_event="evenement") ou
# un nœud Action déclenche (action_type="declencher_evenement") — un
# événement est une pure NOTIFICATION, sans paramètre : "déclencher"
# ne fait que signaler, jamais choisir un élément. C'est à l'ÉCOUTEUR
# (déclencheur → condition → action) de décider quoi faire ensuite,
# avec ses propres réglages habituels (target_element_id normal, pas
# de mécanisme dynamique dédié).
"trigger_custom_event_id", "target_custom_event_id",
# Blocs de logique (voir screens/flow/blocks/) : à quel bloc ce nœud
# appartient. Aucun `update_flow_node` n'existe (éditer un nœud le
# recrée puis supprime l'ancien) — le client doit donc renvoyer
# block_id à chaque (ré)création, pas juste le poser une fois.
"block_id",
# Phase 3 — déclencheur clavier ("clavier") et minuteur récurrent
# ("minuteur"), voir TRIGGER_EVENTS ci-dessus.
"trigger_key", "trigger_interval_ms",
# Phase 5 — condition de collision (cond_source="collision", aux
# côtés de "objet"/"variable") : les deux éléments dont on compare les
# rectangles — voir evaluateConditionClause() dans
# static/js/play/conditions.js.
"cond_element_a", "cond_element_b",
# Fondations multi-éditeurs — jeux "jeu_2d" : équivalents de
# trigger_element_id/target_element_id pour un objet de scène (voir
# ensure_flow_schema.py).
"trigger_object_id", "target_object_id",
}