Commit Graph
263 Commits
Author SHA1 Message Date
williamandClaude Sonnet 5 20f9398bd4 Phase 4 (CI) : securise le job deploy (token registre, cle SSH)
- docker login sur le serveur distant : passe le token via
  --password-stdin (echo | docker login ...) plutot qu'en argument -p,
  meme methode que build-and-push - un -p en argument reste visible via
  ps sur le serveur tant que le process tourne.
- Nouvelle etape "Nettoyage de la cle SSH" (if: always()) : supprime
  ~/.ssh/deploy_key en fin de job, meme si une etape precedente a
  echoue. rm -f (jamais rm nu) : sortie 0 que le fichier ou meme le
  dossier ~/.ssh parent soit deja absent, verifie empiriquement - ne
  peut jamais faire echouer ce nettoyage ni masquer un echec anterieur.
  Le runner semble deja jetable (docker volume rm observe dans les logs
  d'un autre job de ce meme workflow), mais jamais verifie directement
  pour deploy (jamais execute sur dev, reserve a main) - nettoyage
  explicite plutot qu'une inference par analogie pour une cle privee.

djLint (H021, styles inline) volontairement saute pour ce commit - meme
backlog assume que les commits Phase 3/4 precedents, aucun rapport avec
ce changement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 19:39:39 +02:00
williamandClaude Sonnet 5 2ff127f68e Phase 4 (CI) : lint-python/lint-js bloquants + sonarqube non-bloquant
Build and deploy / test-python (push) Successful in 13m44s
Build and deploy / test-js (push) Successful in 1m27s
Build and deploy / lint-python (push) Successful in 4m32s
Build and deploy / lint-js (push) Successful in 3m5s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m3s
Les hooks pre-commit locaux (deja tous verts, voir le commit Phase 3) ne
protegent que la machine du committeur, jamais un push direct ou une PR
mergee depuis ailleurs sans passer par ces hooks. Ajoute donc en CI :

- lint-python (ruff check/format, mypy --strict, vulture, bandit,
  import-linter) et lint-js (eslint, stylelint) : bloquants des
  maintenant, memes commandes que .pre-commit-config.yaml, deja verts en
  local donc aucune raison d'attendre. djlint volontairement exclu (49
  H021 deja en backlog assume, a ajouter ici une fois ce lot traite).
  build-and-push en depend desormais (needs), en plus des tests deja en
  place.

- sonarqube : scan contre l'instance self-hebergee (sonar.forgebase.fr)
  a chaque push. continue-on-error: true au niveau du job (non-bloquant
  pendant cette premiere periode, le temps de trier le rapport deja
  analyse en Phase 3 - code smells/vulnerabilites), et absent du needs
  de build-and-push : un echec ici n'affecte jamais le reste du
  pipeline, meme si l'infra n'est pas encore prete (echec attendu tant
  que SONAR_TOKEN n'est pas configure cote Gitea).

Secret a ajouter dans Gitea (Parametres du depot > Actions > Secrets) :
SONAR_TOKEN (jeton d'analyse genere sur sonar.forgebase.fr, jamais le
mot de passe admin).

djLint (H021, styles inline) volontairement saute pour ce commit - meme
backlog assume que le commit Phase 3, aucun rapport avec ce changement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 16:26:21 +02:00
williamandClaude Sonnet 5 c57420c8c9 Phase 3 : hardening qualite de code - typage strict, securite, dead code, a11y
Config strictement stricte partout (ruff, mypy --strict, bandit, vulture,
import-linter, eslint, stylelint), aucune regle desactivee "pour ne pas
casser le build" - l'existant a ete corrige pour la satisfaire plutot que
l'inverse. Hooks pre-commit locaux (language: system) bloquants.

- Typage mypy --strict propage a tout le moteur (db, screens, auth, core,
  ai, routes, puis publish/scripts/tests/app.py/build_css.py).
- Securite : fuite de handle fichier Windows corrigee dans l'export SCORM
  (routes/publish/export_scorm.py), CSRF/RNG non-crypto/xAPI documentes
  (# nosec, # NOSONAR justifies), nouveau db.json_for_script() (echappe
  "</script>" dans le JSON embarque en <script>, 25 sites).
- Architecture : imports circulaires/F811 nettoyes, contrats
  import-linter respectes, code mort retire (vulture).
- Accessibilite : 69 champs de formulaire sans label correctement
  associe corriges (for/id ou aria-label) sur 11 templates.
- ESLint/Stylelint : lot mecanique JS/CSS, regles ajustees puis
  appliquees (aucune desactivee sans verification individuelle).
- Tests : isolation du compte admin partage (nettoyage ponctuel +
  fixture de teardown automatique en filet de securite), suite complete
  verte (591 tests Python, 241 tests JS).
- SonarQube Community Build self-heberge (Docker + PostgreSQL) : rapport
  complet analyse point par point, faux positifs documentes.
- .gitattributes ajoute (LF force) : core.autocrlf=true sur cette machine
  faisait echouer ESLint (linebreak-style) via un bug connu de git
  (checkout "en place" qui ignore l'eol force sur un fichier deja
  present sur disque - contourne en supprimant puis recreant chaque
  fichier suivi).

djLint (H021, styles inline) volontairement saute pour ce commit -
backlog assume, deja documente, traite dans un lot separe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 16:06:15 +02:00
williamandClaude Sonnet 5 7db4803b93 Ajoute la fonctionnalite quiz autonome/plein ecran a la boite a quiz
Build and deploy / test-python (push) Successful in 12m10s
Build and deploy / test-js (push) Successful in 1m27s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Introduit la double categorie de modeles (boite de dialogue / page de
quiz plein ecran) avec plein ecran, minuteur, score integre et ecran de
resultat pour les modeles page ; ajoute les modeles "Manga" (boite et
page) et "Classique" (page), pilotables aussi par l'assistant IA Ruby.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 15:07:23 +02:00
williamandClaude Sonnet 5 559331f9cf Enrichit les declencheurs/actions de scene (clic/survol/affichage, surbrillance/video/son/visibilite/indication/attendre) et fiabilise la pose d'un fond/decor importe
Build and deploy / test-python (push) Successful in 10m59s
Build and deploy / test-js (push) Successful in 1m27s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
- Ajoute clic/survol/affichage-ecran comme declencheurs, et surbrillance,
  video, son, visibilite, indication, attendre comme actions, utilisables
  aussi bien par l'editeur manuel (menu lateral Objets/Ecran) que par
  Ruby (IA), avec blocs deplacables/supprimables dans une chaine.
- Corrige plusieurs variantes du bug "impossible de poser un objet hors
  du champ de la camera" (troncature du chainage d'actions a 4 maillons,
  fond importe pose a 128x128 au lieu de sa taille reelle, decalage du
  fond au vrai glisser-depose, redimensionnement manuel jamais propage
  au monde).
- Ajoute un vrai glisser-depose depuis la galerie vers la scene, la
  gestion complete de "Mes assets" (sous-sections Fonds/Decors/Sons/
  Videos, suppression, reclassement fond<->decor sans re-upload).
