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
+102
View File
@@ -0,0 +1,102 @@
"""Position/déplacement d'élément + condition de collision — Phase 5 du
plan d'extension du moteur. Comme le reste du graphe de logique côté
client, le comportement RÉEL (déplacement CSS effectif, chevauchement de
rectangles via getBoundingClientRect) n'est pas testable sans navigateur
(voir play_js_bundle() — on vérifie que la logique existe dans le JS
livré et que les nœuds persistent leurs champs, pas qu'ils s'exécutent)."""
import json
import re
from conftest import play_js_bundle, screen_edit_js_bundle
import screens
def _create_element(client, slug, widget="conteneur"):
resp = client.post(f"/game/{slug}/screens/new", data={"name": "Accueil"}, follow_redirects=False)
screen_id = int(re.search(r"/screens/(\d+)/edit", resp.headers["Location"]).group(1))
resp = client.post(f"/game/{slug}/screens/{screen_id}/elements/add", data={"widget": widget}, follow_redirects=False)
element_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
return screen_id, element_id
def _add_flow_node(client, slug, screen_id, **fields):
resp = client.post(
f"/game/{slug}/screens/{screen_id}/flow/nodes/add",
data=json.dumps(fields),
content_type="application/json",
)
assert resp.status_code == 200
return resp.get_json()["id"]
def test_position_action_node_persists_absolute_property(client, game):
screen_id, element_id = _create_element(client, game)
node_id = _add_flow_node(
client, game, screen_id, node_type="action", action_type="modifier_element",
target_element_id=element_id, element_property="pos_x", element_value="42",
)
node = screens.get_flow_node(game, node_id)
assert node["element_property"] == "pos_x"
assert node["element_value"] == "42"
def test_position_action_node_persists_relative_property(client, game):
screen_id, element_id = _create_element(client, game)
node_id = _add_flow_node(
client, game, screen_id, node_type="action", action_type="modifier_element",
target_element_id=element_id, element_property="pos_y_relatif", element_value="-5",
)
node = screens.get_flow_node(game, node_id)
assert node["element_property"] == "pos_y_relatif"
assert node["element_value"] == "-5"
def test_collision_condition_node_persists_both_elements(client, game):
screen_id, element_a = _create_element(client, game)
resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "conteneur"}, follow_redirects=False)
element_b = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
node_id = _add_flow_node(
client, game, screen_id, node_type="condition", cond_source="collision",
cond_element_a=element_a, cond_element_b=element_b,
)
node = screens.get_flow_node(game, node_id)
assert node["cond_source"] == "collision"
assert node["cond_element_a"] == element_a
assert node["cond_element_b"] == element_b
assert node["cond_field"] is None
assert node["cond_operator"] is None
def test_deleting_element_cleans_up_collision_condition_nodes(client, game):
"""Un nœud de collision qui référence un élément supprimé n'a plus de
sens (voir delete_element.py) — même nettoyage que pour un déclencheur/
une action ciblant un élément supprimé."""
screen_id, element_a = _create_element(client, game)
resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "conteneur"}, follow_redirects=False)
element_b = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
node_id = _add_flow_node(
client, game, screen_id, node_type="condition", cond_source="collision",
cond_element_a=element_a, cond_element_b=element_b,
)
client.post(f"/game/{game}/elements/{element_a}/delete", follow_redirects=False)
assert screens.get_flow_node(game, node_id) is None
def test_play_page_exposes_position_and_collision_runtime(client, game):
js = play_js_bundle()
assert "pos_x" in js
assert "pos_y" in js
assert "elementsOverlap" in js
assert "getBoundingClientRect" in js
assert "'collision'" in js
def test_screen_edit_exposes_collision_condition_editor(client, game):
html = client.get(f"/game/{game}/screens/{_create_element(client, game)[0]}/edit").get_data(as_text=True)
assert "nf-cond-collision-fields" in html
assert "nf-cond-element-a" in html
assert "nf-cond-element-b" in html
js = screen_edit_js_bundle()
assert "isCollision" in js
assert "cond_element_a" in js