Skip to content

Releases: wanoo/quest-n-click

v4.5.0

Choose a tag to compare

@github-actions github-actions released this 23 Oct 21:06
Immutable release. Only release title and notes can be modified.
aff6caa

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_docs no longer reads the word "studio" as the variable STUDIO (4.5.0). An identifier written in capitals (an environment variable such as STUDIO, PATH or PORT) 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 (PATH before WORKER_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 gives url.current, the wiki's page, labelled as the current documentation rather than this version's, and url.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 at publication.json), and for any other build null with 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, as check --publish already 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 --publish wants a person's review before it publishes it. translations --ack and review-claim now take --kind human|ai and --relation author|independent.

  • The scenario map's span can be leased on a shared Studio (4.5.0). POST /api/leases, /api/leases/release and /api/leases/takeover answered 400 for the span groupings (groupings.json), though the Spans panel lists it. The routes now accept exactly the spans studio-server/src/ops/spans.ts gives files (plus room:<id> and lang:<code>), so the two lists cannot drift apart again.

  • npx quest-n-click doctor runs in a created project (4.5.0). It imported Playwright at the start and stopped with ERR_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, then npx playwright install chromium.

  • npm run page:world and npm run page:puzzles write a .html file (4.5.0). By default they wrote dist-pages/world and dist-pages/puzzles, without an extension; an explicit --out file.html is 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:pwa prints 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-deps fetched 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, and npm 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, all planned for 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 -- check keeps the manifest, the pages and their links consistent; it fails on errors only and lists the links to pages not written yet. stats counts the pages. build --target wiki writes 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:plan also 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 --init takes the anchor inventory of the 32 pages of docs/en and of docs/fr and 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.json holds 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 -- coverage merges 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 writes wiki/legacy.json, the order in which the eight pages read_doc serves will be rebuilt, and each manifest page's replaces. wiki -- coverage and wiki -- stats print the two counters, "destinations planned" and "migrated and reviewed". docs/ is now frozen: `wiki -- che...

Read more

v4.4.0

Choose a tag to compare

@github-actions github-actions released this 22 Oct 22:48
Immutable release. Only release title and notes can be modified.
c00b44f

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 by npm run validate only; the Studio's
    guard, its processing worker and its in-browser demo said nothing. validate now 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 assets encoded only audio.voices, so the
    clips of audio.voicesByLang (a French recording) never reached public/assets/audio/voices/ and a French page
    played nothing. They are now prepared like the others (keyed <lang>:<id> in assets.gen.json).

  • npm run voices -- check reads each language's own text (4.4.0): 4.3.1 taught status that 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;
    check still read the source text and warned "a clip no line claims".

  • npm run e2e in 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 an abort listener 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's htmlToText looked 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
    abort listener 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 login ended on a bare
    ResponseBodyError when 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 scope offline_access on the CLI client and the realm role offline_access for 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 with input.exitOnTap does (#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.exitOnTap stays 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 portrait can be a list, its faces side by
    side on the pin, and a place that says joins: '<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 as GET hero-matrix on the Studio API, and as heroMatrix(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 or site.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 route GET 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), storeMs and the
    whole wait, sinceWriteMs. Each job the Studio asks of the worker logs a worker-call line (putMs, postMs,
    getMs, delMs), and the worker's own worker-job line says queueMs, fetchMs, unpackMs, childMs and
    putMs. Two histograms read them, studio_preview_phase_seconds{phase} and
    studio_worker_job_phase_seconds{kind,phase}; studio_preview_build_seconds is 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ête for the gull), Grandma's lines voiced
    per hero in both languages (garden.dust~form=gull in `audio.vo...

Read more

v4.3.1

Choose a tag to compare

@github-actions github-actions released this 21 Oct 23:50
Immutable release. Only release title and notes can be modified.
829d112

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. English Ready?, French Prêt{form?witch=e|knight=|robot=} ?. npm run voices -- export --lang fr
    already listed the three French clips (ready~form=witch…), but import refused them as unknown lines, status
    called their clips orphans, the Studio refused their status, and npm run validate said 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 solve says it found one path, not a proof (4.3.1): the hero-choice guide said npm 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 printed solved; --prove reports its softlocks. The guide, CLASSICS, DESIGN and WORKFLOW now name
    npm run solve -- --prove for 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

Choose a tag to compare

@github-actions github-actions released this 21 Oct 14:48
Immutable release. Only release title and notes can be modified.
439ed29

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-scumm names are gone (4.3.0, D47): the release no longer ships web-scumm-<version>.tgz,
    create-web-scumm, web-scumm-bridge, web-scumm-connectors or web-scumm-studio-server (nor their SBOMs), the
    packages no longer carry the web-scumm, web-scumm-bridge, web-scumm-connector, web-scumm-studio-server and
    web-scumm-studio-worker commands, and web-scumm/content and the other old imports no longer resolve. A game still
    under the old name runs npm uninstall web-scumm, installs quest-n-click, then npx quest-n-click migrate, which
    now renames its web-scumm/… imports, its tsconfig's paths and its scripts' commands (--check names what is left;
    UPGRADING §39 also gives the grep/sed by 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 of npm run e2e:studio-hosted fail 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. An eddsa-jcs-2022 proof 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 its publicKeyPem, 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 now invalid.
  • The Bridge checks its limits before it starts (4.3.0): each of config.json's limits must be an integer, at
    least 1 for streamPage, bodyBytes and streamBufferBytes (streamPage: 0 made 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 .wsrun whose world holds a number JSON
    reads as Infinity (1e400), or two keys equal once NFC-composed, is invalid-replay / world-shape; the verifier
    answered inconclusive / 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's do, 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 a do. 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 a choice whose 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
    schemaVersion 3 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 -- status lists 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): img on 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 in audio.voices under <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. voicesByLang uses the same keys. npm run voices
    (and the Studio's Voices tab) lists one row per value, each with its text, and npm run validate warns 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 lint and 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 as action-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 validate refuses a key that is not one character,
    Space and a key two verbs share, in the game and in each translation. With settings: true the 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. Without key, nothing changes.

  • A proportional pixel font for the interface (4.3.0, #204). The default --font-ui is now Galmuri11 (OFL 1.1, a
    125 KB Latin subset) instead of DotGothic16, a Japanese face whose fixed-width Latin left gaps around i and l and
    set the period a pixel above the line. Galmuri11 has a narrow i, a wide m and a period on the baseline, close to
    the LucasArts dialogue fonts, and a size-adjust keeps 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 sets skin.fonts.ui: 'DotGothic16'; a game that already sets skin.fonts.ui k...

Read more

v4.2.5

Choose a tag to compare

@github-actions github-actions released this 20 Oct 23:15
Immutable release. Only release title and notes can be modified.
c81681f

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 now quest-n-click, create-quest-n-click,
    quest-n-click-bridge, quest-n-click-connectors (command quest-n-click-connector) and
    quest-n-click-studio-server (commands quest-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>.tgz and quest-n-click-<tag>-…. Nothing to change in a game: through 4.2.x each release
    also ships web-scumm-<version>.tgz and the other old archives (the same files, the old package name), the old
    commands run after a one-line notice, and web-scumm/content and 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, the X-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

Choose a tag to compare

@github-actions github-actions released this 20 Oct 14:29
Immutable release. Only release title and notes can be modified.
35cf6e0

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; the face command 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" in site.json, next to artStyle, is
    "transparent" or a #RRGGBB colour (default #2B2E45, so a game without it gets the same prompts as before).
    npm run prompts writes 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.py cuts 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.py tells real alpha from a painted checkerboard, and
    tools/pick-bg.py ranks 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 } in game.ts makes 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 its locked line. A picked verb still wins, a tap elsewhere during the
    walk still redirects the hero (and nothing is gone through), a double tap is unchanged. An exit's onTap: '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 an approach point 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. actorGap on 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 an approach point still
    wins. depthScale and scaleAt move to core/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

Choose a tag to compare

@github-actions github-actions released this 20 Oct 02:43
Immutable release. Only release title and notes can be modified.
597e7e5

"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, or WORKER_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, so import <dir>,
    invite --email … and the daily backup --to-bucket of ops/studio/backup-cron.sh failed (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.

  • invite reads 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.fr invited 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 of studio-ops in the
    journal's reads of a past revision are killed (15) or named (1). No behaviour changed; the tag v4.2.2 was 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
    the file:line that writes it, or "not declared". Kept in games/<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/PUT groupings, GET map.

  • 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 in docs/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 secret STUDIO_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's POST /api/engine/migrate runs web-scumm migrate on the processing worker under their exclusive lease (the
    stable ids, then schemaVersion: 3, saveVersion + 1 and the ids' migration step in game.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 of notes.json in.

  • 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) and studio-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/prepare now answers 202 with a job instead of
    the prepared run; poll `GET /api/jobs/...

Read more

v4.2.2

Choose a tag to compare

@github-actions github-actions released this 19 Oct 07:38
Immutable release. Only release title and notes can be modified.
5051530

"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 and publicDir, 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 full prepare of 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 prepare on 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 before publicDir, and the game's assets.gen.json is 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
    (/readyz gives the RPO); studio-server backup writes 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 (replay resumes a replay that stopped); gc lists (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) and studio-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

Choose a tag to compare

@github-actions github-actions released this 18 Oct 16:01
Immutable release. Only release title and notes can be modified.
2a25549

"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, as e2e:pwa does 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.ts and errors.ts leave tools/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/api in 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,
      prompts and remix write 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 /__layout route 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 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 Origin and the X-Studio-Request header. The CLI and
      MCP use Bearer access tokens for the Studio's audience, obtained with studio-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.
  • 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. Configure worker: { url, instance } and the secret STUDIO_WORKER_KEY; create
      the keys with studio-server worker-keys --out <folder>. The worker needs WORKER_PUBLIC_KEY and
      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.
  • 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/events streams 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.
  • 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-server and npm run studio-worker start them; serve also 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-hosted job 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-client builds it; CC_POST_BUILD_HOOK on Clever Cloud), which talks to /api and 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 settings publicDir and clientDir
    (docs/en/STUDIO-SERVER.md).

  • The Studio Server as a release archive (4.2.1): `web-scumm-stud...

Read more

v4.2.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 00:03
Immutable release. Only release title and notes can be modified.
a0ce19d

"Stable World" (LOG #185–#187): 4.1.20's code, the version changed (plan 4.1.20 §12, D34), one Field Kit fix, and
ship preflight-stable reading the reduced verdict below. It
restores strict SemVer: from 4.2.0 a minor adds, a major removes. It ships before the people's passes (D38, the
maintainer's decision): none of the thirteen (#75–#87) was made, so 4.2.0 promises as stable only what needs none,
the content format and its tools, the desktop Chromium PWA and the Bridge on SQLite, and says every other surface
limited or experimental (GO / NO-GO 4.2: GO on a reduced scope). The passes are made on
v4.2.0 itself; promoting a surface in a 4.2.x needs its pass on that 4.2.x. The tag is signed with the maintainer's SSH key
and the release is immutable. npm publishing moves to a later 4.2.x: it needs the maintainer's 2FA at each
step. v4.2.0-rc.1 carried the defect below; v4.2.0-rc.2 was the campaign's candidate, no pass used either.

Fixed

  • field:check and field:report work on a folder fresh from field:init (4.2.0): field:init writes the
    candidate's surfaces and site files beside the thirteen reports (4.1.20), and the two read them as reports, so the
    first check of every pass failed and field:report wrote nothing. Found trying the commands on v4.2.0-rc.1's
    candidate before any pass began; the test folder now holds what field:init writes.

Changes

  • The README installs from 4.2.0's release archive (4.2.0): releases/download/v4.2.0/web-scumm-4.2.0.tgz.
    docs:truth, quality:baseline and readme-quickstart still accept a v<version>-rc.N release for this
    version's archive and nothing else, for the next campaign that needs one.
  • ship preflight-stable reads "GO for 4.2 on a reduced scope" (4.2.0, D38): the maintainer's decision to ship
    before the passes is not a GO, so no report contradicts it; a plain GO the reports contradict still blocks, and
    tests/support-truth.test.ts accepts the reduced verdict only with D38 written.

Manual passes (D12: reported, not blocking)

0 of 13 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