- Ajoute l'upload de son (limite 3 min) et de video (MP4 uniquement,
  limite 5 min), avec validation de la duree reelle du fichier, et une
  replique audio optionnelle dans une bulle de dialogue.
- Fixe la taille de pose d'un objet/decor importe a 200x200 avec une
  boite de collision de 150x150.
- Filtre le selecteur de fichier des actions son/video par type reel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 00:28:46 +02:00
williamandClaude Sonnet 5 b07b231a61 Ajoute l'assistant IA "Ruby" (Claude + Scenario) et corrige plusieurs bugs de la scène
Build and deploy / test-python (push) Successful in 12m24s
Build and deploy / test-js (push) Successful in 1m9s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Intègre un chat IA capable de manipuler la scène via les mêmes fonctions
que l'éditeur manuel (objets, variables, déclencheurs, images générées),
avec conversations multiples par écran façon Claude. Corrige au passage
le rafraîchissement pjax hors-ordre, l'onglet IA/déclencheurs vide après
sélection d'un objet, la comparaison de booléens dans les conditions, et
le blocage du glisser-déposer hors du cadre caméra après un redimensionnement
de fond par l'IA.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 19:40:31 +02:00
williamandClaude Sonnet 5 ab6635eee0 Corrige l'action "Modifier une variable" sans effet en aperçu créateur
Build and deploy / test-python (push) Successful in 5m47s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
compute-operation.js/apply-actions.js n'étaient chargés que dans le
paquet SCORM exporté (offline_mode) ; en aperçu créateur (/game/<slug>/play),
forgeApplyVariableActionOffline était donc absente et l'action ne modifiait
rien. Charge les deux scripts inconditionnellement dans play.html.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 22:24:47 +02:00
williamandClaude Sonnet 5 d664ed5637 Remplace le système de quêtes par des déclencheurs, ajoute l'action variable et le chaînage
Build and deploy / test-python (push) Successful in 8m45s
Build and deploy / test-js (push) Successful in 49s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Supprime le concept de "quête" au profit d'un onglet unique "Déclencheurs"
portant toute la logique (dialogue, condition, marquage terminé) directement
sur l'objet de scène. Ajoute une nouvelle action "Modifier une variable"
(réutilisant le vocabulaire du graphe de flow) utilisable après une
collision, une interaction ou une branche de condition, ainsi qu'un
chaînage d'actions ("then") permettant d'enchaîner plusieurs actions à la
suite et d'étendre un déclencheur déjà posé sans le recréer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 22:05:06 +02:00
william c504ace167 Enrichit le reporting SCORM/xAPI et met en conformité RGAA le player
Build and deploy / test-python (push) Successful in 5m56s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
SCORM/xAPI :
- Ajoute l'export SCORM 2004 (3rd/4th edition) au choix, en plus du 1.2
  par défaut : sépare completion_status/success_status (un échec reste
  "completed" au lieu de retomber à tort en "incomplete" comme force la
  1.2), remonte aussi cmi.interactions.n.* par question répondue.
- Fournit cmi.core.score.min/max (1.2 et 2004), calculé depuis les
  récompenses de quiz du jeu, pour que le LMS affiche un vrai pourcentage
  au lieu du score brut à tort étiqueté "%".
- Libellés de verbes xAPI en français en plus de l'anglais.

Accessibilité (RGAA/WCAG 2.1 AA) sur le player :
- Navigation clavier des objets de scène "au clic"/"au survol"
  (tabindex, role, Entrée/Espace, focus/blur).
- alt sur les images (nom auteur ou décoratif), aria-hidden sur les
  icônes seules, role="dialog"/aria-live sur les boîtes de dialogue/quiz.
- Landmark <main> + titre de page, respect de prefers-reduced-motion.
- Le quiz n'avance plus automatiquement après un délai fixe : bouton
  "Continuer →" explicite (RGAA 2.2.1).
- Avertissement de contraste dans l'éditeur de style de dialogue.
- Déclaration d'accessibilité téléchargeable depuis la modale d'export.
2026-09-06 00:17:51 +02:00
william f7a50e5afe Ajoute un suivi xAPI optionnel (bolt-on) au paquet SCORM exporté
Build and deploy / test-python (push) Successful in 9m41s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Greffe l'envoi de statements xAPI vers un LRS configurable par le
créateur du jeu, en parallèle du reporting SCORM existant : réglages
stockés en base (_meta), formulaire dans la modale d'export, injection
dans le paquet exporté. Relié aussi bien à l'action de flow "Modifier
un score/statut" qu'au parcours quête/quiz (qui alimentait déjà le
SCORM classique via un chemin séparé).

L'export ne se lance plus automatiquement à l'ouverture de la modale,
pour laisser le temps d'enregistrer les réglages xAPI avant de générer
le paquet.
2026-09-05 10:00:57 +02:00
william 7ebc9b143f Corrige une faille d'isolation entre comptes et retire l'email des chemins de projet
Build and deploy / test-python (push) Successful in 9m39s
Build and deploy / test-js (push) Successful in 52s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Le rôle "admin" contournait entièrement l'isolation par projet
(core/auth_guard.py) : il pouvait ouvrir/modifier/supprimer le jeu de
n'importe quel autre compte en connaissant son slug, et la page d'accueil
listait sans filtrage tous les projets de tous les comptes.

- La propriété d'un projet se vérifie désormais sur le segment
  "propriétaire" du slug (id du compte), pour tous les rôles y compris
  admin — un slug "à plat" (sans compte associé) reste réservé à
  l'admin, comportement historique conservé pour ce cas précis.
- routes/games/index.py ne liste plus que les projets du compte connecté.
- Le dossier propriétaire d'un projet est maintenant l'id numérique du
  compte, plus jamais son email slugifié (visible en clair dans chaque
  URL auparavant) — script de migration fourni et déjà exécuté sur les
  données existantes.
- Changer d'email ne renomme plus aucun dossier (n'en dépend plus).
- Deux nouveaux tests de régression, fixtures corrigées en conséquence.
- README réécrit pour refléter l'état actuel du produit (jeu 2D
  uniquement, plus de traces de l'ancien éditeur "document").
2026-09-04 22:52:05 +02:00
william 50835a18e2 Supprime le type d'écran "document" et recentre le produit sur le jeu 2D
Build and deploy / test-python (push) Successful in 9m41s
Build and deploy / test-js (push) Successful in 1m0s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Le document de cadrage produit cible des formateurs non techniques créant
des serious games/quiz gamifiés — l'éditeur générique "document" (blocs de
logique en nœuds, timeline d'animation, définitions d'objets/relations,
templates réutilisables) est une complexité hors cible que l'effort
d'ingénierie récent avait déjà abandonnée au profit du jeu_2d.

- Onboarding : ne garde que le parcours "RPG" (jeu_2d), retire
  Quiz/Embranchement/Créer mon jeu de A à Z (tous document-only)
- Suppression en bloc des modules exclusifs au document : routes/elements,
  routes/element_types, routes/objects, routes/legacy_actions,
  screens/elements, screens/element_types, screens/widgets,
  screens/legacy_actions, le rendu render_element_html.py et son cluster,
  templates/screen_edit.html, templates/game_dashboard.html,
  flow-editor.js/tabs-and-blocks.js/animation-timeline.js
