Conversation
…t = one per press) - three 0.185's OrbitControls.onPointerDown calls domElement.setPointerCapture with no guard, and the editor <OrbitControls> stays MOUNTED in play. Chromium refuses a capture with InvalidStateError while the page holds a pointer lock, and the throw lands before _addPointer, so EVERY press in play threw once and later surfaced as "Something went wrong ... Copy diagnostics" (the Quest diagnostics log, nodes/2.*.js:372:15736). - Half 1: Scene's frame task stands the active OrbitControls down while isLocked === true and restores exactly what it stood down when play ends. Per FRAME, not on the isLocked edge: TransformControls unmounts in play and its auto-pause cleanup writes enabled = true unconditionally a tick later, which would stomp an edge write. - Half 2: setPointerCapture is guarded on the element the controls listen on (skipped while document.pointerLockElement is set). Smaller than subclassing, which would mean replacing threlte's <OrbitControls> with a hand-built <T is>; it also covers the camera preview's OrbitControls, which listens on the same element. It calls the prototype method at call time, and is removed on destroy. - New suite pointer-capture-play (12): Chromium's rule modelled in-page (a pointerLockElement getter + a prototype setPointerCapture that throws under a lock, checked as a premise), 20 real CDP presses in play, a hand re-enable to prove the guard alone, no diagnostics toast after leaving play, and the editor still orbits. - Counterfactuals: both halves removed -> 20 errors for 20 presses, the toast, 6 red (the reported bug, reproduced); capture guard alone removed -> the re-enabled-controls check red (2 errors) + the toast. - svelte-check 336/47 = base (line shifts only); vitest 196/196; build green; selection-extras 19/19 at base. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ers routed by mode
- New LOCAL store `editorMode` ('edit' | 'interact', sceneStore; never sent, never
saved). EDIT (default): a viewport click SELECTS first; a module click handler runs
only if it registered for Edit; On Click nodes do NOT fire from the editor pick.
INTERACT: a click is play's tap aimed by the cursor (module groups, handlers that run
in 'interact', On Click nodes, the miss), nothing is selected and the gizmo is put
away; a press on a dynamic body while a sim runs CARRIES it along the cursor ray with
playInteract's own hold (one spring, one throw, one cancel discipline — only the aim
differs) and the camera controls stand down for that gesture. Play is unchanged.
- SDK: `api.registerClickHandler(fn, {modes})`, modes any of 'edit'|'interact'|'play';
ABSENT = ['interact', 'play'], so a module that never passes it stops eating editor
clicks (the reported Jam Room case: Piano / Drum machine / Sampler never selectable).
One dispatch `runClickHandlers(object, mode)` for the editor pick, Interact and the
play tap. A core handler pushed straight into the array (vrPatch's plug click —
patching is authoring) runs in every mode. VR's trigger passes no mode and keeps
offering every handler first (VR has no mode toggle yet — recorded).
- UI: an Edit/Interact toggle cell ('Interact mode (I)', a real <button> with
aria-pressed, `#editor-mode-toggle`) joins the default Controls bar at the right end
(the place loadLayout already appends a default id an older record lacks, so the well
keeps its slot); key I through the shortcut registry, standing down in text fields,
mesh sessions (MESH_EDIT_KEYS already) and — through a new `registerKeySessionProbe`
seam Scene feeds — sculpt, spline edit and draw. A transform tool (1/2/3, the bar's
Move/Rotate/Scale) picked in Interact goes back to Edit in that tool.
- objectActions: setEditorMode / toggleEditorMode (Interact detaches the gizmo, Edit
re-seats it on the selection), and applySelectionSet attaches no gizmo in Interact.
- New suite editor-modes (41, two peers): toggle + key + registry row + text field;
an edit-only handler runs in Edit and the default one does not (and vice versa in
Interact); an On Click node fires only in Interact and its pulse reaches the peer;
the gizmo leaves and comes back; the REAL music-lab piano (installed zip): an Edit
click on a key selects the piano and sends no devicenote, an Interact click sends one
and selects nothing; an Interact press-drag carries a dynamic crate with the cursor
(up the screen = up in the world), without orbiting, and lets go on release.
- controls-roster updated in-commit for the new default cell (108/108): the default
row, the counts and the rightmost cell; its cell locator addresses the CELL rather
than a <p>.
- Counterfactual: every handler hears every click again (the pre-P1 routing) ->
5 red: the no-{modes} handler runs in Edit (2.3), both counts
double (2.5/2.6), and the Edit click on a piano key plays a note instead of selecting
the piano (5.3/5.4) — the reported bug, reproduced.
- svelte-check 335/47 (base 336/47: one pre-existing implicit-any in Scene removed,
nothing new); vitest 196/196; build green. Held: play-interact 46/46, selection-extras 19/19,
sdk-game-seams 78/78, module-toolbox 46/46, game-untangle 46/46.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The pick returned the NEAREST hit whatever its opacity, so a near-invisible shell won every click (measured: Stars Room's 0.12-opacity walls took 16 of 16 clicks on stars, pads and planets; Football's 0.06 ceiling ate the lamps and bars). New pure leaf `selectThrough.js` (imports nothing): `pickStack` collapses raw hits into the stack of top-level targets (a target is opaque if ANY of its hits is), `isSeeThrough` = the top-level `userData.pick === 'through'` flag, a surface under 0.25 effective opacity (the hit face's own material on a multi-material mesh), or a hidden node on the way up; `primaryIndex` = the first opaque target, else the nearest. - CLICK-CYCLE: a plain repeat within 4 px walks DOWN the stack and wraps (so the shell in front is reachable too), with a one-time "Click again to select behind" hint. TIMING DEVIATES FROM THE PLAN (< 600 ms): a repeat cycles only after the 400 ms double-click window and within 1500 ms — inside 400 ms is the configurable double-click action, and a cycle there would kill double-click-to-open-properties wherever anything sits behind the cursor. Recorded as a fork in the lane's questions file. - Wired into Scene's raycastSelect (Edit and VR; the cycle needs click coordinates, so desktop only) and into Interact's click, so a glass wall does not take a star's click there either. An Edit-mode module handler is offered the CHOSEN hit. - Inspector ▸ Object: "Click-through in the viewport" (#inspector-pick-through), fanned over the selection. objectActions.setPickThrough writes userData.pick through ONE `props` undo entry and the existing `objectParameters` message (parameter 'pick', null = cleared) — the userData.physics shape; commandsHandler gains the receive branch. Absent is the default, so a never-flagged object serialises byte-identically. - New suites: select-through (17, two peers: a box inside a 0.1 shell selects the box, the repeat reaches the shell and comes back, a double-click does not cycle, the real checkbox makes an opaque wall stand aside, the flag reaches the peer, one undo clears it on both, no key on an unflagged object); vitest selectThrough (18). - Counterfactual: nearest hit only -> 5 red (the box inside the shell selects the shell, the cycle checks, and the flagged wall still takes the click). - Probe (SELECT_ONLY, stars-room + football, real Templates modal + feed): Stars Room objects that select THEMSELVES 1/15 -> 10/15 (the rest: the see-through walls themselves, reachable by the repeat, and a star genuinely behind a planet); Football 3/16 -> 8/16 (the remaining misses are the HUD menu buttons over the viewport — roadmap cause 4, 30-core-flow). - svelte-check 335/47 (= after P1, nothing new); vitest 214/214; build green. Held on this commit: editor-modes 41/41, selection-extras 19/19. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…content section
- Scene-root module content (the untangle board, the dungeon, the sabers, ...) lives
outside objectsGroup by golden rule 5, so it was never picked and never LISTED (only
the advanced System filter polled it, naming no module). New LEAF `moduleContent.js`
(THREE + stores + helperLayer): the registry moduleSDK writes on every
registerInteractiveGroup / registerSystemGroup (and the new registerListedGroup), the
rows (named children to depth 2, capped at 200 with a "+N more" line), and the
selection PROXY — a scene-root Box3Helper on the helper layer sized to the group's
bounds, following it at 4 Hz. Never in objectsGroup: it never replicates, never
saves and never enters the selection SET, so the outline, the gizmo and every
act-on-selection command are untouched. Any object selection or deselect clears it.
- `ModuleContent.svelte` under the scene tree: one READ-ONLY row per group present in
the scene, the module's name as a badge, expandable, a local hide-in-viewport eye; a
click selects the proxy, frames it and opens the Inspector; its context menu is
Frame it / Open <the module's toolbox> / Hide in viewport (this device) — no rename,
delete or drag. Re-derives on pokeScene AND every second (module content changes
without a poke — the System view's precedent, which stays).
- Inspector: a card instead of transform rows ("Made by the <module> module — edit it
with its toolbox or nodes", plus the toolbox's button when it has one).
- Viewport: an EDIT click on module content that no edit handler took selects its
proxy when it is nearer than the object hit; in INTERACT the click stays the module's.
- SDK: `api.registerListedGroup(name, {label, icon})` (journalled) names a group for a
person; without it the row reads the group name humanised ('untangle-module' ->
'Untangle module'). moduleSDK re-exports `moduleContentDebug` for suites (no new
debug-hook tail).
- New suite object-list-modules (24, two peers, the REAL untangle zip + template from
the feed / sibling checkout, skip-never-fail): the row, its label and badge, the
dots listed, not in the scene tree, a read-only menu; a click selects a proxy at the
scene root around the board, frames it, the Inspector card, an empty object set;
object counts unchanged on both peers, no proxy on the peer or in a saved payload;
deselect removes it; an Edit viewport click selects the proxy, an Interact one does not.
- Counterfactual: the section filtered out of the list -> 1.1 red (the row is
gone and the suite stops there); the Edit viewport branch disabled -> 5.2 red (an Edit
click on the board selects nothing).
- svelte-check 335/47 (nothing new); vitest 214/214; build green. Held: editor-modes 41/41, selection-extras 19/19,
module-toolbox 46/46, game-untangle 46/46.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Under a pointer lock the cursor is pinned, so pointerRay() kept answering with the mouse position from the moment the lock began — a STALE ray that never moved again. Untangle's carried dot followed it, which is part of why a drag in play went nowhere. While isLocked === true, the pointer is locked and play is not in its menu substate (playPointerFree), pointerRay() now returns the CROSSHAIR ray: the view's centre, NDC (0,0) through the play camera — the ray playInteract already aims with. - Hook for 30-core-flow: a scene whose play block says `cursor: 'free'` plays with the real cursor and no lock, so its ray stays the mouse's. Read defensively through resolvePlaySettings (absent = 'locked', today's play) — nothing to merge when that field lands. - editor-modes gains section 7 (pointer lock stubbed): the mouse ray is off-centre, the locked ray matches the crosshair's direction (dot > 0.99999) and origin, and the mouse ray is back once unlocked. 45/45. - Counterfactual: the mouse ray always -> 7.2 red (dot 0.853839: the locked ray is the stale mouse ray). - svelte-check 335/47 (nothing new); vitest 214/214; build green. Held: sdk-game-seams 78/78, play-interact 46/46. 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-core-modes. Baserelease/next= 1.16.0 (adb356d). Five commits, one per phase.What landed
c162c20— the pointer-capture throw (~100 errors on a Quest = one per press). three 0.185'sOrbitControls.onPointerDowncallssetPointerCaptureunguarded and the editor controls stayed mounted in play; Chromium throwsInvalidStateErrorunder a pointer lock, once per press, later surfacing as "Something went wrong". The active OrbitControls stands down per frame whileisLocked === true(per frame because TransformControls' unmount cleanup re-enables it a tick later) and the capture is guarded on the element while a lock exists.4b1e7ed— Edit and Interact. LOCALeditorModestore; a toggle cell on the Controls bar (#editor-mode-toggle,aria-pressed, appended at the right end — loadLayout's own rule for a default id added later) and key I through the shortcut registry (stands down in text fields, mesh sessions, and via a newregisterKeySessionProbeseam for sculpt/spline/draw). EDIT: a click selects; module click handlers run only if registered for Edit; On Click nodes do not fire. INTERACT: play's tap aimed by the cursor (module groups, handlers, On Click, the miss), no selection, no gizmo, and a press-drag on a dynamic body while a sim runs carries it with playInteract's own hold. SDK:registerClickHandler(fn, {modes}), default['interact','play'].665e68c— select-through + click-cycle. Pure leafselectThrough.js: the pick prefers the first opaque target (opacity < 0.25,userData.pick === 'through', or hidden = see-through); a repeat on the same spot walks down the stack. Inspector ▸ Object "Click-through in the viewport" (replicates as a props write like physics, one undo).5b09954— every module's content in the object list. A read-only "Module content" section (one row per registered scene-root group, module badge, named children), a click selects a scene-root PROXY (never replicated, never saved, never in the selection set) and frames it; an Inspector card says whose it is. An Edit viewport click on module content selects its proxy. SDK:api.registerListedGroup(name, {label, icon}).86d4ee0—api.pointerRay()under a pointer lock is the crosshair ray (it was the stale mouse ray); a free-cursor scene (30-core-flow'splay.cursor: 'free', read defensively) keeps the mouse.Suites (final battery on a fresh server)
Every guard has a counterfactual in its commit body (P0: both halves removed → 20 errors for 20 presses + the toast; P1: every handler hears every click → 5 red incl. the piano; P2: nearest hit only → 5 red; P3: section removed → red, viewport branch off → red; P4: mouse ray always → red).
svelte-check 335/47 (base 336/47; one pre-existing implicit-any removed, nothing new). vitest 214/214 (+18 selectThrough). Build green.
Selection probe (real Templates modal + feed), objects that select themselves
(Untangle reads 0/4 before AND after: the probe's first click, at the floor's centre, lands on the play FAB and enters play — a probe artifact.)
Deviations
controls-rosterupdated in-commit.Outside my lane (minimal)
commandsHandler.svelte.js(oneobjectParametersreceive branch forpick),Inspector.svelte(the checkbox + the Module content card),controls-roster.test.cjs.For the integrator
setPointerCaptureInvalidStateError on Quest (and 'Something went wrong' after leaving play) = one per press: three 0.185 OrbitControls captures the pointer unguarded and the editor controls stayed mounted in play."registerClickHandler(fn, {modes})('edit' | 'interact' | 'play'; absent = interact + play — an editor tool passes{modes: ['edit']}),registerListedGroup(name, {label, icon?}),pointerRay()= crosshair under a lock. The manager's module card could say the same in one line.editorMode+setEditorMode, playInteract's cursor carry,registerKeySessionProbe,runClickHandlers/CLICK_MODES, the timing fork.30-mod-auditbuilds on this branch (handlers that are editor tools want{modes: ['edit']}; labels viaregisterListedGroup).Owed on device
setPointerCaptureerror.🤖 Generated with Claude Code