Skip to content

Dust gets a run sheet, a workbench that can be typed on, and the checkers opponent the disc left out - #388

Merged
dhobi merged 1 commit into
masterfrom
dust-speedrun-sheet
Sep 14, 2026
Merged

dhobi merged 1 commit into
masterfrom
dust-speedrun-sheet

Conversation

@dhobi

@dhobi dhobi commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Six pieces of work that turned out to be one: Dust could not be driven from a sheet, and every attempt to drive it found something else that was not there.

The checkers opponent

CHECKERS.PRP is the whole game except the one thing a game needs — it draws the board, validates the drag, plays the taunts, counts the dead and decides who won, then asks CHECKERS.DLL what the opponent does. Unimplemented, pluginfx answered 0 and playerturn went straight back to true: the player moves, nothing happens, and the game looks like it is still waiting for them.

Nothing is guessed — the script is the authority on the 64-cell board, the three-character move, and the difference between a hop CHAIN and ALTERNATIVES with jumps first. Negamax with alpha-beta at the depth the game asks for, ties broken on the session's rng so a seeded run plays the same game twice. One quirk is kept because the game has it visibly: a man that crowns mid-chain keeps jumping, as a king, and may jump backwards.

The strongest test replays every generated move through an independent transcription of the script's own goodmove/goodjump/makemove.

An idle channel says "none"

currentvoice () and currentsound () answered "", and Dust asks the question the other way round in nine places:

while currentvoice () != "none"
endwhile

"" != "none" is true for ever. Six of those are in CHECKERS.PRP and one is in win () on the path taken when the player loses — so checkers worked until Bolivar won and then hung with his gloat half-said. The other three are the flute room.

It was already found once and worked around: a playthrough rung monkeypatched the builtin, which left the suite green on a room the browser hung in. That patch goes.

The editor hears its own keystrokes

Dust's shortcut handlers listened on window without asking whether the focused element owned the key, so every character typed into the sheet went to the game too. The workbench suites drove this with page.fill, which dispatches no key events — so the bug was invisible to the tests written over it.

Sheet grammar, and two workbench controls

or / and / ! with parentheses, because routes have disjunctive stops. Three conditions the routes ask and the grammar could not: choosing, puppet, star.<name>. global.<name> with no comparison now means "set to something", since "" is a real value in this game.

A mute that sits outside the game — a multiplier on the sink rather than a write the next room change silently undoes, and a mute rather than setSuspended so a muted run takes exactly as long as a loud one. And a click on a line number moves the execution pointer: loud about not having moved the game, refused outright while a run is in flight.

The run sheet and its transcriber

Eight Dust verbs (goto, meet, takeInHand, give, offer, talkOut, doorAt, loadSave) and a tool that writes the sheet from the golden thread's own rungs.

The transcriber used to scan for calls it knew and emit nothing for the rest, which cannot tell "the rung does not do that" from "I do not read that" — five separate bugs were that one bug. Inverted, every call is transcribed, named in a table as something that reads or asserts, or marked; the marked count is a measurement that can only fall by transcribing something or declaring it.

The sheet has now been driven

A full sweep of all 54 legs (npm run speedrun:legs -w dust) finishes 12, and lands the rest on a small number of families rather than 42 separate faults:

n family
12 a door opens and the step through it is refused
5 rung claim the run does not reach
4 say fires before the conversation opens
4 walk blocked or interrupted
4 overlay stage left up, room not showing
3 plaque replies do not match the sheet
2 malformed until: condition — fixed here

The sweep found one bug of its own: the transcriber negated an already-translated condition by prefixing !, so global.blackout != -1 became !global.blackout != -1 — the accessor form negated and then compared, which never comes true. Two mine legs waited on it and pressed an arrow key some 3,675 times apiece. negate() now parenthesises anything that is not a bare atom.

This is not a continuous run and its total is not a time. Every leg still opens by loading the save it starts from, so each inherits a perfect state; joining them is the next milestone, and the 12-leg door cluster is the thing standing in front of it. Notably doorAt(… owner: salout) succeeds on day 1 and fails on day 3 from an identical standpoint, so that cluster is at least partly the world's own state rather than the verb.