- Dashboard toujours simplifié (un seul mode possible désormais)
- Tests document-only supprimés, tests de logique partagée (flow,
  événements personnalisés, animations) retargetés sur des écrans jeu_2d
- Aucune régression jeu_2d : 299 tests passent

Carte d'onboarding retravaillée : argumentaire RH non technique (liste à
coche, badge "Compatible LMS"), taille et interaction de retournement
ajustées.
2026-09-04 20:56:32 +02:00
williamandClaude Sonnet 5 00f7191a8b Corrige les animations figées et le reporting SCORM dans le paquet exporté
Build and deploy / test-python (push) Failing after 1m15s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Deux bugs distincts détectés dans le paquet Web/SCORM :
- L'animation de marche du personnage dépend entièrement des touches
  clavier maintenues (heldKeys), qui ne sont alimentées que par des
  écouteurs keydown/keyup sur window. Un LMS comme SCORM Cloud charge
  index.html dans une iframe qui ne reçoit pas le focus clavier par
  défaut : aucune touche n'atteignait jamais window, donc le personnage
  ne bougeait/animait jamais. On force désormais le focus au chargement
  et on le reprend au premier clic dans le jeu.
- scorm-api.js (qui pousse score/statut au LMS) était bien copié dans
  le zip mais jamais référencé par un <script> dans index.html, car
  build_scorm_package.py ne passait pas scorm_api_wrapper_url au
  template — le reporting SCORM (Completion/Success/Score) ne
  fonctionnait donc jamais depuis un export.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 18:23:17 +02:00
