quest'n'click 4.5.0 · r1

Built for quest'n'click 4.5.0, revision r1. Whether it is published, and which one is current, is recorded at publication.json.

Chapters and objectives

Français · Type: reference + explanation · Audience: author · Context: engine repository, created project · Status: ready · Applies from: 4.1.14

A long game is easier to check, and to play, in parts. Three declarations of game.ts cut it: checkpoints with goals (the chapters the solver proves one by one), invariants (what must never happen), and objectives (what the player has to do, shown in the quest journal). This page is their reference, and says why they are separate.

The tools are written by name below (solve, validate). In a created project they run as npx quest-n-click solve …; in the engine repository as GAME=<id> npm run solve -- ….

Chapters and invariants

A checkpoint is a ready-made state: a room, an inventory, flags, unlocked places, prop states, where the moving characters are, and for a game with several playable characters who is active and where the others stand. In development you can jump to one; the solver can start from one (solve --from=<checkpoint>).

A checkpoint with goals is the end of a chapter. solve --chapters proves each chapter on its own, from the previous checkpoint (or a new game) until its goals hold, then from the last checkpoint to the ending: a long game is checked in small bounded searches, and a broken chapter is named.

checkpoints: {
  garden: { room: 'garden', inventory: ['token'], goals: [{ has: 'token' }] },
  market: { room: 'market', …, goals: ['tank_drained', { unlocked: 'market' }] },
},
// Must never become true (the solver reports the path that makes one true):
invariants: [{ all: [{ not: { has: 'token' } }, '!flowers_done'] }],

The fields of a checkpoint

FieldMeaning
room (required)The room the state is in.
inventory, flags, unlocked, propsThe state's inventory, flags, unlocked map places and prop states (<room>.<prop> keys).
whereWhere the moving characters are (character → room); the others are in their starting room.
active, playersSeveral playable characters: who is active, and the others' room, inventory and used items.
used, seenItems already used, and the topics, listeners and choices already seen (seen keys by id, such as topic.<id>).
goalsConditions that must all hold when the chapter ending here is done. Only a checkpoint with goals ends a chapter.

invariants is a list of conditions of game.ts that must never become true. The solver reports the path that makes one true.

Run it

# in the created project
npx quest-n-click solve --chapters

# in the engine repository
GAME=<id> npm run solve -- --chapters

With --prove, --chapters proves each chapter from every reachable boundary state of the previous one (deduped by what the chapter reads), not from the hand-written checkpoint; that checkpoint must then be one of those boundary states (used and seen let it name what the boundary state holds). A game without any checkpoint with goals is proved by the global search (--prove), and that is not a failure. The options are on Solver options.

Limit · chapters-hand-written-start Without --prove, each chapter starts from its hand-written checkpoint. If that checkpoint is not a state the previous chapter can actually reach, the chapter's proof says nothing about the real game: use --prove to check it.

Objectives and the quest journal

objectives names what the player has to do. Since 4.1.12 it feeds three things:

objectives: {
  key: { title: 'Find the pantry key', done: { any: [{ has: 'key' }, 'pantry_open'] } },
  tank: { title: 'Drain the water tank', done: 'tank_drained', parent: 'key' },
  pantry: { title: 'Open the pantry', done: 'pantry_open' },
  guess: { title: 'Guess what is in the pantry', done: 'guess', optional: true },
},
FieldMeaning
title (required)What the journal shows. Translated under objectives/<id>.title.
done (required)The condition that completes it.
optionalA side objective: not part of 100 %.
parentThe objective this one is a step of: the journal shows it under its parent.

Write done as a condition that stays true once true (a flag set once, an item kept). The journal never takes a completion back, but a game loaded later counts as done only what holds then.

validate refuses:

It warns when a done that is not optional can be taken back by the content: the journal keeps it completed, but --goal=100% needs every objective to hold at the same time.

The titles are translated like every text, and the menu's row is ui.objectives (English default "Objectives"). The Studio's Language tab edits them (Language and objectives); the MCP tool set_value writes them with id: "@game". Every condition and command, with its shape, is in the DSL reference.

Speedrun categories, in the same file

speedrun (4.1.14) declares speedrun categories and splits as content: triggers on the semantic events (a room entered, an item acquired, an objective completed, the ending), the rules of each category (saves, pauses, hints, reloads, Reality, the fingerprint components a run must match, the inputs), and the rules' version. validate checks it; the guide is Speedrun for authors.

Why three declarations, not one

Why objectives are their own primitive (a checkpoint is a place to start from, a flag says a fact, neither says that a player finished something) and when the engine completes them is ADR 0014.


Edit the source of this page · Report an error