Aller au contenu
quest'n'click

CréerGuidePour auteursS'applique de 4.1.2

La fin scellée

This page in EnglishCette page sur le wiki GitHub

La fin scellée est un module facultatif : les derniers textes de votre jeu (un ticket à gratter, une annonce, une carte finale avec photos) sont chiffrés dans un seul fichier avec un mot de passe, et ne sont donc jamais dans le code du jeu. Un joueur qui ouvre les fichiers du jeu construit n'y lit rien ; la fin ne se montre que quand le jeu l'atteint. Cette page la met en place, scelle une issue et vérifie que rien n'a fuité.

Ce que vous obtenez

Un fichier public/data/dossier.bin qui contient une issue choisie, chiffrée ; un jeu dont le module ending joue le ticket à gratter, les confettis et le jingle, puis la carte finale ; et un build dans lequel npm run check:spoilers ne trouve aucun texte de fin en clair.

Contexte et versions

  • Le dépôt du moteur : npm run seal et npm run check:spoilers sont des scripts du dépôt du moteur, pour le jeu sélectionné (GAME=<id> ou npm run game).
  • Le champ ending et sa lecture existent dans tout jeu. Depuis 4.1.2 (npm run seal n'affiche plus le mot de passe). Le type est EndingDef depuis 4.0 ; RevealDef est son ancien nom, déprécié, retiré en 5.0.

Prérequis

  • Le dépôt du moteur, installé (Installer depuis le dépôt).
  • Un script qui termine le jeu avec la commande de fin (plus bas) et, si le joueur fait un pronostic, un drapeau que le script pose avec sa réponse.

Où lancer les commandes

Dans le dépôt du moteur, à sa racine, votre jeu sélectionné. La configuration privée vit dans games/<GAME>/private/, que .gitignore tient hors du dépôt.

Le module ending

GameDef.ending le décrit :

// games/<GAME>/game.ts
ending: {
  file: 'data/dossier.bin',                         // produit par npm run seal
  password: { given: '…' },                         // ou { typed: true, prompt: 'Mot de passe ?' }
  guess: { flag: 'pronostic', labels: { a: '…', b: '…' }, right: '… {guess} …', wrong: '…', none: '…' },   // pronostic jugé sur la carte
  scratch: { ticket: 'items/r4c1', sfx: 'grattage' },   // paramètres du mini-jeu scratch
  card: { accent: '#d4145a' },                       // couleur du cadre de la carte et du texte gratté
},
ChampSens
filele fichier scellé, chargé sous le chemin de base du jeu. npm run seal écrit public/data/dossier.bin : écrivez donc data/dossier.bin
password{ typed: true, prompt } : le joueur le tape, il n'est nulle part dans le bundle. { given } : le moteur l'a, il est donc dans le bundle : une obfuscation, pas un secret
guessfacultatif : le pronostic du joueur, demandé au début (un drapeau que pose un script) et jugé sur la carte finale. labels donne le mot de chaque valeur du drapeau ; {guess} est remplacé dans right, wrong et none
scratchles paramètres du mini-jeu de grattage : ticket (l'image) obligatoire, sfx, color… ; le texte caché vient du fichier scellé
cardaccent : la couleur du cadre et du texte gratté (#d4145a par défaut)

La commande { ending: true, after: […] } joue, dans l'ordre : le ticket à gratter (le texte vient du fichier chiffré), les confettis (skin.icons.confetti, skin.sounds.confetti) et le jingle (skin.sounds.jingle), puis after, puis la carte finale (musique skin.sounds.end). Elle termine le jeu comme end (state.done, l'événement endingReached, une sauvegarde). { reveal: true } est l'ancien nom. Juste avant, le scénario joue ce qu'il veut (la famille autour du canapé, le cadenas qui fond…).

Les textes de la fin ne sont jamais dans le contenu du jeu : ils sont dans games/<GAME>/private/ending.config.ts. Si le fichier ne peut être lu ou déchiffré, le jeu affiche « … » à la place de la fin.

Étapes

  1. Écrire la configuration privée à partir de l'exemple versionné, puis remplir les textes :

    # dans le dépôt du moteur
    mkdir -p games/<GAME>/private && cp games/<GAME>/ending.config.example.ts games/<GAME>/private/ending.config.ts

    Corrigez le chemin de l'import dans la copie : l'exemple importe ../../scripts/seal-types ; depuis private/, c'est ../../../scripts/seal-types. Son password doit être identique à ending.password dans games/<GAME>/game.ts (ou à ce que tapera le joueur). L'exemple du jeu de démo est games/demo/ending.config.example.ts.

  1. Sceller une issue. Seule l'issue choisie est chiffrée :

    # dans le dépôt du moteur
    npm run seal -- --outcome=<key>

    <key> est l'une des clés de outcomes. La commande affiche ✔ Sealed file (<key>) → public/data/dossier.bin (<taille> KB) ; le mot de passe n'est jamais affiché.

  1. Construire et chercher les fuites.

    # dans le dépôt du moteur
    npm run build && npm run check:spoilers

    npm run build lance déjà check:spoilers à la fin (build:game) ; le relancer après un build ne coûte rien.

La configuration et les deux commandes

La configuration privée (EndingConfig, scripts/seal-types.ts) :

ChampSens
passwordidentique au ending.password du jeu (given), ou à ce que tape le joueur (typed)
outcomesdes clés libres, chacune { ticket, headline, lines? } : le texte sous la zone à gratter (HTML simple), l'annonce de la carte, des lignes en plus. La clé scellée est comparée au drapeau du pronostic (ending.guess.flag)
messageun mot court, commun à toutes les issues
photosles photos de la carte, chemins relatifs à games/<GAME>/private/ (jpg, png, webp, gif). seal avertit au-delà de 800 000 octets par photo
linesdes lignes communes, ajoutées après celles de l'issue
judgeGuessfalse : le fichier scellé ne porte pas l'issue, et le pronostic n'est pas jugé (une version de test)

npm run seal -- --outcome=<key> (scripts/seal.ts) lit games/<GAME>/private/ending.config.ts ; sans lui, il prend l'exemple versionné et prévient (« using the EXAMPLE configuration »). Un --outcome absent ou inconnu l'arrête avec la liste des clés. Il chiffre { ticket, headline, message, photos, lines, outcome } en AES-GCM (clé de 256 bits dérivée du mot de passe par PBKDF2, SHA-256, 120 000 itérations ; le mot de passe est comparé sans accents, casse, espaces ni ponctuation) et écrit public/data/dossier.bin.

npm run check:spoilers (scripts/check-spoilers.ts) a besoin de dist/ (il s'arrête sur « dist/ not found: run npm run build first. »). Il cherche dans chaque fichier .js, .html, .css, .json et .txt de dist/ l'annonce, le texte du ticket (au-delà de deux mots) et les lignes de chaque issue, et la première ligne du message ; il refuse un fichier privé dans dist/ (un dossier private, un fichier *ending.config* ou *reveal.config*) et un dossier.bin qui contient ticket ou headline en clair. Sortie 1 avec le nombre de fuites. Un jeu sans fin scellée n'a que la vérification des fichiers privés.

Résultat attendu, et comment le vérifier

check:spoilers affiche « ✔ No sealed-ending text in clear text in dist/. ». Jouez jusqu'à la fin (npm run dev) : le ticket se gratte sur l'issue scellée, puis la carte montre son annonce, le message, les photos et les lignes, avec le verdict sur le pronostic du joueur.

Erreurs courantes

SymptômeCauseCorrection
Le jeu affiche « … » au lieu de la finle fichier manque à ending.file, ou le mot de passe diffèreécrire file: 'data/dossier.bin' ; donner à la configuration le password du jeu
⚠ … not found: using the EXAMPLE configurationpas de games/<GAME>/private/ending.config.tsétape 1
Usage: npm run seal -- --outcome=<key> (keys: …)--outcome absent ou pas une clé de outcomespasser une des clés listées
✘ <fichier> contains in clear text: "…"un texte de fin est aussi dans le contenu du jeul'enlever du contenu : il n'a sa place que dans la configuration privée
✘ private file in dist: …un fichier privé a été copié dans public/l'enlever de public/
⚠ dist/data/dossier.bin missing (npm run seal not run?)aucun fichier scellé dans le buildétape 2, puis reconstruire

Fichiers ou données modifiés

  • games/<GAME>/private/ending.config.ts (le vôtre, jamais versionné : private/ est dans .gitignore).
  • public/data/dossier.bin, écrasé à chaque seal.

Retour arrière

Sceller de nouveau avec un autre --outcome remplace le fichier ; le précédent n'est pas gardé. git checkout public/data/dossier.bin ramène le fichier versionné.

Références