williamandClaude Sonnet 5 413685e871 Relie le score/statut des quêtes aux indicateurs SCORM
Build and deploy / test-python (push) Failing after 1m15s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Les quêtes/quiz mettaient à jour l'état local (statut, score) sans
jamais toucher gameData.scoring, que scorm-api.js interroge déjà pour
reporter Completion/Success/Score au LMS. Ajoute forgeSyncQuizScoreToScorm,
forgeSyncQuestStartedToScorm et forgeSyncAllQuestsCompletionToScorm,
appelées lors d'un bon score, de l'acceptation d'une quête et de la
complétion de toutes les quêtes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 17:56:37 +02:00
williamandClaude Sonnet 5 55c6bb330b Contrôle direct de la taille de la caméra (Largeur × Hauteur, toujours modifiable)
Build and deploy / test-python (push) Failing after 1m13s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Remplace le bouton conditionnel "Agrandir la caméra à la zone visible"
(n'apparaissait que si un fond dépassait déjà la scène) par deux champs
"Caméra (px)" TOUJOURS visibles, pré-remplis avec scene_width/
scene_height actuels — demande explicite : "l'écran de l'éditeur a une
taille fixe, la caméra a la même taille, c'est tout", un réglage direct
plutôt qu'une suggestion automatique liée à la taille d'un fond.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 17:28:03 +02:00
williamandClaude Sonnet 5 b9d3c46087 Bouton "📐 Agrandir la caméra à la zone visible"
Build and deploy / test-python (push) Failing after 1m23s
Build and deploy / test-js (push) Successful in 54s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Fait de tout le "monde" affiché par l'éditeur (la taille réelle d'un
"fond" plus grand que la scène nominale, voir routes/scenes/
scene_edit_view.py::world_width/world_height) la scène/caméra ELLE-MÊME
— jusqu'ici scene_width/scene_height n'étaient fixées qu'à la création
de l'écran, jamais modifiables ensuite. Le bouton n'apparaît que quand
un fond dépasse encore la scène nominale (même condition que le repère
"🎥 Champ de la caméra"), affiche la taille cible, recharge la page une
fois appliqué. La caméra ne recadre alors plus rien en jeu (monde ==
scène == viewport, voir personnage-controller.js::forgeUpdateSceneCamera).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 17:07:24 +02:00
williamandClaude Sonnet 5 14ec76dddd Éditeur : le canevas affiche tout le monde (fond agrandi), pas que la scène
Build and deploy / test-python (push) Failing after 1m20s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Suite au fix du glisser figé à (0,0) : le canevas de l'éditeur restait
malgré tout limité à la scène nominale (960×540 par ex.) — la partie
d'un "fond" plus grand qui dépasse restait invisible/injoignable à la
souris. Le canevas affiche désormais tout le "monde" (max de la scène
et de l'étendue de chaque "fond", voir routes/scenes/scene_edit_view.py),
comme la caméra en jeu le fait déjà (personnage-controller.js).

Nouveau repère visuel "🎥 Champ de la caméra" : un cadre en pointillés
marquant où s'arrête la scène nominale à l'intérieur de ce monde agrandi
(le reste est assombri), pour que l'auteur voie clairement ce que le
joueur verra réellement à l'écran une fois le monde plus grand que le
viewport.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 16:55:09 +02:00
williamandClaude Sonnet 5 6b4846a646 Fix vrai bug : un objet plus grand que la scène (fond/caméra) restait figé à (0,0)
Build and deploy / test-python (push) Failing after 1m22s
Build and deploy / test-js (push) Successful in 56s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Confirmé par l'utilisateur : lié à l'introduction du fond/caméra (voir
add_scene_object.py — un "fond" est posé à sa taille RÉELLE, souvent
bien plus grande que la scène, exprès, pour que la caméra le suive en
défilant sur un monde plus grand que le viewport).

Le glisser en position utilisait Math.max(0, Math.min(SCENE_WIDTH -
width, ...)) — cette formule suppose SCENE_WIDTH - width POSITIF (objet
plus petit que la scène). Pour un objet plus GRAND (ex. un fond de
1920px sur une scène de 960px), cette différence est NÉGATIVE, et
Math.max(0, négatif) ramenait TOUJOURS la position à 0 quel que soit le
glisser — l'objet restait donc figé, impossible à repositionner.

Le redimensionnement plafonnait aussi la largeur/hauteur à "ce qui
reste dans la scène depuis son coin" (SCENE_WIDTH - left) — empêchant
justement de rendre un objet plus grand que la scène par glisser (un
fond ne pouvait être agrandi qu'en le recréant via la galerie).

Les deux bornes sont corrigées pour fonctionner quel que soit lequel
(scène ou objet) est le plus grand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 16:43:08 +02:00
williamandClaude Sonnet 5 7c9b2c9b25 Fix "impossible d'aller plus loin vers la droite" — défilement automatique du canevas
Build and deploy / test-python (push) Failing after 1m13s
Build and deploy / test-js (push) Successful in 55s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Cause confirmée (tous les objets bloquaient au même endroit, pas
spécifique à un widget) : une scène à taille fixe (960×540 par ex.)
peut être plus LARGE que la zone visible de .canvasFrame (overflow:auto)
selon la largeur de fenêtre — sa partie droite/basse défile alors HORS
de vue, et le curseur atteignait le bord de cette zone VISIBLE bien
avant celui de la scène elle-même : la souris ne pouvait tout simplement
plus bouger physiquement plus loin, sans aucun rapport avec la limite
réelle (960px) de la scène.

onSceneObjectMouseDown/onSceneObjectResizeMouseDown font maintenant
défiler .canvasFrame automatiquement quand le curseur approche un bord
pendant un glisser/redimensionnement — et continuent de déplacer
l'objet même si la souris reste immobile près du bord (mirror du
comportement standard d'un glisser-déposer dans une zone scrollable),
grâce à une position ACCUMULÉE plutôt que recalculée depuis le point de
départ fixe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 16:27:51 +02:00
williamandClaude Sonnet 5 046c38b4dc Annule la réservation d'espace des panneaux, corrige le vrai bug (drag invisible sous eux)
Build and deploy / test-python (push) Failing after 1m22s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Retour en arrière sur le commit précédent : les panneaux flottants
doivent RESTER en position:fixed par-dessus le canevas pour maximiser
l'espace utile (remarque explicite) — leur réserver une marge
permanente allait à l'encontre de ce principe et ne réglait rien.

Vrai bug identifié : un objet glissé (déplacé ou redimensionné) sous
l'un de ces panneaux (z-index:60) disparaissait littéralement à
l'écran PENDANT le geste — toujours déplacé/enregistré correctement en
dessous, juste invisible, donnant l'impression qu'on ne pouvait pas y
déposer d'objet. Élevé au-dessus des panneaux (z-index très haut) le
temps du glisser/redimensionnement SEULEMENT, restauré ensuite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 16:18:42 +02:00
williamandClaude Sonnet 5 d705f58c4a Fix accès au canevas masqué + quiz : header quête, réponse fausse n'avance jamais bloquée, animé
Build and deploy / test-python (push) Failing after 1m36s
Build and deploy / test-js (push) Successful in 55s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
- Bug corrigé : les panneaux flottants "🧩 Objets"/"⚙️ Propriétés" sont en
  position:fixed (hors du flux flex de .builder3) — ils flottaient
  PAR-DESSUS le canevas sans jamais réduire sa largeur, rendant sa
  partie droite/gauche inaccessible au clic/glisser tant qu'un panneau
  restait ouvert. .builderCanvasArea réserve maintenant leur largeur
  (marge) dès qu'un panneau est ouvert (:has()).

- Boîte à quiz : header = "Quête : <titre>" (au lieu du texte de la
  question), corps = la question ET ses choix ensemble.

- Mauvaise réponse : ne bloque plus JAMAIS la progression (bug signalé :
  "je suis obligé de bien répondre sinon j'avance pas") — surligne la
  bonne réponse en vert (le choix cliqué en rouge s'il était faux) puis
  avance automatiquement après un court délai, sans jamais octroyer de
  points. Un second clic pendant la révélation est ignoré.

- Animations : la boîte à quiz rejoue une entrée (pop-in) à CHAQUE
  nouvelle question, la bonne réponse pulse en vert, une mauvaise
  réponse "secoue" en rouge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 16:08:59 +02:00
williamandClaude Sonnet 5 b5546df044 Quiz jouable : budget de récompense, boîte à quiz, widget score
Build and deploy / test-python (push) Failing after 1m34s
Build and deploy / test-js (push) Successful in 1m1s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
- Récompense d'une quête = budget MAXIMUM pour ses questions : la somme
  des points des "❓ Question" ne peut plus dépasser recompense_score
  (routes/quests/quest_dialogues.py à l'enregistrement des dialogues,
  quest_update.py si on abaisse la récompense sous ce qui est déjà
  réparti) — message d'erreur explicite (400) dans les deux sens,
  affiché via alert() côté éditeur (quest-editor.js), jamais enregistré
  silencieusement dans un état incohérent.
- Nombre de choix par question plafonné à 4 (au lieu de 8).

- Deux nouveaux widgets "🖥️ Interface" (mêmes fondations que "💬 Boîte
  de dialogue" — screens/rendering/dialogue_box_style.py, même panneau
  "🎨 Style") :
  - "❓ Boîte à quiz" : affiche la question et ses choix, sans pied (une
    question se résout au clic sur un choix, pas de "Suivant").
  - "🏆 Score" : affiche en continu les points gagnés, TOUJOURS visible
    une fois posé (contrairement aux boîtes de dialogue/quiz, masquées
    par défaut).

- Moteur d'exécution (static/js/play/dialogue-box-controller.js,
  refonte) : une conversation de quête alterne maintenant répliques
  (boîte de dialogue) et questions (boîte à quiz) selon le type de
  chaque ligne. Bonne réponse -> crédite le score (compteur runtime
  dédié, jamais lié au score du moteur document) et avance ; mauvaise
  réponse -> rien, la question reste affichée pour réessayer ; aucune
  boîte à quiz posée -> la question est ignorée plutôt que de bloquer
  la conversation. Le dialogue "en_cours" épuisé (donc, s'il contenait
  des questions, toutes répondues) fait passer la quête "terminee" (en
  mémoire seulement, comme le reste de ce système — voir accepter/
  refuser une offre de quête) : le joueur peut alors quitter la scène
  normalement, ces widgets n'étant jamais des obstacles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 15:42:42 +02:00
williamandClaude Sonnet 5 e972acf9e6 Quêtes : bulle "❓ Question" à choix multiples, à côté de "+ Réplique"
Build and deploy / test-python (push) Successful in 6m25s
Build and deploy / test-js (push) Successful in 58s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Nouveau type de ligne dans un dialogue de quête (voir
db/quests/sanitize_quest_dialogues.py) : une bulle JAUNE, dans la même
chaîne verticale que les répliques (même alignement, reliée par un
trait) — header : nombre de choix (2 à 8) + récompense (score, montant
en points) ; body : la question, ses choix et un bouton radio pour
désigner la bonne réponse. Aucune limite au nombre de questions par
colonne, comme pour une réplique.

Les lignes de dialogue portent maintenant explicitement
{"type": "dialogue", ...} (au lieu d'un objet sans type) pour
distinguer les deux formes — migration de forme, tests mis à jour.

Portée de ce commit : l'ÉDITEUR (créer/modifier une question). Le jeu
lui-même (afficher la question au joueur, vérifier la réponse,
attribuer la récompense) reste à construire — le widget "💬 Boîte de
dialogue" ne sait aujourd'hui afficher qu'une réplique.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 14:48:14 +02:00
williamandClaude Sonnet 5 f2ed602459 Fix boutons Accepter/Refuser absents de l'écran d'offre de quête
Build and deploy / test-python (push) Successful in 6m47s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Le footer de la boîte de dialogue n'avait pas data-dialogue-role="footer"
(seul son bouton interne "Suivant" l'avait, sous "next-btn") — le JS
(forgeShowQuestOffer, dialogue-box-controller.js) le cherche par ce
sélecteur pour y injecter les boutons Accepter/Refuser en fin de
dialogue d'une quête "nouvelle". querySelector ne trouvait donc jamais
le footer, et l'écran d'offre gardait silencieusement le bouton
"Suivant" au lieu des deux nouveaux boutons — exactement le bug signalé.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 14:04:26 +02:00
williamandClaude Sonnet 5 e90d941527 Quête "nouvelle" : écran Accepter/Refuser à la fin du dialogue
Build and deploy / test-python (push) Successful in 7m13s
Build and deploy / test-js (push) Successful in 1m5s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Une fois le dialogue épuisé, si la quête est encore "nouvelle", la
boîte de dialogue bascule sur un écran d'offre au lieu de se masquer :
header "Quête : <titre>", body l'objectif, pied deux boutons.

- Accepter -> la quête passe "en_cours" (en MÉMOIRE seulement,
  gameData.quests — jamais persisté en base : le statut en base est
  celui de DÉPART pour toute nouvelle partie, pas un état de partie en
  cours, voir PLAYER_SHARED/full_game_payload.py).
- Refuser -> la boîte se referme SANS toucher au statut, qui reste
  "nouvelle" : la prochaine interaction rejoue exactement le même
  dialogue depuis le début — impossible d'avancer la quête sans
  l'accepter un jour.

Pour tout autre statut (déjà "en_cours"/"terminee"), la boîte se
masque normalement à la fin du dialogue, comme avant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 14:00:02 +02:00
williamandClaude Sonnet 5 df34106752 Collision : une carte par objet occupe toute la largeur
Build and deploy / test-python (push) Successful in 6m24s
Build and deploy / test-js (push) Successful in 54s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
L'ancienne grille (auto-fill, colonnes de 300px minimum) réservait des
colonnes vides même avec une seule carte, la laissant étroite avec un
grand vide à droite. Empilées en pleine largeur à la place — laisse
aussi la place à une longue chaîne de règles de s'étendre sans se
faire couper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 13:42:36 +02:00
williamandClaude Sonnet 5 aa17614c7b Fix bulle "Appuie sur X" trop haute au-dessus du personnage
Build and deploy / test-python (push) Successful in 6m7s
Build and deploy / test-js (push) Successful in 52s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
La bulle s'ancrait sur le cadre BRUT du sprite (<img> style.left/top/
width/height) — beaucoup de sprites (animaux CraftPix notamment, voir
screens/labels/animal_sprite_library.py) ont un très grand canevas
padé autour d'une silhouette bien plus petite, faisant flotter la
bulle loin au-dessus du personnage visible.

Elle s'ancre maintenant sur la BOÎTE DE COLLISION (objRect, déjà
résolue via forgeElementBoxRect — largeur/hauteur/décalage réglables
dans "🧱 Collision"), que l'auteur ajuste déjà pour épouser la
silhouette réelle : ancre bien plus fidèle, sans configuration
supplémentaire à faire.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 13:16:24 +02:00
williamandClaude Sonnet 5 00f9cf33e9 Fix bulle "Appuie sur X" invisible + retire Attaque/Événement du picker
Build and deploy / test-python (push) Successful in 6m12s
Build and deploy / test-js (push) Successful in 52s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Bug corrigé : la bulle "Appuie sur X" était ajoutée comme ENFANT de
l'objet de scène (un <img> pour "personnage"/"decor"/"fond") — un
élément REMPLACÉ dont les enfants DOM ajoutés en JS ne sont JAMAIS
affichés par un navigateur, quel que soit son CSS. La bulle existait
donc bien dans le DOM (aucune erreur) mais restait invisible à l'écran.
Elle est maintenant ajoutée comme SŒUR de l'objet dans son parent
(.sceneWorld/.sceneUI), positionnée en JS aux mêmes coordonnées.

Assistant "+ Action" de l'éditeur de collision : "⚔️ Attaquer la cible"
et "📣 Déclencher un événement" retirés des choix proposés (pas besoin
pour le moment) — le serveur continue d'accepter/d'exécuter une règle
déjà enregistrée avec l'un des deux, rien ne casse pour l'existant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:58:00 +02:00
williamandClaude Sonnet 5 726775fe4e Fix "à la collision" — la marge de 3px restait insuffisante
Build and deploy / test-python (push) Successful in 5m56s
Build and deploy / test-js (push) Successful in 48s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
forgeWouldCollide (personnage-controller.js) bloque le déplacement par
PAS ENTIER (cmd.vitesse px/tick, 4 par défaut mais réglable) : le
joueur peut donc rester bloqué jusqu'à PRESQUE un pas entier de
distance de l'obstacle, pas seulement 1-3px — la marge de tolérance du
correctif précédent (3px) restait insuffisante même pour la vitesse par
défaut dans les cas les moins favorables. Portée à 24px, confortable
pour toute vitesse raisonnablement configurée sans jamais déclencher
"à la collision" alors que les objets sont encore visiblement séparés.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:42:17 +02:00
williamandClaude Sonnet 5 3a67ba622b Fix "à la collision" qui ne se déclenchait jamais
Build and deploy / test-python (push) Successful in 6m27s
Build and deploy / test-js (push) Successful in 54s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Cause réelle : un objet portant une règle de collision est presque
toujours AUSSI un obstacle solide (collision activée par défaut, voir
forgeWouldCollide, personnage-controller.js) — le déplacement du joueur
est donc bloqué PILE au contact, sans jamais laisser les deux boîtes se
chevaucher réellement. Le déclencheur "collision" testait un
chevauchement STRICT (forgeShapesOverlap nu), qui n'était donc jamais
atteint : la règle ne se déclenchait jamais en jeu réel, malgré un
contact visible à l'écran.

Le déclencheur teste maintenant la boîte de l'objet élargie d'une
petite marge (3px) — assez pour détecter un simple contact — SANS
toucher au blocage physique du déplacement (resté strict, inchangé).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:33:59 +02:00
williamandClaude Sonnet 5 5e17e42dfa Test bout en bout : collision -> quête -> boîte de dialogue
Build and deploy / test-python (push) Successful in 6m11s
Build and deploy / test-js (push) Successful in 58s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Vérifie explicitement le lien moteur demandé : une règle de collision
"quete" retrouve la quête, récupère les répliques de son statut actuel,
les affiche dans l'ordre dans le widget "💬 Boîte de dialogue" posé sur
la scène, avance au clic sur "Suivant", disparaît à la dernière
réplique — déjà implémenté (collision-rules-controller.js +
dialogue-box-controller.js), ce test couvre la CHAÎNE COMPLÈTE plutôt
que chaque brique isolément.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:27:50 +02:00
williamandClaude Sonnet 5 077af2234d Fix "+ Réplique" qui remplaçait la bulle au lieu d'en ajouter une
Build and deploy / test-python (push) Successful in 7m9s
Build and deploy / test-js (push) Successful in 1m2s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Bug : questSaveDialogues() adoptait la réponse du serveur comme nouvel
état local après chaque enregistrement. Une bulle fraîchement ajoutée a
un texte encore vide (rien tapé) — sanitize_quest_dialogues.py la
rejette légitimement (jamais de réplique sans texte persistée) — donc
la réponse renvoyait un tableau amputé de cette bulle, que le client
adoptait aveuglément, effaçant la bulle en cours d'écriture. Le clic
suivant sur "+ Réplique" repartait donc du même état qu'avant, semblant
"remplacer" la bulle plutôt que d'en ajouter une seconde. Le client ne
resynchronise plus jamais son état depuis la réponse d'enregistrement —
il est déjà la seule source de vérité pendant l'édition.

Colonnes de dialogue : occupent maintenant toute la hauteur disponible
de la modale plein écran et défilent chacune indépendamment (titre et
bouton "+ Réplique" restent fixes), au lieu d'une hauteur minimale fixe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:08:41 +02:00
williamandClaude Sonnet 5 521792fe00 Dialogues de quête : bulles illimitées, n'importe quel objet, timeline reliée
Build and deploy / test-python (push) Successful in 7m32s
Build and deploy / test-js (push) Successful in 1m2s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
- "qui parle" n'est plus réservé au personnage : N'IMPORTE QUEL objet de
  scène nommé (personnage, décor, fond — voir "ℹ️ Informations",
  screens/rendering/scene_object_names.py, ex-personnage_names.py) peut
  parler dans un dialogue.
- Confirmé/documenté : aucune limite au nombre de répliques par colonne
  (bouton "+ Réplique" reste toujours disponible).
- Bulle = header (menu déroulant du "qui parle") + body (le texte),
  centrée dans sa colonne à 70% de largeur, alignée verticalement et
  reliée à la suivante par un trait — remplace l'ancien alignement
  gauche/droite façon chat.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 11:54:06 +02:00
williamandClaude Sonnet 5 2347a70c45 Quêtes/collision : personnages nommés, dialogues qui parlent, boîte de dialogue en jeu
Build and deploy / test-python (push) Successful in 11m32s
Build and deploy / test-js (push) Successful in 1m12s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
- "ℹ️ Informations" (propriétés d'un personnage) : nom éditable, réutilisé
  comme "qui parle" dans l'éditeur de dialogue de quête (menu déroulant,
  plus "Joueur" toujours disponible) — remplace l'ancien choix binaire
  joueur/pnj. db.sanitize_quest_dialogues accepte maintenant un nom libre.

- Nouveau menu "🖥️ Interface" (palette d'objets) avec le premier widget :
  "💬 Boîte de dialogue" (kind="dialogue_box", screens/rendering/
  dialogue_box_style.py) — position/taille comme tout objet de scène,
  panneau "🎨 Style" dédié (police, taille, épaisseur, couleur du texte,
  couleur header/body/footer). Rendu en <div> à 3 zones, jamais soumis à
  la collision ni à l'éditeur de collision (exclu partout : obstacles,
  liste des règles, payload). Rendu côté jeu dans .sceneUI, une couche
  FIXE au viewport (jamais .sceneWorld, qui défile avec la caméra).

- static/js/play/dialogue-box-controller.js : fait le lien moteur entre
  l'action "quete" de l'éditeur de collision et ce widget — affiche la
  réplique en cours du dialogue correspondant au STATUT ACTUEL de la
  quête (gameData.quests, désormais exposé game-wide par
  full_game_payload.py), avance au clic sur "Suivant" (header = qui
  parle, body = texte, footer = bouton), se masque à la dernière réplique.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 11:20:58 +02:00
williamandClaude Sonnet 5 390ee831ca Quêtes : dialogue dans une modale à part, en plein écran
Build and deploy / test-python (push) Successful in 6m17s
Build and deploy / test-js (push) Successful in 1m14s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Sépare la modale unique en deux étapes distinctes demandées par
l'utilisateur : #questFieldsModal ("je crée" — titre/objectif/
récompense/statut/résultat, taille normale) puis, sur un bouton dédié
"💬 Ajouter les dialogues", #questDialogueModal ("j'ajoute les
dialogues" — les 3 colonnes de répliques SEULES, occupant tout
l'écran). Un bouton "←" ramène à la fiche ; "✓ Utiliser cette quête"
reste disponible dans les deux, pour l'intégration avec l'éditeur de
collision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 20:54:00 +02:00
williamandClaude Sonnet 5 78c1aba8a0 Éditeur de quêtes : onglet à côté de Collision, pas une page à part
Build and deploy / test-python (push) Successful in 6m7s
Build and deploy / test-js (push) Successful in 59s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Corrige la mauvaise interprétation de la demande initiale : la page
dédiée "🗺️ Quêtes" (nav du jeu) est retirée au profit d'un nouvel
onglet "🗺️ Quêtes" DANS l'éditeur de scène 2D, juste à côté de
"🧩 Collision" — même liste tabulaire que l'onglet "📣 Événements"
(titre/objectif/récompense/statut/résultat par ligne, pas des cartes),
un clic sur une ligne ouvrant la modale d'arbre de dialogue déjà
construite. L'assistant "+ Action" de l'éditeur de collision ouvre
toujours cette même modale pour "Déclencher une quête".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 20:46:27 +02:00
williamandClaude Sonnet 5 51427b5297 Éditeur de quêtes : titre/objectif/récompense/statut/résultat + dialogue à 3 colonnes
Build and deploy / test-python (push) Successful in 6m14s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Nouvel espace "🗺️ Quêtes" (game-wide, comme les événements
personnalisés) : liste de cartes + "+ Nouvelle quête", chaque carte
ouvrant une modale d'arbre de dialogue en bulles de théâtre
("speaker: texte"), une colonne par statut de quête (nouvelle/en
cours/terminée) puisque le dialogue joué dépend de l'état de la
quête au moment où le joueur parle au PNJ. Bulles alternées
joueur/PNJ, couleur différente selon qui parle.

L'action "Déclencher une quête" de l'éditeur de collision n'est plus
un champ texte libre : elle ouvre directement CETTE MÊME modale
(picker de quêtes existantes + création à la volée), et finalise la
règle avec l'id numérique de la quête choisie — collision_rules.py
migré en conséquence (quete_id devient un entier, comme evenement_id).

Corrige aussi la miniature d'objet de la carte "🧩 Collision", 3× plus
grande (120px) comme demandé.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 20:24:57 +02:00
williamandClaude Sonnet 5 4391b36d45 Fix miniature de carte "🧩 Collision" échappée du cadre
Build and deploy / test-python (push) Successful in 6m46s
Build and deploy / test-js (push) Successful in 1m3s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
render_scene_object.py pose un positionnement en pixels absolus pensé
pour le canevas de scène (position:absolute, left/top/width/height en
dur, coordonnées d'origine de l'objet) — réinjecté tel quel comme
simple miniature 40×40 dans la carte, l'image se plaçait à ses
coordonnées de scène d'origine au lieu de rester dans son cadre.
Écrase ces styles en !important côté miniature seulement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 19:08:45 +02:00
williamandClaude Sonnet 5 b64541856f Éditeur de collision : cartes "pièces qui s'emboîtent", animées
Build and deploy / test-python (push) Successful in 6m27s
Build and deploy / test-js (push) Successful in 56s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Refonte visuelle de l'onglet "🧩 Collision" — l'ancien rendu (pastilles
plates + flèches) ne rendait pas la métaphore "pièces de puzzle qui
s'emboîtent" demandée. Chaque maillon (déclencheur/action/sous-action)
est maintenant une pièce chevronnée colorée par type, imbriquée dans
la suivante via clip-path + marge négative, avec une entrée animée en
cascade. Cartes objet et choix de l'assistant modal redessinés en
grille de cartes avec icône + libellé, hover/entrée animés.

Purement visuel — aucun changement de logique/données (déjà couvert
par tests/test_collision_rules.py et
static/js/play/__tests__/collision-rules-controller.test.js).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 17:23:42 +02:00
williamandClaude Sonnet 5 ee270d0286 Éditeur de collision (jeu 2D) : règles trigger -> action par objet
Build and deploy / test-python (push) Successful in 6m31s
Build and deploy / test-js (push) Successful in 50s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Nouvel onglet "🧩 Collision" dans l'éditeur de scène 2D : chaque objet
(hors joueur et fond) peut porter des règles "à la collision" ou "dans
un périmètre (px)" déclenchant une action (quête, attaque, événement,
ou interagir - qui affiche "Appuie sur [touche]" puis exécute une
sous-action à l'appui, un seul niveau d'imbrication). Backend
(sanitisation, route de persistance, exposition dans
full_game_payload) + assistant modal en cartes empilées côté client.