Releasing

Dust 0.3.19 and Titanic 0.9.71 — Titanic because the shared engine changed (audio, scheduler, plugins) and its speedrun page gets the same two controls.

Full suite green: 835 tests across 74 files.

🤖 Generated with Claude Code

…kers opponent the disc left out

Six pieces of work that turned out to be one: Dust could not be driven from a
sheet, and every attempt to drive it found something else that was not there.

**The checkers opponent.** `CHECKERS.PRP` is the whole game except the one thing
a game needs — it draws the board, validates the drag, plays the taunts, counts
the dead and decides who won, then asks `CHECKERS.DLL` what the opponent does.
Unimplemented, `pluginfx` answered 0 and `playerturn` went straight back to
true: the player moves, nothing happens, and the game looks like it is still
waiting for them. Nothing here is guessed — the script is the authority on the
64-cell board, the three-character move, and the difference between a hop CHAIN
and ALTERNATIVES with jumps first. Negamax with alpha-beta at the depth the game
asks for, ties broken on the session's rng so a seeded run plays the same game
twice. One quirk is kept because the game has it visibly: a man that crowns
mid-chain keeps jumping, as a king, and may jump backwards.

**An idle channel says "none".** `currentvoice ()` and `currentsound ()`
answered "" and Dust asks the question the other way round in nine places —
`while currentvoice () != "none"` is true for ever. Six of those are in
`CHECKERS.PRP` and one is in `win ()` on the path taken when the player LOSES,
so a game of checkers worked until Bolivar won it and then hung with his gloat
half-said. The other three are the flute room. A rung of the playthrough had
monkeypatched the builtin to get past it, which left the suite green on a room
the browser hung in; that patch goes.

**The editor hears its own keystrokes.** Dust's shortcut handlers listened on
`window` without asking whether the focused element owned the key, so every
character typed into the sheet went to the game as well. The workbench suites
drove this with `page.fill`, which dispatches no key events, so the bug was
invisible to the tests written over it.

**The sheet grammar learns to say `or`.** Routes have disjunctive stops — every
rung that clicks a character stops on "either they answered OR their phase moved
on" — and a grammar without `or` has to drop one half and hope. Three conditions
the routes ask and the grammar could not: `choosing`, `puppet`, and
`star.<name>`. `global.<name>` with no comparison now means "set to something",
because "" is a real value in this game.

**Two controls the workbench wanted:** a mute that sits outside the game (a
multiplier on the sink, not a write the next room change undoes; a mute and not
`setSuspended`, so a muted run takes exactly as long as a loud one), and a click
on a line number that moves the execution pointer — loud about not having moved
the game, and refused outright while a run is in flight.

**The run sheet, and the transcriber that must account for itself.** Eight Dust
verbs (`goto`, `meet`, `takeInHand`, `give`, `offer`, `talkOut`, `doorAt`,
`loadSave`) and a tool that writes the sheet from the golden thread's own rungs.
The transcriber used to scan for calls it KNEW and emit nothing for the rest,
which cannot tell "the rung does not do that" from "I do not read that" — five
separate bugs were that one bug. Inverted, every call is transcribed, named in a
table as something that reads or asserts, or marked; the marked count is a
measurement that can only fall by transcribing something or declaring it.

**And the sheet has now been driven.** A full sweep of all 54 legs
(`npm run speedrun:legs -w dust`) finishes 12 and lands the rest on a small
number of families rather than 42 separate faults: 12 legs where a door opens
and the step through it is refused, 5 rung claims the run does not reach, 4
where `say` fires before the conversation opens, 4 stuck overlays. That sweep
found one bug of its own, fixed here: the transcriber negated an
already-translated condition by prefixing `!`, so `global.blackout != -1` became
`!global.blackout != -1` — the accessor form negated and then compared, which
never comes true. Two mine legs waited on it and pressed an arrow key some 3,675
times apiece. `negate()` parenthesises anything that is not a bare atom.

The leg sweep's `APP_URL` also defaulted to a port Dust's dev server does not
use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dhobi
dhobi merged commit 860ae2e into master Sep 14, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant