Document de travail pour le futur type de hotspot selector (menu de choix, sous-menus, options conditionnelles, SFX par choix).
Rien de ce qui suit n’est encore implémenté dans le code : c’est le cahier des charges pour le refactor prévu.
Pour le contexte général du projet, voir ARCHITECTURE.md.
msg, scene, pick, req, pwd, ou un sous-selector (imbrication).executeAction(payload, …) appelé depuis un hotspot “classique” ou depuis un choix de selector.Rétrocompatibilité JSON : non requise pour ce projet au moment du refactor (usage principal = auteur seul, anciens fichiers surtout POC). Un schéma JSON v2 peut donc casser l’ancien format si cela simplifie le code ; documenter une procédure de migration manuelle ou un script reste utile pour qui aurait gardé d’anciens .json.
| Couche | Rôle |
|---|---|
hotspotDispatcher(hsDiv, args) |
Point d’entrée Pannellum. Si args.type === 'selector' → ouvrir l’UI selector ; sinon → executeAction(...). |
executeAction(payload, hsDiv, uiContext) |
Exécute une action unique (message, changement de scène, ramassage, requis, passcode, etc.). |
openSelector(selectorArgs, uiContext) |
Affiche une seule surface modale ; le contenu (titre, intro, liste de choix) est remplacé quand on descend dans un sous-menu ou qu’on revient en arrière. Pas d’empilement de plusieurs popups les unes sur les autres — uniquement une pile logique (historique) pour le bouton Retour. |
evaluateChoiceVisibility(choice, gameState) |
(Optionnel v1) Décide si un choix est visible / activé selon l’inventaire ou d’autres états. |
Contexte partagé : inventaire, scène courante, états de déverrouillage, etc. Les sous-selectors héritent du même état global (pas de “sandbox” séparé sauf besoin futur explicite).
Anti double-clic : tant qu’un selector (ou une popup modale dérivée) est ouvert, les interactions sur le panorama / autres hotspots sont bloquées (overlay plein écran ou équivalent), pour éviter les clics en cascade.
selector (schéma cible)Champs communs avec les autres types : pitch, yaw, cssClass, type: "selector".
Payload minimal :
{
"type": "selector",
"id": "hs_uid_…",
"title": "Terminal",
"introHtml": "<p>Écran d’accueil…</p>",
"choices": [
{
"id": "choice_1",
"label": "Lire les mails",
"actionType": "selector",
"nested": {
"title": "Boîte mail",
"introHtml": "",
"choices": [
{ "id": "m1", "label": "Mail 1", "actionType": "msg", "txt": "…" },
{ "id": "m2", "label": "Mail 2", "actionType": "msg", "txt": "…" }
]
}
},
{
"id": "choice_2",
"label": "Désactiver l’alarme",
"actionType": "pwd",
"enigmeTxt": "…",
"pwd": "1234",
"onSuccess": { "actionType": "scene", "target": "couloir" }
}
]
}
nested ou actionType: "selector" + sous-objet : à trancher à l’implémentation (une seule convention pour éviter la duplication).introHtml : optionnel ; texte descriptif au-dessus des choix (style “livre dont vous êtes le héros”).choice (tous optionnels sauf label + branche action)| Champ | Usage |
|---|---|
label |
Texte du bouton ou entrée de liste. |
actionType |
msg | scene | pick | req | pwd | selector |
requiresItem |
(Optionnel) ID d’objet : le choix n’apparaît que si l’inventaire contient cet ID. |
hiddenIfHasItem |
(Optionnel) ID d’objet : le choix est masqué si le joueur possède cet objet. |
sfxUrl |
URL du son au clic sur ce choix (optionnel). |
sfxVolume |
0–1, relatif au canal SFX du joueur (sfxVol × masterVol). |
displayMode |
Sur un niveau (nested ou racine via l’éditeur) : buttons (défaut) ou dropdown. |
Les autres champs reprennent la même sémantique que les champs actuels des hotspots (ex. txt, target, itemId, ko, transTxt, etc.) pour que executeAction reste unique.
Conceptuellement :
type: "msg" + texte ≈ selector avec un seul choice { actionType: "msg", … }.En implémentation v1 on peut garder les types existants dans l’éditeur et ne pas forcer la migration visuelle ; la factorisation interne se fait côté runtime (executeAction).
choices[]).actionType: "msg", le texte s’affiche dans la même modale (vue « Message » avec zone défilante pour les longs textes), bouton ← Retour au menu pour revenir au niveau de choix sans fermer le selector — pas de seconde popup par-dessus.msg hors selector) restent sur afficherPopup (comportement inchangé).executeAction). Pour un pick, le hotspot selector n’est pas masqué (contrairement au pick « classique ») : la même zone doit pouvoir rouvrir le menu pour d’autres choix ou indices.inventaire mais l’UI reste masquée).isHidden (ou équivalent) pour qu’un objet ramassé soit compté dans la logique mais non listé dans l’inventaire visible (cas narratifs / quêtes cachées).Les deux sont compatibles avec le même schéma si evaluateChoiceVisibility est bien défini.
.hs-type : selector.actionType (comme aujourd’hui updateHsFields, mais imbriqué).extractHotspotData, saveProject, loadProject, generateGame : sérialiser / désérialiser l’arbre choices (et IDs stables pour les tests)..json si le schéma change (voir §1). Si besoin : export manuel, ou script de migration, ou ressaisie dans l’éditeur.index.html téléchargés) restent des fichiers figés ; seuls les nouveaux jeux générés après le refactor suivront le nouveau moteur.executeAction depuis la logique actuelle de hotspotDispatcher + executeReward ; valider avec des scénarios de test (anciens POC optionnels).js/editeur-generate.js / js/editor-en-generate.js).openSelector minimal (liste de boutons, un niveau, pas d’imbrication).#selector-overlay), une modale, boutons ; choiceToPayload → executeAction pour msg / scene / pick.choices simples.selector + titre / intro / textarea JSON des choix (édition avancée) ; à remplacer plus tard par un formulaire guidé (ajout/suppression de lignes, etc.).requiresItem, hiddenIfHasItem), liste déroulante (displayMode), SFX (sfxUrl, sfxVolume + audioSys.playSFX)player.js / modules) une fois le comportement stable.