Ajoute le moteur d'exécution runtime (collision-rules-controller.js) :
détecte l'entrée en collision/périmètre avec le joueur à chaque tick,
déclenche l'action une seule fois par entrée, pur JS client sans appel
serveur (fonctionne à l'identique en ligne et en export SCORM).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 17:11:42 +02:00
williamandClaude Sonnet 5 dfaf85fac1 Retire la collision sur les images de fond + les blocs de logique/timeline de l'éditeur 2D
Build and deploy / test-python (push) Successful in 6m55s
Build and deploy / test-js (push) Successful in 55s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Deux retours utilisateur sur l'éditeur de scène 2D :

- Le panneau "🧱 Collision" (réglages + aperçu visuel sur le canevas)
  n'apparaît plus pour un objet kind="fond" : jamais un obstacle ni une
  cible de collision possible (déjà exclu de forgeSolidObstacles côté
  jeu), ce panneau n'aurait donc aucun effet.

- Retire les onglets "🧩 Blocs de logique" et "🎬 Timeline d'animation"
  de CET éditeur (l'éditeur document, templates/screen_edit.html, les
  garde tel quel) — en prévision d'un éditeur dédié aux scènes 2D
  (collision/périmètre -> action), pas encore construit. switchBuilderTab()/
  toggleDashCreate() (génériques, nécessaires à l'onglet "Événements"
  restant) extraites dans un nouveau builder-tabs.js partagé par les deux
  éditeurs, pour ne plus avoir à charger flow-editor.js/tabs-and-blocks.js/
  animation-timeline.js (propres aux blocs/timeline) dans l'éditeur 2D.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 16:41:56 +02:00
williamandClaude Sonnet 5 a231bcf2fa Aperçu de la boîte de collision dans l'éditeur + blocage physique au jeu
Build and deploy / test-python (push) Successful in 6m15s
Build and deploy / test-js (push) Successful in 55s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Deux retours utilisateur sur la boîte de collision (ajoutée précédemment) :

- Visible et réglable directement dans l'éditeur de scène : pour l'objet
  sélectionné, un contour pointillé (rectangle/cercle) se superpose sur
  le canevas — glisser son corps ajuste le décalage, glisser sa poignée
  (coin bas-droit) ajuste la taille, les deux se répercutent dans le
  panneau "🧱 Collision" et inversement (édition des champs -> aperçu à
  jour). static/js/scenes/scene-editor.js::onCollisionBoxMouseDown/
  onCollisionBoxResizeMouseDown, mirror des poignées de position/taille
  déjà existantes pour l'objet lui-même.

- Bloque désormais RÉELLEMENT le déplacement au clavier : avant chaque
  pas, personnage-controller.js teste si la position candidate
  chevaucherait la boîte de collision d'un autre objet solide (jamais un
  "fond", jamais un objet à collision désactivée) et annule ce pas — par
  AXE séparément, pour permettre de glisser le long d'un mur en
  diagonale plutôt qu'un blocage total au moindre contact. Réutilise
  forgeShapesOverlap (conditions.js, factorisé depuis elementsOverlap
  pour ne jamais dupliquer la règle "qu'est-ce qui se touche").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 15:48:08 +02:00
