Conversation
…s, object flags
- build() gains capsule / plane / ring / icosahedron / dodecahedron (core three geometries,
rebuilt from their parameters by ObjectLoader) and box `bevel` (+ bevelSegments): a port of
three/examples RoundedBoxGeometry baked into a PLAIN BufferGeometry, because toJSON writes
a subclass type ObjectLoader cannot rebuild (core Box params carry no bevel)
- light `kind`: spot (angle, penumbra, target) / directional (castShadow, shadowMapSize,
frustum FITTED to the built meshes in the light's own frame; `fit: false` opts out) /
hemisphere (groundColor); spot + directional follow createLight (cast by default, the V-1
bias pair) and aim by ROTATION at `target` (24-E1). point (no kind) is untouched
- materials: `physical: true` or any physical-only field (clearcoat, clearcoatRoughness,
transmission, thickness, ior, sheen, sheenColor, sheenRoughness, iridescence,
specularIntensity) -> MeshPhysicalMaterial; `toon: true` -> MeshToonMaterial;
`flatShading`, `side: 'double'|'back'`; emissiveIntensity as before
- flags: `pick: 'through'` -> userData.pick; `origin: [x,y,z]` -> userData.origin;
`particles: '<preset>' | {preset, ...patch}` -> userData.particles from particlePresets;
`anim: '<preset>' | [..]` -> animationPreview.applyPreset (by key or name), an ordinary
authored clip (not playing — a Play Animation node runs it)
- loader: `--def <file.json[,..]>` adds defs from JSON (one or an array); a same-slug def
REPLACES the DEFS entry. Unknown object types now throw instead of silently becoming cones
- suite author-kit (new): authors a def-under-test via --def/--only into a temp dir, loads it
back through sessions.readSessionZip + applySession, one check per feature: 40 PASS
- byte-identity: level-blockout/towers/stars-room/untangle/waves authored before and after,
scripts/compare-authored.cjs reports SAME for all five (thumbs identical in size)
- counterfactual: 'clearcoat' dropped from PHYSICAL_KEYS + the pick line removed -> exactly
"physical: clearcoat 0.8 survives (0)" and "pick: 'through' lands on userData.pick" FAIL
- held: templates-modal 47 / template-modules 29 PASS lines at base (no src change in this
commit); svelte-check 336/47 identical list; vitest 196; build green
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…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>
…, sun, hemi
- a def `env` may be {preset:'custom' | <preset>, base?, exposure, background:'#hex' |
{top, bottom}, fog:{color, near, far} | null, ground:{color, roughness?}, sun:{color,
intensity, dir | position} | null, hemi:{sky, ground, intensity} | null}; colours may be
numbers. It builds a payload from a base preset and commits it through the environment
module's own custom path (applyCustomPreset), so the scene saves it as customPreset and a
peer / late joiner gets it on the existing environment singleton. The payload's own
exposure stays 1 and the def's exposure is the STATE multiplier (applyCustomPreset would
otherwise square it). A plain preset env takes the old line verbatim
- environment.js (ADDITIVE, absent on every stock preset and every older save):
`gradient: {top, bottom}` -> a cached 2x256 CanvasTexture background (`background` keeps
the flat horizon colour for the backgroundColor store and older peers); `ground: {color,
roughness?}` -> an `env-ground` disc in ENV_ROOT (scene root, never synced or saved as an
object) that receives the sun's shadows, so the ShadowMaterial catcher stands down while
it shows; hidden in wireframe and in passthrough (it would cover the real room).
Read only through skyGradientOf / skyGroundOf (a malformed value reads as absent)
- the per-def reset uses environmentRestore(null) — setEnvironment('studio') kept a custom
payload in the state, which environmentSnapshot would have saved into the NEXT def
- suite author-kit: + a sky section (state, texture background, fog, ground visible,
catcher down, sun dir, hemi, exposure not squared) and a PIXEL check on the loaded scene:
sky bands R 206 vs 81 (upper redder), B 206 vs 81 (lower bluer) — 53 PASS
- counterfactuals: the builder ignoring the gradient -> 4 FAIL (saved gradient, texture
background, both pixel bands 34/34, 255/255); environment.js skyGradientOf -> null ->
3 FAIL (texture background + both pixel bands)
- byte-identity: level-blockout/towers/stars-room/untangle/waves SAME vs the pre-lane build
- held: templates-modal 47/0, template-modules 29/0 (= base); svelte-check 336/47 with an
identical list; vitest 196; build green on this environment.js
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>
…n the real GPU - the card renders with the SCENE's look: the live background (flat or the p1 gradient texture), its fog, the environment rig cloned from the scene root (hemi + sun + catcher / ground + extra env lights, at the intensities the viewport uses) and the authored lights (spot/directional targets seated on their -Z forward, the lightHelpers rule); shadows on (PCF). The private hemi + directional pair is the fallback ONLY for a scene with no light - tone mapping = the post stack's Tone mapping curve when it has one, else NONE — what a composed desktop frame shows (the default Shaded+AO view keeps the composer running, and the renderer's own ACES never reaches a composed frame — the documented gotcha). MEASURED: ACES darkened the Waves card to 0.040 centre luminance against the live frame's 0.11-0.13; none reads 0.086. `thumb.toneMapping: 'agx'|'aces'|'neutral'|'reinhard'|'cineon'|'linear'| 'none'` overrides per def - camera: `thumb.camera`, else the def's `view` (NEW — the view the file opens on), else a 3/4 PROJECTED fit of the content box (camera markers and floor-like slabs excluded — a thin mesh covering over a quarter of the footprint), so a 30x40 m ground no longer shrinks every game piece to a speck. Camera markers are hidden in every card now (chrome, not scenery) - the authoring browser uses the per-platform ANGLE backend (vulkan on Linux, the helpers.cjs GPU_ARGS): `--use-angle=gl` fell back to SwiftShader, so every card was a software render. The run now prints its GPU line and WARNs on SwiftShader - suite author-kit: + card checks — the sky def's card shows the authored gradient (top R 242 / bottom B 242), the studio kit card reads (0.416 > 0.2), and the Waves def from the sibling modules checkout shows its OWN sunset sky (51,32,48 vs the scene's 51,32,47) and grew past the old card (2110 B vs 1690). 58 PASS - DEVIATION (QUESTIONS-30-author-kit fork 1, recommendation taken): the brief's "Waves card > 6 KB with centre luminance > 0.2" is not reachable truthfully — the Waves scene is dark (its live frames read 0.11-0.13 on the same metric) and the OLD grey-lit card read 0.198, so a > 0.2 check would pass the wrong card. The 0.2 bar is asserted on a lit controlled scene - counterfactual: HEAD's old thumbnail block restored (private lights, grey, whole-bounds) -> 3 FAIL: the gradient card (top R 80 / bottom B 80), the Waves size (1696 B) and the Waves sky (34,42,51 = the private grey) - scene content: level-blockout/towers/stars-room/untangle/waves canonical session.json SAME as the pre-lane build (only the card and the inline thumbnail sizes differ — new renderer, and the inline one now comes from a real GPU) - no src change in this commit: svelte-check 336/47 and build as at p1; held suites as at p1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- a "THE DEF SCHEMA" block at the top of scripts/author-templates.cjs: def-level fields (kind,
identity, modules/installModules, generate/layout, objects, env, gravity/physics, post,
graphs/hud/shaders/animations, music/sounds, view, thumb {camera, sceneGroups, toneMapping},
contest), every object type with its params and defaults, the material fields (standard,
physical, toon), the object flags (physics, shadow, pick, origin, anim, particles), the custom
sky, the loader flags (--out, --only, --def, APP_URL, MODULES_REPO) and the card rules
- comment-only: node --check clean; the modules repo AUTHORING.md table is handed to the
integrator (this lane may not edit the modules repo)
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>
…t height
- Towers def (scripts/author-templates.cjs, the DEFS region + towersGraph): a sky gradient
with a far fog and a solid ground disc (daylight, exposure 1.05); a 2 m floor tile from a
shader graph (uv -> split -> GLSL expression -> Surface albedo, multiplying the colour);
chamfered walls with a glowing trim; wooden crates (physical + sheen + thin clearcoat,
bevelSegments 1 to keep the file small); the pad under a clearcoat with a bright rim and a
blue light; the height markers are thin glowing TORUS ring sensors (select-through) beside
a marked pole instead of stacked translucent squares; faceted stars that turn and breathe
(the Turntable + Pulse presets folded into ONE `Star glow` clip, played on round start);
`view` + `thumb.camera`; collectible module version 1.1.1; Play again beside Menu on
Round over.
- BEST HEIGHT (Store Value / Stored Value, 30-core-flow's nodes): `towers-best` (max) is
written on every ring crossing and shown on the menu ("Your best tower: N m");
`towers-last` holds THIS round, zeroed by its own `set` node on round start. Never on
`over`: a perRound latch reads un-set the instant the round ends, which is also why the
old Round-over line always read "0 m". `max`, not `set`, for the round's value too: a
crate jittering in a ring re-fires the crossing after the round ended, when the height
reads 0. Game-specific keys (a Games-tab load leaves the scene unnamed).
- core, outside the defs (minimal, named in the handover): HudElement.svelte — a centred or
right-aligned SINGLE-LINE HUD text rendered flush left, in every game. The text box is a
flex row, so its line is an anonymous flex item shrunk to the words and text-align had
nothing to align; the row now takes justify-content from the same `align`. Scoped to
text/timer (the list is a flex column).
- game-towers: TOWERS_TPSCENE env (a lane's staged build); 24 -> 36 objects (the trims, the
pad rim + its light, the pole + 4 marks, the card camera); new checks for the look as
data, no warning toast on load, the menu's best, centred texts, the star clips playing,
centre luminance >= 0.25 in play, the saved best/last, Round over from storage, a
re-fired crossing on the over screen, Play again zeroing the round, Quit showing the best.
The pad is cleared before the round ends: a crate standing through a ring re-crosses it
in the next round — measured, that was the base's red on "Restart cleared the height
latches" (19/20 at base).
Counterfactuals (each broke, went red, restored):
- storelast mode 'set' -> "a crossing re-fired on the Round-over screen leaves this round's
height alone" red ({"best":3,"last":0})
- HudElement back to boxStyle -> "centred HUD texts are centred on their box" red
(offsets TOWERS -124.5, Your best -133 px)
- no `shaders` in the def -> "the arena floor carries its tile shader graph" red
- no `animations` -> "every star carries the Turntable + Pulse clip" and "Start sets all
three stars turning" red
Suites (staged scene): game-towers 40/40 (base 19/20), game-editor-flow 41/41 (base 41).
svelte-check 336 errors / 47 warnings, list identical to base; vitest 204/204; build green.
Staged: cloud-lane-30-staging/towers (scene 48.8 KB, was 13.2 KB; thumb 6.5 KB). Centre
luminance in play 0.547 (before 0.593, both at the probe viewport).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…reen
- Stars Room def (the DEFS region + starObjects/starsGraph): a custom sky (a deep-blue
gradient, a cool hemisphere, a soft key light; exposure 0.55 -> 1.1), a starfield from ONE
continuous emitter flung to a ~20 m shell (speed / drag) that fades in only once it is past
the glass, glass walls (clearcoat, 10% opacity, select-through) hung in a glowing frame, a
glossy deck with a light ring, crystal stars (physical icosahedra, flat-shaded, emissive,
SPHERE colliders), sheen planets, `view` + `thumb.camera`.
- THE EMITTER-CAP TOAST ON LOAD, fixed without touching the cap: a burst node per star was
24 of the runtime's 8 emitters. Now ONE pooled burst on a `Burst anchor` (parked under the
deck, since a burst emitter auto-fires once when it is built) that a Script node moves onto
the star just hit — the star's number is sum(i * hit_i) (each On Hit reads 1 for its 0.3 s
pulse), and the same sum is the burst's trigger. 2 emitters on load (was 24).
- A START SCREEN: Start round or Free play. Free play is a game STATE of its own ('free' —
setGameState keeps an unknown state), so its screen is state-bound and the P menu's Resume
(a `hide`) falls back to it; an override would have fallen back to the start screen.
The round is two minutes (Game Time remaining; time up -> over "Time's up!").
- ROUND OVER FROM STORAGE: every perRound latch reads un-lit the instant the round ends, so
the over screen reads stored values — `stars-best` and `stars-last` (max, written on every
hit; max so a star knocked on the over screen cannot wipe them) and `stars-time` (max of a
value that is 0 until the 24th star lights), each round zeroing its own two by `set` nodes.
The title and the line pick their FORMAT through a Select (the Select-passes-a-string
trick the lit colour already used): EVERY STAR LIT / "Every star lit in Ns" on a win,
TIME'S UP / "Lit N / 24 in time" otherwise; the best round shows on the start screen and
Round over, which also gets the touch leaderboard, Play again and Menu.
- game-stars-room: 36 -> 50 objects; starts on the start screen, Free play enters `free`;
new checks for the glass, the crystals, the starfield/exposure, the emitter budget and the
load toast, the pooled burst standing on the knocked star, centre luminance >= 0.25 in
play, the stored round, and the best on the start screen. "Back to free play" -> "Menu".
Counterfactuals (one broken build, 4 red, restored):
- the Script edge removed -> "the pooled burst moved onto the knocked star" red (anchor
still parked at 0,-3,0)
- a burst node per star again -> "the emitters fit the runtime's cap of 8" (25 + 1) and
"no emitter-cap toast on load" red (the toast is back)
- the over title's Select unwired -> "the over screen names the time and the best, from
storage" red
Suites (staged scene): game-stars-room 47/47 (base 36/36). No src change in this phase
(svelte-check/vitest/build as at p0: 336/47 identical, 204, green).
Staged: cloud-lane-30-staging/stars-room (scene 49.7 KB, was 34.7 KB; thumb 9.4 KB).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Jam Room def (JAM_DEF + jamGraph, replacing the inline DEFS entry; same place in DEFS, so the index order is unchanged): a small studio around the same twelve cabled devices and layout — a warm wooden floor, two rugs, plastered walls and a ceiling (select-through; the ceiling casts no shadow and glows faintly, with two light discs), five fabric acoustic panels, two floor lamps with their lights, a spot on the beat lab, an amp, a plant, a stool; a warm custom sky, the post floor, `view` inside the room (Play starts from the editor camera's spot) + `thumb.camera`. - `play.cursor: 'free'` (30-core-flow's P3): you play the instruments by pointing at them — no pointer lock, the real cursor clicks keys, pads and the drum grid. No simulation. - THE SHELL: a start screen (Start jam, this device's best tempo), a three-second COUNT-IN (its own screen, the number derived from Game Time), then the goal: keep the band playing for EIGHT BARS. Round over is "Session complete — 8 bars at N BPM", with Play again and Menu; the P menu has Resume / Restart / Quit. - WHAT THE GOAL IS BUILT ON: core's Transport node (musicClock's shared transport as a flow value — beat, bpm, playing, identical on every peer). The music modules publish nothing to the flow (no notes-played count, no transport control), so the round cannot START the transport: the HUD says "Press ▶ on the Transport" until it runs, then "Keep the band going · N BPM". The beat the count-in ended on is kept with Set Variable (a replicated sample-and-hold), so bars count from THIS round; a transport restarted mid-round counts from its own zero (Select on beat < start). The goal gates on bars >= 8 AND the transport playing AND the round on and counted in, through All Players. - THE SCORE is the tempo you finished at (the Transport's BPM −/+), `jam-best-bpm` (Store Value max) shown on the start screen and Round over; `jam-last-bpm` for the line. - NEW suite game-jam-room (30): the twelve devices + the studio as data, the start screen, the free cursor in Play, the count-in, the goal waiting for the transport, the bars counting at 240 BPM, Session complete + the saved best, Play again counting from its own start, Quit showing the best, and centre luminance >= 0.25 on the goal screen (measured AFTER Start: the start screen's panel covers the centre, so it would measure the menu). Loads JAM_ROOM_TPSCENE or the sibling scenes checkout; the real music-lab/music-fx zips. Counterfactuals (one broken build, 7 red, restored): - the count-in's Set Variable trigger removed -> "the count-in kept the beat it ended on" and "the new round counts from its own start" red (bars 18.9 on Play again) - no `play.cursor` -> "a free-cursor game" and "in Play the cursor stays free" red - the best-tempo Store Value untriggered -> "Session complete names the tempo and the best", "the best tempo is saved", "the start screen shows the saved best" red Suites (staged scene): game-jam-room 30/30 (new; luminance 0.425). No src change in this phase (as at p0: svelte-check 336/47 identical, vitest 204, build green). Staged: cloud-lane-30-staging/jam-room (scene 67.4 KB, was 35.6 KB; thumb 8.4 KB). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Roadmap 30, lane
30-visuals-core: the three core-authored Games-tab games brought to the standard shell and a finished look. Built onfeat/30-author-kit(#240) andfeat/30-game-flow(#241), merged in clean.What landed
HudElement.svelte, justify-content now followsalign).play.cursor: 'free'. Start plays a 3-second count-in, then the goal is to keep the band playing for 8 bars. The goal uses core's Transport node, and the start beat is held with Set Variable. The music modules publish no flow values, so the player presses ▶ on the Transport device. The best tempo is saved. New suitegame-jam-room.Suites (staged scenes, restarted server)
svelte-check 336/47 with an identical list; vitest 204; build green. Each commit body has its counterfactuals (16 reds across the three phases).
Centre luminance in play, before → after: towers 0.593 → 0.563, stars 0.218 → 0.271, jam 0.233 → 0.426 (edit views 0.20/0.24/0.22 → 0.44/0.31/0.30). Staged content:
cloud-lane-30-staging/{towers,stars-room,jam-room}.Owed
For the integrator
CHANGELOG lines and CLAUDE.md gotchas are in the lane handover (
STATUS-30-visuals-core.md). Re-author the three games on the union (--only towers,stars-room,jam-room).🤖 Generated with Claude Code