"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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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>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>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>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>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>