Repository navigation
Make the board read the feed, so two pages stop mapping it twice - #84
Merged
Merged
Conversation
The mapping from a frame on the wire to a drawing on the board existed in
two places. app.js made it from the replies to its own POSTs; replay-app.js
made it from the frames in a file. Same wire, same four types, two copies —
and a copy that drifts is a recording that stops looking like the session it
recorded.
hud/driver.js is now the one copy. It takes { board, feed } and subscribes:
state to setState, a verdict that named an action to its set-piece, flow to
probe, and packet to whatever the shot asked to watch. `feed` is the widget
contract's feed and nothing more, so window.LabFeed satisfies it and so does
the player replay.js builds out of a file — which is what lets the same
three listeners serve a lab and a recording.
What the pages keep is what is genuinely theirs. app.js keeps the POSTs, the
busy guard, the text trace, the tabs and the rails; it reads the lab back
after a change and hands the snapshot to driver.state() rather than to the
board, because a switch has to move the picture without waiting for the poll.
replay-app.js keeps the transport, the moments and the verdict line — the
sentence lands even with no WebGL to draw the shot on.
Two rules the copies agreed on, now owned in one place and pinned by tests:
`set` and `preset` draw no shot. A switch moves the board by changing the
lab and letting the next snapshot redraw it; the board has never rendered
one as an event, and a driver that played a shot per verdict would draw
something no reader ever saw.
A held frame outranks a snapshot. run() calls hold() the moment a shot
starts and setState repaints the standing board underneath it, so a
snapshot arriving under a held frame waits — and it is kept rather than
dropped, so it goes on before the next shot rather than leaving the board
standing on the configuration from before the last one. `stat` is the
heartbeat that releases it when nothing else follows, which is the case
where a switch flipped under a held frame used to half-wipe the frame on
the live page and be dropped outright on the player.
Driven against the real lab on 2026-09-13, control rebuilt from this branch:
a probe drew from the `flow` frame (rung 5, "direct · punched"), scan-public
and scan-tailnet drew their set-pieces from `verdict` frames, a switch
flipped under scan-public's held frame left the frame intact, and the flat
view still draws nothing while the text trace lands. A 39-frame recording of
that session, opened in replay.html, drew the same sniff and probe off the
same driver, twice, with an empty console.
Closes #82
17 of 26 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Target
develop: the normal case for a fix, a chapter, alab change, a typo
develop→main, shipping to the published guidemain, because published advice iscurrently locking readers out or leaving them exposed
What this changes
The mapping from a frame on the wire to a drawing on the board existed twice —
app.jsmade it from the replies to its own POSTs,replay-app.jsmade itfrom the frames in a file — and two copies written against the same wire have
to stay identical or a recording stops looking like the session it recorded.
hud/driver.jsis now the one copy, and both pages hand it{ board, feed }instead of driving the board by hand.
Closes #82
What moved
hud/driver.jssubscribes to four types and nothing else:feedis the widget contract's feed and nothing more —on,want, and therelease functions they hand back — so
window.LabFeedsatisfies it and sodoes the player
replay.jsbuilds out of a file. That substitution is thesame one the three shipped widgets already rely on, and it is what lets one
set of listeners serve a live lab and a recording.
What each page kept
app.jskeeps the POSTs, the busy guard, the text trace, the tabs and therails. It still reads the lab back after a change, because a switch has to
move the picture without waiting up to three seconds for the poll — but it
hands that snapshot to
driver.state()rather than to the board, so theheld-frame rule below only exists in one place.
replay-app.jskeeps the transport, the moments and the verdict line. Thesentence is deliberately still the page's: a Result carries text as well as a
shot, and a reader with no WebGL should still get a readable session out of a
recording.
Two rules, now owned once and pinned by tests
setandpresetdraw no shot. A switch flip moves the board by changingthe lab and letting the next
statesnapshot redraw it; the board has neverrendered one as an event. A driver that played a shot per verdict would draw
something no reader ever saw, and it would look like a feature.
A held frame outranks a snapshot.
run()callshold()the moment a shotstarts, and
setStaterepaints the standing board underneath it — puttingback a machine
expired-keymoved, undoing the dim the shot drew over. So asnapshot arriving under a held frame waits.
One behaviour changed here, and on purpose. The waiting snapshot is now kept
rather than dropped, and it goes on just before the next shot clears the
board — which is the order the live page used to get by fetching the state
before playing the shot.
statis the wire's heartbeat, sent every tickwhether or not anything moved, so it releases the snapshot when no shot
follows. Before this, flipping a switch under a held frame half-wiped the
frame on the live page (
refresh()calledsetStatewith no guard) and wasdropped outright on the player (leaving a board drawn from before the flip).
Both now do the same thing, and it is the thing the guard was written for.
Driven in the lab
controlrebuilt from this branch withdocker compose up -d --build --no-deps control, against the running lab on 2026-09-13:flowframe.rung 5 of 5, chipdirect · punched,reached 100.71.4.3:22scan-public→ set-piece drew off theverdictframe: camera on lab-vps,four ports reading
no answer,HELD · ufw default incoming policy: deny,rung 4 on the ladder
scan-tailnet→INCONCLUSIVE · it never got onto the tailnet, so the policy was never the thing under testvps.allowPublic22underscan-public's held frame → the frame wasstill intact, and the page verdict updated. Flipped back to
false, whichis how the lab was found
lab-tabmoving topacketsas beforereplay.html→ thesniffset-piece and the probe both drew off the same driver. Opened asecond time to exercise the driver being rebuilt on a new player. Console
empty on both pages throughout
Not in this change
Two things noticed while driving it, neither touched, neither introduced here:
HOLDINGchip stays up after a probe that follows a held set-piece,because nothing between them calls
clearScratch(). Same before and afterGET /favicon.ico404s on both pagesKind
If this changes advice or a command
Does not apply. No chapter, command or claim about the world moved — this is
the lab's own UI. What was measured is under Driven in the lab above.
Checks
make -C lab checkpasses — 59 tests, 18 files parse, everything heldgofmtis clean (make checkfails on any output at all)SPDX-License-Identifierheader —hud/driver.jsandchecks/driver.test.mjsIf this touches the guide's pages
Does not apply. Nothing under
chapters/orassets/changed, and noguide page loads any file in this diff.
If this touches the lab or sandbox engine
Result or a State snapshot through; it invents no fields and reads none
is still
reading.js' judgement inside the set-piecedangerstill outranksok— unchanged, same reasonis still the set-piece's fixed length
pass/fail — the Result is handed on whole
lab/README.mdmoved with it— no score moved; no check, attack or probe changed
Licence
CC BY-SA 4.0 (guide) or GPL-3.0-or-later (code), per the file I touched