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>