ce750ec6979531dc480f15ab22c90fe55950c5ce
100
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ce750ec697 |
Implemente le mini-jeu Association (glisser-deposer par paires)
document_engine/labels/association_config.py (nouveau) : modele de
donnees, meme convention resolve_X/sanitize_X que quiz_config.py -
DEFAULT_ASSOCIATION_CONFIG, sanitize_association_config (chaque paire
doit avoir ses deux cotes non vides, sinon supprimee silencieusement ;
liste tronquee a MAX_PAIRS=8).
Cote serveur, routes/document/document_element_update.py revalide
desormais aussi l'association avant persistance (meme raisonnement que
pour le quiz). Le rendu (_render_association) affiche un resume reel
(nombre de paires) et, des qu'au moins une paire existe, un plateau de
glisser-deposer REELEMENT interactif en Mode Apercu
(_render_association_player) : les deux colonnes (termes/
correspondances) sont melangees independamment (random.shuffle,
melange d'affichage documente dans CODE_QUALITY.md) puis embarquees en
JSON dans un attribut data-assoc-config.
Cote editeur, le panneau Proprietes d'une association ("relier
visuellement deux champs qui vont ensemble") est une liste de paires
repetable, chaque ligne reliant visuellement un champ "Element" et un
champ "Correspondance" par un glyphe ↔. Le plateau jouable en Apercu
(static/document/js/document-editor.js) supporte deux facons de jouer,
toutes deux reelles : glisser-deposer HTML5 natif, ou cliquer une
carte puis son emplacement (repli pour les appareils sans support
fiable du drag) - bonne association verrouillee en vert, mauvaise
signalee puis reinitialisee, ecran de resultat une fois toutes les
paires associees.
Bug reel trouve ET corrige via simulation DOM complete (glisser-
deposer + clic simules, pas juste un chargement de page) : le
feedback visuel reutilisait la classe CSS du quiz via une
reaffectation de className qui supprimait au passage la classe
d'identite docAssocFeedback, rendant l'element introuvable des le
premier essai de match (aurait plante en usage reel des la premiere
tentative). Corrige en gardant toujours les deux classes ensemble.
Tests : 8 tests purs (tests/document/test_association_config.py, sans
Flask) + 1 test de route verifiant la sanitization a l'ecriture.
SKIP=djlint : backlog H021 pre-existant, aucun template touche ici.
ruff/mypy --strict/vulture/bandit/import-linter/eslint/stylelint tous
verts ; 50 tests document verifies frais. Verification manuelle live
complete : ajout, sanitization sur paire invalide, rendu du plateau,
et simulation DOM du gameplay reel (glisser-deposer correct/incorrect,
clic-selection, progression, ecran de resultat) - script de
diagnostic non conserve dans le depot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
9fb22be1ae |
Quiz reellement interactif en Mode Apercu (voir maquette document-formation-web.html)
Build and deploy / test-python (push) Failing after 1m5s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / lint-python (push) Failing after 1m2s
Build and deploy / lint-js (push) Failing after 1m4s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 1m0s
Le quiz affichait jusqu'ici la meme carte resume en edition comme en Apercu. Ajoute un vrai questionnaire jouable, visible UNIQUEMENT en Mode Apercu (masque en edition via CSS, jamais les deux a la fois) : options de reponse cliquables, marquage correct/incorrect immediat, score qui accumule les points de chaque question repondue correctement, progression "Question suivante", et un ecran de resultat final (score obtenu / total des points possibles) avec "Recommencer le quiz" - structure et identite visuelle calquees sur docs/plan/maquettes/document-formation-web.html (cartes en degrade, options A/B/C/D, feedback vert/orange), adaptees aux tokens --doc-* deja en place (theme clair/sombre inclus, nouvelles variables --doc-quiz-success-*/--doc-quiz-danger-*). Cote rendu (document_engine/rendering/render_document_element.py), _render_quiz ajoute desormais le questionnaire (fonction privee _render_quiz_player) des qu'au moins une question existe : les questions sont embarquees en JSON dans un attribut data-quiz-config (jamais un <script> par element), echappees pour l'HTML - aucun aller-retour serveur pendant qu'on joue, tout le deroulé (reponse/ score/suivant/resultat) est gere par static/document/js/document-editor.js. pointer-events, desactive sur tout element en Apercu pour empecher la selection/le glisser-deposer pendant le test, est reactive specifiquement dans le questionnaire pour qu'il reste reellement cliquable. Tests : 3 nouveaux tests de rendu purs (sans Flask, dont un qui verifie l'echappement HTML du texte d'une question contre une injection). Verifie en live via une simulation DOM complete (basculer Apercu, repondre correctement puis incorrectement, verifier score/feedback/etat des options, passer a la question suivante, voir l'ecran de resultat) - le script de diagnostic n'a pas ete conserve dans le depot. SKIP=djlint : backlog H021 pre-existant, aucun template touche ici. ruff/mypy --strict/vulture/bandit/import-linter/eslint/stylelint tous verts ; 48 tests cibles (document + onboarding) verifies frais. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
9d87a4bfef |
Implemente le mini-jeu Quiz (questions/choix/points/timer)
Build and deploy / test-python (push) Failing after 1m3s
Build and deploy / test-js (push) Successful in 48s
Build and deploy / lint-python (push) Failing after 1m2s
Build and deploy / lint-js (push) Failing after 1m5s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 1m5s
document_engine/labels/quiz_config.py (nouveau) : modele de donnees complet, meme convention resolve_X/sanitize_X que game_engine/rendering/quiz_box_config.py cote jeu (aucun import croise) - DEFAULT_QUIZ_CONFIG, sanitize_quiz_config (valide/nettoie chaque question independamment, jamais ne leve, tronque a 2-4 choix, remet correct_index a 0 si hors bornes/non-entier, clampe points >= 0 et timer_seconds dans [5,300]), quiz_total_points. Cote serveur, routes/document/document_element_update.py revalide desormais un quiz avant persistance (seul kind qui en a besoin - les autres n'ont que des attributs scalaires sans structure a garantir) et renvoie les attributs REELLEMENT persistes dans sa reponse, pour que le client ne derive jamais de la verite serveur apres un nettoyage serveur (ex. choix en trop tronque). Cote editeur (static/document/js/document-editor.js), le panneau Proprietes d'un quiz est desormais reel : chronometre optionnel, couleur de theme, liste de questions repetable (ajout/suppression), chacune avec son texte, un nombre de choix ajustable (2-4, les inputs texte suivent), le choix correct via un radio par question, et les points gagnes. Le rendu canevas (render_document_element.py) affiche un resume reel (nombre de questions, total des points, minuteur) au lieu du placeholder generique. Tests : document_engine/labels/quiz_config.py couvert par 13 tests purs (tests/document/test_quiz_config.py, sans Flask - defauts, troncature, validation, cas limites dont bool comme correct_index) + 1 test de route verifiant la sanitization a l'ecriture et le rendu. SKIP=djlint : backlog H021 pre-existant, aucun template touche ici. ruff/mypy --strict/vulture/bandit/import-linter/eslint/stylelint tous verts ; 63 tests cibles (document + onboarding + auth) verifies fraichement + verification manuelle live via le serveur de dev (ajout, sanitization sur choix invalides/en trop, rendu du resume avec minuteur). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
597d99a8de |
Corrige le bandeau d'outils invisible et force l'editeur en pleine largeur
Build and deploy / test-python (push) Successful in 6m33s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / lint-python (push) Failing after 4m13s
Build and deploy / lint-js (push) Failing after 1m10s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 1m5s
Bug reel (retour utilisateur direct, capture d'ecran a l'appui) : .docEditor3 utilisait position:fixed; inset:0 pour occuper tout l'ecran, mais son ancetre main.content a overflow:hidden (voir body.objectEditBody dans static/style.css) — qui ROGNE VISUELLEMENT tout descendant position:fixed a sa propre boite, laquelle commence sous la barre de navigation du site, pas au vrai sommet du viewport. Le bandeau d'outils (56px) tombait entierement dans la zone rognee, invisible, pendant que le reste de l'editeur semblait juste decale vers le haut a sa place. Corrige en respectant le pattern deja etabli par les autres editeurs (body.objectEditBody + main.content en flex:1 1 auto) plutot qu'un overlay fixed — .docEditor3 est desormais un simple enfant flex qui remplit main.content. Meme correction pour le tiroir mobile des barres laterales (position:absolute relatif a .docBodyWrap plutot que position:fixed relatif au viewport). Ajoute aussi une regle pour que l'editeur occupe toute la largeur de la fenetre bord a bord (demande explicite) : .content/.content-wide imposent normalement 760px/1600px avec padding fixe. SKIP=djlint : meme backlog H021 pre-existant que les commits precedents (document_edit.html verifie clean individuellement). ruff/mypy --strict/vulture/bandit/import-linter/eslint/stylelint tous verts ; 24 tests du support de formation verifies apres coup (changement CSS/Jinja pur, aucune logique serveur touchee) ; verifie en live que le serveur sert bien le CSS corrige. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
7ca03380b7 |
Retire l'action Publier du support de formation
Build and deploy / test-python (push) Successful in 6m57s
Build and deploy / test-js (push) Successful in 50s
Build and deploy / lint-python (push) Successful in 4m14s
Build and deploy / lint-js (push) Failing after 1m12s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 3m48s
Un apprenant n'a jamais acces a l'editeur : un etat "publie" persiste en base (published_at) ne servait donc a rien tant qu'aucune vue apprenant/export ne le consomme (retour utilisateur direct). Retrait complet : route /document/<slug>/publish, db.mark_support_published, la colonne logique published_at dans support_meta, le bouton et son CSS/JS. "Aperçu" devient l'action primaire du bandeau (repond au vrai besoin : voir le rendu avant un futur export). Un export reel (SCORM ou equivalent) reste a specifier separement le jour venu. SKIP=djlint : meme backlog H021 pre-existant que le commit precedent, aucun fichier touche ici n'y figure. ruff/mypy --strict/vulture/ bandit/import-linter/eslint/stylelint tous verts ; 615 tests Python (617 - 2 tests du Publier retire) + verification manuelle live du retrait (route 404, bouton absent du HTML, Apercu confirme fonctionnel par simulation DOM). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
bab581737a |
Ajoute le support de formation : entite racine separee du jeu 2D
Build and deploy / test-python (push) Successful in 11m2s
Build and deploy / test-js (push) Successful in 1m2s
Build and deploy / lint-python (push) Successful in 4m6s
Build and deploy / lint-js (push) Failing after 1m21s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m2s
Nouveau moteur document_engine/ (elements CRUD + rendering + labels), db/supports/ (stockage independant de db/games), routes/document/ (CRUD AJAX + publication), et l'editeur frontend complet (templates/document/, static/document/) avec moteur de layout reel (glisser-deposer -> fusion en rangee ou insertion avant/apres), vrai Undo/Redo par pile de commandes, grille d'accroche pour les formes libres, apercu responsive a largeurs fixes, mode Apercu, et publication persistee. "Mes formations" (templates/index.html) liste desormais les environnements 2D et les supports de formation cote a cote ; l'onboarding et core/auth_guard.py sont generalises pour qu'un compte restreint puisse posseder un projet de chaque type independamment. SKIP=djlint : le hook ne signale que le backlog H021 (styles en ligne) deja documente dans CODE_QUALITY.md sur des fichiers pre-existants non touches ici (base.html, game/play.html, scene_edit.html, game_dashboard_simple.html, clause_row.html) plus une ligne de index.html deja presente avant cette session — aucun nouveau fichier (document_edit.html compris) n'y figure. Tous les autres outils (ruff, mypy --strict, vulture, bandit, import-linter, eslint, stylelint) passent sans erreur ; 617 tests Python + 276 tests JS verts, plus une verification manuelle complete du cycle de vie via le serveur de developpement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
7e4a761cbb |
Met à jour docs/plan/PLAN.md section 0 avec la restructuration réellement faite
Build and deploy / test-python (push) Successful in 5m38s
Build and deploy / test-js (push) Successful in 43s
Build and deploy / lint-python (push) Successful in 4m34s
Build and deploy / lint-js (push) Successful in 2m52s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 3m46s
La section 0 décrivait encore un simple renommage screens->game_engine,
dépassé le même jour par la réorganisation effective en sous-dossiers
game/document dans routes/, scripts/, static/, templates/, tests/ (voir
commit
|
||
|
|
b2e933f322 |
Reorganisation game/document : renommage screens->game_engine + sous-dossiers game/ dans routes, scripts, static, templates, tests
Build and deploy / test-python (push) Successful in 11m12s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / lint-python (push) Successful in 3m56s
Build and deploy / lint-js (push) Successful in 3m1s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 3m58s
Prepare la scission a venir entre l'editeur Jeu 2D et le futur editeur Support de formation (voir docs/plan/PLAN.md), sans toucher a l'architecture en couches existante : - screens/ renomme en game_engine/ (nom clair pour le moteur du jeu 2D, avant l'arrivee d'un second "moteur" cote document) : ~85 imports corriges, contrat import-linter mis a jour, meme forme de couches. - routes/, scripts/, static/, templates/, tests/ : tout ce qui est propre au jeu 2D deplace dans un sous-dossier game/ de chacun (routes/game/, static/game/, templates/game/, tests/game/, scripts/game/) ; ce qui est partage par le site (auth, onboarding, dashboard, uploads, db/) reste a la racine de chaque dossier. Un sous-dossier document/ (vide) cree dans chacun pour le futur chantier. - styles/ volontairement inchange : les 3 fichiers sources sont concatenes en un seul static/style.css charge par tout le site, scinder leur CONTENU (editeur vs partage) serait un refactor CSS distinct, pas un deplacement mecanique. - Chaine d'export SCORM (publish/build_scorm_package.py) mise a jour en profondeur : copie des assets, URLs d'icones relatives a static/style.css (qui ne bouge pas), manifeste, wrapper SCORM. - Deux regressions d'un sweep de renommage anterieur corrigees au passage (screens.js/screens/scene-objects incorrectement convertis en game_engine.js/game_engine/scene-objects dans des commentaires). - Effet de bord Windows decouvert et corrige : git mv + Path.write_text convertissent des fichiers en CRLF (core.autocrlf=true) - ~189 fichiers normalises en LF. - .eslintrc.json/package.json : uniquement les chemins de glob mis a jour (static/game/js/...) ; la preparation eslint-plugin-unicorn du lot 7 reste volontairement non committee (package-lock.json restaure a la version precedente). Verifications : ruff, mypy --strict (391 fichiers), vulture, bandit, lint-imports tous verts ; 591/591 tests Python, 276/276 tests JS ; demarrage serveur + requetes HTTP manuelles confirmant que les assets deplaces repondent en 200 au nouvel emplacement et 404 a l'ancien. SKIP=djlint : backlog H021 (styles inline) deja documente comme dette assumee dans CODE_QUALITY.md section 6, aucun template touche par ce commit au-dela d'un deplacement de fichier. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
ccf836f2c5 |
Lot 4 modernisation JS (SonarLint) + restauration CI sonarqube non-bloquante
Build and deploy / test-python (push) Successful in 11m18s
Build and deploy / test-js (push) Successful in 1m16s
Build and deploy / lint-python (push) Successful in 4m24s
Build and deploy / lint-js (push) Successful in 2m51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Successful in 5m30s
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> |
||
|
|
66d8eaeae8 |
Lots 1-3 modernisation JS (S8786/S2703/S2486) + retrait Sonar CI/prod
- 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> |
||
|
|
55e81c0fbb |
Ajoute CODE_QUALITY.md et corrige S7632 a la racine (helper assert_not_none)
- 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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
7db4803b93 |
Ajoute la fonctionnalite quiz autonome/plein ecran a la boite a quiz
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> |
||
|
|
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
- 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> |
||
|
|
b07b231a61 |
Ajoute l'assistant IA "Ruby" (Claude + Scenario) et corrige plusieurs bugs de la scène
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> |
||
|
|
ab6635eee0 |
Corrige l'action "Modifier une variable" sans effet en aperçu créateur
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> |
||
|
|
d664ed5637 |
Remplace le système de quêtes par des déclencheurs, ajoute l'action variable et le chaînage
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>
|
||
|
|
c504ace167 |
Enrichit le reporting SCORM/xAPI et met en conformité RGAA le player
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. |
||
|
|
f7a50e5afe |
Ajoute un suivi xAPI optionnel (bolt-on) au paquet SCORM 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. |
||
|
|
7ebc9b143f |
Corrige une faille d'isolation entre comptes et retire l'email des chemins de projet
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"). |
||
|
|
50835a18e2 |
Supprime le type d'écran "document" et recentre le produit sur le jeu 2D
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. |
||
|
|
00f7191a8b |
Corrige les animations figées et le reporting SCORM dans le paquet exporté
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> |
||
|
|
413685e871 |
Relie le score/statut des quêtes aux indicateurs SCORM
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> |
||
|
|
55c6bb330b |
Contrôle direct de la taille de la caméra (Largeur × Hauteur, toujours modifiable)
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> |
||
|
|
b9d3c46087 |
Bouton "📐 Agrandir la caméra à la zone visible"
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> |
||
|
|
14ec76dddd |
Éditeur : le canevas affiche tout le monde (fond agrandi), pas que la scène
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> |
||
|
|
6b4846a646 |
Fix vrai bug : un objet plus grand que la scène (fond/caméra) restait figé à (0,0)
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> |
||
|
|
7c9b2c9b25 |
Fix "impossible d'aller plus loin vers la droite" — défilement automatique du canevas
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> |
||
|
|
046c38b4dc |
Annule la réservation d'espace des panneaux, corrige le vrai bug (drag invisible sous eux)
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> |
||
|
|
d705f58c4a |
Fix accès au canevas masqué + quiz : header quête, réponse fausse n'avance jamais bloquée, animé
- 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> |
||
|
|
b5546df044 |
Quiz jouable : budget de récompense, boîte à quiz, widget score
- 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> |
||
|
|
e972acf9e6 |
Quêtes : bulle "❓ Question" à choix multiples, à côté de "+ Réplique"
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>
|
||
|
|
f2ed602459 |
Fix boutons Accepter/Refuser absents de l'écran d'offre de quête
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> |
||
|
|
e90d941527 |
Quête "nouvelle" : écran Accepter/Refuser à la fin du dialogue
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> |
||
|
|
df34106752 |
Collision : une carte par objet occupe toute la largeur
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> |
||
|
|
aa17614c7b |
Fix bulle "Appuie sur X" trop haute au-dessus du personnage
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> |
||
|
|
00f9cf33e9 |
Fix bulle "Appuie sur X" invisible + retire Attaque/Événement du picker
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> |
||
|
|
726775fe4e |
Fix "à la collision" — la marge de 3px restait insuffisante
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> |
||
|
|
3a67ba622b |
Fix "à la collision" qui ne se déclenchait jamais
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> |
||
|
|
5e17e42dfa |
Test bout en bout : collision -> quête -> boîte de dialogue
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> |
||
|
|
077af2234d |
Fix "+ Réplique" qui remplaçait la bulle au lieu d'en ajouter une
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> |
||
|
|
521792fe00 |
Dialogues de quête : bulles illimitées, n'importe quel objet, timeline reliée
- "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> |
||
|
|
2347a70c45 |
Quêtes/collision : personnages nommés, dialogues qui parlent, boîte de dialogue en jeu
- "ℹ️ 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> |
||
|
|
390ee831ca |
Quêtes : dialogue dans une modale à part, en plein écran
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>
|
||
|
|
78c1aba8a0 |
Éditeur de quêtes : onglet à côté de Collision, pas une page à part
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> |
||
|
|
51427b5297 |
Éditeur de quêtes : titre/objectif/récompense/statut/résultat + dialogue à 3 colonnes
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> |
||
|
|
4391b36d45 |
Fix miniature de carte "🧩 Collision" échappée du cadre
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> |
||
|
|
b64541856f |
Éditeur de collision : cartes "pièces qui s'emboîtent", animées
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> |
||
|
|
ee270d0286 |
Éditeur de collision (jeu 2D) : règles trigger -> action par objet
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> |
||
|
|
dfaf85fac1 |
Retire la collision sur les images de fond + les blocs de logique/timeline de l'éditeur 2D
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> |
||
|
|
a231bcf2fa |
Aperçu de la boîte de collision dans l'éditeur + blocage physique au jeu
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> |
||
|
|
bc3feb5080 |
Panneau scène simplifié + boîte de collision + rôle joueur/ennemi/pnj
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> |
||
|
|
9199897784 |
Image de fond de scène + caméra qui suit le personnage, fps d'animation cohérent
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> |
||
|
|
5891c82c62 |
Commandes personnage : détection de touche au clavier + touches d'animation supplémentaires
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>
|
||
|
|
74f7ca396f |
Déplacement/animation automatiques d'un personnage (scène 2D)
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> |
||
|
|
e82eb7ce88 |
Retire l'export exécutable (.exe) et la partie publique en ligne (/jouer)
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> |
||
|
|
82d00b3800 |
Export Web/SCORM : inline les icônes en data: URI (CORS Firefox/file://)
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>
|
||
|
|
b60fe63a54 |
Export Web/SCORM : corrige le chemin de l'icône doublé "static/static/"
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>
|
||
|
|
e08c53e042 |
Export Web/SCORM : corrige la résolution des champs "relation" hors ligne
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>
|
||
|
|
7bc4c89eaa |
Export Web/SCORM : corrige les URLs absolues cassées en local (file://)
Bug signalé : icône disparue et texte suivant du dialogue absent après export. render_icone.py (et son miroir JS, forgeRenderIcone) bakaient une URL "/static/icons/..." en dur, correcte en ligne (racine du serveur Flask) mais bloquée par CORS une fois ouverte en local (file:///static/...). Même souci pour les fichiers envoyés par le créateur (/game/<slug>/uploads/..., jamais dans static/, jamais copiés par le paquet). Ajoute _relativize_absolute_urls (réécriture globale post-rendu du HTML) + copie du dossier uploads/, et relativise le port JS de l'icône pour toute régénération après action en mémoire. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
f3b72d73c9 |
Export Web/SCORM : port complet du runtime jouable côté navigateur
Phase 1.2 de la feuille de route produit — un paquet SCORM tourne seul dans le LMS du client, sans serveur Forge Engine disponible. Ajoute static/js/play/offline/ (miroir JS de screens/rendering/ et data_actions/, sous window.FORGE_OFFLINE) pour que variables, score, données d'objet, répéteurs et conditions de visibilité fonctionnent entièrement en mémoire côté navigateur ; branche actions.js et bindings.js dessus au lieu d'un fetch() serveur. Ajoute publish/build_scorm_package.py (paquet statique + imsmanifest.xml SCORM 1.2 + wrapper API SCORM) et le bouton "Exporter (Web/SCORM)". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
9776fbc057 |
Score/Progression (Phase 1.1 de la feuille de route produit)
Concept de premier ordre, distinct du système de variables globales — préalable identifié aux futurs exports SCORM/xAPI (note de cadrage .claude/Forge_Engine_Cadrage.pdf) : ces standards ont besoin d'un signal "score"/"terminé" propre, pas d'une convention sur une variable choisie par le créateur. - db/scoring/ (même patron que db/global_vars/) : table _scoring, un score numérique + un statut (non_commence/en_cours/termine/reussi/ echoue) par joueur (voir db.PLAYER_SHARED pour l'aperçu créateur). - Deux nouvelles actions de flux, dans les deux éditeurs (document et scène) : "Modifier le score" (réutilise le vocabulaire d'opérations déjà là pour "Modifier une variable" — incrémenter/définir/etc., aucune nouvelle colonne de nœud) et "Définir le statut de la partie". - Routes créateur (routes/flow/flow_node_run_score.py, flow_node_run_status.py) + miroirs publics par joueur (routes/public_play/) — même principe que flow_node_run_variable.py. - GET /game/<slug>/scoring/<player_id> : lecture interne, pas exposée au joueur, préparée pour être consommée par le futur export Web/SCORM. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
76a50fa87a |
Onboarding guidé systématique + tableau de bord simplifié
Comportement SYSTÉMATIQUE à chaque création de jeu (admin compris), pas une formalité réservée à l'inscription : /onboarding (routes/onboarding/) devient le point d'entrée unique — 4 cartes retournables (survol = explication au dos), défilement horizontal animé vers le nom du jeu. Un admin y repasse à volonté (pas de project_slug dédié, jamais bloqué/ redirigé vers un projet précédent) ; un compte "user" n'en a plus qu'un créé d'office (routes/auth/register_2fa.py), guidé ici à la place. - db/games/game_type_catalog.py : catalogue des 4 types (Quiz/ Embranchement-escape game/RPG/Créer mon jeu de A à Z), _meta['onboarding_type'] décide du "kind" du premier écran créé et si le tableau de bord complet reste accessible. - routes/games/game_dashboard.py, templates/game_dashboard_simple.html : un type restreint (quiz/embranchement/rpg) voit désormais SON tableau de bord (même route que "custom"), rendu en version simplifiée — juste ses écrans en cartes avec un aperçu RÉEL du contenu (scène mise à l'échelle par container query CSS, adaptée à la largeur réelle de la carte). "+ Ajouter un écran" n'y propose pas de choix de type : imposé par le projet (routes/screens/screens_new.py), verrouillé aussi côté serveur. - core/auth_guard.py : plus de blocage de game_dashboard par type — la restriction se fait au rendu, pas à l'accès à la route. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
0f23024882 |
Structure de dossiers par utilisateur : projects/<propriétaire>/<projet>/
Remplace le rangement plat projects/<slug>/ par une vraie arborescence par compte créateur, sans toucher aux routes existantes (aucune ne déclare de "/" dans son <slug> : le chemin composé "propriétaire_projet" reste un détail interne à db/, décodé par db/games/project_slug.py, seul fichier à modifier si la convention change un jour). - db/game_dir.py, create_game.py, delete_game.py, list_games.py, move_game.py : résolvent/construisent ce chemin composé. Bénéfice direct : deux comptes différents peuvent chacun avoir un projet nommé pareil sans collision (l'unicité ne se vérifie plus que par dossier propriétaire). move_game.py renomme désormais le dossier PROPRIÉTAIRE entier (changement d'email), prêt pour un futur multi-projet. - scripts/migrate_flat_project_slugs.py : migration des projets déjà créés sous l'ancien rangement plat (simulation par défaut, --apply pour exécuter). - routes/auth/profile.py, tests/conftest.py : suppression de compte/jeu de test via db.delete_game (nettoie aussi le dossier propriétaire devenu vide) plutôt qu'un shutil.rmtree direct du seul dossier projet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
d5a59bdbd8 |
Fusion des deux moteurs : type d'écran par écran, plus par projet
"document" (écrans %) et "jeu_2d" (scène pixels) devient une propriété PAR ÉCRAN (_screens.kind, migration automatique idempotente dans ensure_schema.py, source = l'ancien game_type au niveau projet) plutôt qu'un choix figé pour tout le jeu — un même projet peut désormais mélanger écrans classiques et scènes 2D librement. - routes/screens/screen_edit.py : dispatch vers l'éditeur de scène selon screen["kind"] (l'écran demandé), plus game["game_type"]. - screens/payload/full_game_payload.py, templates/play.html, static/js/play/screens.js : le rendu jouable (payload, markup, bascule du mode plein-écran #playFrame) décide écran par écran, y compris en cours de partie (changer d'écran ne recharge pas la page). - screens/screens_repo/create_screen.py : nouveau paramètre kind. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
f767ed4fab |
Bibliothèque de sprites animaux CraftPix (3/3) : sprites générés
Dernier lot de la fonctionnalité (voir |
||
|
|
727af55a5e |
Mouvement continu, limites de scène et priorité d'animation (jeu 2D)
Répond au manque signalé par l'utilisateur : le déclencheur "clavier"
existant (keydown) ne se déclenche qu'UNE FOIS par appui, insuffisant
pour "maintenir une touche fait avancer/sauter le personnage en continu".
- Deux nouveaux déclencheurs (screens/flow/constants.py,
screens/scenes/flow_palette.py) : "Tant qu'une touche est maintenue"
(se répète ~20 fois/seconde tant que la touche reste enfoncée,
runScreenHeldKeyTriggers() dans triggers.js — même patron PAR ÉCRAN que
"minuteur", arrêté au changement d'écran) et "Au relâchement d'une
touche" (un seul déclenchement, scan global comme "clavier"). Combinés
à l'action existante "Modifier un objet de scène → Déplacer de... px
(relatif)", ça permet un vrai déplacement continu.
- preventDefault() sur toute touche que le jeu écoute réellement
(isGameKey(), triggers.js) : Espace/Flèches font défiler la page par
défaut, et Espace réactive en plus le bouton actuellement focus (souvent
le bouton "Jouer" qui garde le focus après l'ouverture de l'aperçu) —
ça pouvait donner l'impression qu'une touche du jeu ne faisait rien.
- Le personnage pouvait sortir du cadre de la scène en se déplaçant :
applyObjectProperty()/clampSceneObjectPosition() (static/js/play/actions.js)
bornent maintenant toute position (absolue ou relative) à
[0, scene_width/height − la taille de l'objet].
- Vitesse d'animation par défaut adaptée au nombre d'images : la valeur
fixe (8 i/s) venait d'un formulaire pensé pour les cycles Kenney (8
images) — un cycle CraftPix (walk=30 images) au même 8 i/s prenait
~4 secondes, "très lent". Le choix d'une animation dans la galerie
calcule maintenant une vitesse par défaut proportionnelle à son nombre
d'images (flow-editor.js, animation-timeline.js).
- Priorité d'animation (bug : "je ne peux pas me déplacer et sauter") :
un déclencheur de déplacement (touche maintenue) redemande "marche" à
chaque tick, écrasant aussitôt une animation ponctuelle ("sauter")
démarrée entre-temps avant qu'elle ait pu s'afficher. runSpriteAnimation()
(actions.js) laisse maintenant une animation NON BOUCLÉE en cours
(même à une seule frame, ex. une pose Kenney figée) aller jusqu'au bout
avant qu'une autre demande puisse l'interrompre.
Nouveaux tests : static/js/play/__tests__/{actions,triggers}.test.js
(idempotence + priorité d'animation, bornage aux limites de la scène,
isGameKey) ; tests/test_scene_edit_view.py (persistance d'un nœud
"touche_maintenue").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
c240181518 |
Fix éditeur de scène 2D : personnage déformé, animations muettes, galerie petite
Trois correctifs remontés en test manuel sur l'éditeur de scène : 1. Personnage écrasé/déformé dans sa boîte de sélection : les frames CraftPix (screens/labels/animal_sprite_library.py) sont de grands canevas très paddés (~788×504 px), très différents des sprites Kenney déjà recadrés serré (~96×128 px) — sans object-fit, l'image remplissait quand même sa boîte (width/height:100%, voir la règle .canvasElementInner > img de scene_edit.html) mais DÉFORMÉE. Nouvelle classe .sceneObjectSprite (object-fit:contain) posée sur le <img> rendu par render_scene_object.py, éditeur ET jeu jouable. 2. Aucun clip de Timeline (sprite, Animate.css ou personnalisé) ne se jouait jamais sur un objet de scène : applyAnimationClip() (static/js/play/screens.js) et animation-timeline.js sélectionnent TOUJOURS leur cible via l'attribut data-anim-target (posé sur .canvasElement/.playElement pour le DOM), jamais data-element-id/ data-object-id — cet attribut n'était simplement jamais posé sur les objets de scène. Ajouté sur le <img> rendu (render_scene_object.py). 3. Boîte par défaut d'un nouvel objet de scène trop petite (64×64 px sur une scène de 960×540) pour bien voir le personnage ou saisir la poignée de redimensionnement confortablement — portée à 128×128 (ensure_scene_schema.py ; n'affecte que les NOUVEAUX objets, les objets déjà posés gardent leur taille actuelle). Au passage : galerie de personnages ~3x plus grande (2 colonnes au lieu de 6 dans .iconGallery.personnageGallery) — les tuiles étaient trop petites pour bien distinguer les personnages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a27d08c6b0 |
Bibliothèque de sprites animaux CraftPix (2/3) : export .zip sélectif
Corrige aussi un import cassé du commit précédent : screens/__init__.py importe déjà screens.rendering.list_used_forge_characters (nécessaire à screens.ADMIN_ONLY_CHARACTER_SLUGS/la galerie filtrée), mais ce fichier n'avait pas été inclus dans ce commit — screens/ ne s'importait plus en l'état. Corrigé ici, dans le même commit que sa seule vraie utilisation. publish/build_package.py copiait jusqu'ici static/characters/ dans SON INTÉGRALITÉ pour CHAQUE jeu exporté (.zip), sans filtrage — avec la bibliothèque d'animaux (~2,1 Go), ça aurait gonflé chaque export du poids de toute la bibliothèque, même un jeu qui n'utilise aucun animal. screens.list_used_forge_characters(slug) (nouveau) parcourt tous les écrans du jeu (écrans-modèles compris, même patron que full_game_payload.py) et renvoie l'ensemble des personnages Forge réellement référencés. _copy_engine_sources()/_copy_characters() copient désormais : le rangement plat Kenney (petit, comme avant) + toujours static/characters/animals/manifest.json (nécessaire à l'IMPORT de screens/labels/animal_sprite_library.py dans le paquet exporté, sinon le jeu exporté ne démarre plus) + UNIQUEMENT les dossiers animal/variante réellement utilisés par CE jeu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
449c36fd5d |
Bibliothèque de sprites animaux CraftPix (1/3) : catalogue, galerie, accès admin
Premier commit d'une fonctionnalité découpée en plusieurs lots (voir le plan "Bibliothèque de sprites animaux CraftPix") : intègre 14 familles d'animaux (15 variantes de couleur chacune) comme personnages Forge sélectionnables, à côté des 6 Kenney existants — réservé au rôle admin, licence CraftPix oblige (interdiction contractuelle de rendre ces sprites utilisables par un compte "user" via l'application). - screens/labels/animal_sprite_library.py (nouveau) : charge un manifest JSON généré une fois (voir scripts/generate_animal_sprite_manifest.py, commit suivant) et construit ADMIN_SPRITE_LIBRARY, dans le même format que l'existant PUBLIC_SPRITE_LIBRARY (screens/labels/sprite_library.py, ex-SPRITE_LIBRARY, renommé pour distinguer les deux). screens.SPRITE_LIBRARY reste le catalogue FUSIONNÉ (utilisé par resolve_personnage_animations pour la résolution runtime, sans filtrage par rôle — voir le constat d'exploration : le payload de jeu et /jouer/<slug> ne vérifient déjà aucun rôle nulle part). - screens/labels/sprite_gallery.py (nouveau) : sprite_gallery_families() groupe la galerie par famille — un animal n'apparaît qu'une fois (sa variante "de base"), ses 15 couleurs se choisissent depuis le panneau de propriétés (render_variant_gallery, templates/screen_edit.html), répondant à la suggestion de l'utilisateur plutôt que d'encombrer la galerie d'ajout de 210 tuiles quasi identiques. - routes/screens/screen_edit.py, routes/scenes/scene_edit_view.py : la galerie passée au template est filtrée par rôle (PUBLIC_SPRITE_LIBRARY pour un compte "user", SPRITE_LIBRARY complet pour un admin) — même idiome que core/auth_guard.py. - core/sprite_gate.py (nouveau) + 4 routes d'écriture (element_add, element_set_personnage_data, scene_object_add, scene_object_personnage_data) : ferme la brèche d'un POST direct qui contournerait la galerie filtrée (403 si un compte non-admin tente d'assigner un personnage animal). - tests/conftest.py : nouvelles fixtures user_client/user_game (compte "user" non-admin avec un projet assigné) pour tester le filtrage par rôle de bout en bout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
43abfc9b93 |
Fix éditeur de scène 2D : décalage visuel des objets + crash Timeline
Deux bugs remontés en test manuel sur l'éditeur de scène (Phase A,
commit
|
||
|
|
f0070faced |
Phase A (2/2) : éditeur de scène 2D — route, template, runtime jouable
Deuxième et dernier commit de la Phase A du plan "Fondations d'une
plateforme multi-éditeurs" (le premier,
|
||
|
|
efa7d1d9b0 |
Fondations multi-éditeurs (1/3) : game_type + schéma/CRUD des objets de scène
Première brique du plan "Fondations d'une plateforme multi-éditeurs" — l'utilisateur veut un éditeur dédié aux jeux 2D/serious games (scène à coordonnées pixel fixes, objets en couches, collision, personnages animés), distinct de l'éditeur générique actuel, sans dupliquer ce qui peut être partagé (comptes, objets de données, moteur de logique de flow, hébergement/publication). - db/games/get_game_type.py (nouveau) : "document" (défaut, éditeur actuel) ou "jeu_2d" (nouveau), stocké dans _meta comme is_public_played — aucune migration pour les jeux déjà créés (retombent sur "document"). Choisi obligatoirement à la création (templates/index.html), jamais modifiable ensuite. - screens/scenes/ (nouveau sous-module) : table _scene_objects (une scène = des objets en pixels fixes, pas les % fluides de _screen_elements — indispensable pour la collision/l'animation), CRUD complet, rendu HTML. kind="personnage" réutilise TELLE QUELLE la structure _personnage_data et les fonctions resolve_personnage_* déjà écrites pour le widget "personnage" de l'éditeur document (Phase 8) — même bibliothèque Forge, même moteur d'animation, juste une autre table de stockage. - screens/flow/ensure_flow_schema.py : += trigger_object_id/ target_object_id (ALTER TABLE sans contrainte FK, même patron que cond_element_a) — le moteur de logique de flow (_flow_nodes/_flow_edges, flow-engine.js) reste EXACTEMENT le même pour les deux éditeurs, seule la palette de nœuds change (screens/scenes/flow_palette.py, nouveau : sous-ensemble direct des triggers/actions existants, déjà génériques). - screens/screens_repo/ensure_schema.py : += scene_width/scene_height sur _screens (taille de scène fixe en pixels, sans effet sur un écran "document"). Reste à faire (prochains commits) : route + template de l'éditeur de scène, puis le rendu en mode jouable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
70c2b8df05 |
Corrige : une animation déjà posée dans la logique ignorait le changement de personnage
Bug réel identifié grâce à la vidéo fournie + inspection directe de la
base de jeu de test : quand un nœud de flow "Jouer une animation"
(ou un clip de Timeline "sprite") était configuré pour un personnage,
ses frames étaient résolues et FIGÉES dans data_value/custom_keyframes
au moment de la configuration — changer ensuite le personnage Forge de
l'élément (galerie des propriétés) n'avait donc aucun effet sur les
animations déjà posées, qui continuaient à jouer indéfiniment les
frames de l'ANCIEN personnage.
Le nœud/clip ne stocke désormais que le NOM de l'animation
({"animation": "walk", "fps": 8, "loop": true}) — ses frames sont
résolues à l'EXÉCUTION, à partir du personnage ACTUELLEMENT assigné à
l'élément cible :
- screens/payload/full_game_payload.py expose un nouveau
gameData.personnage_animations (élément → animations), reconstruit à
chaque chargement de la page de jeu depuis _personnage_data — donc
toujours à jour, y compris après un changement de personnage.
- static/js/play/actions.js (resolveSpriteFrames) et
static/js/play/screens.js (applyAnimationClip) résolvent le nom
d'animation en frames à ce moment précis, plutôt que d'utiliser des
frames figées — repli sur l'ancien format {frames,...} pour les
nœuds/clips déjà créés avant ce correctif.
- Éditeur (flow-editor.js/animation-timeline.js) : simplifié en
conséquence — plus besoin de deviner rétroactivement quelle animation
correspond à une liste de frames stockées (l'ancien hack de
comparaison), le nom est maintenant stocké directement.
Nouveau test de régression (test_swapping_forge_character_updates_
already_configured_flow_action) qui reproduit exactement le scénario
filmé : configure l'action pour "male-adventurer", change le personnage
en "zombie", vérifie que gameData.personnage_animations reflète bien
zombie sans avoir à retoucher le nœud de flow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
5ead091c75 |
Personnages : toutes les animations, tous les personnages ; retrait de l'import de sprites personnalisés
À la demande de l'utilisateur (validation de la refonte visuelle du widget Personnage), deux ajustements : - Bibliothèque de sprites Forge étendue aux 6 personnages du pack Kenney "Toon Characters" (aventurier/aventurière, personnage homme/femme, robot, zombie), avec TOUTES leurs poses (45 par personnage) plutôt que 3 — regroupées en 31 animations nommées par personnage (poses numérotées type walk0..walk7 fusionnées en un seul cycle "walk", les autres restant des poses figées à une image). ~1,2 Mo au total, toujours un sous-ensemble curé du pack source (assets/characters/, non versionné) — HD/Parts/Tilesheet/Vector toujours exclus. - Import de sprites personnalisés retiré pour le moment : plus de tuile "➕ Sprite personnalisé" dans la galerie d'ajout, plus de section d'import dans les propriétés d'un personnage — seuls les personnages Forge restent proposés. La résolution serveur de _personnage_data garde son support générique de la source "custom" (aucune migration requise si cette possibilité revient plus tard), mais plus aucune UI ne permet de la créer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
9e076f8cf9 |
Refonte : vrai widget "Personnage" visuel (remplace l'UI de la Phase 7)
L'utilisateur a testé la Phase 7 (sprites pilotés via un menu déroulant + zone de texte dans la logique de flow) et l'a rejetée à raison : ce n'est pas comme ça qu'un moteur de jeu (Phaser, Unity) gère un personnage. Cette refonte remplace tout le flux d'AUTEURING par un vrai widget visuel — le moteur d'exécution de la Phase 7 (runSpriteAnimation, activeSpriteAnimations, orientation, kind="sprite" de la Timeline) reste inchangé. Nouveau widget "personnage" (screens/widgets/registry.py) : - Rendu serveur dédié (special_render, screens/rendering/render_personnage.py) qui affiche la pose "idle" dès le HTML généré — jamais une image cassée à configurer après coup. - Toutes ses données (source Forge ou sprites propres au créateur, quel personnage/quelles animations) vivent dans une seule clé JSON _personnage_data (screens/rendering/personnage_data.py), sans aucune migration de schéma (même patron que c_clause_list). - Nouvelle échappatoire "custom_panel", symétrique à "special_render" mais pour le panneau de propriétés : ce widget affiche une galerie/un import de sprites sur-mesure plutôt que les contrôles génériques. Galerie visuelle de personnages Forge (VRAIES miniatures, pas un emoji) : - Panneau gauche "🎭 Personnages" : pose un personnage déjà configuré, animé immédiatement. - Panneau droit (propriétés) : change le personnage Forge de l'élément sélectionné, ou importe les animations d'un sprite personnalisé. - static/js/screen_edit/personnage-preview.js : lance l'aperçu animé de CHAQUE personnage du canevas dès le chargement de la page et après toute sauvegarde — le cœur de la demande ("je dois voir ça bouger"). Sélecteur d'animation à miniatures (nœud de flow "Jouer une animation" et clip de Timeline "sprite") : remplace le menu déroulant/la zone de texte par une grille de vraies miniatures, scopée aux SEULES animations du personnage réellement ciblé (ELEMENT_ANIMATIONS_MAP, résolu côté serveur à partir des propriétés de cet élément précis) — jamais un catalogue global. Le format stocké sur le nœud/le clip ({frames, fps, loop}) est inchangé, seule l'UI d'édition change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
d2c2f20b65 |
Phase 7 : personnage animé par sprites (poses/images successives)
Ajoute une nouvelle action de flow "jouer_animation_sprite" qui joue une
séquence de PNG sur un élément image (cycle en boucle type marche, ou
une fois type saut) — réutilise target_element_id (déjà whitelisté) et
data_value en JSON {frames, fps, loop}, exactement le patron déjà établi
par "jouer_son" (Phase 6) et "alea" (Phase 2) : aucune nouvelle colonne,
aucune migration de schéma.
Ajoute une propriété d'élément "orientation" (Modifier un élément) pour
retourner un personnage en miroir (gauche/droite/bascule) sans nécessiter
une 2e feuille de sprites "vue de dos" — même patron que "surbrillance"/
"désactivé".
Étend aussi la Timeline d'animation existante (kind="sprite", aux côtés
de "animate_css"/"custom") pour qu'un personnage puisse animer tout seul
dès l'affichage de l'écran (ex. idle en boucle perpétuelle), pas
seulement en réaction à un événement — réutilise la colonne générique
custom_keyframes (JSON) et le réglage iteration_count déjà là pour la
boucle, aucune migration non plus. Les deux entrées (action de flow et
clip de timeline) partagent le même moteur côté client
(runSpriteAnimation()/activeSpriteAnimations dans static/js/play/
actions.js, indexé par nœud DOM plutôt que par id d'élément pour
supporter plusieurs instances d'un même écran-modèle animées
indépendamment).
Une bibliothèque de sprites Forge (2 personnages Kenney CC0, sous-
ensemble curé idle/marche/saut copié dans static/characters/) est
proposée dans l'éditeur, mais un créateur peut tout aussi bien utiliser
ses propres sprites uploadés (même route d'upload générique que le son
de la Phase 6).
Périmètre volontairement limité à ce qui a été demandé : pas d'avatar
modulable en couches (assemblage cheveux/haut/bas par le joueur),
écarté du plan initial à la demande de l'utilisateur.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
76331f9285 |
Phase 6 : son (musique de fond par écran + action "Jouer un son")
Ajoute deux mécanismes distincts, à la demande de l'utilisateur qui a précisé qu'un son doit pouvoir être attaché à une SCÈNE (pas seulement joué ponctuellement par une action de flow, comme prévu initialement) : - Musique de fond par écran (nouveau champ background_music_url sur _screens, ALTER TABLE nullable) : réglée dans le panneau gauche de l'éditeur (URL ou envoi de fichier, réutilise la route d'upload générique existante), enregistrée en AJAX au même patron que le format d'aperçu (screen_set_aspect.py). Démarrée en boucle à l'affichage de l'écran et arrêtée au changement d'écran (runScreenBackgroundMusic(), appelée depuis showScreen() dans static/js/play/screens.js) — un seul Audio actif à la fois, jamais cumulé avec une musique restée d'un écran précédent. - Action de flow "jouer_son" (aux côtés des actions existantes) : effet sonore ponctuel, non bouclé, déclenchable sur n'importe quel nœud Déclencheur. Réutilise data_value (déjà un champ texte générique sur le nœud action, comme pour "attendre") plutôt qu'une nouvelle colonne dédiée — fire-and-forget côté client (runActionNode), ne bloque jamais la suite du graphe. Les deux réutilisent le même mécanisme d'upload de fichier déjà en place ailleurs dans l'éditeur (ex. source d'une vidéo), sans nouvelle route. Ceci complète les 6 phases du plan d'extension du moteur (état par joueur, hasard/maths, clavier/minuteur, ajout de ligne, position/ collision, son) : Forge Engine peut désormais couvrir des jeux bien au-delà du narratif/puzzle/quiz (action, arcade, jeux à contrainte de temps). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
9e263435e6 |
Phase 5 : position/déplacement d'élément + condition de collision
Ajoute le positionnement absolu (pos_x/pos_y, réutilise left/top en % déjà en place) et relatif (pos_x_relatif/pos_y_relatif, ajoute un delta à la position actuelle plutôt que de l'écraser) comme nouvelles propriétés de l'action "Modifier un élément". Ajoute une nouvelle source de condition "collision" (aux côtés de "objet"/"variable") : deux éléments (cond_element_a/cond_element_b, ALTER TABLE sans contrainte FK, même patron que block_id/ trigger_custom_event_id) dont on compare les rectangles à l'écran via getBoundingClientRect() côté client (elementsOverlap(), dans conditions.js). Pas d'opérateur/valeur à choisir : le chevauchement EST directement le booléen vrai/faux du nœud — le créateur relie le port "Faux" pour "ne se touchent pas", exactement comme pour n'importe quelle autre condition (design plus simple que réinterpréter égal/différent, qui n'a pas de sens pour superieur/inferieur). delete_element.py et flow_nodes_referencing_element.py nettoient désormais aussi les nœuds de collision référençant un élément supprimé (ou l'un de ses descendants), pour rester cohérents avec le nettoyage déjà en place pour trigger_element_id/target_element_id. Combiné à la Phase 3 (minuteur récurrent) et au déplacement au clavier, ça couvre des jeux type casse-briques/Pong/ramasse-objets sans construire un vrai moteur physique (pas de vélocité/accélération/ gravité continues, cadrage volontairement limité). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
92fbfbc9dd |
Phase 4 : ajout dynamique d'une ligne à l'exécution
Nouveau sentinel LAST_INSERTED_ROW_ID = -3 (screens/flow/constants.py), aux côtés de CLICKED_ROW_ID = -1 — même principe : un id de ligne n'existe qu'APRÈS l'insertion, jamais connu à la création du nœud. Permet d'enchaîner un nœud "Ajouter une ligne" (crée une ligne VIDE) puis un ou plusieurs nœuds "Modifier une donnée" déjà existants (ciblant "➕ Dernière ligne ajoutée" dans le sélecteur "Ligne concernée", partagé avec les conditions) pour renseigner ses champs — réutilise 100% du mécanisme actuel, aucun nouveau format de payload multi-champs. screens/data_actions/apply_add_row_action.py : db.get_definition + db.insert_row(slug, definition, {}, player_id) — la ligne appartient au joueur qui agit pour un objet per_player (Phase 1). Nouvelles routes (créateur ET publique, comme prévu dès la Phase 1) : POST /game/<slug>/flow/nodes/<id>/run-add-row et son miroir /jouer/<slug>/.../run-add-row — renvoient {"ok", "row_id"}. routes/flow/flow_node_run_data.py (+ son miroir public) : résout aussi LAST_INSERTED_ROW_ID (en plus de CLICKED_ROW_ID déjà en place) via last_inserted_row_id transmis par le client. static/js/play/actions.js : runActionNode branche "ajouter_ligne" -> fetch la nouvelle route, pose window.lastInsertedRowId, puis refreshRuntimeData() (un Répéteur lié affiche la nouvelle ligne au prochain rendu, confirmé par l'audit préalable — aucun ajustement du mécanisme de rafraîchissement nécessaire). La branche "modifier_donnee" transmet désormais aussi last_inserted_row_id, comme clicked_row_id. templates/screen_edit.html + static/js/screen_edit/flow-editor.js : nouveau type d'action "Ajouter une ligne à un objet" (juste un sélecteur d'objet, aucun champ à remplir — le rappel du fonctionnement enchaîné est affiché directement dans le formulaire) ; le sélecteur "Ligne concernée" (partagé Condition/Modifier une donnée) gagne l'option "➕ Dernière ligne ajoutée" à côté de "🖱️ Ligne cliquée". Vérifié : 259 tests passent (4 nouveaux, dont un bout-en-bout via HTTP qui enchaîne réellement les deux nœuds et vérifie le champ renseigné, et un qui verrouille l'isolation par joueur de la ligne créée), 13 tests node:test toujours au vert, syntaxe JS validée. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
727c3c97ba |
Phase 3 : déclencheur clavier + minuteur récurrent
screens/flow/constants.py : TRIGGER_EVENTS += "clavier" (À l'appui sur une touche) et "minuteur" (Toutes les X millisecondes) — ni élément ni écran précis pour les deux, comme "evenement" déjà en place. FLOW_NODE_FIELDS += trigger_key/trigger_interval_ms. ensure_flow_schema.py : ALTER TABLE pour les 2 colonnes (patron trigger_custom_event_id). templates/screen_edit.html + static/js/screen_edit/flow-editor.js : - "clavier" : un champ "Touche à surveiller" qui capture lui-même la touche pressée (onkeydown sur l'input, captureFlowTriggerKey()) plutôt que de faire deviner la syntaxe attendue (ev.key du navigateur, ex. "ArrowUp", "a", " " pour Espace). - "minuteur" : un simple champ numérique (millisecondes). - nodeLabel() affiche "⌨️ Touche « X »"/"⏱️ Toutes les N ms" sur le nœud. static/js/play/triggers.js (moteur de jeu) : - bindKeyboardTriggers() : UN SEUL window.addEventListener('keydown', ...) posé une fois pour tout le jeu (voir l'amorçage en fin de templates/play.html) — même patron de scan global que dispatchGameEvent() pour "Sur un événement personnalisé". - runScreenTimerTriggers(screenId) : géré PAR ÉCRAN (appelé depuis showScreen(), static/js/play/screens.js) — démarre les setInterval des nœuds "minuteur" de l'écran affiché, arrête d'abord tous ceux de l'affichage précédent (même principe que runAnimationTimeline) pour ne jamais accumuler des minuteurs sur des écrans quittés. Vérifié : 255 tests passent (5 nouveaux, dont un qui verrouille que la touche Espace — très probablement utilisée en jeu — n'est pas filtrée comme une valeur "vide" par add_flow_node.py), 13 tests node:test toujours au vert, syntaxe JS validée sur tous les fichiers de static/js/play/ et static/js/screen_edit/. Comme le reste du graphe de logique côté client, le comportement RÉEL d'un keydown/setInterval n'est pas testable sans navigateur — test manuel recommandé (touche assignée à un saut, minuteur faisant avancer un compteur). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
7476ed229e |
Phase 2 : hasard + opérations mathématiques
screens/labels/data_operations.py : 6 nouvelles opérations pour "Modifier une donnée"/"Modifier une variable" — multiplier, diviser, modulo (garde-fou division par zéro : valeur inchangée plutôt qu'une ZeroDivisionError qui interromprait le graphe), minimum/maximum (borne la valeur ACTUELLE — utile pour une variable globale, qui n'a pas de min_value/max_value comme un champ d'objet), et alea (tire un nombre aléatoire entre deux bornes). screens/data_actions/compute_operation.py (déjà factorisé en Phase 0, donc une seule implémentation pour apply_data_action.py/ apply_variable_action.py) : implémente les 6. "alea" est la seule à deux opérandes — réutilise data_value au format "min,max" plutôt qu'une nouvelle colonne de nœud (bornes remises dans l'ordre si inversées). random.uniform pour un résultat décimal, random.randint pour un entier. static/js/screen_edit/flow-editor.js + templates/screen_edit.html : petit indice visuel — le champ "Valeur / montant" du formulaire de nœud affiche "min,max (ex. 1,6)" quand "alea" est choisi, pour ne pas laisser deviner ce format à deux nombres, différent de toutes les autres opérations. Sinon aucun nouveau champ/changement de schéma nécessaire, le <select> était déjà généré depuis DATA_OPERATIONS. Vérifié : 250 tests passent (19 dans test_compute_operation.py, dont un qui a dû être corrigé — il utilisait "multiplier" comme exemple d'opération INCONNUE, devenu un mauvais exemple maintenant qu'elle existe), 13 tests node:test toujours au vert, syntaxe JS validée. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
e9d12945a9 |
Corrige la perte du compte admin à chaque déploiement : persiste /app/data
L'utilisateur signale devoir recréer un compte admin après chaque
déploiement en prod. Cause : docker-compose.prod.yml ne montait un
volume que pour /app/projects (les jeux) — la base des comptes créateurs
(data/users.db, voir auth/connection.py) et la clé de session Flask
(data/secret_key, voir core/flask_app.py::_load_or_create_secret_key)
vivaient toutes les deux dans /app/data, jamais monté : chaque nouveau
conteneur (à chaque déploiement) repartait d'un /app/data vide, donc
d'une base de comptes vide ("premier compte = admin" recommençait à
zéro) ET d'une nouvelle clé de session (tout le monde déconnecté, en
plus de la perte du compte).
Nouveau volume nommé forge_data:/app/data, à côté de forge_projects.
docker-entrypoint.sh corrige aussi sa propriété (root par défaut à la
création d'un volume nommé, comme pour forge_projects déjà) avant
d'abandonner les privilèges root.
Note : ce correctif ne prend effet qu'au déploiement SUIVANT sur main
(le conteneur actuellement en prod n'a pas ce volume) — un dernier compte
admin à recréer après ce déploiement, plus jamais ensuite.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
cc89b3f7e2 |
Corrige la CI (suite) : neutralise .dockerignore pour le build de test Python
"no tests ran ... file or directory not found: tests/" : .dockerignore (à la racine, pensé pour l'image de PROD buildée par build-and-push) exclut tests/ du contexte de build — COPY . . dans le Dockerfile jetable de test-python ne l'incluait donc jamais, quel que soit le Dockerfile utilisé (.dockerignore s'applique au contexte entier envoyé au démon, pas à un -f en particulier). Renomme .dockerignore avant ce build précis (le checkout de ce job est jetable, propre à lui, jamais repoussé vers le dépôt réel) — test-js n'a pas besoin du même correctif, il ne copie que static/js/play/, jamais exclu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
d474303a55 |
Corrige la CI (suite) : exécute les tests pendant un docker build, pas dans un container de job
"container: image: python:3.13-slim" (tentative précédente) casse
actions/checkout@v4 : c'est une action Node.js, qui a besoin de Node
dans l'environnement d'exécution des steps — "container:" remplace CET
environnement en entier par l'image donnée, qui n'a pas Node
("command not found", nektos/act#107), pas seulement l'environnement
des commandes qu'on y lance soi-même.
Nouvelle approche : le job tourne sur le runner par défaut (checkout
fonctionne normalement, Node y est déjà disponible), et les tests
s'exécutent PENDANT un `docker build` (Dockerfile jetable passé par
stdin, jamais commité, un par langage) plutôt que dans un conteneur
lancé après coup — le transfert du contexte de build vers le démon
Docker passe par le protocole API (tar), jamais par un chemin hôte à
monter, donc insensible au problème Docker-outside-of-Docker qui avait
fait échouer le tout premier essai (docker run -v "$PWD":/app).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
8e8a159e88 |
Corrige la CI : les jobs de test tournent dans "container" au lieu d'un docker run imbriqué
Le job "test" échouait ("Could not open requirements file:
requirements-dev.txt") : docker run -v "$PWD":/app lancé DEPUIS un
runner qui exécute déjà le job dans son propre conteneur (Docker-
outside-of-Docker) ne peut pas monter "$PWD" — ce chemin vit dans le
conteneur du job, pas sur l'hôte où tourne le vrai démon Docker sollicité
par ce docker run imbriqué ; /app se retrouvait donc vide dans le
conteneur imbriqué.
Corrigé en utilisant la clé "container" (standard Gitea/GitHub Actions) :
le job tourne DIRECTEMENT dans l'image voulue, le checkout dépose les
fichiers dans son propre système de fichiers, aucun montage de volume à
faire. Un seul job "test" ne peut avoir qu'UNE image : scindé en
test-python (python:3.13-slim) et test-js (node:20-slim, pour les tests
node:test de static/js/play/__tests__/), build-and-push dépend des deux.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
4585a72588 |
Phase 1 (3/3) : bascule "par joueur" dans le tableau de bord
Dernier morceau du plan d'état par joueur : le créateur choisit, à la création d'une variable globale ou d'un objet, si chaque joueur aura sa propre valeur/ses propres lignes (coché par défaut) ou si elle est explicitement PARTAGÉE par tous les joueurs (ex. un compteur de visiteurs global, un catalogue commun) — voir db/global_vars/create_global_variable.py et db/definitions/create_definition.py (Phase 1, 1er commit). routes/global_vars/create_global_var.py, routes/objects/object_new.py : lisent la case à cocher "per_player" du formulaire (absente => reste per_player=1, comportement par défaut). templates/game_dashboard.html : case à cocher sur les deux panneaux de création + colonne "Par joueur" dans les deux tableaux existants, pour que ce réglage (immuable après création, comme le nom d'une variable) reste visible. db/definitions/list_definitions.py appelait _definitions directement sans jamais migrer son schéma — un tableau de bord ouvert avant la toute première création/modification d'objet aurait affiché "Non — partagé" pour un objet en réalité per_player=1 (colonne absente => Undefined, donc faux en Jinja) : corrigé en appelant ensure_field_bounds_schema() ici aussi, comme le fait déjà create_definition.py/get_definition.py. Vérifié : 239 tests passent (2 nouveaux, dont un qui aurait détecté le bug ci-dessus). Phase 1 (état par joueur) est maintenant complète : couche db/, route publique /jouer/<slug>, et ce réglage créateur. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
3c39f1a249 |
Phase 1 (2/3) : route publique /jouer/<slug> + identité visiteur
Ajoute la vraie route hébergée multi-joueurs qui manquait totalement
(voir le constat d'exploration : /game/<slug>/play est réservé au
créateur connecté, "Publier" ne génère qu'un exécutable mono-joueur) —
un visiteur anonyme peut maintenant jouer un jeu explicitement publié en
ligne, avec sa propre partie (variables/objets per_player posés dans le
commit précédent).
core/player_identity.py : identité visiteur via un cookie NON SIGNÉ
(forge_player_id, secrets.token_urlsafe(16), 1 an) — une simple clé de
partition, jamais un jeton d'autorisation.
routes/public_play/ : 4 routes, miroirs des routes existantes de l'aperçu
créateur mais threadées avec le vrai player_id du cookie au lieu du
sentinel PLAYER_SHARED :
- GET /jouer/<slug> (game_play_public.py)
- GET /jouer/<slug>/runtime-payload
- POST /jouer/<slug>/flow/nodes/<id>/run-data
- POST /jouer/<slug>/flow/nodes/<id>/run-variable
Chacune vérifie elle-même db.is_public_played(slug) (404 sinon) — un jeu
n'est exposé publiquement que si le créateur l'a explicitement basculé
"Publier en ligne" (nouveau db/games/is_public_played.py, réutilise la
table générique _meta, comme game_meta.py pour 'name').
core/auth_guard.py : les 4 endpoints publics ajoutés à _PUBLIC_ENDPOINTS
— la garde générique de connexion les laisse passer sans session, mais
chaque vue vérifie quand même is_public_played elle-même (défense en
profondeur, pas seulement une liste d'exceptions). CSRF (core/csrf_guard.py)
n'a besoin d'AUCUN changement : le jeton est déjà lié à la session Flask,
qui existe pour n'importe quel visiteur (connecté ou non).
templates/play.html : FORGE_PLAY_URLS (posé en Phase -1) ne construit
plus ses URLs via des noms de endpoint fixes (url_for('runtime_payload',
...)) mais reçoit des URLs déjà résolues par la route elle-même
(runtime_payload_url/flow_node_run_data_url/flow_node_run_variable_url)
— nécessaire puisque ce même template sert maintenant DEUX familles de
routes (aperçu créateur ET partie publique), chacune avec ses propres
noms de endpoint. routes/play/game_play.py (aperçu créateur, INCHANGÉ
comportement) et game_play_public.py passent chacun ses propres URLs.
templates/base.html : bascule "🌐 Publier en ligne" dans la barre de
navigation du jeu, à côté de "📦 Publier" (export .zip) — deux
fonctionnalités distinctes. db/games/game_meta.py expose maintenant
is_public_played, disponible partout où `game` est dans le contexte.
Vérifié : 237 tests passent (5 nouveaux dans test_public_play.py, dont un
bout-en-bout via HTTP avec deux VRAIS clients de test anonymes — deux
cookies forge_player_id différents — qui obtiennent des valeurs de
variable indépendantes, et un qui verrouille que l'aperçu créateur reste
inchangé). Syntaxe JS validée sur les deux variantes de play.html rendu
(aperçu créateur et partie publique).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
236d6b4b46 |
Phase 1 (1/3) : état par joueur — couche db/ et chaîne de rendu
Fil directeur du plan : chaque variable globale et chaque objet de données gagne un player_id (sentinelle PLAYER_SHARED='__shared__' par défaut partout — l'aperçu créateur et tous les tests existants continuent de fonctionner À L'IDENTIQUE, aucune signature appelée sans argument explicite ne change de comportement), plus un réglage per_player choisi une fois à la création : - per_player=1 (par défaut) : chaque joueur a sa propre valeur/ses propres lignes. - per_player=0 : valeur/lignes partagées par tous les joueurs (ex. un compteur de visiteurs global, un catalogue commun). db/global_vars/ : _global_variables passe de UNIQUE(name) à UNIQUE(name, player_id) — SQLite ne permet pas de modifier une contrainte UNIQUE via ALTER TABLE, reconstruction de la table détectée et faite une seule fois (ensure_global_vars_schema.py) pour les jeux créés avant cette phase. La ligne "modèle" (player_id=PLAYER_SHARED, créée par le créateur) porte le réglage per_player et sert de valeur PAR DÉFAUT : la première écriture d'un joueur sur une variable per_player crée paresseusement SA propre ligne (copiée depuis le modèle) ; une lecture sans ligne encore écrite retombe sur le modèle (nouveau resolve_player_key.py). list_global_variables() (tableau de bord) ne montre toujours que les lignes modèles ; nouveau list_global_variables_for_player() expose la valeur EFFECTIVE d'un joueur au runtime (full_game_payload.py). db/definitions/ + db/rows/ : chaque table d'objet généré (create_definition.py) gagne une colonne player_id (ADD COLUMN simple, pas de contrainte UNIQUE en jeu ici) ; _definitions gagne per_player. Contrairement aux variables, PAS de repli sur une ligne "modèle" pour les lignes d'un objet per_player — une LISTE n'a pas de valeur par défaut unique à copier comme un scalaire, un nouvel objet per_player démarre VIDE pour chaque joueur (nouveau resolve_row_player_key.py). get_row/update_row/update_row_field/delete_row filtrent aussi par player_id (pas seulement id) : garde-fou contre un row_id d'un AUTRE joueur, nécessaire dès qu'un objet per_player sera exposé sur la future route publique /jouer/<slug>. Migration : nouveau ensure_player_id_column(slug, table_name), appelé avant toute requête sur une table d'objet créée avant cette phase. Chaîne de rendu (screens/elements/list_elements.py -> screens/rendering/render_element_html.py -> render_repeater.py/ render_jauge.py/resolve_bound_row.py/visibility_condition.py/ filter_repeater_rows.py) : player_id transite dans le ctx déjà utilisé partout pour "champ en cours" (ctx["_forge_player_id"], même patron que ctx["_forge_play_mode"], posé une seule fois par list_elements quand enforce_visibility=True) — pas de nouveau paramètre positionnel à threader dans chaque fonction, juste une clé de plus dans un mécanisme déjà en place. Nouveau tests/test_player_state.py : verrouille à la fois le nouveau comportement (deux joueurs => valeurs/lignes indépendantes ; per_player=0 => partagé ; nouveau joueur => valeur par défaut pour une variable, liste VIDE pour un objet ; get_row ne fuite jamais vers un autre joueur) et la non-régression de l'aperçu créateur (comportement historique inchangé). Vérifié : 232 tests passent (9 nouveaux). Reste à faire (prochains commits) : route publique /jouer/<slug>, identité visiteur (cookie), bascule "Publier en ligne" dans le tableau de bord. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
bb84b7b377 |
Phase 0 : factorise compute_new_value + ajoute pytest/node test à la CI
1. Duplication éliminée avant que la Phase 2 (hasard/opérations mathématiques) n'en ajoute 6 de plus aux DEUX fichiers : la chaîne d'opérations quasi identique entre screens/data_actions/ apply_data_action.py (champ d'objet) et apply_variable_action.py (variable globale) est factorisée dans un nouveau compute_operation.py::compute_new_value(operation, current, raw_value, is_decimal), réutilisé par les deux. Nouveau tests/test_compute_operation.py verrouille le comportement des 7 opérations existantes (dont les cas limites : valeur invalide, type décimal vs entier, opération inconnue) avant d'en ajouter d'autres. 2. .gitea/workflows/deploy.yml déployait en prod à chaque push sur main sans jamais exécuter la suite de tests — rien ne bloquait techniquement un commit cassé. Nouveau job "test" (pytest + node:test sur la logique pure de static/js/play/, via des conteneurs officiels plutôt que des actions du marketplace, cohérent avec le choix déjà fait dans ce fichier) tourne sur CHAQUE push (main ET dev, utile pour ce dépôt qui travaille sur dev) ; "build-and-push"/"deploy" gagnent un "needs: test" et restent réservés à main (filtre sur gitea.ref) — un push sur dev ne redéploie jamais la prod, seulement les tests. Vérifié : 223 tests passent (8 nouveaux), YAML validé. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
fe807ba51e |
Phase -1 (suite) : découpe l'éditeur de screen_edit.html en modules JS
Même chantier que le commit précédent (moteur de jeu, play.html) — templates/screen_edit.html était un unique fichier HTML+CSS+JS de 3312 lignes, tout l'éditeur (arborescence, panneaux flottants, canevas, formulaire de propriétés, éditeur de flow à nœuds, blocs de logique, timeline d'animation) vivant dans UN SEUL <script>. Ce fichier est plus imbriqué que play.html : de nombreux appels s'exécutent au niveau racine du script (pas seulement des déclarations de fonctions), et JavaScript hoiste les déclarations `function` sur TOUT le script — un appel au niveau racine peut donc référencer une fonction déclarée PLUS LOIN dans le même fichier. Découper naïvement casserait cet ordre implicite. Un audit dédié (analyse ligne par ligne de chaque appel racine + son graphe d'appel transitif) a identifié 3 références "en avance" réelles, toutes regroupées dans la même zone (initBuilderPanel()/toggleActionFields() → bindAspectButtons/ toggleElementPropertyValue/onDataDefinitionChange) — le découpage respecte cette contrainte : chaque fichier est une TRANCHE SÉQUENTIELLE de l'original (jamais une réorganisation), et cette zone spécifique reste un seul fichier (panel-init.js) pour que le hoisting continue de fonctionner exactement comme avant. 5 fichiers sous static/js/screen_edit/ : - tree-panels.js — arborescence, menu contextuel, panneaux flottants gauche/droite, galerie d'icônes, modale de suppression/choix d'icône, glisser-déposer du canevas, panneau de propriétés (autosave). - panel-init.js — (ré)initialisation du panneau central après chaque changement de sélection, filtres de répéteur/donnée liée, condition de visibilité, champs d'action du formulaire de nœud. - flow-editor.js — éditeur de flow à nœuds (rendu du graphe, formulaire d'ajout de nœud, blocs de logique — currentBlockNodes/Edges). - tabs-and-blocks.js — onglets du centre, panneaux flottants génériques (drag/resize/plein écran), modale d'un bloc de logique. - animation-timeline.js — timeline d'animation (clips Animate.css/ personnalisés). Toutes les données injectées par Jinja (GAME_SLUG, SCREEN_ID, DEFINITIONS_DATA, FLOW_NODES_INITIAL, ELEMENTS_LABELS, CUSTOM_EVENTS_MAP, ANIM_CLIPS...) sont posées UNE FOIS par un petit <script> inline resté dans le template, avant les <script src> — même patron que static/js/play/. Le seul bout de logique resté inline est la toute petite IIFE d'ouverture initiale (?tab=/?block=), qui dépend directement de request.args et doit s'exécuter après que tous les fichiers soient chargés. tests/conftest.py : screen_edit_js_bundle() (même principe que play_js_bundle(), Phase -1 précédente) — 4 tests qui vérifiaient la présence de telle fonction/chaîne dans le HTML de l'éditeur (le JS y était inline) sont mis à jour pour chercher dans ce bundle. Un des deux échecs révélait un test déjà fragile (assert "Ligne cliquée" in html vérifiait en réalité le TEXTE SOURCE d'un <script> inline, jamais du HTML réellement rendu — ce texte ne peut plus s'y trouver une fois la fonction qui le construit dynamiquement déplacée dans un fichier externe) : corrigé pour vérifier le bundle JS + la disponibilité de la route séparément. Vérifié : 215 tests passent, syntaxe JS validée sur les 5 nouveaux fichiers (node --check) et sur les <script> inline restants (rendus via le client de test). Test manuel recommandé (édition complète d'une scène : arborescence, propriétés, glisser-déposer, logique de flow, blocs, timeline) avant de considérer ce découpage définitivement sans risque — comme pour play.html, ce fichier n'a pas de harnais de test DOM automatisé. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
dbada333d5 |
Phase -1 : découpe le moteur de play.html en modules JS + premiers tests JS
templates/play.html était un unique fichier HTML+CSS+JS de 1069 lignes,
tout le moteur de jeu vivant dans UN SEUL <script>, sans aucune
couverture de test sur cette logique (seuls le rendu HTML et la syntaxe
JS étaient vérifiés). La feuille de route à venir (état par joueur,
hasard, clavier/minuteur, position/collision, son — voir le plan) va
justement faire grossir ce moteur : "un fichier = une fonction, un
dossier = une responsabilité" s'applique aussi au JS, pas seulement au
Python — le moment de découper est avant d'ajouter encore plus de code,
pas après.
Découpage en 6 fichiers sous static/js/play/, calqués sur les sections
déjà présentes dans le code (aucune réorganisation de logique, une pure
extraction) : screens.js (affichage d'écran, timeline d'animation),
conditions.js (évaluation des conditions — la partie 100% PURE, sans
DOM, la plus testable), actions.js (exécution des actions), triggers.js
(recherche des nœuds déclencheurs, attache des écouteurs), bindings.js
(résolution des {{champ}}, rafraîchissement des données), flow-engine.js
(parcours du graphe, événements personnalisés).
Zéro nouvel outillage : plusieurs <script src> dans l'ordre, partageant
le même espace global qu'avant (aucun bundler, aucune étape de build).
Les 2 URLs de route dont ces fichiers ont besoin (flow_node_run_data/
run_variable, runtime_payload) ne peuvent plus être injectées par Jinja
directement dans le code (un fichier statique n'est jamais passé par le
moteur de templates) — elles sont maintenant posées une fois dans
window.FORGE_PLAY_URLS par le petit <script> inline restant dans
play.html, qui ne porte plus que les données Jinja (gameData) et
l'amorçage (bindClicks() etc. au chargement).
publish/build_package.py : ajoute static/js/play à la liste des fichiers
copiés dans l'exécutable exporté (le mode jouable en dépend désormais).
Premiers tests JS (static/js/play/__tests__/conditions.test.js, lancés
via `node --test`, zéro nouvelle dépendance npm — decision prise avec
l'utilisateur de commencer par la logique PURE seulement, pas par une
couverture DOM via jsdom) : compareValues, resolveVariablePath,
evaluateConditionClause/Node, exactement la logique que les phases à
venir (opérations mathématiques, condition de collision) vont étendre.
tests/conftest.py : nouveau helper play_js_bundle() (concatène tout
static/js/play/*.js) — 13 tests existants qui vérifiaient la présence de
telle fonction/chaîne dans le HTML de /game/<slug>/play (tout le JS y
était inline avant ce découpage) sont mis à jour pour chercher dans ce
bundle à la place ; les tests qui vérifient un CSS/HTML réellement resté
dans play.html (forgeHighlight, forgeDisabled, #playFrame...) continuent
de chercher dans le HTML.
Vérifié : 215 tests pytest passent (aucune régression comportementale,
juste une réorganisation), 13 tests node:test passent, node --check sur
chacun des 6 nouveaux fichiers. Test manuel recommandé (jeu joué de bout
en bout : navigation, clic, survol, répéteur, condition, animation)
avant de considérer le découpage définitivement sans risque.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
a445b72a6e |
Corrige "Ouvrir"/"Modifier" inertes dans l'onglet Blocs de logique
Deux bugs distincts, signalés par capture d'écran (bouton "Ouvrir" sans
effet, ligne d'édition affichée en permanence au lieu d'être cachée) :
1. onclick="openLogicBlockPanel({{ b.id }}, {{ b.name|tojson }})" cassait
l'attribut HTML : tojson produit des guillemets DOUBLES (valides en
JSON), qui terminaient prématurément l'attribut onclick="..." lui-même
entre guillemets doubles — le gestionnaire de clic généré était donc
tronqué et invalide, provoquant une erreur JS non interceptée qui
arrêtait aussi tout le script restant dans la même balise <script>
(dont l'IIFE qui devait poser window.openLogicBlockPanel). Corrigé en
ne passant que l'id dans l'attribut et en retrouvant le nom du bloc
côté client depuis FLOW_BLOCKS (déjà chargé) — plus aucune chaîne
utilisateur à échapper dans un attribut HTML.
2. <tr class="hidden" id="blockEditRow..."> ne se cachait jamais : le
CSS ne définissait .hidden que scopé (.floatPanel.hidden,
.columnFilterMenu.hidden), jamais en règle générique — ajoutée dans
styles/forge-custom.css.
Vérifié : 215 tests passent, syntaxe JS validée sur un scénario avec un
vrai bloc existant (reproduisant exactement la situation signalée),
onclick généré inspecté directement dans le HTML rendu.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
1b7706b357 |
Ajoute les blocs de logique : organise le graphe de flow en sous-graphes nommés
Le graphe de logique d'une scène s'affichait jusqu'ici sur un seul canevas plat (toutes les scènes accumulant leurs nœuds sur la même grille), ce qui ne tient pas à l'échelle dès qu'une scène évolue au fil de l'avancée du joueur et accumule des centaines/milliers de nœuds. Ajoute les "Blocs de logique" : un bloc regroupe un sous-ensemble de nœuds/arêtes d'un écran sous un nom et une description (comme une fonction). L'onglet "Logique de la scène" devient une liste de blocs (nom, description tronquée à 3 phrases, éléments concernés, nombre de nœuds, bouton "Ouvrir"). Ouvrir un bloc affiche SON graphe dans une modale plein écran, redimensionnable et déplaçable (patron déjà mûr dans game_dashboard.html, porté tel quel : makeFloatPanelDraggable/ Resizable/Fullscreenable). Décision d'architecture : un bloc est un automate FERMÉ — impossible de relier un nœud d'un bloc à un nœud d'un autre bloc (rejeté côté serveur dans flow_edge_add.py). Toute communication entre deux blocs passe par le système d'événements personnalisés déjà en place (declencher_evenement / trigger_event="evenement"). Détails techniques : - Nouvelle colonne _flow_nodes.block_id (nullable, sans FK — même rationale que trigger_element_id/target_element_id, voir screens/elements/delete_element.py) et nouvelle table _flow_blocks (screens/flow/ensure_flow_schema.py, screens/flow/blocks/ensure_flow_blocks_schema.py). - Migration douce et automatique : les nœuds posés avant l'existence des blocs (block_id NULL) sont rattachés, à la première ouverture de l'onglet, à un "Bloc principal" auto-créé (screens/flow/blocks/ list_flow_blocks.py) — aucun script de migration séparé, aucune donnée perdue. - Suppression d'un bloc = cascade complète (bloc + tous ses nœuds/ arêtes), patron identique à screens/custom_events/delete_custom_event.py mais scopé à un seul bloc plutôt que game-wide. - Routes CRUD sous routes/flow_blocks/, montées comme routes/custom_events/. - templates/screen_edit.html : FLOW (global unique) renommé en ALL_FLOW (toutes les données de l'écran) ; un seul bloc ouvert à la fois (modale unique, à la Unity) — currentBlockNodes()/currentBlockEdges() filtrent ALL_FLOW par CURRENT_BLOCK_ID à chaque rendu, sans tenir de seconde copie à synchroniser manuellement. Vérifié : 215 tests passent (7 nouveaux dans tests/test_flow_blocks.py, dont un qui verrouille l'ordre d'appel list_flow_blocks()/ list_flow_nodes() dans screen_edit.py — la migration douce doit tourner AVANT le chargement des nœuds, sinon le compte de nœuds affiché juste après une migration est périmé), syntaxe JS validée (script de screen_edit.html rendu via le client de test puis node --check). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a7a315cce7 |
Corrige un bug moteur : plusieurs déclencheurs "Au clic" (ou survol) sur le même élément n'exécutaient que le premier
Diagnostic effectué directement sur projects/test/game.db de
l'utilisateur (suite à son signalement "je ne parviens pas à mettre fin
à la surbrillance") : deux nœuds Déclencheur distincts ("Au clic",
id=4 et id=42) référençaient le même trigger_element_id=65. La fonction
findTriggerNode() utilisait Array.find(), qui ne retourne que la
PREMIÈRE correspondance — le second nœud (celui qui devait couper la
surbrillance) n'était donc jamais exécuté, silencieusement, quel que
soit le graphe construit dans l'éditeur.
Ce n'est pas un bug lié aux événements personnalisés ni aux conditions
par variable (deux pistes explorées avant ce diagnostic) : c'est une
limitation générale du moteur, qui n'a jamais géré plus d'un déclencheur
"Au clic"/"Au survol"/"À la fin du survol" par élément.
Renomme findTriggerNode() en findTriggerNodes() (pluriel) : retourne
désormais TOUS les nœuds correspondants (élément + type d'événement),
et bindClicks()/bindHoverTriggers() exécutent chaque flow trouvé au lieu
de s'arrêter au premier.
Vérifié : 208 tests passent, syntaxe JS validée (script de play.html
rendu via le client de test puis node --check). Comme pour le reste du
graphe de logique côté client, ce changement n'est pas couvert par les
tests automatisés (pas de harnais navigateur/DOM) — vérification
manuelle recommandée sur le scénario réel de l'utilisateur.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
2c59e54556 |
Simplifie les événements : notification pure, sans paramètre
Retour de l'utilisateur sur le premier jet : "Déclencher un événement" ne doit JAMAIS faire choisir un élément — c'est une notification pure, rien de plus. C'est à l'ÉCOUTEUR (déclencheur "Sur un événement personnalisé" → condition → action) de décider quoi faire ensuite, avec ses réglages habituels (cible fixe, "Ligne cliquée"...), jamais à l'événement de transporter un paramètre. Retire donc tout le mécanisme de transmission ajouté au tour précédent (has_element_param, target_element_from_event, EVENT_ROW_ID, window.lastEventParams) : - db/custom_events/ : _custom_events perd sa colonne has_element_param — un événement n'est plus qu'un nom + une description. - screens/flow/ : retire target_element_from_event (colonne ajoutée par ALTER TABLE, laissée inerte sur les bases déjà migrées — sans conséquence, plus jamais lue ni écrite) et la constante EVENT_ROW_ID. - routes/flow/flow_node_run_data.py : retire la résolution EVENT_ROW_ID, revient à sa forme d'origine (seul CLICKED_ROW_ID reste géré). - templates/screen_edit.html : le nœud Action "Déclencher un événement" n'a plus qu'un sélecteur d'événement — plus de champs élément/ligne. Le nœud Action "Modifier un élément" perd la case "Utiliser l'élément transmis par l'événement en cours". L'onglet Événements perd la case à cocher "Paramètre" (création et édition). - templates/play.html : window.dispatchGameEvent(eventId) ne prend plus que l'id de l'événement — scan global inchangé, mais ne pose plus aucun window.lastEventParams. modifier_element et readFieldValue reviennent à leur résolution d'origine (plus de branche event-aware). 208 tests au total (2 tests retirés, devenus sans objet : la persistance de target_element_from_event et la résolution serveur d'EVENT_ROW_ID). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
666aa892e0 |
Corrige le ciblage élément+ligne transmis par un événement dans un Répéteur
Signalé par l'utilisateur : quand "Modifier un élément" utilise la case
"Utiliser l'élément transmis par l'événement en cours"
(target_element_from_event), et que cet élément vit à l'intérieur d'un
Répéteur, la résolution ne ciblait jamais que le PREMIER élément
correspondant trouvé dans toute la page — jamais forcément la bonne
ligne.
Vérifié directement sur un rendu réel : chaque ligne d'un Répéteur
rejoue le MÊME modèle (voir render_repeater.py), donc le MÊME
data-element-id se répète à l'identique sur CHAQUE .repeaterItem — seul
data-row-id (posé sur l'enveloppe .repeaterItem) distingue réellement
une ligne d'une autre. Un simple
document.querySelector('[data-element-id]') global tombe donc toujours
sur la première ligne rencontrée dans le DOM, sans rapport avec la ligne
réellement transmise par l'événement (window.lastEventParams.row_id).
Corrigé : quand l'événement transmet aussi une ligne, la recherche est
désormais scopée à l'intérieur du .repeaterItem[data-row-id=...]
correspondant avant d'y chercher l'élément — sinon (élément fixe, ou
événement sans paramètre de ligne), le comportement global d'avant reste
inchangé.
210 tests toujours verts (ce correctif est purement côté client, jamais
couvert par les tests automatisés — vérifié manuellement via un rendu
réel confirmant la structure .repeaterItem/data-row-id décrite
ci-dessus).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
f436190f90 |
Ajoute les événements personnalisés (éditeur + exécution)
Deuxième moitié de la fonctionnalité "événements" (voir le commit précédent pour le backend) : un nouvel onglet "📣 Événements" dans screen_edit.html (scène ET modèle, même route) pour créer/modifier/ supprimer un événement (nom, description, "a un paramètre élément" oui/ non) et voir où il est déjà écouté/déclenché ; deux nouveaux types de nœud dans le graphe de logique ; exécution réelle côté client (play.html). - templates/screen_edit.html : - Onglet Événements : barre de création + tableau (patron form/name="..." + attribut `form="eventEditFormN"` déjà utilisé par l'onglet Variables du tableau de bord, pour éditer plusieurs champs d'une ligne sans emboîter un <form> dans un <tr>). Colonne "Utilisé par" avec liens directs vers les écrans/modèles concernés (list_custom_event_usages, déjà en place côté backend). - Nœud Déclencheur "Sur un événement personnalisé" : un simple sélecteur d'événement (trigger_custom_event_id) — ni élément ni écran, retrouvé par un scan global côté client (comme "À l'affichage de l'écran"). - Nœud Action "Déclencher un événement" : sélecteur d'événement (target_custom_event_id), avec élément/ligne concernés affichés seulement si l'événement a has_element_param (réutilise le sélecteur d'élément existant + "Ligne cliquée" pour la ligne, pas de définition d'objet ici donc pas de liste de lignes fixes possible). - Nœud Action "Modifier un élément" : nouvelle case "Utiliser l'élément transmis par l'événement en cours" (target_element_from_event) — v1, seule cette action l'expose (extensible plus tard sans nouveau changement de schéma). - nodeLabel()/submitNodeForm()/toggleFlowTriggerFields()/ toggleFlowActionFields() étendus en conséquence, CUSTOM_EVENTS_MAP (id -> nom/has_element_param) exposé côté JS pour le rendu des libellés et l'affichage conditionnel des champs paramètre. - templates/play.html : - window.dispatchGameEvent(eventId, elementId, rowId) : scan de gameData.flows (toutes scènes ET modèles à la fois, même principe que findTriggerNode()/runScreenShowTriggers()) pour trouver chaque écouteur, pose window.lastEventParams puis exécute son graphe (runFlowFrom) — au même titre que window.lastClickedRowId pour "Ligne cliquée". - runActionNode : nouvelle branche "declencher_evenement" (résout CLICKED_ROW_ID pour la ligne transmise, comme "Modifier une donnée") ; branche "modifier_element" étendue pour résoudre dynamiquement l'élément depuis window.lastEventParams quand target_element_from_event est actif. - readFieldValue (évaluation de condition) et l'appel serveur de "Modifier une donnée" (routes/flow/flow_node_run_data.py) résolvent désormais aussi EVENT_ROW_ID (-2), au même endroit que CLICKED_ROW_ID (-1) déjà en place. - routes/screens/screen_edit.py : passe custom_events/ custom_event_usages/custom_events_map_json au template (même patron que global_variables déjà threadé pour le sélecteur de variable dans le formulaire de Condition). Nouveaux tests dans tests/test_custom_events.py : forme exacte du payload runtime exposé au JS (types entiers, pas des chaînes — une comparaison stricte "===" échouerait silencieusement sinon), target_element_from_event bien persisté, résolution serveur d' EVENT_ROW_ID depuis le corps de la requête. 210 tests au total. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |