Repository navigation
EN Create Reactions
Français · Type: reference · Audience: author · Context: engine repository, created project · Status: ready · Applies from: 4.3.0
What answers the player's action: a written rule, a look line, a conversation, a reaction by kind, a refusal, or a fallback line. This page gives the fields of a rule, the exact order in which the engine looks for an answer, and the verb a double tap uses.
A reaction says: "when VERB happens on A (with or to B), if CONDITION, then these commands".
| Field | Meaning |
|---|---|
id |
Its stable id, required in schema 3 (identity contract). |
verb |
A verb id of the game: in the template give, open, close, take, look, talk, use, push, pull. A list accepts several verbs. |
a |
The targeted thing. For Use/Give with two terms, it is the inventory item. A list = any of them. |
b |
The target of "Use A with B" or "Give A to B". Absent = a single-term action only. |
if |
Optional condition. The first rule whose condition is true wins: put special cases first. |
do |
The commands to run. |
on: [
{ id: 'pull.stool', verb: 'pull', a: 'stool', if: { prop: ['stool', 'shelf'] }, do: [{ prop: ['stool', 'pulled'] }, 'There. A step up.'] },
{ id: 'tie.rope', verb: 'use', a: 'rope', b: ['hook', 'bucket'], do: [{ lose: 'rope' }, { set: 'tied' }, 'Tied.'] },
]Two inventory items combine both ways: a rule a: 'piece1', b: 'piece2' also works for "Use piece 2 with piece 1"
(only when both are in the bag). Rules valid everywhere (combining two items, for instance) go in the game's
rules.on, not in a room. Room rules are tried before the game's.
Verb ids are free: the game's verbs declare them. Four have a meaning for the engine: look (look lines), talk
(conversations, the hint item), give and use (two terms: an inventory item, then a target; tapping an item with no
verb picked selects it for Use). Any other verb is handled only through written rules and fallback lines.
The engine looks for an answer in this order, and stops at the first one found
(src/engine/core/interactions.ts):
- a room rule (
on), then a game rule (rules.on), whose verb,a,band condition match; - Look on one thing: the room's
lookline for it, else, for an inventory item, the item'slook; - Talk on one thing: to the hint item (when it is in the bag), the hints; to an actor with topics, the conversation menu;
- a reaction by kind (
rules.kinds): first one aimed at that exact id (target), then one by kind (kind); - Give to a character: their refusal line (
refuseon their sheet); - the verb's fallback line (
rules.fallbacks,use2for "Use A with B"), drawn at random, never the same twice in a row. A verb with no fallback list uses thelooklist, else says "…".
// rules.ts
export const rules = {
fallbacks: {
look: ['Hmm. {item}. Nothing suspicious. For now.'],
take: ['Not mine to take.', 'Too heavy. Even for me.'],
use2: ['{item} with {target}? Does not work. But it was worth a try.'],
// … one array per verb: give, open, close, take, look, talk, use, push, pull, use2
},
kinds: [
{ verb: 'pull', kind: 'cat', say: 'No! Never pull a cat’s tail.' },
{ verb: 'take', target: 'grandma', say: 'Grandma already has enough to carry.' }, // precise target: wins first
{ verb: 'take', kind: 'person', say: 'I cannot carry {name}. But {name} could carry me.' },
{ verb: 'use', item: 'stinky_cheese', kind: 'person', say: 'No. Even I am not that mean.' },
],
on: [
{ id: 'combine.postcard', verb: 'use', a: 'left_half', b: 'right_half', do: [{ lose: 'left_half' }, { lose: 'right_half' }, { gain: 'postcard' }] },
],
};In fallback, kind and refusal lines, {item} is replaced by the item's name, {target} by the target's (empty without
one), {name} by the name of whatever is targeted. {objet}, {cible} and {nom} are the same three under their
names of 4.0: every game written with them keeps working. A kind rule's item restricts it to an item used or given.
Guarantee ·
fallback-order· referenced · scope self-checked by the author (AI), not reviewed With no written rule, a reaction by kind answers when one matches. An item given to a character who refuses gets the refusal, not the verb's fallback line. Anything else gets the fallback line.
When a reaction by kind and a refusal both match a gift, the reaction by kind answers.
Writing the fallback layer. Most clicks hit no written rule: what answers them is the game's voice.
-
3 to 6 variants per verb in
rules.fallbacks, plususe2. The demo has two or three; aim higher. -
Write them in character. Pixel's
push: "It does not move. I need more breakfast." Pixel'suse2: "These do not go together. Like cats and baths." -
kindsanswer a whole family at once: everycatrefuses to be pulled, everypersonrefuses to be carried. A precisetargetwins over akind. -
refuseper character answers any Give that has no rule: Lou says "Thanks, but my pockets are full. Of other people's things." - "That doesn't work" is never acceptable. It tells the player nothing. A fallback should be a joke, a hint at the hero's want, or both (puzzle design).
The word names one idea, an effect that happens once per game, on four things: the command { once: […], id } runs
its list the first time only; once: true on an option of a choice shows it once; once: true on a listener
(events) fires it once; a signal from the world outside applies once by default (once: false repeats it on each new
delivery). The engine remembers each in the save under the thing's stable id.
A single tap walks to a thing; a double tap acts on it without choosing a verb
(src/engine/core/default-verb.ts):
- an exit is gone through (its first verb the game has);
- a character is talked to;
- anything else is looked at.
When that is not right, the thing names its own: hotspots: { cupboard: { name: 'cupboard', defaultVerb: 'open' } }
(also on a prop, an actor, an exit). An item picked from the bag without a verb is given to a character and used on
anything else; the sentence line says which before the tap. A verb the game does not have is never chosen. The player's
side of it: controls.
feature since 4.2.4
In the LucasArts classics, a click on a door with the default "Walk to" took the hero through it.
input: { exitOnTap: true } in game.ts does the same: a single tap on an exit with no verb picked walks there and
goes through on arrival, with the double tap's verb (a locked exit says its locked line). A picked verb still wins, a
tap elsewhere during the walk still redirects the hero, and nothing is gone through; a double tap is unchanged. An exit
can say otherwise: onTap: 'walk' keeps it walk-only in a game with the switch, onTap: 'go' makes it go through in a
game without it (exits: { path: { name: 'path', to: 'village', onTap: 'go' } }). Off by default.
Limit ·
input-onlyexitOnTap,onTap,defaultVerband the verb keys are input only: the rules, the solver and saves do not see them.
Applies from 4.4.0 · feature since 4.4.0
A hotspot can say onTap: 'go' too: a single tap walks there and acts on arrival with its defaultVerb. For example
an endless aisle that says why one does not walk down it. Without it a tap on a hotspot only walks there;
input.exitOnTap is about exits and never changes that.
{
"room": {
"id": "shop",
"name": "The shop",
"decor": "decor/shop",
"hotspots": {
"aisle": { "name": "aisle", "defaultVerb": "use", "onTap": "go" },
"cupboard": { "name": "cupboard", "defaultVerb": "open" }
},
"look": { "aisle": "Shelves, as far as the eye can see.", "cupboard": "A cupboard." },
"on": [
{ "id": "shop.use-aisle", "verb": "use", "a": "aisle", "do": ["That aisle never ends. I am not going down it."] },
{ "id": "shop.open-cupboard", "verb": "open", "a": "cupboard", "do": ["Empty. Of course."] }
],
"hints": [{ "id": "shop.hint", "until": "never", "lines": ["Not down the aisle."] }]
},
"layout": {
"walk": { "area": [[20, 300], [620, 300], [630, 395], [10, 395]] },
"entries": { "default": [320, 360] },
"hotspots": {
"aisle": { "rect": [400, 120, 120, 180], "approach": [460, 320] },
"cupboard": { "rect": [80, 150, 90, 150], "approach": [125, 320] }
}
}
}
feature since 4.3.0
On a desktop, the LucasArts games picked a verb at the keyboard: O for Open, L for Look at. A verb's key does the
same: { id: 'open', label: 'Open', color: '#8fd3ff', key: 'o' }. The key, in any case and without Ctrl, Alt or Meta,
picks the verb as a tap on its button would (and drops it if it is already picked). It does nothing while the engine
is busy, while a line is on screen, or while a conversation, the map, a minigame or a menu is over the scene. The
button underlines the letter's first occurrence in its label and says it to a screen reader (aria-keyshortcuts).
The key is translated with the label (verb:open/key in the locales: Close's c is Fermer's f).
npm run validate refuses a key that is not one character, Space (it advances a line) and a key two verbs share, in
the game and in each translation, and warns when the label does not contain the letter. Choose letters once the
labels are written: "Pick up", "Push" and "Pull" all start with P, so one of them takes another letter of its label
(s for Push). Without key, no shortcut.
With settings: true, the settings offer a "Verb keys" row (ui.verbKeys) in any game whose verbs have keys, so a
player can turn them off, as WCAG 2.1.4 asks of a single-letter shortcut (someone who types by voice, or hits keys by
accident); npm run validate warns about keys without a settings menu. Nothing happens either while the credits roll.
-
on,rules.on,rules.kinds,rules.fallbacks,refuse,defaultVerb,onTap,input.exitOnTap,VerbDef.key. - Commands, conditions, exits, the DSL reference, ADR 0006, the double tap.
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.4.0 r1, documentation revision · révision de la documentation 8dc717c69acb62c4742b69c54a7e7dfb620f75d8.
Earlier versions and permalinks · versions précédentes et liens permanents: https://wanoo.github.io/quest-n-click/docs/
An edit made in this wiki is overwritten at the next publication · une modification faite dans ce wiki est écrasée à la publication suivante.
Edit the source · Modifier la source · Report an error · Signaler une erreur
Index (6)
Start (6)
Play (9)
Install (17)
- Requirements
- Install from the release archive
- Install from npm, once published
- Install from the repository to work on the engine
- doctor: checking your machine
- Image toolchain
- Audio toolchain: ffmpeg, fluidsynth, Furnace, uv
- Upgrade
- Upgrade notes: 4.4
- Upgrade notes: 4.3
- Upgrade notes: 4.2
- Upgrade notes: 4.1
- Upgrade notes: v3
- Upgrade notes: v2 to v3
- Coming from web-scumm
- Package reference
- Install troubleshooting
Create a game (55)
- Your first game
- Layout of a created project and of the engine repository
- The working loop
- Content principles and the identity contract
- Rooms
- Geometry: placement, walk zones, masks, layers, camera
- Something is visible but unreachable
- Reactions
- Game commands
- Conditions
- Flags, variables and events: which to use
- Custom code
- Characters
- Items and inventory
- World map
- Dialogues
- Scripts and events
- Moving characters: patrols and walks
- Exits
- Chapters and objectives
- State and saves: persistent or temporary, renaming an id, migrations
- Staging: wide rooms and animations
- The stage: lights, particles, floors
- Skin and fonts
- Title and credits
- Heroes: several characters and hero choice
- Built-in minigames
- A custom minigame
- Translations
- Music in stems
- Sound effects
- Voices: per language and per hero
- Images: prompts, cutting sheets, transparency, scale
- Asset provenance
- The storyboard, source of truth
- Puzzle design
- Classic mechanics
- Review pages
- DSL reference
- Checking your game
- What the solver proves
- Solver options
- Playtests
- Remix
- Speedrun for authors
- The sealed ending
- Production template
- Example games
- Working with an AI
- Build and release a game
- Publish on GitHub Pages
- The static game on Clever Cloud
- Other static hosts
- Sub-path, cache and service worker, incomplete deploys, going back
- Testing on a phone over the LAN
Local Studio (24)
- The local Studio: overview and launch
- Edit a room
- Placement and stage
- Preview, apply, undo
- Storyboard
- Assets and cutting
- Check: validator, solver, critical path, heat, health, coverage
- Before and after captures
- Semantic diff
- Play and replay a session
- Rule explainer
- Language and objectives
- Voices and the heroes × languages matrix
- Map
- Notes
- Remix in the Studio
- Music
- The Assistant: bring your own key
- Demo mode and studio-apply
- Local and LAN security
- Studio API
- Local MCP: setting up each client
- MCP tools
- Studio troubleshooting
Team Studio (21)
- What runs where
- Concepts: revision, transaction, span, lease, preview, build
- Starting as an invited dev
- Working together day to day
- Roles and actions: the full matrix
- Invitations
- Managing members
- Write refused on the hosted Studio: stale base revision (409)
- Write refused on the hosted Studio: span locked (423)
- Recovering after a lost lease, a disconnection or a stale preview
- Comments
- Map and semantic diff
- Publishing builds
- Remote MCP
- The game in Git
- Upgrading the engine of a hosted game
- Hosted assets
- The server's command line
- Health and metrics routes
- Trying it locally with compose.yml
- Known limits
Clever Cloud (23)
- Clever Cloud: overview, cost and prerequisites
- Apps and add-ons
- Domains and TLS
- Keycloak
- Generic OIDC and GitLab
- Configuration
- First deployment
- Backups
- RPO and RTO
- Restore the Studio on a fresh installation
- Redeploys
- Upgrading the server and checking state after a migration
- Limits of going back
- Monitoring
- Security
- Rotating keys
- Recovering admin access
- Changing domain or OIDC provider
- Worker, PostgreSQL or Cellar down
- Exporting the game and leaving the installation
- Retention
- Clever Cloud troubleshooting
- Costs and teardown
Reality Bridge and connectors (16)
- The Reality Bridge: what it is
- Quick start
- Guarantees
- Install and run the Bridge
- Backup and restore
- Several tenants
- Signals and the manifest
- The Bridge's command line
- Connectors overview
- Email connector
- Telnet connector
- SSH connector
- Open Badges connector
- The connector SDK
- Security: Biscuit and signatures
- Bridge and connector troubleshooting
Contribute (25)
- Welcome
- Developer setup
- The dev loop
- Before a pull request
- Conventions
- Writing documentation
- Architecture
- Code tour
- Import boundaries
- Which tests run when
- Mutation testing
- The coverage ratchet
- The quality baseline
- CI explained
- Field passes
- Benchmarks
- Releasing
- npm packages
- Decisions and ADRs
- Governance
- Bot pull requests
- Contributing with AI agents
- Security policy
- Code of conduct
- Licences and credits