williamandClaude Sonnet 5 bc3feb5080 Panneau scène simplifié + boîte de collision + rôle joueur/ennemi/pnj
Build and deploy / test-python (push) Successful in 11m52s
Build and deploy / test-js (push) Successful in 56s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Trois retours utilisateur sur l'éditeur de scène 2D :

- Retire l'arborescence "Objets de cette scène" (un objet reste
  sélectionnable en cliquant dessus sur le canevas) et le bouton
  "+ Ajouter un décor" (le type "decor" reste supporté côté serveur,
  juste plus accessible depuis ce menu).

- Chaque objet de scène a désormais une boîte de collision AUTOMATIQUE
  (= sa propre boîte, comportement inchangé pour une condition de
  collision déjà posée) réglable dans un nouveau panneau "🧱 Collision" :
  activée/désactivée, forme (rectangle/cercle), taille et décalage —
  utile pour un sprite très paddé (CraftPix) dont la silhouette réelle
  est bien plus petite que son canevas. elementsOverlap() (conditions.js)
  applique ces réglages en restant identique par défaut.

- Nouveau champ "🏷️ Rôle" (Joueur/Ennemi/PNJ) sur un personnage : SEUL un
  personnage "Joueur" est désormais déplacé/animé au clavier et suivi
  par la caméra (personnage-controller.js) — "Ennemi"/"PNJ" restent
  immobiles tant qu'aucune logique de flow ne les pilote (déplacement
  ennemi automatique/apparition, dialogue de PNJ... hors scope de ce
  réglage, qui ne fait qu'identifier "quel objet est le joueur").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 15:30:34 +02:00
