Repository navigation
EN Create DSL Reference
Français · Type: reference · Audience: author · Context: engine repository, created project · Status: ready · Applies from: 4.4.0
The content format and its tools · contract: stable · verified by CI (support matrix of 4.4.0, 2026-10-23): Node 22 and 24 on Ubuntu; Windows (windows-latest): the unit suite but six POSIX files · automated checks: jobs
check,coverage,second-game,upgrade; CI plannode24,windows,secondGame,freshInstall,upgrade,reference; 4 test files · people's passes:heroes-two-languagesnot run (#237) · known limits:generated-dsl
A game is data: conditions and commands inside rooms, rules, topics, listeners and scripts, typed by
quest-n-click/content (create.rooms.md shows how to write them). This page is the reference of
that vocabulary. Its Reference part is generated from the schemas the tools themselves read, so what you
read there is what the validator, the solver and the Studio accept.
The reference below is generated from three sources of the engine:
- the Studio's forms,
src/studio/schema.ts(COND_KINDS,CMD_SPECS), with the sentence of each condition and command intools/dsl-meta.ts; - the IR's schema,
src/engine/core/ir-schema.ts(the objectives' fields); - the table that says what each field of a game and of a room counts as,
src/engine/core/ir-fields.ts.
To regenerate it, in a clone of the engine repository, from its root folder:
# in the engine repository
npm run wiki -- generate create.dslnpm run wiki -- check fails when this page is behind the schemas. npx tsx tools/dsl-doc.ts writes it again (run in
the engine repository; --check exits 1 when the page is behind), as npm run wiki -- generate create.dsl does, and
tests/dsl-doc.test.ts fails when it is behind. A created project reads the
same vocabulary from its installed engine: there is nothing to generate there.
Since 4.1.12 the names and meanings listed below are stable (decision D22): a change to one is a break that ships
with its migration of the bundled games and saves. What could still grow until 4.1.15, and what is frozen after it for
4.2, is written in docs/dev/DSL-STABILITY.md, with the primitives proposed for
that release and why each was admitted or refused. A new primitive enters with a short decision record and reaches the
runtime, the validator, the solver, the replay, the Studio, MCP and this page
(tests/propagation.test.ts checks it). What the product as a whole promises is in
start.stability.md.
npm run ir (in the engine repository; GAME=<id> picks the game, --json prints the whole IR) prints the game's
intermediate representation: its logic as plain data, each id with the file and line that writes it. The Studio's
Language tab shows the same (studio.language-objectives.md), and the MCP tool
get_ir returns it (studio.mcp-tools.md#get-ir). The game's fingerprint hashes it
(logic), with the trusted extensions, the presentation and the engine apart; the pause menu shows its short form. See
docs/dev/adr/0013-game-ir-and-fingerprint.md and, for the
files themselves, engine.file-formats.md.
Limit ·
generated-dslThe tables below are generated: edit the schemas ortools/dsl-meta.ts, never this part of the page. They list thecustomandminigamecommands, not what a game's own commands or minigames do: those belong to the game.
A condition is data: a string for a flag, an object for the rest, nested with not, all, any.
| Written | Means |
|---|---|
'flag' |
The flag is set (true, a number other than 0, a word). |
'!flag' |
The flag is false or was never set. |
{ has: 'item' } |
The player holds the item. |
{ flag: 'x', eq?: value, gte?: n, lt?: n } |
The flag's value equals, reaches or stays under a number. |
{ not: cond } |
The condition does not hold. |
{ all: [cond, …] } |
Every condition holds. |
{ any: [cond, …] } |
At least one condition holds. |
{ visited: 'room' } |
The room has been entered at least once. |
{ room: 'room' } |
The player is in this room. |
{ prop: ['room.prop', 'state'] } |
The prop is in that state. |
{ unlocked: 'place' } |
The place is unlocked on the map. |
{ seen: 'key' } |
The topic, choice or listener was already seen. |
{ actorIn: ['who', 'room'] } |
The character is in that room. |
{ player: 'who' } |
This character is the one the player controls. |
A command is one key of CMD_KEYS; its fields are the Studio form’s. A plain string is the hero’s line.
| Command | Shape | Does |
|---|---|---|
say |
{ say: [who, line], id?: id, shout?: true, voice?: id } |
A character says a line (a plain string is the hero). |
walk |
{ walk: target | [x, y], who?: who } |
A character walks to a thing or a point. |
face |
{ face: target, who?: who } |
A character turns left, right or towards a thing. |
pose |
{ pose: [who, pose] } |
A character takes a pose until told otherwise. |
anim |
{ anim: [who, pose], ms?: number, at?: … } |
A character plays a pose once, with commands at chosen frames. |
place |
{ place: [who, at], face?: 'left' | 'right' } |
A character appears at a point, without walking. |
wait |
{ wait: number } |
Waits that many milliseconds. |
parallel |
{ parallel: [[cmd, …], …] } |
Runs several lists at the same time, until all end. |
camera |
{ camera: … } |
Moves the camera of a wide room: follow, pan, or centre on a thing. |
play |
{ play: [prop, animation] } |
Plays a prop's animation. |
stopAnim |
{ stopAnim: prop } |
Stops a prop's looping animation. |
launch |
{ launch: { target: who, to: target | [x, y], from?: target | [x, y], height?: number, ms?: number, rotate?: number } } |
Throws a thing along a ballistic arc (presentation only). |
spring |
{ spring: { target: who, axis?: 'x' | 'y' | 'rot', amplitude?: number, frequency?: number, damping?: number, ms?: number } } |
Makes a thing swing and settle (presentation only). |
path |
{ path: { target: who, points: [[x, y], …], ms?: number, orient?: true } } |
Moves a thing along a smooth path (presentation only). |
follow |
{ follow: { target: who, leader: who, offset?: [x, y], ms: number } } |
Keeps a thing next to another for a while (presentation only). |
prop |
{ prop: [prop, state] } |
Puts a prop in a state. |
show |
{ show: target, fade?: number } |
Shows a hidden thing, with an optional fade. |
hide |
{ hide: target, fade?: number } |
Hides a thing, with an optional fade. |
gain |
{ gain: item } |
The player gains an item. |
lose |
{ lose: item } |
The player loses an item. |
used |
{ used: item } |
Marks items used: kept, greyed, offered only to a rule that names them. |
set |
{ set: flag } |
Sets a flag (true, or the value given). |
unset |
{ unset: flag } |
Removes a flag. |
inc |
{ inc: flag, by?: number } |
Adds to a flag counted as a number. |
unlock |
{ unlock: place } |
Unlocks a place on the map. |
goto |
{ goto: room, at?: id | [x, y] } |
Takes the player to a room, at an entry or a point. |
map |
{ map: true } |
Opens the travel map. |
moveActor |
{ moveActor: [who, room], at?: id | [x, y] } |
Moves a character to another room. |
emit |
{ emit: event } |
Fires an event: the room's listeners, then the game's. |
waitUntil |
{ waitUntil: cond } |
In a script: pauses until the condition holds. |
waitEvent |
{ waitEvent: event } |
In a script: pauses until the event is fired. |
switchPlayer |
{ switchPlayer: char } |
The player takes another playable character. |
transfer |
{ transfer: [item, to] } |
Hands an item to another playable character. |
custom |
{ custom: command, args?: … } |
Runs a custom command: its declared effects, then its browser code. |
startScript |
{ startScript: script } |
Starts a script from its first command. |
stopScript |
{ stopScript: script } |
Stops a script until it is started again. |
sfx |
{ sfx: sfx, caption?: text } |
Plays a sound effect, with an optional caption. |
music |
{ music: … } |
Changes the music: a track, push, pop, stop, once or a stinger. |
toast |
{ toast: text, id?: id } |
Shows a short message at the top of the screen. |
shake |
{ shake: number } |
Shakes the screen that many milliseconds. |
if |
{ if: cond, then: [cmd, …], else?: [cmd, …] } |
Runs then when the condition holds, else else. |
once |
{ once: [cmd, …], id?: id } |
Runs its list the first time only. |
nth |
{ nth: [[cmd, …], …], id?: id } |
Runs the next list each time, the last one again after. |
cycle |
{ cycle: [[cmd, …], …], id?: id } |
Runs its lists in turn, looping. |
random |
{ random: [[cmd, …], …], id?: id } |
Runs one of its lists at random (recorded for replays). |
cutscene |
{ cutscene: [cmd, …] } |
Runs a list the player can skip. |
choice |
{ choice: [{ id?: id, text: text, img?: image, if?: cond, once?: true, do: [cmd, …] }, …] } |
Offers the player options, each with its commands; an option with an img shows as a card (the image, its text under it), the cards first. |
minigame |
{ minigame: minigame, params?: …, then?: [cmd, …] } |
Starts a minigame, then runs then. |
phone |
{ phone: char, do: [cmd, …] } |
An incoming call from one or several characters. |
guide |
{ guide: { verb: verb, target: target, say: text }, id?: id } |
A tutorial step: waits for that verb on that target. |
talk |
{ talk: char } |
Opens a conversation with a character. |
hint |
{ hint: true } |
Gives the next hint of the room. |
ending |
{ ending: true, after?: [cmd, …] } |
The sealed ending: decrypted, scratched, celebrated, then its card. |
reveal |
{ reveal: true, after?: [cmd, …] } |
The old name of ending (removed in 5.0). |
end |
{ end: true } |
Ends the game. |
| Field | Type | Means |
|---|---|---|
title |
string | What the quest journal shows (translated under objectives/<id>.title). |
done |
cond | Done the first time this condition holds after an action; best a condition that stays true. |
optional |
boolean (optional) | A side objective: not part of 100% (npm run solve -- --goal=100%). |
parent |
string (optional) | The objective this one is a step of: shown under it. |
Written inside any text; anything else between braces stays as written. A switch compares String(value) of the flag exactly; no value matching (or the flag unset) takes *=, else the first text. No nesting, no escaping: an alternative holds no {, } or |. The validator reports a malformed switch, a flag no command sets, a value listed that no set writes, and a value written that the switch neither lists nor covers with *=.
| Placeholder | Filled |
|---|---|
{hero} |
when shown: the active hero’s name (its variant’s); lines, choices, looks, hints, toasts, captions, item and object names, objective titles |
{<flag>?<value>=<text>|…|*=<text>} |
when shown, after the translation: the text listed for the flag’s current value ({form?m=him|f=her}, content{form?f=e|*=}) |
{item} {target} {name} |
in a fallback, kind or give line: the thing acted on, what it is used on, the target |
{code:<id>} {hint:<id>} |
at boot, by Remix: a coupled dimension’s code and its clue |
From core/ir-fields.ts: logic is in the IR and the fingerprint’s logic; presentation in its presentation; both splits; meta is for the tools only.
The game (GameDef):
-
logic:
schemaVersion,id,saveVersion,hero,players,hintItem,hintVoice,rules,scripts,events,globalTalk,start,reality,checkpoints,invariants,migrations,objectives,remix,variant -
both:
verbs,characters,items,rooms,map -
presentation:
title,renderer,audio,skin,ending,saves,settings,input,actorGap,ui,titleScreen,creditsScreen,credits -
meta:
lang,offline,lint,i18n,assetBudgets,speedrun
A room (RoomDef):
-
logic:
id,name,hotspots,look,on,talk,hints,onEnter,scripts,events,hero,anchors -
both:
props,actors,exits,stage -
presentation:
decor,music,renderer -
meta:
description,furniture
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