Backhander

Making a packFaire un paquet

A pack is a folder, or a .zip of that folder, that you drop into the game. It holds JSON files, images and sounds: no script, so a pack cannot run code. Every file has a JSON Schema, and the game checks everything when it loads the pack. The reference is docs/pack-format.md in the project; this page is the short tour.

my-pack/
  pack.json         name, version, author, licence, languages, dependencies
  resources.json    the resources
  cards/*.json      the cards, in as many files as you like
  gods.json         the gods and the cards they unlock
  punishments.json  punishments and their triggers
  chains.json       card chains
  quests.json       side quests
  rules.json        starting hand, blessings, tutorial, music
  i18n/en.json …    texts, one file per language
  art/  sounds/     images (PNG) and sounds (OGG)

Only pack.json is required. Ids are lowercase words joined by hyphens (market, catch-of-the-day). Inside a pack you write market; from another pack you write underhand:market, and that pack must be listed in dependencies. A pack whose id matches an installed one replaces it.

{
  "format": 1,
  "id": "my-pack",
  "version": "1.0.0",
  "author": "You",
  "license": "CC0-1.0",
  "languages": ["en", "fr"],
  "dependencies": []
}

Un paquet est un dossier, ou un .zip de ce dossier, que l’on dépose dans le jeu. Il contient des fichiers JSON, des images et des sons : aucun script, donc un paquet ne peut pas exécuter de code. Chaque fichier a un schéma JSON, et le jeu vérifie tout au chargement. La référence est docs/pack-format.md dans le projet ; cette page en est le tour rapide.

mon-paquet/
  pack.json         nom, version, auteur, licence, langues, dépendances
  resources.json    les ressources
  cards/*.json      les cartes, en autant de fichiers qu’on veut
  gods.json         les dieux et les cartes qu’ils débloquent
  punishments.json  les punitions et leurs déclencheurs
  chains.json       les chaînes de cartes
  quests.json       les quêtes annexes
  rules.json        main de départ, bénédictions, tutoriel, musique
  i18n/fr.json …    les textes, un fichier par langue
  art/  sounds/     images (PNG) et sons (OGG)

Seul pack.json est obligatoire. Les identifiants sont des mots en minuscules reliés par des tirets (marche, prise-du-jour). Dans un paquet on écrit marche ; depuis un autre paquet on écrit underhand:marche, et ce paquet doit figurer dans dependencies. Un paquet dont l’id est celui d’un paquet installé le remplace.

{
  "format": 1,
  "id": "mon-paquet",
  "version": "1.0.0",
  "author": "Vous",
  "license": "CC0-1.0",
  "languages": ["en", "fr"],
  "dependencies": []
}

cards/*.jsoncards/*.json

Each file holds { "cards": [ … ] }. The files are read in alphabetical order and merged. A card has one to three options.

{ "cards": [
  { "id": "market", "weight": 10, "recurring": true,
    "art": "art/cards/market.png",
    "startingDeck": [{ "chance": 1 }],
    "options": [
      { "id": "buy",  "cost": [{ "amount": 2, "accepts": ["coin"] }], "rewards": { "bread": 1 } },
      { "id": "sell", "cost": [{ "amount": 1, "accepts": ["bread"] }], "rewards": { "coin": 2 } },
      { "id": "leave" }
    ] }
] }
FieldMeaning
weightHow likely the card is when the deck is shuffled (integer ≥ 1).
recurringtrue: back to the discard pile after being played. false: leaves the game.
artA 256 × 385 picture without text (optional).
startingDeckWhether, and with what chance, the card is in the starting deck: { game?, chance, minSummonedGods?, place? }.
optionsOne to three options (below).

Chaque fichier contient { "cards": [ … ] }. Les fichiers sont lus par ordre alphabétique et fusionnés. Une carte a de une à trois options.

{ "cards": [
  { "id": "market", "weight": 10, "recurring": true,
    "art": "art/cards/market.png",
    "startingDeck": [{ "chance": 1 }],
    "options": [
      { "id": "buy",  "cost": [{ "amount": 2, "accepts": ["coin"] }], "rewards": { "bread": 1 } },
      { "id": "sell", "cost": [{ "amount": 1, "accepts": ["bread"] }], "rewards": { "coin": 2 } },
      { "id": "leave" }
    ] }
] }
ChampSens
weightProbabilité de la carte au mélange du paquet (entier ≥ 1).
recurringtrue : retourne en défausse après avoir été jouée. false : sort de la partie.
artUne image de 256 × 385 sans texte (facultatif).
startingDeckSi la carte est dans le paquet de départ, et avec quelle chance : { game?, chance, minSummonedGods?, place? }.
optionsDe une à trois options (ci-dessous).

What an option can doCe que peut faire une option

An option is what the player chooses on a card. Every field except id is optional, and they combine freely.

FieldWhat it does
costWhat the player pays: slots { amount, accepts }, where amount is a number, "halfOfHeld" (half of the cards held, rounded up), "allOfThem" (all of the cards held) or "allButOne" (all but one), and accepts lists the resources that can pay.
conditionsWhen the option is available: a resource held between min and max, a flag set or not, a chain step reached, a quest in a given state.
rewardsResources given to the player: { "bread": 1 }.
loseRandomThe player loses that many cards at random.
setFlag, clearFlagSet or clear named markers of the game, so that a story can branch without more cards.
startQuest, completeQuest, failQuestStart, complete or fail a side quest.
foresightReveals the next count cards. With discard, the player may cast some aside (maxDiscard caps how many). With reorder, the player puts them back in the order of their choice. With pick, the player keeps one card, drawn first: the others are cast aside ("discard") or stay in the deck below it ("deal").
shuffleShuffles the draw pile, or only its top count cards, before the option adds cards.
addCardsAdds cards to the deck: [{ card, count, place?, after? }]. place says where (below). after: { "reshuffles": N } adds the card only once the deck has been reshuffled N times.
addRandomCardsDraws cards by weight and adds them: { choices: [{ card, weight }], count, unique, place?, after? }. A choice whose card is null means “nothing happens”.
outcomeEnds the game: { "type": "win", "god": … } summons a god, { "type": "lose" } loses.
lastResortThe option is available only when no other option is.
radioAn OGG clip that the radio plays after a delay.

Where an added card goes (place):

ValueThe card goes
"discard"into the discard pile: it enters at the next reshuffle
"top"on top: it is the next card
{ "depth": N }N cards below the top (deeper than the pile: the bottom)
{ "depth": [A, B] }at a depth drawn between A and B
"randomInDeck"at a random position in the draw pile
"bottom"under the draw pile
{ "id": "ask-the-oracle",
  "cost": [{ "amount": 1, "accepts": ["relic"] }],
  "foresight": { "count": 3, "discard": false, "pick": "deal" },
  "addRandomCards": {
    "choices": [{ "card": "omen", "weight": 2 }, { "card": null, "weight": 1 }],
    "count": 1, "unique": false, "after": { "reshuffles": 1 } } }

Une option est ce que le joueur choisit sur une carte. Tous les champs sauf id sont facultatifs et se combinent librement.

ChampCe qu’il fait
costCe que le joueur paie : des emplacements { amount, accepts }, où amount est un nombre, "halfOfHeld" (la moitié des cartes détenues, arrondie au-dessus), "allOfThem" (toutes les cartes détenues) ou "allButOne" (toutes sauf une), et accepts liste les ressources qui peuvent payer.
conditionsQuand l’option est disponible : une ressource détenue entre min et max, un drapeau posé ou non, une étape de chaîne atteinte, une quête dans un état donné.
rewardsRessources données au joueur : { "bread": 1 }.
loseRandomLe joueur perd ce nombre de cartes au hasard.
setFlag, clearFlagPose ou retire des marqueurs nommés de la partie, pour qu’une histoire bifurque sans cartes en plus.
startQuest, completeQuest, failQuestDémarre, accomplit ou fait échouer une quête annexe.
foresightRévèle les count prochaines cartes. Avec discard, le joueur peut en écarter (maxDiscard plafonne le nombre). Avec reorder, il les remet dans l’ordre de son choix. Avec pick, il garde une carte, piochée en premier : les autres sont écartées ("discard") ou restent dans la pioche en dessous ("deal").
shuffleMélange la pioche, ou seulement ses count premières cartes, avant que l’option n’ajoute des cartes.
addCardsAjoute des cartes au paquet : [{ card, count, place?, after? }]. place dit où (ci-dessous). after: { "reshuffles": N } n’ajoute la carte qu’après N remélanges de la pioche.
addRandomCardsTire des cartes selon leur poids et les ajoute : { choices: [{ card, weight }], count, unique, place?, after? }. Un choix dont la carte vaut null signifie « il ne se passe rien ».
outcomeTermine la partie : { "type": "win", "god": … } invoque un dieu, { "type": "lose" } perd.
lastResortL’option n’est disponible que si aucune autre ne l’est.
radioUn extrait OGG que la radio joue après un délai.

Où va une carte ajoutée (place) :

ValeurLa carte va
"discard"dans la défausse : elle entre au prochain remélange
"top"sur le dessus : c’est la prochaine carte
{ "depth": N }N cartes sous le dessus (plus profond que la pioche : le fond)
{ "depth": [A, B] }à une profondeur tirée entre A et B
"randomInDeck"à une position au hasard dans la pioche
"bottom"sous la pioche
{ "id": "ask-the-oracle",
  "cost": [{ "amount": 1, "accepts": ["relic"] }],
  "foresight": { "count": 3, "discard": false, "pick": "deal" },
  "addRandomCards": {
    "choices": [{ "card": "omen", "weight": 2 }, { "card": null, "weight": 1 }],
    "count": 1, "unique": false, "after": { "reshuffles": 1 } } }

gods.jsongods.json

A god is summoned by a card with a win outcome. Summoning it unlocks cards that are offered as blessings in later games.

{ "gods": [
    { "id": "elder", "image": "art/gods/elder.png", "thumbnail": "art/gods/elder-thumb.png",
      "summonCards": ["summoning"], "unlocks": ["gift"] }
  ],
  "extensions": [
    { "god": "underhand:yacare", "unlocks": ["swamp-gift"] }
  ] }

summonCards are the cards with an option that summons the god. unlocks are the blessings. extensions add cards to a god of another pack without editing it (the pack must be in dependencies).

Un dieu est invoqué par une carte dont une option a pour issue win. L’invoquer débloque des cartes proposées comme bénédictions dans les parties suivantes.

{ "gods": [
    { "id": "elder", "image": "art/gods/elder.png", "thumbnail": "art/gods/elder-thumb.png",
      "summonCards": ["summoning"], "unlocks": ["gift"] }
  ],
  "extensions": [
    { "god": "underhand:yacare", "unlocks": ["swamp-gift"] }
  ] }

summonCards sont les cartes dont une option invoque le dieu. unlocks sont les bénédictions. extensions ajoute des cartes à un dieu d’un autre paquet sans le modifier (ce paquet doit figurer dans dependencies).

The other filesLes autres fichiers

All of these files are optional, and every field marked “optional” can be left out.

resources.json

The kinds of cards the player holds: money, cultists, relics, whatever your pack needs.

{ "resources": [
  { "id": "bread", "order": 2, "color": "#e0e8c2", "tags": ["food"],
    "canReplace": ["grain"], "art": { "symbol": "art/resources/bread-symbol.png" } }
] }
FieldMeaning
orderRank in the hand fan and on the altar.
color#rrggbb.
tagsLabels that punishments can measure, such as food (optional).
canReplaceResources this one can stand in for, one card for one unit (optional). It does not chain.
art.symbolA 256 × 256 picture shown in the card’s round medallion; without it, the medallion is plain colour (optional).

punishments.json

A punishment puts a card on top of the deck when something in the player’s hand goes too far, such as suspicion rising or food running out.

{ "punishments": [
  { "id": "famine", "card": "hunger", "priority": 10,
    "when": { "measure": "tag:food", "max": 0 },
    "chance": { "base": 20 }, "signal": "art/signals/famine.png" }
] }
FieldMeaning
cardThe card put on top of the deck when the punishment fires.
priorityPunishments are tested from the highest to the lowest.
cooldownDraws after its card during which it is not tested (default 1).
whenmeasure is handTotal, a resource or tag:<tag>, with an inclusive min and/or max.
chancePercent: base, plus perUnit for each unit measured, plus perTurn for each consecutive draw the condition has held.
signalA 257 × 128 alert icon shown when the risk is near (optional).

chains.json

A chain is a story told over several cards, which options add one after the other. The game remembers how far you got.

{ "chains": [
  { "id": "expedition",
    "steps": ["expedition/rumour", ["expedition/discovery"], "summoning"],
    "god": "elder" }
] }

steps are card references in order; a step that is a list holds cards that are alternatives at that step (forks, random additions). god is the god the chain leads to, shown on its cards (optional). A card belongs to one chain at most. A condition { "chain": …, "step": N } on an option tests how far the player has got.

quests.json

Quests are optional goals. An option starts one (startQuest); it is completed when its objective is met or when an option completes it, and it fails at its deadline or when an option fails it. A quest is started once.

{ "quests": [
  { "id": "feed-the-flock",
    "objective": { "type": "holdResources", "resource": "tag:food", "amount": 5 },
    "deadline": 8,
    "rewards": { "resources": { "coin": 2 } },
    "onFail": { "addCards": [{ "card": "hungry-flock", "count": 1, "place": "top" }] } }
] }
FieldMeaning
scoperun (default): it lasts one game. meta: it spans games and is kept in the player’s progress.
deadlineTurns to complete it, else it fails (run quests, optional).
rewardsOn completion: resources into the hand, addCards, unlockCards (added to the starting deck of every later normal game).
onFailOn failure: addCards, a punishment for example.
ObjectiveMet when
playCard (card, option, times?)that option was played that many times since the quest started
reachStep (chain, step)the chain’s step reached is at least step
holdResources (resource, amount)at the end of a turn, the hand holds at least that many cards of the resource or tag:…
survive (turns)that many turns have ended since it started
sacrifice (resource, amount)that many cards of the resource were paid in costs since it started

Making a card depend on a quest

Three ways, which you can combine:

// 1. The quest adds the card when it is completed
{ "id": "feed-the-flock", …,
  "rewards": { "addCards": [{ "card": "grand-feast", "count": 1, "place": "top" }] } }

// 2. A meta quest unlocks the card for every later game
{ "id": "old-faith", "scope": "meta", …,
  "rewards": { "unlockCards": ["relic-shrine"] } }

// 3. An option is available only once the quest is completed
{ "id": "claim-the-feast",
  "conditions": [{ "quest": "feed-the-flock", "state": "completed" }], … }

On failure, onFail.addCards does the same for a punishment. No condition keeps a card from being drawn until a quest is done: a card arrives (1, 2), or its options are locked (3).

rules.json

The rules of the game. Every field is optional, and the rules of all active packs are merged: some fields add up (starting hand, tutorial tips), the others are single values where the last pack loaded wins.

{
  "startingHand": { "normal": { "coin": 2, "bread": 2 } },
  "startingHandChoice": { "coin": { "min": 1, "max": 4 } },
  "startingDeckChoice": { "min": 0, "max": 6 },
  "addedCards": { "normal": "discard", "tutorial": "top" },
  "deckExhausted": "lose",
  "blessings": { "maxGods": 3, "previousSummonIncluded": true },
  "radio": { "delaySeconds": { "min": 20, "max": 34 }, "maxPending": 1 },
  "music": { "menu": "sounds/menu.ogg", "game": "sounds/game.ogg", "gameOver": "sounds/over.ogg" }
}
FieldMeaning
startingHandThe resources held at the start, for a normal game and for the tutorial (added up across packs).
startingHandChoiceResources whose starting count the player sets, between min and max.
startingDeckChoiceLets the player pick between min and max of the cards that are otherwise drawn by chance.
addedCardsWhere an added card goes by default: discard or top.
deckExhaustedWhat happens when both piles are empty (lose).
punishmentAfterPunishmentallowed (default) or blocked: no punishment is rolled right after a punishment card, as in the original.
blessingsmaxGods and previousSummonIncluded: how many gods give blessings at the start of a game.
tutorialpunishments and blessings on or off, the tips shown (each triggered by a card or an event), and seenOnCompletion.
radioThe delay (delaySeconds) and the number of clips waiting (maxPending).
musicOGG paths for the menu, the game and the game over.

Tous ces fichiers sont facultatifs, et chaque champ marqué « facultatif » peut être omis.

resources.json

Les sortes de cartes que le joueur détient : argent, cultistes, reliques, ce dont votre paquet a besoin.

{ "resources": [
  { "id": "bread", "order": 2, "color": "#e0e8c2", "tags": ["food"],
    "canReplace": ["grain"], "art": { "symbol": "art/resources/bread-symbol.png" } }
] }
ChampSens
orderRang dans l’éventail de la main et sur l’autel.
color#rrggbb.
tagsÉtiquettes que les punitions peuvent mesurer, comme food (facultatif).
canReplaceRessources que celle-ci peut remplacer, une carte pour une unité (facultatif). Ça ne s’enchaîne pas.
art.symbolUne image de 256 × 256 dans le médaillon rond de la carte ; sans elle, le médaillon est uni (facultatif).

punishments.json

Une punition pose une carte sur le dessus de la pioche quand quelque chose va trop loin dans la main du joueur, comme la suspicion qui monte ou la nourriture qui manque.

{ "punishments": [
  { "id": "famine", "card": "hunger", "priority": 10,
    "when": { "measure": "tag:food", "max": 0 },
    "chance": { "base": 20 }, "signal": "art/signals/famine.png" }
] }
ChampSens
cardLa carte posée sur le dessus de la pioche quand la punition se déclenche.
priorityLes punitions sont testées de la plus haute à la plus basse.
cooldownTirages après sa carte pendant lesquels elle n’est pas testée (1 par défaut).
whenmeasure vaut handTotal, une ressource ou tag:<tag>, avec un min et/ou un max inclusifs.
chancePourcentage : base, plus perUnit par unité mesurée, plus perTurn par tirage consécutif où la condition tient.
signalUne icône d’alerte de 257 × 128 affichée quand le risque approche (facultatif).

chains.json

Une chaîne est une histoire racontée sur plusieurs cartes, que les options ajoutent l’une après l’autre. Le jeu retient jusqu’où vous êtes allé.

{ "chains": [
  { "id": "expedition",
    "steps": ["expedition/rumour", ["expedition/discovery"], "summoning"],
    "god": "elder" }
] }

steps sont des références de cartes dans l’ordre ; une étape qui est une liste contient des cartes alternatives à cette étape (bifurcations, ajouts aléatoires). god est le dieu auquel mène la chaîne, affiché sur ses cartes (facultatif). Une carte appartient à une chaîne au plus. Une condition { "chain": …, "step": N } sur une option teste jusqu’où le joueur est arrivé.

quests.json

Les quêtes sont des objectifs facultatifs. Une option en lance une (startQuest) ; elle s’accomplit quand son objectif est atteint ou quand une option l’accomplit, et elle échoue à son échéance ou quand une option la fait échouer. Une quête ne se lance qu’une fois.

{ "quests": [
  { "id": "feed-the-flock",
    "objective": { "type": "holdResources", "resource": "tag:food", "amount": 5 },
    "deadline": 8,
    "rewards": { "resources": { "coin": 2 } },
    "onFail": { "addCards": [{ "card": "hungry-flock", "count": 1, "place": "top" }] } }
] }
ChampSens
scoperun (défaut) : elle dure une partie. meta : elle dure sur plusieurs parties et reste dans la progression du joueur.
deadlineTours pour l’accomplir, sinon elle échoue (quêtes run, facultatif).
rewardsÀ l’accomplissement : resources dans la main, addCards, unlockCards (ajoutées au paquet de départ de toutes les parties normales suivantes).
onFailÀ l’échec : addCards, une punition par exemple.
ObjectifAtteint quand
playCard (card, option, times?)cette option a été jouée ce nombre de fois depuis le début de la quête
reachStep (chain, step)l’étape atteinte de la chaîne vaut au moins step
holdResources (resource, amount)en fin de tour, la main contient au moins ce nombre de cartes de la ressource ou de tag:…
survive (turns)ce nombre de tours est terminé depuis le début de la quête
sacrifice (resource, amount)ce nombre de cartes de la ressource ont été payées en coûts depuis le début de la quête

Faire dépendre une carte d’une quête

Trois façons, qui se combinent :

// 1. La quête ajoute la carte quand elle est accomplie
{ "id": "feed-the-flock", …,
  "rewards": { "addCards": [{ "card": "grand-feast", "count": 1, "place": "top" }] } }

// 2. Une quête meta débloque la carte pour toutes les parties suivantes
{ "id": "old-faith", "scope": "meta", …,
  "rewards": { "unlockCards": ["relic-shrine"] } }

// 3. Une option n’est disponible qu’une fois la quête accomplie
{ "id": "claim-the-feast",
  "conditions": [{ "quest": "feed-the-flock", "state": "completed" }], … }

À l’échec, onFail.addCards fait la même chose pour une punition. Aucune condition n’empêche qu’une carte soit piochée tant qu’une quête n’est pas finie : une carte arrive (1, 2), ou ses options sont verrouillées (3).

rules.json

Les règles du jeu. Chaque champ est facultatif, et les règles de tous les paquets actifs sont fusionnées : certains champs s’additionnent (main de départ, astuces du tutoriel), les autres sont des valeurs uniques où le dernier paquet chargé l’emporte.

{
  "startingHand": { "normal": { "coin": 2, "bread": 2 } },
  "startingHandChoice": { "coin": { "min": 1, "max": 4 } },
  "startingDeckChoice": { "min": 0, "max": 6 },
  "addedCards": { "normal": "discard", "tutorial": "top" },
  "deckExhausted": "lose",
  "blessings": { "maxGods": 3, "previousSummonIncluded": true },
  "radio": { "delaySeconds": { "min": 20, "max": 34 }, "maxPending": 1 },
  "music": { "menu": "sounds/menu.ogg", "game": "sounds/game.ogg", "gameOver": "sounds/over.ogg" }
}
ChampSens
startingHandLes ressources détenues au départ, pour une partie normale et pour le tutoriel (additionnées entre paquets).
startingHandChoiceRessources dont le joueur règle le nombre de départ, entre min et max.
startingDeckChoiceLaisse le joueur choisir entre min et max des cartes qui sont sinon tirées au hasard.
addedCardsOù va par défaut une carte ajoutée : discard ou top.
deckExhaustedCe qui se passe quand les deux piles sont vides (lose).
punishmentAfterPunishmentallowed (défaut) ou blocked : aucune punition n’est tirée juste après une carte de punition, comme dans l’original.
blessingsmaxGods et previousSummonIncluded : combien de dieux donnent des bénédictions au début d’une partie.
tutorialpunishments et blessings activés ou non, les tips montrées (chacune déclenchée par une carte ou un événement), et seenOnCompletion.
radioLe délai (delaySeconds) et le nombre d’extraits en attente (maxPending).
musicChemins OGG du menu, du jeu et de la fin de partie.

TranslationsTraductions

The texts are not in the cards: each language has a file i18n/<language>.json, a flat object from a key to a message. Keys come from ids. Add a language by adding its file and listing it in pack.json › languages; a missing key falls back to the first language of the pack.

KeyText
pack.name, pack.descriptionThe pack’s name and description
card.<id>.title, card.<id>.flavorCard title and flavour text
card.<id>.option.<option>.textWhat the option says (and ….effect for its effect)
resource.<id>.name, ….letterResource name, and the letter on its coins
god.<id>.name, ….epithetGod name and epithet
punishment.<id>.name, tip.<id>Punishment name, tutorial tip
quest.<id>.name, ….descriptionQuest name and description

Messages use ICU syntax, for plurals and the like. The interface’s own texts (ui.*) belong to the engine and are translated there.

Les textes ne sont pas dans les cartes : chaque langue a un fichier i18n/<langue>.json, un objet plat d’une clé vers un message. Les clés viennent des identifiants. On ajoute une langue en ajoutant son fichier et en la listant dans pack.json › languages ; une clé absente retombe sur la première langue du paquet.

CléTexte
pack.name, pack.descriptionNom et description du paquet
card.<id>.title, card.<id>.flavorTitre de la carte et texte d’ambiance
card.<id>.option.<option>.textCe que dit l’option (et ….effect pour son effet)
resource.<id>.name, ….letterNom de la ressource, et la lettre sur ses pièces
god.<id>.name, ….epithetNom et épithète du dieu
punishment.<id>.name, tip.<id>Nom de la punition, astuce du tutoriel
quest.<id>.name, ….descriptionNom et description de la quête

Les messages suivent la syntaxe ICU, pour les pluriels et autres. Les textes de l’interface (ui.*) appartiennent au moteur et sont traduits dans le moteur.

Checking a packVérifier un paquet

The schemas catch structural mistakes (a missing field, a wrong type, a malformed id). The game then checks consistency: references to missing cards, a god nobody can summon, a card that cannot be reached, a flag that nothing sets, a missing text. Errors disable the pack, with their list; warnings do not. validate_schemas.py <pack dir> checks the files against the schemas, and make_placeholders.py <pack dir> draws a plain picture for every missing image, so that you can play before drawing anything.

Online validators, in JavaScript, check a pack in your browser without installing anything. Link coming soon

Les schémas attrapent les erreurs de structure (un champ manquant, un mauvais type, un identifiant mal formé). Le jeu vérifie ensuite la cohérence : références à des cartes absentes, un dieu que personne ne peut invoquer, une carte inatteignable, un drapeau que rien ne pose, un texte manquant. Les erreurs désactivent le paquet, avec leur liste ; les avertissements non. validate_schemas.py <dossier> vérifie les fichiers contre les schémas, et make_placeholders.py <dossier> dessine une image simple pour chaque image manquante, pour qu’on puisse jouer avant d’avoir rien dessiné.

Des validateurs en ligne, en JavaScript, vérifient un paquet dans votre navigateur, sans rien installer. Lien bientôt disponible