williamandClaude Sonnet 5 9199897784 Image de fond de scène + caméra qui suit le personnage, fps d'animation cohérent
Build and deploy / test-python (push) Successful in 6m26s
Build and deploy / test-js (push) Successful in 57s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Trois retours utilisateur :
- fps d'idle/interagir/touches supplémentaires alignés sur celui de la
  marche (8 i/s partout, était 4 pour idle — perçu comme "les autres
  animations sont lentes à côté de la marche").
- Nouveau menu "🏞️ Images de fond" dans "Objets de cette scène" : pose
  un objet kind="fond" à la taille RÉELLE de l'image choisie (pack
  CraftPix intégré en galerie, admin seulement — licence, voir
  core/sprite_gate.py), derrière tout le reste, insensible au clic.
- Si ce fond dépasse la scène, elle devient le "monde" : .playScreen.
  playScene est désormais le viewport (overflow:hidden, taille fixe),
  .sceneWorld le monde à l'intérieur — la caméra centre le premier
  personnage trouvé, bornée pour ne jamais montrer au-delà des bords
  (voir personnage-controller.js::forgeUpdateSceneCamera). Le personnage
  peut désormais se déplacer sur tout le monde, pas seulement le petit
  cadre visible (clampSceneObjectPosition, actions.js). Un jeu sans fond
  XXL garde un comportement strictement identique à avant (monde ==
  scène, transform vide).

