Conversation
…hState warning, rapier's init warning
- App.svelte: <Canvas shadows={PCFShadowMap}>. threlte defaults to PCFSoftShadowMap, which
three 0.185 deprecates and downgrades to PCF on the first shadow render - so the picture is
identical and the warning is gone.
- playMode.js: the back-button marker is a SvelteKit SHALLOW entry (pushState from
$app/navigation) instead of a raw history.pushState, which the router answers in dev with
"will conflict with SvelteKit's router". Same url, same navigation index, so the Back that
spends it never navigates. New playMarkerState() reads the state under sveltekit:states.
- physics.js: the wasm "deprecated parameters for the initialization function" warning is
rapier-compat's OWN init() handing wasm-bindgen a bare byte array; the call is inside the
frozen dependency, so there is no single-object form to pass. It is filtered for the
length of that one call (exact prefix, restored in finally) - every other warn passes.
- helpers.cjs: page.__console collects every console message from BEFORE the first
navigation (the PCF warning fires on frame one; a listener attached after boot misses it).
- touch-play: reads the marker through playMarkerState() (the state moved under the
router's key).
Suite console-hygiene (new) 13/13. Counterfactual: all three source fixes reverted at once
-> 4 red (the PCF warning, the marker not being a shallow entry, the router warning, the
wasm warning), restored -> green. touch-play 39/39.
Held at base: game-state 45, game-presence 61, hud-actions 65, game-towers 20,
play-interact 46. svelte-check 336/47 (no new message), vitest 196, build green.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- hudDocs: `isGameHud(docs)` + the `hudIsGame` store. THE RULE: a scene is a game when any
HUD document holds a screen bound to a game state (`showWhile` set) - that binding is the
one thing that makes the editor masquerade (the shared state picks the screen, so the
menu paints itself over the editor with nobody in Play). Every Games-tab template has
one. A hudbutton->setgamestate wiring alone was considered and NOT taken: with no
state-bound screen nothing masquerades, and reading the graph would put a flow edge
into a leaf that keeps none. A HUD with no state-bound screen keeps its old behaviour.
- HudLayer: outside Play a game's screens (scene and camera docs) are not drawn. The HUD
editor's preview eye (`hudPreviewInViewport`) still draws them, with every element INERT:
HudElement `editor` (the artboard's swallow, out of the tab order) AND the whole preview
click-through (`.hud-inert`), so a previewed menu can never fire the game nor eat a
viewport click (the roadmap's selection finding 4). An inert preview claims no keys.
- GameChip.svelte (new, mounted in App.svelte beside HudLayer): "Game · <state>" + an eye
that toggles the same preview store. Editor-only: hidden in Play, VR and embed mode.
Top-left under the logo (tucks under a docked Connect bar via --connect-bottom), z 38 -
the authoring HUD band, under every window. Test play joins it in P2.
- Suites broken by the change, updated in place: game-towers ("the menu renders" moved to
after the play press), game-loop (the 21-D scenario presses Start IN PLAY now - both
peers play, the menu screen frees the pointer, A approves the late joiner from the
editor while B keeps the round alive), game-waves/game-untangle/game-dungeon-realms
(the editor-side menu check becomes "the chip stands in for it", the menu check moves
into play).
Suite game-editor-flow (new) 17/17 on the real Towers .tpscene + collectible zip.
Counterfactual: HudLayer restored to base -> 8 red, incl. "clicking where Start used to
be leaves the game on its menu (playing)" - the reported bug, reproduced.
game-loop 31/31 · game-untangle 47/47 · game-dungeon-realms 65/65 · game-waves 85/91
with the SAME six ledger/run-log reds on base source (A/B'd: pre-existing, module-side:
a run logged twice 1 ms apart, an overkill pulse count).
Held: game-towers 20 · hud-actions 65 (base 20/65). svelte-check 336/47, no new message.
Build green.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…aves - gamePresence.testPlay(): back to the menu (through requestResetGame, so it obeys canResetGame: the host, anyone alone, an admin under a roles plugin; anybody else is told and joins the game as it is), this peer's screen overrides cleared (the state-bound screen decides), then requestPlay() synchronously inside the click - the Start screen. - Entry points: the game chip's ▶ Test play, and a "Test play (start from the menu)" row in the play button's right-click menu (Controls playModeItems, games only). - THE ABANDON WATCH, extended rather than a second writer: every state but `menu` is a round a player can leave (paused and over used to linger forever); ALONE the reset is immediate (nobody's reload or connect dance to protect), with peers the 10 s window stands; the watch also runs on the edges (isLocked, gameState, peerPlayModes - deferred a macrotask, never writing a store inside its own subscriber), so a round entered and left inside the first second is still armed. Still host-only (sessionHost === null). - Leaving play clears THIS peer's screen overrides on a game (a pause menu opened with P no longer greets the next Play over a round that has returned to its menu). game-editor-flow 41/41 (sections 3 Test play solo: stale pause override undone, Start screen, Start -> playing, Esc -> editor with `menu` and no HUD, the right-click row; 4 two peers: A leaves while B plays -> still playing after the 10 s window, B leaves -> `menu` on both, written by the host alone). Counterfactual: the watch's resetGame() dropped -> 3 red (solo reset, the chip's state, the two-peer reset), restored -> green. Held: game-presence 61 · game-state 45 (base 61/45). Also green: play-menu-mode 32, hud-editor-wysiwyg 53, contest-starters 27, game-stars-room 36 (suites that leave play around game state). svelte-check 336/47, no new message. Build green. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- scenePhysics.play.cursor: 'free' | 'locked'. Normalized at the one boundary; 'locked' is the default and is NEVER written (the play block carries `cursor` only when free), so a scene that never uses it saves byte-identically. resolvePlaySettings resolves it too, so a module can ask for it through the userData.play contract, field by field. - playCursor.js (new leaf): THE one answer to "where does play mode aim" - playAimNdc() is the cursor NDC in free-cursor play and the crosshair (0,0) otherwise; playCursorFree(); cursorClient(). Recorded in CLIENT px, converted against the canvas at read time (the W9 rule). This is the seam 30-core-modes' pointerRay reads in play (it also answers the stale mouse ray under a lock) and 30-untangle feature-detects. - PointerLockControls: no lock is requested in a free-cursor game (entry, the menu-substate re-lock, the canvas-press recapture); movement keys still walk/fly, Escape still leaves. - playInteract: tap and grab aim through playAimNdc, and the carry target follows the aim ray; in free mode only a press on the VIEWPORT canvas is a world gesture (a HUD button or any page chrome is not). PlayReticle draws nothing when the cursor itself aims. - Grid.svelte: the editor grid is not drawn while playing, on every scene and either cursor (the existing "Show helpers in Play (debug)" toggle is the one way to keep it); the mesh is named `editor-grid`. - debug hook: playCursor added to all three tails (213/213/213). Suite play-free-cursor (new) 27/27: the field omitted at default, 0 pointer-lock requests (spy on Element.prototype.requestPointerLock), no reticle, a REAL click at an OFF-centre object's pixel fires its On Click, a REAL press-drag carries a dynamic body under the cursor (792px vs cursor 792px), page chrome is not a world tap, the grid hidden in play in both cursor modes, a locked game still asks for the lock (the spy works). Counterfactual: lock requested anyway + grid always drawn + aim pinned to the crosshair -> 7 red (5 lock requests; both grid checks; click, grab, carry position), restored -> green. Held: play-interact 46 · game-towers 20 (base 46/20). Also green: play-menu-mode 32, touch-play 39, scene-physics-state 37. svelte-check 336/47, no new message. Build green. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- gameStorage.js (new leaf, safeStorage only): THE KEY CONTRACT, byte for byte -
`tp:mod:<moduleId>:<key>` for a module, `tp:scene:<scene name or 'untitled'>:<key>` for
the flow nodes; values are JSON text; a missing or unparseable entry hands back the
fallback; a parsed-value cache kept current by every write through the leaf.
- moduleSDK makeApi: `api.storage.{get(key, fallback), set(key, value) -> bool, remove,
keys, clear, bytes}` - 256 KB per module measured as the JSON text + key of every entry
(a set over it returns false and ONE toast per module per session), LOCAL (never
replicated, never in a scene file, never undone), deliberately NOT journalled: storage
outlives the module (a reinstall keeps progress; clear() is the module's own reset).
A throwing localStorage falls back to memory for the session through safeStorage.
- Flow nodes, palette group Game, in BOTH registries (nodeCatalog + CORE_NODE_TYPES) with
manual lines: Store Value (trigger + value; key, mode set|max|min|add) acts on the stamp
EDGE inside the actionSeenAt family and sends NOTHING - its card says "Saved on this
device only"; Stored Value (key, output number|text). flowSockets types both.
- hudActions: "Save best score" on a button - Store Value (best, max) fed by the `score`
variable; the press role gains an additive `via` (absent on every other press action).
- nodeDocs: a manual line for `onhit`, which 24-A shipped without one (flow-node-docs had
been red on base for it) - one line outside this lane's nodes.
- debug hook: gameStorage added to all three tails.
tests/unit/gameStorage.test.js (8, no browser) · module-storage (new) 36/36: the api
round-trips, the raw key, two modules apart, the cap, a throwing localStorage (no error,
memory fallback, nothing reached storage), Counter -> Store Value (max) keeping the best
under tp:scene:Arcade:best with nothing on the wire and nothing on the second peer, a
reload keeping both halves, another scene's namespace not seeing it, and "Save best score"
built and pressed.
Counterfactual: the module namespace dropped from the key -> 5 red (the key contract, two
modules colliding 7/7, keys and clear crossing, the reload), restored -> green.
Held: hud-actions 65 (base 65). flow-node-docs green (was red on base: onhit),
flow-unknown-node 24. palette-groups 10/11 with its one red ("Music" has no card accent)
identical on base - not this lane. vitest 204 (+8). svelte-check 336/47, no new message.
Build green.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 23, 2026
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.
Lane 30-core-flow of roadmap 30 ("Games that look finished"). Five commits, one per phase.
What landed
688dc40) —<Canvas shadows={PCFShadowMap}>(identical picture, no deprecation warning); the play back-marker is a SvelteKit shallow entry (pushStatefrom$app/navigation); rapier-compat's own init warning is filtered for that one call (it is inside the frozen dependency — no single-object form is reachable).helpers.cjscollectspage.__consolefrom before the first navigation.0da7522) — a scene is a game when a HUD screen is bound to a game state (showWhile); outside Play its screens are not drawn, the HUD editor's eye previews them inert and click-through, andGameChip("Game · ") stands in for them.7745a0c) — ▶ Test play (chip + the play button's right-click row) resets to the menu (respectingcanResetGame), enters Play and shows the Start screen; the abandon watch now resets any non-menu round when the last player leaves (solo: at once; with peers: the 10 s window; host-written).7484009) —scenePhysics.play.cursor: 'free'(omitted at default) plays without a pointer lock: the cursor aims, clicks fire On Click, press-drag carries bodies. New leafplayCursor.js(playAimNdc()is the seam30-core-modes'pointerRayreads). The editor grid is hidden in Play.dd59c8b) —api.storage.{get,set,remove,keys,clear,bytes}(JSON,tp:mod:<id>:<key>, 256 KB/module, local) and the Store Value / Stored Value flow nodes (tp:scene:<scene>:<key>, stamp-edge, send nothing), plus the HUD action "Save best score".Suites (count vs base)
Every guard has a counterfactual in its commit body. Suites updated because they asserted the old behaviour: game-towers, game-loop, game-waves, game-untangle, game-dungeon-realms, touch-play. Pre-existing reds, A/B'd on base: game-waves' 6 ledger/run-log checks (module-side) and palette-groups' "Music has no accent". flow-node-docs was red on base (
onhitundocumented) and is fixed here.Owed (headless cannot judge)
For the integrator
api.storage+ Store/Stored Value (+ "Save best score"); the three console warnings gone.isGameHudrule beside THE HUD; the abandon-watch changes (21-F3 entry);playCursor.jsandgameStorage.js(+ the key contract) in the map;page.__consolein Verification.nodes/storevalue.md,nodes/storedvalue.md, "Test play" inbuild-a-game.md,play.cursor,api.storagein module-sdk.md.tp:scene:untitled:*— templates should use game-specific keys.🤖 Generated with Claude Code