Repository navigation
EN Bridge Signals And Manifest
Français · Reference · Published, partly reviewed by a person · Applies from: 4.2.5
What a game declares to receive signals from the world outside (reality in its definition), what the build makes of
it (the Reality manifest the Bridge checks), and what a signed signal holds. For authors; the reference of every field.
A signal is an identifier from a finite list the game declares, and it reaches the game as the event of the same name:
nothing from outside but this identifier reaches the game, no text, no address, no payload, so nothing outside can
inject a line, a flag or HTML.
A complete declaration, from the sample game's family (games/signals/game.ts is the real one):
reality: {
bridge: 'https://bridge.example/', // the Bridge the pause menu links to (http://127.0.0.1 while developing)
signals: [
{ id: 'mail.answer.correct', source: 'mail', availability: 'required', replay: 'record',
fallback: { verb: 'use', a: 'radio' } }, // what a player does when the outside never answers
{ id: 'mail.answer.wrong', source: 'mail', availability: 'optional', replay: 'record' },
{ id: 'hook.bell', source: 'webhook', availability: 'optional', replay: 'record', once: false },
],
},
events: [
{ id: 'mail-opens-gate', on: 'mail.answer.correct', do: [{ set: 'gate_open' }, 'A letter: "Yes."'] },
],Audience, context, and the statuses of what this page rests on
Audience: author · Context: Reality Bridge
The Reality Bridge,
localprofile (SQLite) · contract: stable · verified by CI (support matrix of 4.4.0, 2026-10-23): Node 22.13+ with node:sqlite, on Ubuntu; Chromium and WebKit end to end · automated checks: jobsreality,reality-xcheck; CI planreality; 2 test files · people's passes: none · known limits: none listed on this page
feature since 4.1.1
A URL, a string.
The Reality Bridge the pause menu's World link pairs with: https://…, or http://127.0.0.1 or http://localhost
(with a port) while developing. Absent: the signals reach the game only through the Studio's simulator. It is public,
not a secret.
npm run validate refuses another scheme or host: reality.bridge: "<url>": an https:// URL (http:// only for 127.0.0.1 or localhost).
reality: { bridge: 'https://bridge.example.org/', signals: [/* … */] }See reality.signals.
4.1.1.
feature since 4.1.1
An array of signals; each is { id, source, availability, replay, once?, fallback? }.
-
id: the signal, and the event it emits. Letters, digits and. _ : -, at most 128 characters (mail.answer.correct). -
source: where it comes from, a connector's family (mail,webhook,email,terminal,badge…). A connector's token names the sources it may propose, and the Bridge refuses a signal proposed under another source (422source). -
availability:optional, orrequiredwhen the main ending needs it. -
replay:record, the only mode: the signal is an entry of the session, replayed offline. -
once(defaulttrue): the signal's effect runs once per game, however many times it arrives.false: each distinct signal from the Bridge runs it again; a repeated delivery of the same one never does. -
fallback: for arequiredsignal, the action a player can use instead,{ verb, a, b?, do? }.
npm run validate says, for reality.signals[<i>]:
-
signal id "<id>": letters, digits and . _ : - only, at most 128;signal "<id>" is declared twice; -
a signal names its \source` (mail, webhook…)`; -
\availability` is "optional" or "required";`replay` is "record" (the only mode of 4.1.1)`; -
required signal "<id>" has no \fallback`: what a player does when the outside never answers, orthe fallback of "" ( ) matches no rule`; - a warning,
signal "<id>" is declared but nothing listens to it.
In a game with reality, an event listened to that nothing emits nor declares is an error (event "<ev>" is never emitted nor declared in reality.signals): a typo would otherwise wait forever for a signal the Bridge refuses. A
required signal also needs a scenario that sends it, and the game must be finishable without the outside: the
content lint checks the closed witness plays the fallback (Guarantees).
reality: {
signals: [{ id: 'hook.bell', source: 'webhook', availability: 'optional', replay: 'record' }],
},
events: [{ id: 'bell-rings', on: 'hook.bell', do: ['Somewhere, a bell.'] }],The validator does not look at it, and TypeScript refuses it when the game is typed GameDef (npm run check). The
manifest keeps only id, source, availability and replay of each signal: nothing else reaches the Bridge.
4.1.1.
feature since 4.1.9
An object, { email?, telnet?, ssh?, 'open-badge'? }: what each connector may turn into a signal, as data. Public:
it goes into the manifest.
-
email:{ answers: [{ words: string[], signal }], otherwise? }. The first answer whose words all appear in the subject or the text (whole words, any case) proposes its signal; elseotherwise. -
telnet:{ banner?, prompt?, commands: [{ says, reply, signal? }] }.saysis matched whole, never as a pattern;replyis printed back; a command withoutsignalonly answers. -
ssh: the same, plusfiles: the virtual disk,{ "/notes/lamp.txt": "text" }, read withls,cd,cat. -
'open-badge':{ issuers: string[], valid, invalid?, expired?, revoked?, indeterminate? }: the issuers whose badges count, and the signal of each verdict.
reality: {
signals: [
{ id: 'letter.door', source: 'email', availability: 'optional', replay: 'record' },
{ id: 'terminal.lamp', source: 'terminal', availability: 'optional', replay: 'record' },
],
connectors: {
email: { answers: [{ words: ['open', 'door'], signal: 'letter.door' }], otherwise: 'letter.unclear' },
telnet: { prompt: 'shed> ', commands: [{ says: 'lamp on', reply: 'Click.', signal: 'terminal.lamp' }] },
ssh: { commands: [/* the same */], files: { '/notes/lamp.txt': 'The lamp obeys two words.' } },
'open-badge': { issuers: ['https://badges.example.org/issuer'], valid: 'badge.valid', revoked: 'badge.refused' },
},
}Checked by npm run validate; every signal named is one of reality.signals (signal "<id>" is not declared in reality.signals):
-
email.answers[].words: one or more, each 1 to 64 letters or digits, no space; -
telnetandsshcommands[].says: lower-case words of letters, digits and-, one space apart (at most 8 words of 32 characters); nothelp,exit,quit,clear,ls,cd,catorpwd(the terminal's own); not twice.reply: plain text, at most 2 000 characters; -
ssh.files: absolute paths of plain names, no.or.., at most 8 deep; at most 16 KB of text each; -
open-badge.issuers: one or more issuer ids (URLs or DIDs), at most 512 characters each.
No pattern, script or path of the author's reaches a connector.
connectors: { telnet: { commands: [{ says: 'lamp on', reply: 'Click.', signal: 'terminal.lamp' }] } }Kept in the manifest as written, and ignored by the connectors, which read only the fields above.
4.1.9. Each connector: Connectors overview.
feature since 4.1.1
One JSON file per scenario in games/<id>/reality/scenarios/ (in a created project, the game's reality/scenarios/
folder): { "signals": ["<id>", …] }.
The signals the world sends, in that order, each able to arrive at any point after the one before.
npm run solve:reality proves the game in each scenario, then in each replay the connectors recorded
(games/<id>/replays/*.json, their signals in order), as well as in the closed and adversarial worlds.
Each signal is a declared one (scenario <name>: "<id>" is not declared). When the game has required signals,
at least one scenario sends every one of them (no scenario sends every required signal (…): add one in reality/scenarios/).
{ "signals": ["mail.answer.wrong", "mail.answer.correct"] }Only signals is read.
4.1.1.
feature since 4.1.1
{
"format": "web-scumm-reality-manifest",
"schema": 1,
"gameId": "signals",
"signals": [{ "id": "hook.bell", "source": "webhook", "availability": "optional", "replay": "record" }],
"connectors": { "telnet": { "commands": [] } }
}Made from the game's content: npm run build writes it to dist/reality-manifest.json when the game declares
reality, and npm run bridge -- init reads it from the content. The signals are sorted by id; connectors is
present only when the game declares it. It holds no secret. Its hash, manifestHash, is the SHA-256 of its JSON, keys in this fixed order, in hexadecimal.
The Bridge keeps the hash in its config.json and refuses to start on another manifest: the game's manifest (…) is not the one configured (…), so an old configuration cannot publish a signal the game renamed or removed. It answers
GET /v1/manifest with { manifest, hash }; a connector reads it there, or from its manifestFile.
The standalone Bridge starts from it: npx quest-n-click-bridge init --manifest=<game>/dist/reality-manifest.json.
The manifest is made, not written by hand; a signal's fields beyond the four are left out.
4.1.1; connectors since 4.1.9.
feature since 4.1.1
A compact JWS (alg: EdDSA, a kid naming the key), at most MAX_SIGNAL_CHARS (4 096) characters. The player
verifies the signature on the exact bytes before it parses them.
Both versions: format (web-scumm-world-signal), schema (1 or 2), id (unique per signal on the Bridge),
sequence (per player, from 1), gameId, playerId, signal, source, occurredAt? (the connector's time, for
information), receivedAt, expiresAt?, dedupeKey, policyVersion, evidenceHash? (a SHA-256 of what the
connector saw, never the evidence). V2 adds tenantId, environment (prod, staging, dev), audience (the origin
the player paired from, or the Bridge's audience), sessionId (the link) and keyId (equal to the header's kid).
The player checks the size, the algorithm, the key (each within its window), the signature, then the game, the player,
the manifest and the expiry (five minutes of tolerance for the device's clock), and for V2 the context
(audience-mismatch). A refusal has a code (14 of them, RefusalCode). A multi-tenant Bridge signs V2 only; a
single-tenant Bridge signs V1 unless its configuration says signalVersion: 2; the player accepts both by default.
What the session keeps of one: { external: { id, sequence, signal, source, receivedAt, playerId } }.
Refused: both payloads are strict schemas.
V1 4.1.1; V2 4.1.10 (ADR 0010).
- The module
@quest-n-click/engine/reality(verifySignal,RealityClient,httpPort,SignalSimulator,realityManifest,manifestHash,verifyDayToken…), for a game with its own transport (WorldSignalPort): Public API. - The interface texts of the World link (
realityLink…): Quick start. -
npm run validate,npm run solve:reality,npm run lint: Checking your game. - The sample game:
games/signals/(Example games).
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.5.4 r2, documentation revision · révision de la documentation 63112c877c587cc4fad16b25a3400408db2c36bd.
Latest documentation · dernière documentation: https://quest-n-click.cleverapps.io/docs · https://quest-n-click.cleverapps.io/fr/docs; earlier versions · versions précédentes: each release's quest-n-click-docs-<version>.tar.gz · l'archive quest-n-click-docs-<version>.tar.gz de chaque release, https://github.com/Quest-n-Click/quest-n-click/releases
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 (18)
- Requirements
- Install from the release archive
- Install from npm
- 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.5
- 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