Bibliothèque générée une fois par scripts/generate_background_manifest.py
(assets/background/, non versionné, licence CraftPix) vers
static/backgrounds/ (committé), même patron que
generate_animal_sprite_manifest.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 12:24:10 +02:00
williamandClaude Sonnet 5 5891c82c62 Commandes personnage : détection de touche au clavier + touches d'animation supplémentaires
Build and deploy / test-python (push) Successful in 6m17s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Deux retours utilisateur sur le panneau "Commandes" (déplacement/
animation automatiques, voir précédent commit) :
- Les champs de touche (haut/bas/gauche/droite/interagir) capturent
  maintenant la touche au clavier (clic puis appui — event.key, même
  valeur que heldKeys/triggers.js) au lieu d'être tapés à la main,
  source d'erreurs ("Espace" vs " ", "flèche haut" vs "ArrowUp"...).
- Nouvelle section "Animations supplémentaires" : un nombre illimité de
  touches, chacune liée à UNE pose au choix parmi celles réellement
  disponibles pour ce personnage (sauter, attaquer, courir...) — pas
  seulement les 4 touches de déplacement + interagir.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 11:50:29 +02:00
williamandClaude Sonnet 5 74f7ca396f Déplacement/animation automatiques d'un personnage (scène 2D)
Build and deploy / test-python (push) Successful in 6m25s
Build and deploy / test-js (push) Successful in 55s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Simplification demandée pour l'éditeur de jeu RPG : un personnage posé
sur une scène 2D se déplace et s'anime TOUT SEUL avec ZQSD (+ E pour
interagir) dès qu'il est posé — plus besoin de poser le moindre nœud de
flow pour un mouvement de base. Le panneau "🕹️ Commandes" du personnage
permet de remapper les 4 touches de déplacement et la touche
d'interaction, et de bloquer un axe (horizontal/vertical seulement).

Entièrement client (static/js/play/personnage-controller.js), réutilise
heldKeys (triggers.js) et applyObjectProperty/clampSceneObjectPosition/
runSpriteAnimation (actions.js) — aucune logique dupliquée, et ça
fonctionne aussi bien en ligne que dans l'export Web/SCORM (aucune
requête serveur). "walk"/"idle"/"interact" (poses déjà présentes pour
tout personnage Forge/CraftPix) sont utilisées telles quelles ; une pose
absente dégrade silencieusement (déplacement sans animation) plutôt que
de planter.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 11:26:37 +02:00
williamandClaude Sonnet 5 e82eb7ce88 Retire l'export exécutable (.exe) et la partie publique en ligne (/jouer)
Build and deploy / test-python (push) Successful in 7m5s
Build and deploy / test-js (push) Successful in 58s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Décision produit : seul l'export Web/SCORM (LMS) est pertinent — l'export
exécutable Windows autonome (publish/build_package.py, bouton "Publier")
et la partie publique par joueur (/jouer/<slug>, routes/public_play/,
"Publier en ligne") sont jugés redondants et retirés.

Conserve le mécanisme d'état "par joueur" (db/global_vars,
db/rows, per_player) : infrastructure générique déjà utilisée par
Score/Progression et testée indépendamment de toute route publique
(voir tests/test_player_state.py), aucune raison de la retirer.

_STATIC_ITEMS/_copy_characters (copie sélective des sprites CraftPix
réellement utilisés) migrent de build_package.py vers
build_scorm_package.py, seul appelant restant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 10:59:28 +02:00
williamandClaude Sonnet 5 82d00b3800 Export Web/SCORM : inline les icônes en data: URI (CORS Firefox/file://)
Build and deploy / test-python (push) Successful in 6m16s
Build and deploy / test-js (push) Successful in 54s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Le chemin relatif ("icons/x.svg") était enfin correct, mais Firefox
bloque encore par CORS TOUT chemin de fichier pour mask-image sous
file:// — chaque ressource file:// y est une origine opaque distincte,
indépendamment du chemin (troisième rapport du même utilisateur). Seule
une data: URI (aucune requête réseau séparée) contourne le problème.
_build_icon_data_uris (build_scorm_package.py) encode chaque icône
RÉELLEMENT posée dans le jeu (élément direct ou niché dans un élément de
jeu réutilisable) en base64, ajouté au payload sous
gameData.icon_data_uris ; forgeRenderIcone (JS) le consulte pour toute
régénération après une action, avec repli sur l'ancien chemin relatif
si jamais une icône n'a pas été pré-encodée.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 10:27:04 +02:00
williamandClaude Sonnet 5 b60fe63a54 Export Web/SCORM : corrige le chemin de l'icône doublé "static/static/"
Build and deploy / test-python (push) Successful in 6m44s
Build and deploy / test-js (push) Successful in 56s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Le premier correctif ("static/icons/x.svg") ne suffisait pas : cette URL
est posée en style inline (--icon-url) mais CONSOMMÉE par `mask-image:
var(--icon-url)` dans static/style.css (.icon-svg) — une URL relative
dans une propriété personnalisée CSS se résout par rapport à la feuille
de style où le var() est UTILISÉ, pas où elle est définie (piège CSS
connu). Résultat : "static/icons/x.svg" redevenait
"static/static/icons/x.svg" une fois résolu depuis static/style.css
(CORS bloqué — deuxième rapport du même utilisateur). Seul "icons/x.svg"
(sans le préfixe "static/") est correct une fois résolu depuis
static/style.css. Corrigé côté rendu initial
(_relativize_absolute_urls) et côté port JS (forgeRenderIcone, qui
régénère ce widget après une action).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 10:12:44 +02:00
williamandClaude Sonnet 5 e08c53e042 Export Web/SCORM : corrige la résolution des champs "relation" hors ligne
Build and deploy / test-python (push) Successful in 6m55s
Build and deploy / test-js (push) Successful in 1m4s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Bug signalé : une Donnée liée/un filtre référençant un champ "relation"
affichait "{{champ}}" tel quel une fois exporté, alors qu'il fonctionnait
en ligne. Deux causes cumulées :

- full_game_payload.py lisait la colonne SQL "<champ>" au lieu de
  "<champ>_id" (seule vraie colonne d'un champ relation, voir
  _field_column dans filter_repeater_rows.py) en construisant
  gameData.data — une valeur toujours None. Invisible en ligne (les
  filtres y requêtent la base fraîche, jamais cette snapshot), mais
  fatal hors ligne (aucune base à requêter).
- Une fois ce None corrigé, le port JS (forgeFieldColumn) relisait
  cette même valeur sous une clé slugifiée+suffixée ("boss_id") alors
  que gameData.data est déjà indexé par le nom D'AFFICHAGE du champ
  ("boss") — mismatch qui ne se voyait que sur un champ relation (le
  seul cas où slugify(nom)+suffixe diverge du nom original). Supprime
  ce port erroné, lit directement row[fieldName] partout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 10:02:26 +02:00