Repository navigation
Releases: Quest-n-Click/quest-n-click
Release list
v4.5.1
LOG #290–#294: a patch on 4.5.0. The maintainer's audit of the published wiki (24 October, 24 findings) is fixed: every
link whose label is written as code now reaches its target in the wiki, the HTML and the bundle; the first tutorial
starts with a command that works; the saves pages and the support window say one thing; search finds « pas de son »;
a page's header is one short line that says who read it. A game may name the spans of its own files (spans.json) and
allow a word of the leak audit (audit-allow.json); site.json's url gives a deployed game an absolute og:image
and an og:url. Nothing to change in a game (Upgrade notes: 4.5, from 4.5.0 to 4.5.1): both files and the url are
optional, and no content field, save, schema or fingerprint moves.
Fixed
- Every link of the published documentation reaches its target (4.5.1, the maintainer's wiki audit, F01–F03). A link whose label was written as code (
[`src/x.ts`](../../src/x.ts)) was invisible to the scanner that rewrites links for the build and reads them for the check: 2,472 links to the repository stayed relative (a 404 on GitHub) and 84 page links kept their<id>.mdsource name (GitHub sent them to Home). The scanner now reads a line as CommonMark does, code spans inside a link's label included; the wiki, the HTML and the bundle are rebuilt with code links at the product commit and page links at their page and section, and a test reads every output back with an independent reader, the audit's 2,556 cases included. - The first tutorial starts with a command that works (4.5.1, F04, F12–F15). "Your first game" installs from the release archive first (the packages are not on npm yet), asks for Node 22.13 (the Studio it uses needs it), writes the storyboard before the room as the working loop says, gives the lamp two images the template has so switching it on shows, says what the screen shows before and after, and says that
solvefinds a winning path whilesolve --provelooks for dead ends. - The saves pages say the same thing when a room disappears (4.5.1, F05): a save whose current room (or active hero) is gone is refused, anything else gone is dropped with a warning, a room renamed with a migration loads; on Saves, Troubleshooting and State and saves.
- One support policy (4.5.1, F06): the security policy,
SECURITY.mdand Stability and support give the same table (4.4 until 23 January 2027, 4.3 until 22 January 2027, 4.2.5 until 21 January 2027 on its branch). - No hard-coded "current release" in the FAQ (4.5.1, F07): it points to the page's footer and the releases page.
- Home says what the wiki's search box does (4.5.1, F09, F23): it filters titles only; Home sends error messages and variables to the indexes and the pages' text to GitHub's code search, and Home gets a short bilingual sidebar.
- Search finds « pas de son » (4.5.1, F10, F11): « son » is no longer a stop word where it is the noun, a symptom typed as a troubleshooting section names it ranks that section first, and the acceptance reports the top 1 apart from the top 3, with ten questions that must come first.
- A page's header is one short line (4.5.1, F17, F18): its twin, its type, "published" with who read it (an AI or a person) and its versions; the audience, the context and the surfaces' statuses follow the page's first paragraph, folded.
- Smaller corrections (4.5.1, F16, F19–F22, F24): the gallery names the recent screens it does not show yet;
read_doc {name}is said to shorten long sections; the code of conduct names one private channel; Safari's seven-day rule is said not to apply to a game added to the Home Screen; Domains and TLS says which browsers were checked; the four big references open with a quick entry by task. - The release's dependency audit is clean again (4.5.1, the candidate). Five advisories published after #275 stopped the candidate's
npm audit --audit-level=high:serialize-javascript7.1.2 (XSS through</scriptat the end of a serialized function, the PWA build's terser),happy-dom21.0.1 (the test DOM),body-parser2.3.0,ip-address10.7.3 andfast-uri3.1.8 (the MCP server's Express, rate limiter and JSON Schema). Each moves to its patched release within the range already declared (7.1.4, 21.0.4, 2.4.0, 10.7.4, 3.1.9, withqs6.16.1):npm audit fix, no direct dependency or override changed, the versioned locks untouched. - A game's own spans are pinned at their edges (4.5.1, the candidate). The mutation set
studio-opsfound eight survivors inspans.json's reader (#270):tests/studio-spans-mutants.test.tsnow pins a literal key that names its file only, the 256 entries, the 256-character key and the 64 KiB file taken at the limit and refused one past it; four loop bounds and a guard no input can tell apart are explained indocs/dev/mutants.json.
Changes
-
A game names the spans of its own files (4.5.1, #269). The modules a game's
index.tsimports (minigames/,commands/, data shared by rooms), its notes and its scripts belonged to no span: the hosted Studio refused a write to them (422), andtests/studio-collab.test.tsfailed on any game that had one.games/<id>/spans.jsonnow maps a path, a folder or a glob to a span a client may take; the engine's rules come first, a file neither names is still refused, and the hosted Studio reads the file of the game it serves. -
A game may allow a word of the leak audit (4.5.1, #269).
audit:assetsblocks the words of the engine's private origin; when one is also, by chance, a word of a game (a real person's first name),games/<id>/audit-allow.jsonallows it by its SHA-1 prefix and a reason, in that game's folder and inpublic/assets/built from it, and nowhere else. A prefix the audit does not block stops the audit. -
A deployed game's link preview shows its image (4.5.1, #271).
index.htmlwrote the share image as%BASE%og.png, a relative URL that LinkedIn's and Facebook's crawlers often ignore. An optionalurlingames/<id>/site.json, the game's public address, makesog:imageabsolute (<url>og.png) and addsog:url; without it, or with an address that is not an absolutehttp(s)URL (or has a query, a fragment or credentials), the page is as before.
Manual passes (D12: reported, not blocking)
0 of 22 done.
| Pass | Status | Who, when | Device, OS, browser, versions | What failed |
|---|---|---|---|---|
A multi-instance Bridge behind HTTPS on Postgres, a worker killed mid-verification (bridge-postgres-https) |
not-run | |||
Story, Remix and Daily runs submitted by real people (runs-real-players) |
not-run | |||
A run resumed after the machine was stopped and started again (run-resume-power-cycle) |
not-run | |||
The code wheel printed and used at the table; the text alternative by a screen-reader user (code-wheel-human) |
not-run | |||
A Mystery world played from the Remix menu against a deployed Bridge (mystery-deployed) |
not-run | |||
Safari offline on an iPhone, and its update (safari-ios-offline-update) |
not-run | |||
Firefox offline on a real machine (firefox-real-offline) |
not-run | |||
A real phone on both painters (phone-both-renderers) |
not-run | |||
A real LiveSplit and OBS session (livesplit-obs) |
not-run | |||
Real connectors and a person's security pass (connectors-real-security) |
not-run | |||
Playtesters who do not know the puzzles (blind-playtesters) |
not-run | |||
The voices listened to (voices-listening) |
not-run | |||
The published archive installed by a person (archive-human-install) |
not-run | |||
The Studio Server deployed on Clever Cloud (Node, PostgreSQL, Cellar, Keycloak, a TLS domain), from its archive, by its guide; a real Safari on macOS and iOS (studio-hosted) |
not-run | |||
Three people on one hosted Studio, a network cut and a redeploy while they edit (studio-collab) |
not-run | |||
On a hosted Studio, people play the shared revision without a dev server, a save carried across revisions, the health chip, their own undo, a published build (studio-preview) |
not-run | |||
A hosted Studio's prepare on the worker (Python, ffmpeg), its prepared set played in the Play tab (studio-assets-hosted) |
not-run | |||
An AI agent on a hosted Studio through its remote MCP (/mcp): a real MCP client, a member's token, a write in the journal as the member, a span another holds refused (studio-mcp-remote) |
not-run | |||
The Studio Server behind a second OpenID Connect provider (GitLab, D44): login, roles, a revoked member refused, verified by a person (studio-oidc-generic) |
not-run | |||
The heroes example (games/heroes) on a real phone, in English and French: each hero's card, name, lines and voice, a save resumed (heroes-two-languages) |
not-run | |||
The Studio's hero × language matrix read by an author on their own game: what it shows, what is missing, what it should say (studio-hero-matrix) |
not-run | |||
A beginner reads the published documentation for answers to ten real questions (docs/dev/acceptance/BEGINNER-READER.md): found, missing or misread, in their language (docs-beginner-reader) |
not-run |
v4.5.0
LOG #268–#289: "Wiki", the documentation moved. wiki/en and wiki/fr are now the documentation: 210 pages in
English and in French, each paired with its twin section by section, published as the GitHub wiki and as
/docs/<version>/ on Pages from the release's own documentation archive, never rebuilt from main. The old docs/
pages are transition pages: every old heading and anchor stays and links to its new place and to the 4.4.0 text. An
assistant finds the pages with two new MCP tools, search_docs and list_docs, and reads one section with
read_doc {id, section}; read_doc {name} keeps 4.4.0's eight names and its answer. The engine's package carries its
documentation (docs-bundle/), so read_doc works in a created project. Every guarantee a page states is tied to a
named test, and the published page says whether that test ran in the candidate. Writing the pages found three bugs,
fixed below (the scenario map's lease, doctor in a created project, page:world's file name). The 132 mutation
exemptions due at 4.5.0 read again: five killed by a test. One new people's pass, docs-beginner-reader; none of the
twenty-two has passed yet. Nothing to change in a game (Upgrade notes: 4.5). The tag is signed with the maintainer's SSH
key on their yes of 23 October for v4.5.0 (D37, D50).
Fixed
-
search_docsno longer reads the word "studio" as the variableSTUDIO(4.5.0). An identifier written in capitals (an environment variable such asSTUDIO,PATHorPORT) now counts only when the query holds it exactly, or is that one identifier in another case (studio_session_key); before, any question with the word "studio" ranked the environment variable page high. The section named exactly by the identifier comes first (PATHbeforeWORKER_PYTHON_PATH), its excerpt shows the description after the name, and an excerpt no longer doubles the page's own bold nor touches the stars of code (ops/**/*.yml). -
read_doc {id}from an installed package now says where the page lives online (4.5.0). Its structured content givesurl.current, the wiki's page, labelled as the current documentation rather than this version's, andurl.versioned: for a release build (a candidate's build, not dirty) the Pages permalink that build has once it is the one published (/docs/<version>/r<n>/<lang>/<id>.html#<section>, checked atpublication.json), and for any other buildnullwith the reason. -
A published page's guarantees say whether their test passed in the candidate (4.5.0). The documentation archive is now built after the candidate's tests, from their report (
npm run wiki -- archive --test-report <file> --candidate <run>), so a guarantee whose test passed shows "executed on " on the published page, ascheck --publishalready required. Before, the archive was built without the report and every guarantee read "referenced" only. -
Reviews say who made them (4.5.0). A translation pair's and a claim's review record whether a person or an AI read it, and whether the reviewer wrote what they read. A page whose pairs or claims were only checked by the AI that wrote them says "self-checked by the author (AI), not reviewed", and
check --publishwants a person's review before it publishes it.translations --ackandreview-claimnow take--kind human|aiand--relation author|independent. -
The scenario map's span can be leased on a shared Studio (4.5.0).
POST /api/leases,/api/leases/releaseand/api/leases/takeoveranswered 400 for the spangroupings(groupings.json), though the Spans panel lists it. The routes now accept exactly the spansstudio-server/src/ops/spans.tsgives files (plusroom:<id>andlang:<code>), so the two lists cannot drift apart again. -
npx quest-n-click doctorruns in a created project (4.5.0). It imported Playwright at the start and stopped withERR_MODULE_NOT_FOUND, since a created project does not install it. Without the package, the doctor now reports Playwright as optional and missing, with the fix:npm install --save-dev playwright, thennpx playwright install chromium. -
npm run page:worldandnpm run page:puzzleswrite a.htmlfile (4.5.0). By default they wrotedist-pages/worldanddist-pages/puzzles, without an extension; an explicit--out file.htmlis still used as given. -
A Markdown link whose text wraps onto the next line is seen (4.5.0). The wiki's link scanner read one line at a time:
[the\npage](x.md)was neither checked nor rewritten for the published wiki. A link whose text breaks across one line of its paragraph is now read whole, as CommonMark reads it. -
The hosted Studio's end-to-end run no longer fails at random on "Load the latest preview" (4.5.0, #255): the
script read the last ready preview, then clicked; when a preview finished building in between, the Studio rightly
loaded the newer one and the check failed (seen on a PR's CI, HTTPS pass). It now waits for the head's preview first.
Nothing changes in the Studio. -
An image cached for offline play is served even when the worker's lookup fails (4.5.0, #257): Firefox's service
worker now and then failed a cached image offline ("A ServiceWorker intercepted the request and encountered an
unexpected error"), a sprite frame then missing. When the image route fails, the worker now looks the image up in
every cache before giving up, and says why it failed;npm run e2e:pwaprints that reason. Nothing to change in a
game. -
The browser jobs of CI no longer time out installing Playwright's system packages (4.5.0, #261): seven Linux
jobs (e2e,reality,pwa-firefox,cross-runtime,fresh-install,second-game,reference) run in
Playwright's own image, the lockfile's version pinned by digest, where the browsers and their libraries are already
installed;--with-depsfetched 52 MB from an apt mirror that sometimes served 50 kB/s. A test keeps the image's
version equal to the lockfile's. ffmpeg likewise comes from a static build on GitHub first, apt only if it fails. -
The WebKit e2e run no longer fails at random on "Context is stopped" after its save check (4.5.0, #263): the
save round trip reloads the page; when the offline warm-up had a Cache API call in flight, WebKit logged "Cache API
operation failed: Context is stopped" and the run failed, every check passed (4 times in 5 days on CI). WebKit logs
it itself, even for a promise the page catches, so the run now tolerates it during that reload as it already did
during the teardown: reported, not counted, on WebKit only, in that exact form. Nothing changes in the engine. -
The hosted Studio's end-to-end run no longer waits on a Play frame that never answers after "Load the latest
preview" (4.5.0, #264): on the Firefox pass, the script took the frame as soon as its address showed the expected
revision and spent 30 s on it, and the second click still expected the revision the first one read. Both clicks now
go through one helper that reads the latest revision at each click, looks the frame up again at each try with each
evaluation bounded, and loads the page once more (said in the log) if it stays silent. Nothing changes in the Studio.
Changes
-
The documentation's map,
wiki/manifest.json, andnpm run wiki(4.5.0). Every page of the coming bilingual wiki has an entry: 213 pages across the domains of the plan's catalogue, allplannedfor now except the four generated indexes. Each entry gives a stable id, a type, an audience, a context, its French and English titles, its public URLs (EN-Create-Rooms,FR-Creer-Salles), the source files it rests on and the questions a reader would ask.npm run wiki -- checkkeeps the manifest, the pages and their links consistent; it fails on errors only and lists the links to pages not written yet.statscounts the pages.build --target wikiwrites the folder the GitHub wiki will be published from: a bilingual home page, a sidebar per language, a footer, each ready page under its URL, and indexes by task, symptom, identifier and vocabulary. The repository also gains a bilingual glossary (wiki/data/glossary.json, with synonyms such as lease, bail and verrou) and page templates (wiki/_templates/).npm run ci:planalso names the wiki pages whose source files a change touches. Nothing is published yet. -
The coverage matrix of the documentation's move,
npm run wiki -- coverage(4.5.0). Before anything moves to the wiki, every section of the old documentation has a recorded destination.wiki -- coverage --inittakes the anchor inventory of the 32 pages ofdocs/enand ofdocs/frand of the root pages (both READMEs, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, CREDITS): every heading with the anchor GitHub gives it, the explicit<a id>and<a name>anchors, every code block, and the sentences that look like a limit (only, not, uniquement, pas encore…), proposed to a reviewer.wiki/coverage.jsonholds one row per section: its destinations in the wiki, keep, rewrite or drop (a drop says why), and two states, planned and migrated. Today all 1,036 sections are planned and none is migrated. Only roadmap milestones that are fully shipped are dropped, each naming its CHANGELOG version or git tag; a milestone that still lists open items moves to the roadmap page. A dropped section counts as reviewed only once each sentence that looks like a limit has been ticked.wiki -- coveragemerges a new inventory and keeps what a person wrote, following a renamed heading by its text; a changed section must be rechecked and acknowledged (--ack <key>); it also writeswiki/legacy.json, the order in which the eight pagesread_docserves will be rebuilt, and each manifest page'sreplaces.wiki -- coverageandwiki -- statsprint the two counters, "destinations planned" and "migrated and reviewed".docs/is now frozen: `wiki -- che...
v4.4.0
LOG #256–#267: "Author's Desk", the release an outside reading of 4.3 asked for: making a whole game simpler to make
and to check. A playable example of choosing the hero in two languages, games/heroes, with its browser test (building
it found three bugs, fixed below). The Studio's hero × language matrix (the Voices tab). Before/after captures of the
rooms the semantic diff names (a local Studio). The hosted preview measured phase by phase and made about three times
faster on the reference deployment: from a write to a ready preview, median 17.8 s → 5.6 s, max 26.0 s → 11.7 s over
ten revisions each (QUALIF-4.2.3-REFERENCE §6). From Horacio (LostInBrittany), thank you: a hotspot that acts on a
single tap (#221), one map pin for several places (#223), the phone's column on a short landscape screen (#227). The
137 mutation exemptions due at 4.4.0 read again, then read a second time: four bugs fixed with tests. Two new people's
passes (heroes-two-languages, studio-hero-matrix); none of the twenty-one has passed yet. Nothing to change in a
game (UPGRADING §41; strict SemVer: the new fields are optional). The tag is signed with the maintainer's SSH key on
their yes of 22 October for v4.4.0.
Fixed
-
The phone layout's column fits a short landscape screen (4.4.0, #227): on a phone in landscape under its
browser's bars (e.g. 844 × 320), the right-hand column was 300 px wide, each bag slot a square 87 px tall, the verbs
44 to 56 px, and the tools row (map, pause, music) went out at the bottom of the screen; six items or more (the bag's
page buttons) cut it on a 568 × 320 phone too. The column now fits its height: the slots stop being square first,
then the verbs come down to 44 px, and only when 44 px no longer fits do verbs and slots share the room; the tools
row never shrinks, and past a floor the column scrolls instead of cutting. The layout also reads the room inside the
safe areas (notch, home indicator) instead of the whole screen. Nothing to change in a game. -
The Studio checks a translation's verb keys too (4.4.0): a translation that gives two verbs the same key
(fr: verbs.take.key › "F" is already the key of "close") was refused bynpm run validateonly; the Studio's
guard, its processing worker and its in-browser demo said nothing.validatenow runs that check whenever it is
given the translations, so every caller says the same. -
A translation's voice clips ship with the game (4.4.0):
npm run assetsencoded onlyaudio.voices, so the
clips ofaudio.voicesByLang(a French recording) never reachedpublic/assets/audio/voices/and a French page
played nothing. They are now prepared like the others (keyed<lang>:<id>inassets.gen.json). -
npm run voices -- checkreads each language's own text (4.4.0): 4.3.1 taughtstatusthat a clip keyed on a
switch only the translation has (Reviens sain{form?gull=e|*=} et sau{form?gull=ve|*=f} !) belongs to that line;
checkstill read the source text and warned "a clip no line claims". -
npm run e2ein two languages in one run (4.4.0): the e2e harness kept the verb labels of the first page it
read, so a game script that reloaded the page in another language (?lang=fr) tapped the English labels. It reads
them again after each load. -
A Reality page that polls no longer piles up listeners (4.4.0): the HTTP port's wait between two polls (or two
reconnections of the stream) added anabortlistener to the port's signal and removed it only on an abort, never
when the wait ended on time. A page polling once a second held some 86 000 more closures a day, for as long as it
stayed open. Each wait now removes its listener when it ends, either way. -
An email's HTML read with a Turkish capital İ hides its scripts and comments again (4.4.0): the email
connector'shtmlToTextlooked for tags in the message's lower case at the original's positions, and a character
whose lower case is longer (İ is two characters in lower case) shifted them: after an İ, a<script>'s or a
comment's content leaked into the text an answer is read from, and visible text after a<style>could vanish.
Tags are now matched in ASCII lower case, as HTML names them, which keeps every position. -
The Studio's simulated Bridge no longer leaks a listener per wait (4.4.0): each wait for a signal left an
abortlistener on the connection, as the Reality port's sleep did (fixed in the same release); a long Studio
session gathered thousands. -
The CLI's login says why the provider refused it (4.4.0):
studio-server loginended on a bare
ResponseBodyErrorwhen the OpenID provider refused the device login; it now names the provider's code and reason,
e.g.not_allowed (Offline tokens not allowed for the user or client). STUDIO-SERVER says what that one needs: the
client scopeoffline_accesson the CLI client and the realm roleoffline_accessfor each member.
Changes
-
A hotspot can act on a single tap, as an exit can (4.4.0, #221): a hotspot that says
onTap: 'go'answers a
single tap with no verb picked as an exit withinput.exitOnTapdoes (#188): the hero walks there and acts on
arrival with the double tap's verb (defaultVerb). E.g. an endless aisle that says why one does not walk down it,
the LucasArts "Walk to" that gives a comment. Without it a tap on a hotspot only walks there, as before;
input.exitOnTapstays about exits. Input only: rules, solver and saves do not see it. Nothing to change in a game. -
One map pin for a city with several places (4.4.0, #223): a place's
portraitcan be a list, its faces side by
side on the pin, and a place that saysjoins: '<place>'sits on that place's pin instead of having its own: once
it is unlocked its face is added there, a tap on a face travels to that face's place, from any of them the pin is
"here" (only the current face ringed), and moving between two places of one pin draws no journey. E.g. a city whose
reception and office upstairs are both destinations. A pin with one place renders as before; the validator wants
the joined place to exist, in the same region, and not to join another itself. Nothing to change in a game. -
The Studio's hero × language matrix (4.4.0): the Voices tab has a second view, "Heroes × languages", read-only
and in the in-browser demo too. For each line with an id whose text (in any language) has a text switch or
{hero}, a column per hero (each value the switched flags take, headed by the variant's portrait and name) and per
language; each cell shows the text that hero reads, the clip that plays, and the keyed clip that is missing (the
plain id named when it plays instead). A row flags an untranslated language, a translation that drops a choice the
source makes, and a hero's value no switch of the line lists without*=; "Lines with a problem" keeps only those.
The same data asGET hero-matrixon the Studio API, and asheroMatrix(game, locales)in
src/engine/tools/hero-matrix.ts. -
The semantic diff draws the rooms it names, before and after (4.4.0): in the Check tab, under "Changes between
revisions", Capture the rooms (before / after) shows each room the changes name (its layout, its source, its
texts in a language; the first room when only shared files changed, such as the cast orsite.json; six at most)
at both revisions, side by side, with the rectangle where they differ outlined, or "no visible change". A moved
prop, a font or a text that overflows is seen at once. Each revision is built as a preview and drawn in headless
Chromium at a fixed 960 × 600; the PNGs are kept under.cache/studio/. New routeGET diff/<from>..<to>/captures.
A local Studio only (npm run studio, with Playwright): a hosted Studio answers that the captures need one, its
worker having no browser (docs/en/STUDIO.md "Check" lists the other limits). -
A hosted preview's time, phase by phase (4.4.0): a preview's log line (
"event":"preview") now says where its
time goes:queuedMs(from the write to the build's start: the coalescing, a build before it, the retries),
snapMs,jobMs(the worker's job as the Studio waits for it, its queue on the worker included),storeMsand the
whole wait,sinceWriteMs. Each job the Studio asks of the worker logs aworker-callline (putMs,postMs,
getMs,delMs), and the worker's ownworker-jobline saysqueueMs,fetchMs,unpackMs,childMsand
putMs. Two histograms read them,studio_preview_phase_seconds{phase}and
studio_worker_job_phase_seconds{kind,phase};studio_preview_build_secondsis said to start after the snapshot,
as it always did. -
A faster hosted preview (4.4.0): the worker runs one job at a time per lane, a preview's build in a lane of its
own, so it no longer waits behind the health's validator and solver (up to 45 s) or a load; each lane holds 8 jobs
at most. The Studio packs one snapshot per revision, shared by the preview, the health, the published builds and the
engine's upgrade (the lock, the repair and the walk once, not three times); stores a build's files 8 at a time; does
not write again a blob it stored less than an hour ago; and answers once it has read the worker's result, the
staging deleted after (a delete that fails is logged,worker-staging-left). Nothing changes for a game. -
A playable example of choosing the hero (4.4.0):
games/heroes, "Who Am I Today?", one garden and one puzzle
for every 4.3 hero-choice feature at once. Three heroes on portrait cards at New Game (Pixel, Biscuit, Gaby the
gull), their names in the lines ({hero}), Grandma's words switched for each ({form?…}), one rule only the gull
gets, English and French (the French table adds its own agreements:Prêtefor the gull), Grandma's lines voiced
per hero in both languages (garden.dust~form=gullin `audio.vo...
v4.3.1
LOG #254–#255: "Voices by Language", a patch on 4.3.0 after an outside reading of it. A translation that agrees a line
its source never agrees (Ready?, Prêt{form?witch=e|knight=|robot=} ?) now gets its voices by value through the
whole chain: export, import, orphans, the validator's warning (the command line, the Studio's server and its
in-browser demo alike) and playback. npm run solve is said to find one winning path, and --prove to prove every
state, every hero included, in the guides and in the solver's own sentence. A new mutation set, voices, gated from
the start. Nothing to change in a game (UPGRADING §40): no content field, save, schema or fingerprint moves (strict
SemVer, as since 4.2).
Fixed
- A translation's own switches get their voices (4.3.1): a translation may agree a line the source language never
agrees, e.g. EnglishReady?, FrenchPrêt{form?witch=e|knight=|robot=} ?.npm run voices -- export --lang fr
already listed the three French clips (ready~form=witch…), butimportrefused them as unknown lines,status
called their clips orphans, the Studio refused their status, andnpm run validatesaid nothing when one was
missing: those read the switches of the text as written, not as translated. Each language's clips now come from
its own text everywhere, the command line, the Studio's server (its guard and the processing worker) and its
in-browser demo alike; a switch a translation drops no longer asks for clips by value in that language either. The
engine already played the right clip; a test now holds the whole chain. And a line whose switches list only a
default ({form?*=there}) showed its braces in the recording table; it now reads as spoken. npm run solvesays it found one path, not a proof (4.3.1): the hero-choice guide saidnpm run solve"proves
every hero"; it stops at the first winning path, which one hero may be enough for. A game where only the robot can
finish printedsolved;--provereports its softlocks. The guide, CLASSICS, DESIGN and WORKFLOW now name
npm run solve -- --provefor that, and the witness's sentence, shared by the command line, its JSON, the Studio
and the MCP tool, reads "the ending is reached on one path (…; only --prove checks every state, every answer of a
choice included)".
Manual passes (D12: reported, not blocking)
0 of 19 done.
| Pass | Status | Who, when | Device, OS, browser, versions | What failed |
|---|---|---|---|---|
A multi-instance Bridge behind HTTPS on Postgres, a worker killed mid-verification (bridge-postgres-https) |
not-run | |||
Story, Remix and Daily runs submitted by real people (runs-real-players) |
not-run | |||
A run resumed after the machine was stopped and started again (run-resume-power-cycle) |
not-run | |||
The code wheel printed and used at the table; the text alternative by a screen-reader user (code-wheel-human) |
not-run | |||
A Mystery world played from the Remix menu against a deployed Bridge (mystery-deployed) |
not-run | |||
Safari offline on an iPhone, and its update (safari-ios-offline-update) |
not-run | |||
Firefox offline on a real machine (firefox-real-offline) |
not-run | |||
A real phone on both painters (phone-both-renderers) |
not-run | |||
A real LiveSplit and OBS session (livesplit-obs) |
not-run | |||
Real connectors and a person's security pass (connectors-real-security) |
not-run | |||
Playtesters who do not know the puzzles (blind-playtesters) |
not-run | |||
The voices listened to (voices-listening) |
not-run | |||
The published archive installed by a person (archive-human-install) |
not-run | |||
The Studio Server deployed on Clever Cloud (Node, PostgreSQL, Cellar, Keycloak, a TLS domain), from its archive, by its guide; a real Safari on macOS and iOS (studio-hosted) |
not-run | |||
Three people on one hosted Studio, a network cut and a redeploy while they edit (studio-collab) |
not-run | |||
On a hosted Studio, people play the shared revision without a dev server, a save carried across revisions, the health chip, their own undo, a published build (studio-preview) |
not-run | |||
A hosted Studio's prepare on the worker (Python, ffmpeg), its prepared set played in the Play tab (studio-assets-hosted) |
not-run | |||
An AI agent on a hosted Studio through its remote MCP (/mcp): a real MCP client, a member's token, a write in the journal as the member, a span another holds refused (studio-mcp-remote) |
not-run | |||
The Studio Server behind a second OpenID Connect provider (GitLab, D44): login, roles, a revoked member refused, verified by a person (studio-oidc-generic) |
not-run |
v4.3.0
LOG #246–#253: "Hero Choice". The player chooses the hero at New Game, among any number of heroes a game declares:
portraits in a choice, a name per character variant, and text switches ({hero}, {form?knight=him|witch=her}) so
the other characters' words follow the hero, in every language; voices follow the same keys (D49, ADR 0022). Most of
this release comes from an outside contributor, Horacio (LostInBrittany), thank you: the hero choice itself (#202),
a key per verb (#205), a proportional pixel font for the interface by default (#206), a job's late
answer that no longer overwrites its result (#211), a naming convention for titles and branches (#208) and a
conversation saved after each topic (#219); each was
reviewed, completed on its branch and merged into main, the 4.3 line. A game that uses none of it keeps its
saves, its logic fingerprint and schemaVersion 3; its interface font changes unless it sets its own (UPGRADING §39).
Breaking: the former web-scumm names, aliases through 4.2.x, are gone. A game that still imports
web-scumm/… runs npx quest-n-click migrate once, after installing quest-n-click (UPGRADING §39); the formats
keep their names. The tag is signed with the maintainer's SSH key on their yes of 21 October, « yes sign v4.3.0 »,
the yes for v4.3.0 only.
Breaking
- The
web-scummnames are gone (4.3.0, D47): the release no longer shipsweb-scumm-<version>.tgz,
create-web-scumm,web-scumm-bridge,web-scumm-connectorsorweb-scumm-studio-server(nor their SBOMs), the
packages no longer carry theweb-scumm,web-scumm-bridge,web-scumm-connector,web-scumm-studio-serverand
web-scumm-studio-workercommands, andweb-scumm/contentand the other old imports no longer resolve. A game still
under the old name runsnpm uninstall web-scumm, installsquest-n-click, thennpx quest-n-click migrate, which
now renames itsweb-scumm/…imports, its tsconfig's paths and its scripts' commands (--checknames what is left;
UPGRADING §39 also gives thegrep/sedby hand). On a hosted Studio such a game does not load until its owner runs
the engine's upgrade, which renames its imports first, as a revision of its own judged by loading only (a game that
cannot be finished yet is never stuck) and not undoable, then migrates its schema as before. Format identifiers (web-scumm-save, sessions,.wsrun,
storage keys, headers,WEB_SCUMM_*) keep their names, and releases up to 4.2.5 keep their files.
Fixed
- A prepared job keeps its result on screen (4.3.0, #210): on a hosted Studio, the Assets panel could replace
"prepared from revision 3: 218 files" with "Preparing the assets: done" a moment after the job ended, when a status
check sent before the end answered after it. Once a job has ended, the page hears nothing more of it, nor of a Cancel
sent just before the end and refused after it (the panel used to add "(the job has ended)" to its last word). This
also made the Firefox pass ofnpm run e2e:studio-hostedfail now and then. - A badge signed with a weak or foreign key is refused (4.3.0): the badge connector takes the key from the
issuer's documents, and the algorithm's name said nothing of its strength. Aneddsa-jcs-2022proof is now checked
with an Ed25519 key only (an RSA-512 key, whose signature is exactly 64 bytes, made a valid proof); a JWS (an OB3
JWT, an OB2 signed assertion and itspublicKeyPem, a status list) signed RS256 needs a modulus of at least 2048
bits, and ES256 a P-256 key. A 512-bit RSA key can be factored, so a badge it signed could be forged. Such badges
are nowinvalid. - The Bridge checks its limits before it starts (4.3.0): each of
config.json'slimitsmust be an integer, at
least 1 forstreamPage,bodyBytesandstreamBufferBytes(streamPage: 0made a fetch by cursor loop forever)
and at least 0 for the others, and a name that is no limit is refused. Before, any value was taken. A Bridge given
such a value does not start, and says which limit, the least it takes and what it was given; on a shared Bridge
that stops every tenant (UPGRADING §39 says how to look first). - A run whose world cannot be hashed is refused, not a crash (4.3.0): a
.wsrunwhose world holds a number JSON
reads as Infinity (1e400), or two keys equal once NFC-composed, isinvalid-replay/world-shape; the verifier
answeredinconclusive/crash. Loading such a stored world says the same. - A conversation saves after each topic (4.3.0, #218): a conversation is one action, and the autosave came only
when it ended, at "Bye". What a topic changed (a flag, an item, a minigame won) was lost if the page died before:
on a phone, a tab killed in the background or a battery gone flat sent the player back before the topic. The state
is now saved after each topic'sdo, once the topic is marked as seen, when the conversation is the rest of its
action (the talk verb, or a{ talk }that ends a rule); a talk followed by more commands is still saved at the
action's end, so a save never holds half ado. A loaded save opens no conversation: the player is in the room,
the topics seen marked. Nothing to change in a game.
Changes
-
The player chooses the hero at New Game (4.3.0): any number of heroes, one character with a variant per hero.
A game asks in its intro with achoicewhose options set a flag ({ set: ['form', 'witch'] }); the rest follows
from that flag. Nothing changes for a game that does not use it (no new condition, command or state, saves and
schemaVersion3 unchanged). CONTENT_GUIDE, "Choosing the hero"; UPGRADING §39. -
A character variant may carry a
name(4.3.0): while the variant applies, the speech box, the transcript, the
talk header and{name}in a fallback line call the character by it. Translated like any text
(char:<id>/variants[<i>].name). -
Text switches and
{hero}(4.3.0):{hero}is the active hero's name;{form?knight=him|witch=her|*=it}is the
text listed for the flag's current value,*=covering the rest. Filled when a text is shown (lines, choices,
looks, hints, toasts, captions, item and object names, objective titles), after the translation, so each language
writes its own words inside the braces (content{form?witch=e|*=}). The validator reports a malformed switch, a
flag no command sets, a value listed that nothing writes and a value written that a switch does not cover;
npm run i18n -- statuslists the translations that drop a choice their source's switches make. Room, verb,
character and variant names are shown as written: a switch there is an error. A 4.2 text holding a
literal{hero}or{word?…=…}is now filled (UPGRADING §39). -
Portraits in a choice (4.3.0):
imgon a choice option shows it as a card, the image with the option's text as
its label and its accessible name; the cards wrap and scroll, so a phone fits any number of heroes. -
Voices for lines with switches (4.3.0): a line whose words follow a flag (
{form?m=him|f=her}) is recorded once
per value: its clips go inaudio.voicesunder<id>~<flag>=<value>(grandma_saw~form=f); with several flags in
one line, sorted by name and joined with~(greet~form=f~mood=calm), values written as text (true,2). The
engine plays the clip for the flags as they stand and falls back to the plain id when that clip is missing or a flag
is not set yet, so a game can ship before every recording exists.voicesByLanguses the same keys.npm run voices
(and the Studio's Voices tab) lists one row per value, each with its text, andnpm run validatewarns when a voiced
line lacks a value's clip. The keys are only map keys: the file names stay the author's (AUDIO.md, "Voices"). -
The Studio previews each hero (4.3.0): the Play tab's Text switches picker sets the flag a game's switches
read (and shows{hero}'s name), and the Rooms tab shows under a switched text how it reads (local Studio;
STUDIO.md). -
npm run lintand the chosen hero (4.3.0):hero-looks-default(info) says when two or more values of the
hero's flag match no variant, so those heroes would look like the written one; a rule that only sets a flag read by
a character's variant or a text switch (the intro's choice) is no longer reported asaction-dead. -
A key per verb: O for Open, as in the LucasArts games (4.3.0, #203): a verb's optional
key
({ id: 'open', label: 'Open', color: '#8fd3ff', key: 'o' }) picks it at the keyboard as a tap on its button would,
in any case and without Ctrl, Alt or Meta, and does nothing while the engine is busy, a line is on screen or
something covers the scene. The button underlines the letter in its label and announces it (aria-keyshortcuts).
The key is translated with the label (verb:<id>/key).npm run validaterefuses a key that is not one character,
Space and a key two verbs share, in the game and in each translation. Withsettings: truethe player can turn the
keys off (a "Verb keys" row,ui.verbKeys, shown only when a verb has a key: WCAG 2.1.4), and the validator warns
about keys in a game without a settings menu. Withoutkey, nothing changes. -
A proportional pixel font for the interface (4.3.0, #204). The default
--font-uiis now Galmuri11 (OFL 1.1, a
125 KB Latin subset) instead of DotGothic16, a Japanese face whose fixed-width Latin left gaps aroundiandland
set the period a pixel above the line. Galmuri11 has a narrowi, a widemand a period on the baseline, close to
the LucasArts dialogue fonts, and asize-adjustkeeps a game's text at the same size. DotGothic16 now stands behind
every interface family, so Japanese text and the few symbols Galmuri11 lacks still render. A game that wants the old
look setsskin.fonts.ui: 'DotGothic16'; a game that already setsskin.fonts.uik...
v4.2.5
LOG #244–#245: the project is now called quest'n'click. It was web-scumm, named in homage to SCUMM, the LucasArts
engine whose spirit it follows, and is renamed so its name sparks no needless debate. Nothing changes for a
game: the formats keep their identifiers, so saves, runs and sessions load as before, and a game made for 4.2.4
installs, builds and plays under its old names. The old names (web-scumm, create-web-scumm, web-scumm-bridge,
web-scumm-connectors, web-scumm-studio-server, the web-scumm/… imports) work until 4.3: each 4.2.x release also
ships them as full copies. A release's files are named quest-n-click-… from this one, and its attestations name the
repository wanoo/quest-n-click. The tag is signed with the maintainer's SSH key on their yes of 20 October,
« Yes, sign v4.2.5 », the yes for v4.2.5 only.
Changes
-
web-scumm becomes quest'n'click (4.2.5, D47): the project, its repository (
github.com/wanoo/quest-n-click, the
old links redirect) and its Pages site (wanoo.github.io/quest-n-click; the old address redirects there through
wanoo.github.io) take the name quest'n'click. The packages and commands are nowquest-n-click,create-quest-n-click,
quest-n-click-bridge,quest-n-click-connectors(commandquest-n-click-connector) and
quest-n-click-studio-server(commandsquest-n-click-studio-server,quest-n-click-studio-worker); a game imports
quest-n-click/content,/player,/minigames,/testing,/reality; a release's files are named
quest-n-click-<version>.tgzandquest-n-click-<tag>-…. Nothing to change in a game: through 4.2.x each release
also shipsweb-scumm-<version>.tgzand the other old archives (the same files, the old package name), the old
commands run after a one-line notice, andweb-scumm/contentand the other old imports still resolve; the aliases go
in 4.3. The formats keep their identifiers (web-scumm-save,web-scumm-session,.wsrun,web-scumm-remix-1, the
IndexedDB and localStorage names, theX-Web-Scumm-*headers,WEB_SCUMM_*), so saves, runs, sessions and
deployments load and run as before. Releases up to v4.2.4 keep their files' names and their attestations' repository,
wanoo/web-scumm. The READMEs open on the new logo and a short "Formerly web-scumm" note; UPGRADING §38 says how to
switch, when you like. -
The sample games' credits say quest'n'click (4.2.5): the Pantry Key's credits ("A quest'n'click sample game",
"Sound effects: generated by quest'n'click") and the Night Market's credits and title-screen line ("The reference
chapter of quest'n'click"), in English and French. Their presentation fingerprint moves with them, so the reference
run is recorded again; their rules, saves and proofs do not change.
Manual passes (D12: reported, not blocking)
0 of 19 done.
| Pass | Status | Who, when | Device, OS, browser, versions | What failed |
|---|---|---|---|---|
A multi-instance Bridge behind HTTPS on Postgres, a worker killed mid-verification (bridge-postgres-https) |
not-run | |||
Story, Remix and Daily runs submitted by real people (runs-real-players) |
not-run | |||
A run resumed after the machine was stopped and started again (run-resume-power-cycle) |
not-run | |||
The code wheel printed and used at the table; the text alternative by a screen-reader user (code-wheel-human) |
not-run | |||
A Mystery world played from the Remix menu against a deployed Bridge (mystery-deployed) |
not-run | |||
Safari offline on an iPhone, and its update (safari-ios-offline-update) |
not-run | |||
Firefox offline on a real machine (firefox-real-offline) |
not-run | |||
A real phone on both painters (phone-both-renderers) |
not-run | |||
A real LiveSplit and OBS session (livesplit-obs) |
not-run | |||
Real connectors and a person's security pass (connectors-real-security) |
not-run | |||
Playtesters who do not know the puzzles (blind-playtesters) |
not-run | |||
The voices listened to (voices-listening) |
not-run | |||
The published archive installed by a person (archive-human-install) |
not-run | |||
The Studio Server deployed on Clever Cloud (Node, PostgreSQL, Cellar, Keycloak, a TLS domain), from its archive, by its guide; a real Safari on macOS and iOS (studio-hosted) |
not-run | |||
Three people on one hosted Studio, a network cut and a redeploy while they edit (studio-collab) |
not-run | |||
On a hosted Studio, people play the shared revision without a dev server, a save carried across revisions, the health chip, their own undo, a published build (studio-preview) |
not-run | |||
A hosted Studio's prepare on the worker (Python, ffmpeg), its prepared set played in the Play tab (studio-assets-hosted) |
not-run | |||
An AI agent on a hosted Studio through its remote MCP (/mcp): a real MCP client, a member's token, a write in the journal as the member, a span another holds refused (studio-mcp-remote) |
not-run | |||
The Studio Server behind a second OpenID Connect provider (GitLab, D44): login, roles, a revoked member refused, verified by a person (studio-oidc-generic) |
not-run |
v4.2.4
LOG #241–#243: four pull requests from an outside contributor, Horacio (LostInBrittany), found while making his game
"Jack cherche un taf", each reviewed, completed on its branch and merged: face: 'hero' turns a character toward the
hero (#194); the sprite sheets' background, transparent or a chosen colour (#187); an exit gone through on a single
tap (#191); actorGap, the hero's distance to a character following their size (#199). A game that uses none of them
plays, saves and proves as on 4.2.3: the same fingerprints, golden saves and reference runs. The content format gains
three optional, presentation-only fields, input.exitOnTap, an exit's onTap and actorGap, under D46 (a 4.2.x may
add such a field when it needs no schema, save envelope or IR change); the public API and the save envelope are
4.2.0's. The tag is signed with the maintainer's SSH key on their yes of 20 October, « Yes, sign v4.2.4 », the yes for
v4.2.4 only.
Fixed
face: 'hero'turns a character toward the hero (4.2.4):{ face: 'hero', who: 'grandma' }turned grandma to
the right wherever the hero stood, since the hero is not a hotspot, a prop nor an actor; thefacecommand now reads
the hero's position from the state, as it already did for the hero turning toward someone. Found while making
"Jack cherche un taf", in an opening cutscene.
Changes
-
The sprite sheets' background (4.2.4):
"sheetBackground"insite.json, next toartStyle, is
"transparent"or a#RRGGBBcolour (default#2B2E45, so a game without it gets the same prompts as before).
npm run promptswrites every sheet's background from it; in transparent mode the prompts ask for real alpha, forbid
a painted checkerboard and keep carved or open shapes (mouths, eyes) opaque.tools/cut-sheet.pycuts a sheet on a
transparent background on its alpha, keying nothing by colour (a flat-background sheet cuts exactly as before). Two
small scripts help choose:tools/check-alpha.pytells real alpha from a painted checkerboard, and
tools/pick-bg.pyranks the flat colours by their distance to a character's drawing (PROMPTS.md, "Choosing the
sheet background"). -
An exit can be gone through on a single tap (4.2.4, #188):
input: { exitOnTap: true }ingame.tsmakes a
single tap on an exit with no verb picked walk there and go through on arrival, with the double tap's verb, as in
the LucasArts classics; a locked exit says itslockedline. 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'sonTap: 'go' | 'walk'overrides the game's setting either way; the Studio offers it on the exit's sheet. Off by default: a game
without either plays exactly as before. Input only (presentation in the fingerprint): the rules, the solver and
saves do not see it. -
actorGap: the hero's distance to a character follows their size (4.2.4, #198): without anapproachpoint in
the layout, the hero stopped 44 units from any character, so tall characters who gesture as they talk put a hand in
each other's head.actorGapon the game (e.g.0.8) sets the distance to that share of the two characters' mean
height, times the depth scale where the character stands; omitted, nothing changes, and anapproachpoint still
wins.depthScaleandscaleAtmove tocore/stage.ts. Found while making "Jack cherche un taf".
Manual passes (D12: reported, not blocking)
0 of 19 done.
| Pass | Status | Who, when | Device, OS, browser, versions | What failed |
|---|---|---|---|---|
A multi-instance Bridge behind HTTPS on Postgres, a worker killed mid-verification (bridge-postgres-https) |
not-run | |||
Story, Remix and Daily runs submitted by real people (runs-real-players) |
not-run | |||
A run resumed after the machine was stopped and started again (run-resume-power-cycle) |
not-run | |||
The code wheel printed and used at the table; the text alternative by a screen-reader user (code-wheel-human) |
not-run | |||
A Mystery world played from the Remix menu against a deployed Bridge (mystery-deployed) |
not-run | |||
Safari offline on an iPhone, and its update (safari-ios-offline-update) |
not-run | |||
Firefox offline on a real machine (firefox-real-offline) |
not-run | |||
A real phone on both painters (phone-both-renderers) |
not-run | |||
A real LiveSplit and OBS session (livesplit-obs) |
not-run | |||
Real connectors and a person's security pass (connectors-real-security) |
not-run | |||
Playtesters who do not know the puzzles (blind-playtesters) |
not-run | |||
The voices listened to (voices-listening) |
not-run | |||
The published archive installed by a person (archive-human-install) |
not-run | |||
The Studio Server deployed on Clever Cloud (Node, PostgreSQL, Cellar, Keycloak, a TLS domain), from its archive, by its guide; a real Safari on macOS and iOS (studio-hosted) |
not-run | |||
Three people on one hosted Studio, a network cut and a redeploy while they edit (studio-collab) |
not-run | |||
On a hosted Studio, people play the shared revision without a dev server, a save carried across revisions, the health chip, their own undo, a published build (studio-preview) |
not-run | |||
A hosted Studio's prepare on the worker (Python, ffmpeg), its prepared set played in the Play tab (studio-assets-hosted) |
not-run | |||
An AI agent on a hosted Studio through its remote MCP (/mcp): a real MCP client, a member's token, a write in the journal as the member, a span another holds refused (studio-mcp-remote) |
not-run | |||
The Studio Server behind a second OpenID Connect provider (GitLab, D44): login, roles, a revoked member refused, verified by a person (studio-oidc-generic) |
not-run |
v4.2.3
"Coordination" (LOG #218–#240): the last of the three 4.2.x steps (D40, D44). A team organises its game in the
hosted Studio: a scenario map of chapters, puzzles and characters built on the game's stable ids, comments anchored
on those ids, and a diff between two revisions that names what changed. A write says the revision its editor read, and
a late one is refused with what changed, never merged on its own. AI agents work on the hosted Studio through its
remote MCP, as their member; an owner pushes the game to a Git repository and imports it back; a game made on 4.1.x is
upgraded on the server. The Assets tab works hosted (uploads, sheets cut on the worker), Prepare and the cuts can be
followed and cancelled, and a worker installed from the release archives prepares assets. An operator gets metrics, a
daily backup to the bucket and a runbook, run on Clever Cloud's test organisation. The Studio Server stays
experimental until its people's passes are made (studio-hosted, studio-collab, studio-preview,
studio-assets-hosted, and 4.2.3's studio-mcp-remote and studio-oidc-generic). Nothing a game or a player uses
changed: the public API, the content format and the save envelope are 4.2.0's. The tag is signed with the
maintainer's SSH key on their yes of 19 October, « Yes, sign v4.2.3 » (D37, D44).
Fixed
-
A Studio Server worker installed from the release archives prepares assets (4.2.3): the engine's package carries
the worker's Python lock (ops/studio/worker-requirements.lock), and the worker looks for the packages its build
hook installed in its own folder (.python, orWORKER_PYTHON_PATH) rather than beside the engine. STUDIO-SERVER
gives the hook for a worker from the archives. In 4.2.2 Prepare assets needed a worker deployed from the repository. -
The daily backup ran nothing (4.2.3, found by the reference deployment): with the configuration in
STUDIO_CONFIG_JSON(no--config), the Studio Server's commands dropped their first argument, soimport <dir>,
invite --email …and the dailybackup --to-bucketofops/studio/backup-cron.shfailed (a new file is required). Each now reads its arguments whole. -
An operation another process confirmed is shipped (4.2.3): a game imported by the CLI against the same database
emitted no event in the running instance, so it reached the bucket only at the next write there or a restart, and
the RPO grew meanwhile. The import now ships its operation before it ends, and the instance also ships every 30 s. -
invitereads its flags strictly (4.2.3): without--role, or with a flag where a value belongs, it took the
first argument instead (invite owner --email a@b.frinvited an owner); it now refuses, as its usage says. -
A slow start never serves older prepared assets (4.2.3): when an instance started, it wrote the latest prepared
set again in the background; a Prepare that ended meanwhile could see the start finish after it, serve the older set
again and delete the newer one's folder. The older set now steps aside. -
A deploy no longer refuses writes for minutes (4.2.3, found by the reference deployment): on Clever Cloud the old
instance lives on, renewing the service lease, for minutes after the new one is up, and was never seen to get
SIGTERM, so writes got 503s for about 2.5 min at each deploy. The new instance now asks for the lease and the old one
gives it back at its next renewal (10 s); an older instance never takes it from a newer one. A write that waited
for another instance's journal lock is a 503 to retry, no longer a 409 a client could take as a conflict, and only
the instance serving the writes repairs its folder (at once when it takes the lease, then every 30 s). The first
deploy of 4.2.3 replaces an instance that does not ask or give back: it still waits as before; the next ones do not. -
A worker on a machine whose clock is a little behind no longer refuses jobs (4.2.3, found by the reference
deployment): the Studio signs a job to live 5 min to the millisecond, and the worker refused one that lived longer
by its own clock, so a few milliseconds of skew between the two machines refused about half the jobs (health and
previews failed with "refused the job (403)"). The worker now allows 30 s of skew, logs why it refuses a job, and the
Studio says the worker's reason.
Changes
-
The gated mutation sets read again before 4.2.2's tag (4.2.2): a survivor of
mutation (core)found by the
4.2.2 candidate (a slot's revision never copied) is killed by a test, and the 16 new survivors ofstudio-opsin the
journal's reads of a past revision are killed (15) or named (1). No behaviour changed; the tagv4.2.2was made on
this commit, and its note is filed here. -
A plan for the coordination (4.2.3):
docs/dev/PLAN-4.2.3-COORDINATION.md(in French), ten guarantees each with
its limit and its test (H1–H10), the pull requests in order, the exit criteria, what stays open after 4.2.3, and
decisions D44 (the name, GitLab as the second OpenID provider, a deploy key, the diff's captures local) and D45 (no
captures in 4.2.3's diff). -
A write says what its editor read (4.2.3): on a hosted Studio each write carries the revision its editor read
(x-studio-base). When one of its files was written since, it is refused (409) with that file as it was read and as
it is now (the same tab's own writes since never refuse it): the Studio shows the lines that changed, keeps the
draft, and offers to apply the edit on the latest version, or for a whole document (a layout, the storyboard) to
replace the other change, said as such; never on its own. -
The scenario map (4.2.3): a Studio tab, Map, where a team declares its chapters, puzzles and characters, each a
list of what the game already names (a room, a rule id, an item, a character, an objective, a checkpoint), shown with
thefile:linethat writes it, or "not declared". Kept ingames/<id>/groupings.json, a span of its own on a shared
Studio; a grouping never writes what it names. The map is derived from the game's IR at each read, never stored.
Routes: GET/PUTgroupings, GETmap. -
The Studio Server's workshop mutation set is gated (4.2.3): the 104 survivors of
studio-workshop(the
backup, the journal shipped, the collection, the health, the previews, the builds and the prepared assets) are read
one by one: 94 killed by short tests (tests/studio-workshop-{backup,health,previews,prepared}-mutants.test.ts),
10 named indocs/dev/mutants.json. A survivor not named now fails the set's job, as for the other sets. -
The MCP on a hosted Studio (4.2.3, ADR 0029): a Studio Server serves its game's MCP tools at
/mcp(MCP's
Streamable HTTP, stateless), for an access token of the Studio's audience, never the session cookie; it says where its
authorization is (/.well-known/oauth-protected-resource, RFC 9728). Each tool is a request to the server's own API
with the caller's token: an agent writes as its member, with their role, their spans, in the journal.
npm run mcp -- --server <url>points the local MCP server at a hosted Studio the same way (the CLI's token). -
The game in Git (4.2.3): a hosted Studio configured with
git(a repository, a branch, a folder; its deploy key
the secretSTUDIO_GIT_KEY) lets an owner push the revisions since the last push as one commit, each author a
Co-authored-by, never forced (a branch that moved is refused), and import the branch as one revision. The Play tab
shows where the game goes and the last revision pushed. -
A hosted game made on 4.1.x is upgraded to this engine's authoring schema from the Studio Server (4.2.3): an
owner'sPOST /api/engine/migraterunsweb-scumm migrateon the processing worker under their exclusive lease (the
stable ids, thenschemaVersion: 3,saveVersion+ 1 and the ids' migration step ingame.ts), judges the migrated
game there (the validator, then the solver), and writes it as one undoable revision; a game it would break, or that
the solver cannot judge in time, is refused (422) and nothing is written. A game already at schema 3 has nothing to
do. STUDIO-SERVER (en, fr): "Upgrading the engine of a hosted game". -
Comments on a shared Studio (4.2.3): in the Notes tab, threads anchored on a stable id of the game (or the game)
and the revision they were written at, open to reviewers (kept by the Studio, never in a game file); an anchor the
game no longer names is said orphan; an owner brings the notes ofnotes.jsonin. -
What changed between two revisions, by name (4.2.3): the Check tab's "Changes between revisions" (and GET
diff/<from>..<to>) says, file by file, the ids a source added, removed or changed with the fields that changed,
the value paths of a JSON file, or "file changed" when nothing more can be said. The sources are read, never run. -
The gates of 4.2.3: two human passes asked from 4.2.3 on,
studio-mcp-remote(an AI agent on a hosted Studio
through its remote MCP) andstudio-oidc-generic(the Studio Server behind a second OpenID Connect provider, GitLab). -
The Assets tab on a hosted Studio (4.2.3): a team on the Studio Server now uploads sounds and decors, cuts
generated sprite sheets and keys single cells from the Assets tab, as on a local Studio. An upload is 10 MiB at most
(413) and its type is read from its bytes (PNG, JPEG or WebP images; Ogg, MP3 or WAV sounds; 415 otherwise); it is
written as one revision, never run. The cutting runs on the processing worker under the project's exclusive lease,
and its cells, with the backups of those recut, are one revision. The uploaded sheet itself is not kept on a hosted
Studio. -
For scripts calling the Studio Server (4.2.3):
POST /api/assets/preparenow answers 202 with a job instead of
the prepared run; poll `GET /api/jobs/...
v4.2.2
"Playable Workshop" (LOG #206–#217): the second of the three 4.2.x steps (D40, D42). On a hosted Studio, everyone
plays the shared revision without a dev server: the processing worker builds a preview of each revision, and a save
carried into another revision asks before it is resumed (ADR 0026). Every diagnostic names its revision; the header
gives the game's health at the head and, when it breaks, the revision that broke it. Undo and redo come back, each
member's own. The team publishes immutable, numbered builds with signed manifests, prepares the game's assets on the
worker (sprite sheets and uploads stay local until 4.2.3), and an operator backs the Studio up, ships its journal to
the bucket (the RPO is in /readyz) and restores it. Vite has left the Studio Server. It stays experimental
until its people's passes are made (studio-hosted, studio-collab, and 4.2.2's studio-preview and
studio-assets-hosted). Nothing a game or a player uses changed: the public API, the content format and the save
envelope are 4.2.0's. The tag is signed with the maintainer's SSH key on their yes of 18 October, « yes sign
4.2.2 » (D37, D42).
Changes
-
Dependencies (4.2.2): zod 4.6.7, Playwright 1.64.1, @axe-core/playwright 4.14, @vitest/coverage-v8 5.0.5; the
bridge's locks follow zod. Nothing changes for a player, an author or a host. -
The hosted Studio shows the preview of a revision (4.2.2): after each change the processing worker builds the
game at the head (a new job kind,preview; a burst of changes builds its last revision only), the Studio stores its
files by content and serves them on the game's origin at/preview/<rev>/, behind the game session. The room view
loads a newer preview once it is built, unless a placement is not saved; the label under it names the revision
shown, the one building, and a revision that does not build, with its reason. The editing tools enter the preview's
build (VITE_STUDIO_PREVIEW); the player's build is unchanged. -
Vite has left the Studio Server (4.2.2): 4.2.1's live preview (Vite in middleware mode on the game's origin) is
gone, with its allow-list of engine paths; the game's origin serves a revision's built files and the prepared assets
of the prepared set andpublicDir, and the published builds. The worker still needs the engine's Vite to build. -
A save made in another revision asks before it is resumed (4.2.2, ADR 0026): in a hosted Studio's preview, a
save records the revision and the fingerprint of the content it was made in (beside it: the save format does not
change). Resumed where the content differs, on Continue or from a slot, the game asks: Resume here (it may fail if
this revision removed what the save refers to), Restart, or Stay in the save's own revision. The same content under
another revision number asks nothing. Escape cancels the question. The player's builds are unchanged. -
A plan for the playable workshop (4.2.2):
docs/dev/PLAN-4.2.2-ATELIER-JOUABLE.md, eight guarantees each with
its limit and its test, the pull requests in order, the exit criteria, and decision D42 (the preview's path, the
fresh rebuild, the RPO target, public builds). -
Every diagnostic says its revision (4.2.2): on a hosted Studio each read answer carries the revision it was made
at (x-studio-revision); the Check tab shows it, and marks its results stale as soon as the head moves on. -
Lint, the solver and the playtests run on the processing worker (4.2.2), with the game's own commands (before,
a hosted Studio ran them on the data it read back, without them); the solver is bounded to 45 s there. -
The game's health by revision, and the revision that broke it (4.2.2): a hosted Studio's header says the
verdict at a revision (the validator, then the solver from a new game, on the processing worker, after each burst
of changes). When a revision fails where the last judged one passed, a bisection names the first failing revision,
with what its operation did and its files, in ⌈log₂ n⌉ checks; a dev can ask for one (POST /api/health/bisect). A
solver cut short says "unknown", never "valid". -
Personal undo on a shared Studio (4.2.2): Undo and Redo come back on a hosted Studio, each member's own: their
last operation is undone by a new revision (its files back to their content before), whatever the others wrote
meanwhile; refused when another wrote one of its files since (409) or holds its span (423). A new edit drops the
member's redo; the stacks are kept in the database. -
Published builds (4.2.2): a hosted Studio's Play tab publishes the head as an immutable, numbered build of the
player's game, served at/play/<n>/on the game's origin;/play/is the last green build (its revision judged
valid). Each build's manifest (revision, engine, snapshot and file hashes) is signed with the Studio's job key; an
owner can rebuild one fresh and compare it byte for byte.publicBuilds(off by default) opens/play/without a
session. -
The asset tools on the processing worker, qualified (4.2.2): their Python packages are locked with hashes for
Clever Cloud's native runtime (ops/studio/worker-requirements.lock) and installed by the worker's build hook
(STUDIO-SERVER gives the variable); measured on the spike's worker: 7 s to install, 4 s for a fullprepareof the
sample game (docs/dev/QUALIF-4.2.2-ASSETS.md). -
Prepare assets on a hosted Studio (4.2.2): the Play tab's "Prepare assets" (a dev) runs
prepareon the
processing worker from the head's art and sounds, under the project's exclusive lease; the prepared set is kept by
the Studio and served beforepublicDir, and the game'sassets.gen.jsonis written as one operation. Sprite sheets
and uploads stay on a local Studio (4.2.3). -
Backup, restore and the journal shipped (4.2.2): a hosted Studio ships each confirmed operation to the bucket
(/readyzgives the RPO);studio-server backupwrites the project's tables in one snapshot,restore --replay
brings them back into an empty database then replays the journal shipped since, each operation checked against the
head it continues (replayresumes a replay that stopped);gclists (and with--delete
deletes) the blobs no row names and the worker's old staging. -
The gates of 4.2.2: two human passes asked from 4.2.2 on,
studio-preview(people play the shared revision of a
hosted Studio without a dev server, carry a save across revisions, read the health chip, undo their own work,
publish a build) andstudio-assets-hosted(prepare on the worker, its set played); a mutation set
studio-workshop(undo, health, previews, builds, prepared assets, backup, the journal shipped, the collection),
measured until its survivors are read.
Manual passes (D12: reported, not blocking)
0 of 17 done.
| Pass | Status | Who, when | Device, OS, browser, versions | What failed |
|---|---|---|---|---|
A multi-instance Bridge behind HTTPS on Postgres, a worker killed mid-verification (bridge-postgres-https) |
not-run | |||
Story, Remix and Daily runs submitted by real people (runs-real-players) |
not-run | |||
A run resumed after the machine was stopped and started again (run-resume-power-cycle) |
not-run | |||
The code wheel printed and used at the table; the text alternative by a screen-reader user (code-wheel-human) |
not-run | |||
A Mystery world played from the Remix menu against a deployed Bridge (mystery-deployed) |
not-run | |||
Safari offline on an iPhone, and its update (safari-ios-offline-update) |
not-run | |||
Firefox offline on a real machine (firefox-real-offline) |
not-run | |||
A real phone on both painters (phone-both-renderers) |
not-run | |||
A real LiveSplit and OBS session (livesplit-obs) |
not-run | |||
Real connectors and a person's security pass (connectors-real-security) |
not-run | |||
Playtesters who do not know the puzzles (blind-playtesters) |
not-run | |||
The voices listened to (voices-listening) |
not-run | |||
The published archive installed by a person (archive-human-install) |
not-run | |||
The Studio Server deployed on Clever Cloud (Node, PostgreSQL, Cellar, Keycloak, a TLS domain), from its archive, by its guide; a real Safari on macOS and iOS (studio-hosted) |
not-run | |||
Three people on one hosted Studio, a network cut and a redeploy while they edit (studio-collab) |
not-run | |||
On a hosted Studio, people play the shared revision without a dev server, a save carried across revisions, the health chip, their own undo, a published build (studio-preview) |
not-run | |||
A hosted Studio's prepare on the worker (Python, ffmpeg), its prepared set played in the Play tab (studio-assets-hosted) |
not-run |
v4.2.1
"Shared Floor" (LOG #188–#205): the Studio online for a team, the first of three 4.2.x steps. One game per
server, several people at once: they log in through an OpenID Connect provider (Keycloak or another), get a role in
the Studio, and edit the same game live, each change one operation of a durable journal, each part of the game held
by one member at a time. The game's code runs in a separate processing worker that holds no secret, and the game is
played on its own origin, apart from anyone's Studio session. It is deployed as two native Node.js applications on
Clever Cloud with PostgreSQL and Cellar, from the repository or from the new web-scumm-studio-server archive. It
is experimental until its two people's passes are made (studio-hosted, studio-collab): the game in the Studio
is a live preview, not frozen at a revision (per-revision previews come in 4.2.2), and a real Safari has not tried
the game's frame yet. Nothing a game or a player uses changed: the public API, the content format and the save
envelope are 4.2.0's. An author may notice that the Studio and the tools that write a game need Node 22.13+
(their writes go through the journal, in SQLite) and refuse an edit that would add a validation error
(UPGRADING §34). The tag is signed with the maintainer's SSH key on their yes of 18 October.
Fixed
- The e2e run tolerates WebKit's "Context is stopped" during its teardown (WebKit stopping its context as the run
ends, a Cache API call cut short): reported as a warning, ase2e:pwadoes since 4.1.20, only for an error that
came after every check passed, on WebKit, in that exact form.
Changes
-
A plan to put the Studio online for a team (4.2.1 → 4.2.3):
docs/dev/PLAN-4.2.1-4.2.3-STUDIO-SERVER.md, behind
OpenID Connect on Clever Cloud's native Node.js runtime, with ADR 0025 and decisions D39 to D41. -
The Studio's operation core moves to
studio-server/src/ops/(4.2.1, neutral).core.ts,source.ts,
apply.tsanderrors.tsleavetools/studio/for the Studio Server's package, where the transactional operations of
the next pull requests grow. Nothing an author sees changes: the dev server, the MCP server,npm run studio-apply
and the demo Studio run the same code from its new place. The browser Studio now names its API through one constant
(src/studio/api-base.ts) instead of spelling/__studio/apiin six modules. -
Every Studio write is one journaled operation (4.2.1). The files an edit touches are written together or not at
all, durably, and an edit a crash interrupted is finished when the Studio opens again (.cache/studio-journal/).
A change made outside the Studio (an editor, an agent, git) is recorded before the next edit touches that file;
a file another process rewrote while an edit was prepared is a 409, never a lost write. -
One guard for every edit, on the game as it would be (4.2.1): text edits, layouts, new entities and voices are
refused (422) when they would add a validation error, like structured values since 3.4. Nothing is written first and
taken back any more. -
Adding a prop, hotspot or actor is one step of undo (4.2.1): the room and its layout come back together.
-
The authoring tools write a game through the Studio's journal (4.2.1):
npm run ids,i18n,voices(import),import-layout,provenance -- --lock,page:storyboard -- --md,
promptsandremixwrite their game files as one operation of the journal;- asset uploads (sheets, cells, decors, sounds) too. Backups and the cells a cut produced land together, or none
of them; - a Studio open on the same game sees each operation, and a file it rewrote meanwhile is a 409.
- The batch tools (
ids,i18n,provenance) are journaled, not guarded: they rename paths, which changes the
text of existing validation errors.
-
The placement editor saves through the Studio's API (4.2.1):
PUT /__studio/api/room/<id>/layout, one guarded
operation. The dev server's/__layoutroute is gone;layoutWriter()stays exported as a deprecated plugin that
does nothing. -
studio-server, the Studio as a server (4.2.1, first part; authentication comes next and it serves the loopback
only until then).studio-server serve --config <file>serves the Studio's API under/api/, with:- the journal's cursor (
/api/revision,/api/ops?epoch&after); - the probes
/livez,/startz(started) and/readyz(serving writes); - a service lease so that one instance serves the writes;
- operation ids from the client (
x-studio-op): a retry gets the same answer and is applied once.
Other commands:
studio-server import <dir>makes a game the instance's project;studio-server export <dir>writes the game at its head with a manifest of hashes.
On Postgres and an S3 store (Cellar), the database and the blobs are the truth and the instance's folder is rebuilt
at start and checked every 30 s. - the journal's cursor (
-
The Studio Server behind OpenID Connect (4.2.1).
- Login: with Keycloak or any OIDC provider (Authorization Code + PKCE). The rights live in the Studio: owner,
dev or reviewer, given by one-time invitations to a verified email (studio-server invite,POST /api/invitations). The owner's routes: members, roles, sessions, audit. - Mutations: a mutation from the browser needs the exact
Originand theX-Studio-Requestheader. The CLI and
MCP use Bearer access tokens for the Studio's audience, obtained withstudio-server login --server <url>(device
flow). - Access ends:
- at the next request when a member is removed;
- at once on the provider's back-channel logout;
- within 15 min when the provider disables the user, or when it stays unreachable (the session is then
suspended until it answers).
- Without OIDC: the server stays on the loopback.
- Login: with Keycloak or any OIDC provider (Authorization Code + PKCE). The rights live in the Studio: owner,
-
A hosted Studio never runs the game's code itself (4.2.1, ADR 0028).
- With OIDC, validation and the loads behind the Studio's reads run in
studio-worker, a separate application
with no secret of the platform. Configureworker: { url, instance }and the secretSTUDIO_WORKER_KEY; create
the keys withstudio-server worker-keys --out <folder>. The worker needsWORKER_PUBLIC_KEYand
WORKER_INSTANCE. - Captures are unavailable on a hosted Studio for now.
- Without OIDC (the local server, on the loopback), nothing changes.
- A game that imports files outside its own folder cannot run on a hosted Studio.
- With OIDC, validation and the loads behind the Studio's reads run in
-
Working together on a shared Studio (4.2.1).
- Each file has a span: a room (its code and layout), a language, the story, the items, and so on. A write
takes the spans it touches. If another member is editing one of them, the write is refused (423) with their
name, and the client keeps its draft. A lease lasts 2 min and is renewed by the heartbeat
(POST /api/presence). Members can take spans ahead (POST /api/leases) and release them. - Owners can take a span over (audited) and take the whole game for an exclusive change.
GET /api/eventsstreams every revision (with its files and spans), the leases and who is here. A stream
closes as soon as its member is removed or logged out, or when its token ends.- Undo is not available on a shared Studio until 4.2.2.
- Each file has a span: a room (its code and layout), a language, the story, the items, and so on. A write
-
The game runs on its own origin (4.2.1): a hosted Studio serves everything that runs the game on
playUrl, never
on the Studio's origin. The Studio opens it with a one-time ticket (POST /api/play-ticket). The game's access ends
at the next request when the member is removed or their Studio session ends. -
A guide to put a Studio online (4.2.1):
docs/en/STUDIO-SERVER.md(French:docs/fr/STUDIO-SERVER.md) deploys
the Studio Server and its worker as two native Node.js applications on Clever Cloud, with PostgreSQL, Cellar and
Keycloak or another OpenID provider. An example configuration (ops/studio/studio.config.example.json), a test
Keycloak realm (ops/studio/keycloak/realm.json) and an optional local stack (ops/studio/compose.yml) come with
it.npm run studio-serverandnpm run studio-workerstart them;servealso takes its configuration from
STUDIO_CONFIG_JSON. -
The hosted Studio is tested against a real Postgres and S3 in CI (4.2.1): a
studio-hostedjob runs its hosted
tests on every change to the Studio Server; its code counts in the coverage, with floors on its trust boundaries. -
The Studio Server's journal is held by mutation testing (4.2.1): the set
studio-ops(the journal, its durable
writes, the spans) is gated, every surviving mutant killed by a test or named. -
The Studio Server's trust boundaries are held by mutation testing (4.2.1): the set
studio-auth(the gate,
the OpenID client, the routes and store of authentication, the spans' leases, the game's origin) is gated, every
surviving mutant killed by a test or named. -
The Studio Server is tested against a real Keycloak in CI (4.2.1): a login through Keycloak's own form, the
identity it gives, a back-channel logout Keycloak sends, and a user an admin disables. -
The hosted Studio opens in a browser (4.2.1): the Studio Server serves the Studio's page
(npm run studio-clientbuilds it;CC_POST_BUILD_HOOKon Clever Cloud), which talks to/apiand logs in
through the provider. The game runs on its own origin as a live preview (Vite behind the game's session, not frozen
at a revision): the Rooms editor and the Play tab open there with a ticket, and the editor hands its layout to the
Studio, which writes it as the member. Undo, the Assistant and the Assets tab are not on a hosted Studio yet; the Play tab's
inspector panels need the local Studio. New optional settingspublicDirandclientDir
(docs/en/STUDIO-SERVER.md). -
The Studio Server as a release archive (4.2.1): `web-scumm-stud...