Corrige un bug moteur : plusieurs déclencheurs "Au clic" (ou survol) sur le même élément n'exécutaient que le premier
Diagnostic effectué directement sur projects/test/game.db de
l'utilisateur (suite à son signalement "je ne parviens pas à mettre fin
à la surbrillance") : deux nœuds Déclencheur distincts ("Au clic",
id=4 et id=42) référençaient le même trigger_element_id=65. La fonction
findTriggerNode() utilisait Array.find(), qui ne retourne que la
PREMIÈRE correspondance — le second nœud (celui qui devait couper la
surbrillance) n'était donc jamais exécuté, silencieusement, quel que
soit le graphe construit dans l'éditeur.
Ce n'est pas un bug lié aux événements personnalisés ni aux conditions
par variable (deux pistes explorées avant ce diagnostic) : c'est une
limitation générale du moteur, qui n'a jamais géré plus d'un déclencheur
"Au clic"/"Au survol"/"À la fin du survol" par élément.
Renomme findTriggerNode() en findTriggerNodes() (pluriel) : retourne
désormais TOUS les nœuds correspondants (élément + type d'événement),
et bindClicks()/bindHoverTriggers() exécutent chaque flow trouvé au lieu
de s'arrêter au premier.
Vérifié : 208 tests passent, syntaxe JS validée (script de play.html
rendu via le client de test puis node --check). Comme pour le reste du
graphe de logique côté client, ce changement n'est pas couvert par les
tests automatisés (pas de harnais navigateur/DOM) — vérification
manuelle recommandée sur le scénario réel de l'utilisateur.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2c59e54556
commit
a7a315cce7
+47
-31
@@ -265,28 +265,39 @@
|
||||
}
|
||||
|
||||
// Trouve, pour un élément et un type de déclencheur ("clic"/"soumission"),
|
||||
// le nœud Déclencheur qui lui est rattaché — c'est le point d'entrée du
|
||||
// graphe à exécuter. On cherche dans TOUS les écrans (pas seulement
|
||||
// l'écran courant) : bindClicks() attache les gestionnaires une seule
|
||||
// fois, sur tous les éléments de tous les écrans (déjà tous présents dans
|
||||
// le DOM, seuls les écrans non actifs sont masqués) — chaque identifiant
|
||||
// d'élément est unique dans tout le jeu, donc pas d'ambiguïté possible.
|
||||
// Cette recherche globale couvre aussi les écrans-MODÈLES (un élément de
|
||||
// jeu réutilisable, ex. "mail content", posé sur une scène) : un
|
||||
// déclencheur "Au clic"/"Au survol" posé dans l'éditeur du modèle, sur un
|
||||
// de SES PROPRES enfants, est donc retrouvé même quand ce modèle est
|
||||
// utilisé ailleurs — d'où le besoin de renvoyer aussi l'écran d'origine du
|
||||
// nœud (screenIdKey), pas seulement le nœud : runFlowFrom() doit exécuter
|
||||
// le graphe dans CET écran-là (celui du modèle), jamais dans celui
|
||||
// réellement affiché, sans quoi le nœud trouvé n'existerait pas dans le
|
||||
// graphe utilisé et l'action ne se déclencherait jamais.
|
||||
function findTriggerNode(elementId, event) {
|
||||
// TOUS les nœuds Déclencheur qui lui sont rattachés — ce sont les points
|
||||
// d'entrée des graphes à exécuter. Renvoie un TABLEAU (jamais un seul
|
||||
// nœud) : rien n'empêche de poser plusieurs déclencheurs indépendants
|
||||
// "Au clic" sur le MÊME élément (ex. l'un ouvre une ligne de Répéteur,
|
||||
// l'autre éteint une surbrillance) — un seul clic doit alors exécuter
|
||||
// CHACUN de ces graphes, pas seulement le premier trouvé (régression
|
||||
// vécue : un second déclencheur posé sur un élément qui en avait déjà un
|
||||
// restait silencieusement inerte, aucune erreur, juste jamais exécuté).
|
||||
//
|
||||
// On cherche dans TOUS les écrans (pas seulement l'écran courant) :
|
||||
// bindClicks() attache les gestionnaires une seule fois, sur tous les
|
||||
// éléments de tous les écrans (déjà tous présents dans le DOM, seuls les
|
||||
// écrans non actifs sont masqués) — chaque identifiant d'élément est
|
||||
// unique dans tout le jeu, donc pas d'ambiguïté possible. Cette recherche
|
||||
// globale couvre aussi les écrans-MODÈLES (un élément de jeu réutilisable,
|
||||
// ex. "mail content", posé sur une scène) : un déclencheur "Au clic"/"Au
|
||||
// survol" posé dans l'éditeur du modèle, sur un de SES PROPRES enfants,
|
||||
// est donc retrouvé même quand ce modèle est utilisé ailleurs — d'où le
|
||||
// besoin de renvoyer aussi l'écran d'origine de CHAQUE nœud (screenIdKey),
|
||||
// pas seulement le nœud : runFlowFrom() doit exécuter le graphe dans CET
|
||||
// écran-là (celui du modèle), jamais dans celui réellement affiché, sans
|
||||
// quoi le nœud trouvé n'existerait pas dans le graphe utilisé et l'action
|
||||
// ne se déclencherait jamais.
|
||||
function findTriggerNodess(elementId, event) {
|
||||
const found = [];
|
||||
for (const screenIdKey in gameData.flows) {
|
||||
const flow = gameData.flows[screenIdKey];
|
||||
const found = flow.nodes.find(n => n.node_type === 'trigger' && n.trigger_element_id === elementId && (n.trigger_event || 'clic') === event);
|
||||
if (found) return { node: found, screenId: screenIdKey };
|
||||
gameData.flows[screenIdKey].nodes.forEach(function(n) {
|
||||
if (n.node_type === 'trigger' && n.trigger_element_id === elementId && (n.trigger_event || 'clic') === event) {
|
||||
found.push({ node: n, screenId: screenIdKey });
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
// Ré-attache les gestionnaires de clic sur chaque élément — appelé au
|
||||
@@ -313,8 +324,8 @@
|
||||
function bindClicks() {
|
||||
document.querySelectorAll('[data-element-id]').forEach(elDiv => {
|
||||
const elementId = parseInt(elDiv.dataset.elementId, 10);
|
||||
const trigger = findTriggerNode(elementId, 'clic');
|
||||
if (trigger) {
|
||||
const triggers = findTriggerNodess(elementId, 'clic');
|
||||
if (triggers.length) {
|
||||
// Garde-fou anti-doublon : refreshRuntimeData() rappelle bindClicks()
|
||||
// après chaque changement de donnée, mais un élément qui n'est ni un
|
||||
// Répéteur ni une Jauge garde le MÊME nœud DOM d'un rafraîchissement
|
||||
@@ -337,7 +348,9 @@
|
||||
const rowEl = ev.target.closest('.repeaterItem');
|
||||
window.lastClickedRowId = rowEl ? parseInt(rowEl.dataset.rowId, 10) : null;
|
||||
window.lastClickedDefinitionId = elDiv.dataset.definitionId ? parseInt(elDiv.dataset.definitionId, 10) : null;
|
||||
runFlowFrom(trigger.node.id, trigger.screenId);
|
||||
// Exécute CHAQUE déclencheur "Au clic" posé sur cet élément, pas
|
||||
// seulement le premier (voir le commentaire de findTriggerNodess).
|
||||
triggers.forEach(trigger => runFlowFrom(trigger.node.id, trigger.screenId));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -354,12 +367,15 @@
|
||||
document.querySelectorAll('[data-element-id]').forEach(elDiv => {
|
||||
if (elDiv.dataset.hoverTriggerBound) return;
|
||||
const elementId = parseInt(elDiv.dataset.elementId, 10);
|
||||
const enterTrigger = findTriggerNode(elementId, 'survol');
|
||||
const leaveTrigger = findTriggerNode(elementId, 'fin_survol');
|
||||
if (!enterTrigger && !leaveTrigger) return;
|
||||
const enterTriggers = findTriggerNodess(elementId, 'survol');
|
||||
const leaveTriggers = findTriggerNodess(elementId, 'fin_survol');
|
||||
if (!enterTriggers.length && !leaveTriggers.length) return;
|
||||
elDiv.dataset.hoverTriggerBound = '1';
|
||||
if (enterTrigger) elDiv.addEventListener('mouseenter', () => runFlowFrom(enterTrigger.node.id, enterTrigger.screenId));
|
||||
if (leaveTrigger) elDiv.addEventListener('mouseleave', () => runFlowFrom(leaveTrigger.node.id, leaveTrigger.screenId));
|
||||
// Exécute CHAQUE déclencheur trouvé, pas seulement le premier — voir
|
||||
// le commentaire de findTriggerNodess (plusieurs déclencheurs
|
||||
// indépendants sur le même élément/événement doivent tous s'exécuter).
|
||||
if (enterTriggers.length) elDiv.addEventListener('mouseenter', () => enterTriggers.forEach(t => runFlowFrom(t.node.id, t.screenId)));
|
||||
if (leaveTriggers.length) elDiv.addEventListener('mouseleave', () => leaveTriggers.forEach(t => runFlowFrom(t.node.id, t.screenId)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -982,10 +998,10 @@
|
||||
// continue sur son (ou ses) fil de sortie ; chaque nœud Condition évalue
|
||||
// une donnée du jeu et suit le(s) fil(s) "Vrai" ou "Faux" en conséquence.
|
||||
// flowScreenId : l'écran PROPRIÉTAIRE du graphe à exécuter — par défaut
|
||||
// l'écran affiché, mais peut être un écran-MODÈLE (voir findTriggerNode)
|
||||
// l'écran affiché, mais peut être un écran-MODÈLE (voir findTriggerNodes)
|
||||
// quand le nœud de départ vient d'un déclencheur posé dans l'éditeur d'un
|
||||
// élément de jeu réutilisable, sur un de ses propres enfants. Sans ce
|
||||
// paramètre, un tel déclencheur était bien retrouvé (findTriggerNode
|
||||
// paramètre, un tel déclencheur était bien retrouvé (findTriggerNodes
|
||||
// cherche dans tous les écrans) mais son graphe n'existait jamais dans
|
||||
// l'écran réellement affiché : rien ne se déclenchait, silencieusement.
|
||||
function runFlowFrom(nodeId, flowScreenId) {
|
||||
@@ -1018,7 +1034,7 @@
|
||||
// Déclenche un événement personnalisé (voir screens/custom_events/,
|
||||
// action "declencher_evenement") : une pure NOTIFICATION, sans aucun
|
||||
// paramètre — scan GLOBAL de gameData.flows, même principe que
|
||||
// findTriggerNode()/runScreenShowTriggers() ci-dessus, puisqu'un
|
||||
// findTriggerNodes()/runScreenShowTriggers() ci-dessus, puisqu'un
|
||||
// écouteur (nœud Déclencheur trigger_event="evenement") peut vivre sur
|
||||
// N'IMPORTE QUELLE autre scène ou modèle, pas seulement l'écran affiché.
|
||||
// C'est à CHAQUE écouteur de décider quoi faire ensuite (via sa propre
|
||||
|
||||
Reference in New Issue
Block a user