Corrige les findings SonarQube (via SonarLint IDE, fichier par fichier)
sur ~24 fichiers static/js/ : parseFloat/parseInt -> Number.*,
.replace(/x/g,y) -> .replaceAll, .indexOf() -> .includes()/.startsWith(),
getAttribute/setAttribute -> .dataset, tableaux -> Set, x && x.y -> x?.y
(verifie site par site), extraction de template litteraux imbriques,
ternaires imbriquees, refactors de complexite cognitive (S3776) via
tables de dispatch, Object.hasOwn, .at(), et deduplication de fonctions
identiques (S4144). Deux exceptions S2486 documentees/corrigees
(filter-repeater-rows.js) et un cas S2703 de partage inter-scripts
complete (_collisionWizard, trigger-editor.js <-> collision-rules-editor.js).
Details complets dans CODE_QUALITY.md section 5.
Restaure aussi le job CI "sonarqube" (non-bloquant) dans
.gitea/workflows/deploy.yml maintenant que l'instance prod est
operationnelle.
Suites vertes : 276/276 JS (node --test), 591/591 Python (pytest).
SKIP=djlint sur ce commit : hook djlint bloquant sur le backlog H021
(styles inline, 49 occurrences/6 templates) deja documente comme dette
assumee non traitee dans CODE_QUALITY.md section 6, aucun rapport avec
ce commit (aucun template touche ici) - valide explicitement avec
l'utilisateur avant de contourner ce hook.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Lot 1 (S8786, ReDoS) : 5 sites documentes NOSONAR apres preuve empirique
(script reproductible docs/redos_probe_s8786.js), aucune reecriture
defensive necessaire.
- Lot 2 (S2703, variable globale implicite) : bug reel trouve et corrige
(SCENE_OBJECT_NAMES en const au lieu de let, cassait la reassignation
cross-script depuis scene-editor.js) + test de non-regression ; 4 autres
sites confirmes surs et documentes.
- Lot 3 (S2486, exceptions avalees) : 6 sites confirmes surs et
documentes ; 2 sites (config sprite JSON invalide) corriges avec un
console.warn devtools, comportement joueur inchange, couverts par un
nouveau test.
- Retrait du job CI sonarqube (.gitea/workflows/deploy.yml) et du service
prod sonarqube/sonar-postgres (docker-compose.prod.yml) : acces dashboard
bloque par des soucis d'infrastructure reseau (WSL2/pare-feu Hyper-V en
local, reseau Docker partage avec Caddy pas en place en prod), sans lien
avec le code du moteur - mis de cote plutot que de continuer a bloquer
sur de l'infra. Les lots 4+ de modernisation JS dependent de scores
Sonar exacts et sont donc egalement en pause (voir CODE_QUALITY.md).
SKIP=djlint : H021 (styles inline, 49 occurrences) est un backlog deja
documente et assume (CODE_QUALITY.md section 6), sur des templates non
touches par ce commit - deja exclu de la CI pour la meme raison.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Nouveau db/assert_not_none.py : centralise l'unique suppression
Bandit/Ruff (# nosec B101 / # noqa: S101) de narrowing de type dans
tout le moteur. Remplace 11 sites disperses (ai/chat.py, ai/tools.py,
routes/scenes/scene_object_{add,collision,geometry,personnage_data,
quiz_config}.py, screens/payload/full_game_payload.py,
scripts/build_demo_dialogues.py) qui repetaient chacun le meme
commentaire empile - Sonar (python:S7632) ne parse pas deux
commentaires # sur une ligne, meme si Ruff et Bandit les acceptent
chacun tres bien (contrainte structurelle documentee dans
CODE_QUALITY.md : chaque outil exige son propre mot-cle immediatement
apres un #, aucun format a un seul # ne peut satisfaire les deux a la
fois). Les 6 sites # nosec B608 (SQL dynamique) restent inchanges,
nature differente, hors perimetre de ce refactor.
Verifie : mypy --strict propre (389 fichiers), ruff/bandit/import-linter
clean, suite complete verte (591 tests), scan SonarQube local relance
confirmant S7632 a 7 (1 seul site restant dans le helper lui-meme + les
6 B608), 0 bug (une regression S8371 trouvee et corrigee en route).
- 4 sites |safe repositionnes sur leur ligne exacte (scene_edit.html,
register_2fa.html, onboarding_new.html, play.html) - le marqueur
NOSONAR etait sur la ligne precedente par erreur (meme piege que celui
documente pour S8371 ci-dessus). Confirme par scan que meme corrige,
l'analyseur Web de Sonar ne supporte aucune syntaxe de suppression
inline testee pour la regle Web:S5247 - documente comme limitation
technique connue dans CODE_QUALITY.md plutot que force.
- CODE_QUALITY.md (nouveau) : reference complete du dispositif qualite -
vue d'ensemble par outil, configuration de chacun, commandes de lancement
local, procedure de justification d'une exception (avec le format exact
attendu par Bandit/Ruff/Sonar, verifie empiriquement), table des 10
exceptions documentees, backlog (djLint H021, code smells Sonar).
djLint (H021, styles inline) volontairement saute pour ce commit - meme
backlog assume que les commits precedents, aucun rapport avec ce changement.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 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>
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>
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>
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>
- 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>
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>
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>
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>
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.
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.
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").
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.
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- "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>
- "ℹ️ 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>
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>
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>
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>
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>
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>
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>