Phase 5 : position/déplacement d'élément + condition de collision
Build and deploy / test-python (push) Successful in 1m29s
Build and deploy / test-js (push) Successful in 7s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Ajoute le positionnement absolu (pos_x/pos_y, réutilise left/top en %
déjà en place) et relatif (pos_x_relatif/pos_y_relatif, ajoute un delta
à la position actuelle plutôt que de l'écraser) comme nouvelles
propriétés de l'action "Modifier un élément".

Ajoute une nouvelle source de condition "collision" (aux côtés de
"objet"/"variable") : deux éléments (cond_element_a/cond_element_b,
ALTER TABLE sans contrainte FK, même patron que block_id/
trigger_custom_event_id) dont on compare les rectangles à l'écran via
getBoundingClientRect() côté client (elementsOverlap(), dans
conditions.js). Pas d'opérateur/valeur à choisir : le chevauchement EST
directement le booléen vrai/faux du nœud — le créateur relie le port
"Faux" pour "ne se touchent pas", exactement comme pour n'importe quelle
autre condition (design plus simple que réinterpréter égal/différent,
qui n'a pas de sens pour superieur/inferieur).

delete_element.py et flow_nodes_referencing_element.py nettoient
désormais aussi les nœuds de collision référençant un élément supprimé
(ou l'un de ses descendants), pour rester cohérents avec le nettoyage
déjà en place pour trigger_element_id/target_element_id.

Combiné à la Phase 3 (minuteur récurrent) et au déplacement au clavier,
ça couvre des jeux type casse-briques/Pong/ramasse-objets sans
construire un vrai moteur physique (pas de vélocité/accélération/
gravité continues, cadrage volontairement limité).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-30 17:34:51 +02:00
co-authored by Claude Sonnet 5
parent 92fbfbc9dd
commit 9e263435e6
10 changed files with 271 additions and 41 deletions
+45 -18
View File
@@ -50,8 +50,18 @@ function nodeLabel(node) {
var clauseSubject = function(c, field, variable) {
return (c.source ?? c.cond_source) === 'variable' ? ('🔧 ' + (variable || c.cond_variable || '?')) : (field || c.cond_field || '?');
};
var mainSubject = clauseSubject(node, node.cond_field, node.cond_variable);
var mainClause = '❓ SI ' + mainSubject + ' ' + (CONDITION_OPERATOR_LABELS_MAP[node.cond_operator] || node.cond_operator) + ' ' + (node.cond_value || '');
// Phase 5 — condition de collision : pas de champ/opérateur/valeur,
// juste les deux éléments comparés (voir evaluateConditionClause()
// dans static/js/play/conditions.js).
var clauseFullLabel = function(c, field, variable) {
if ((c.source ?? c.cond_source) === 'collision') {
var elA = ELEMENTS_LABELS[c.element_a ?? c.cond_element_a] || '?';
var elB = ELEMENTS_LABELS[c.element_b ?? c.cond_element_b] || '?';
return '💥 ' + elA + ' touche ' + elB;
}
return clauseSubject(c, field, variable) + ' ' + (CONDITION_OPERATOR_LABELS_MAP[c.operator ?? c.cond_operator] || (c.operator ?? c.cond_operator)) + ' ' + ((c.value ?? c.cond_value) || '');
};
var mainClause = '❓ SI ' + clauseFullLabel(node, node.cond_field, node.cond_variable);
if (node.cond_clauses) {
// 2.4 — plusieurs clauses combinées : on affiche chaque champ/variable
// testé, relié par le connecteur ET/OU choisi, plutôt qu'une seule clause.
@@ -59,7 +69,7 @@ function nodeLabel(node) {
var clauses = JSON.parse(node.cond_clauses);
var combLabel = node.cond_combinator === 'ou' ? ' OU ' : ' ET ';
return '❓ SI ' + clauses.map(function(c) {
return clauseSubject(c, c.field, c.variable) + ' ' + (CONDITION_OPERATOR_LABELS_MAP[c.operator] || c.operator) + ' ' + (c.value || '');
return clauseFullLabel(c, c.field, c.variable);
}).join(combLabel);
} catch (e) { /* JSON invalide : on retombe sur la clause historique */ }
}
@@ -363,11 +373,18 @@ function onFlowFieldChange() {
// pour une clause SUPPLÉMENTAIRE : voir onFlowClauseSourceChange() plus
// bas, sur le même principe mais scopée par index.
function onFlowConditionSourceChange() {
var isVariable = document.getElementById('nf-cond-source').value === 'variable';
document.getElementById('nf-cond-objet-fields').style.display = isVariable ? 'none' : 'block';
var source = document.getElementById('nf-cond-source').value;
var isVariable = source === 'variable';
var isCollision = source === 'collision';
document.getElementById('nf-cond-objet-fields').style.display = (isVariable || isCollision) ? 'none' : 'block';
document.getElementById('nf-cond-variable-fields').style.display = isVariable ? 'block' : 'none';
var hint = document.getElementById('nf-cond-no-objet-hint');
if (hint) hint.style.display = isVariable ? 'none' : 'block';
if (hint) hint.style.display = (isVariable || isCollision) ? 'none' : 'block';
// Collision (Phase 5) : ni opérateur ni valeur de comparaison — le
// chevauchement EST directement le booléen vrai/faux du nœud (voir
// evaluateConditionClause() dans static/js/play/conditions.js).
document.getElementById('nf-cond-collision-fields').style.display = isCollision ? 'block' : 'none';
document.getElementById('nf-cond-operator-value-row').style.display = isCollision ? 'none' : 'block';
}
// ---------- 2.4 — clauses supplémentaires (ET/OU) d'un nœud Condition ----------
@@ -572,20 +589,30 @@ function submitNodeForm() {
} else if (currentNodeFormType === 'condition') {
var condSource = document.getElementById('nf-cond-source').value;
payload.cond_source = condSource;
payload.cond_operator = document.getElementById('nf-cond-operator').value;
payload.cond_value = document.getElementById('nf-cond-value').value;
if (condSource === 'variable') {
payload.cond_variable = document.getElementById('nf-cond-variable').value;
payload.cond_variable_chemin = document.getElementById('nf-cond-variable-chemin').value;
if (condSource === 'collision') {
// Phase 5 — ni opérateur ni valeur de comparaison pour une
// collision (voir onFlowConditionSourceChange()) : juste les deux
// éléments dont on compare les rectangles. Pas de clauses combinées
// (ET/OU) pour une collision — même limitation assumée que pour les
// autres sources tant qu'aucune clause supplémentaire n'a été ajoutée.
payload.cond_element_a = parseInt(document.getElementById('nf-cond-element-a').value, 10);
payload.cond_element_b = parseInt(document.getElementById('nf-cond-element-b').value, 10);
} else {
var fieldSel = document.getElementById('nf-cond-field');
var fieldOpt = fieldSel.options[fieldSel.selectedIndex];
payload.cond_definition_id = parseInt(document.getElementById('nf-cond-definition').value, 10);
payload.cond_row_id = parseInt(document.getElementById('nf-cond-row').value, 10) || null;
payload.cond_field = fieldSel.value;
payload.cond_field_type = fieldOpt ? fieldOpt.dataset.type : '';
payload.cond_operator = document.getElementById('nf-cond-operator').value;
payload.cond_value = document.getElementById('nf-cond-value').value;
if (condSource === 'variable') {
payload.cond_variable = document.getElementById('nf-cond-variable').value;
payload.cond_variable_chemin = document.getElementById('nf-cond-variable-chemin').value;
} else {
var fieldSel = document.getElementById('nf-cond-field');
var fieldOpt = fieldSel.options[fieldSel.selectedIndex];
payload.cond_definition_id = parseInt(document.getElementById('nf-cond-definition').value, 10);
payload.cond_row_id = parseInt(document.getElementById('nf-cond-row').value, 10) || null;
payload.cond_field = fieldSel.value;
payload.cond_field_type = fieldOpt ? fieldOpt.dataset.type : '';
}
}
if (flowConditionExtraClauses.length) {
if (condSource !== 'collision' && flowConditionExtraClauses.length) {
// 2.4 — clauses combinées : la clause "historique" ci-dessus devient
// la première clause de la liste, suivie des clauses supplémentaires.
var mainClause = condSource === 'variable'