Repository navigation
Release 1.45.0 — Build in the headset (roadmap 52) - #482
Merged
Merged
Conversation
…ns, readable troika labels, kept as 48 B2 items - Tools > Measure (radial) arms a trigger tool: every press puts a point down, snapped through the SAME snap-engine search as the desktop (vertex / edge / face, the measure flags) inside a 3 deg cone around the laser - a virtual camera down the ray turns the engine's px radius into an angle; the desktop bias is scaled to the cone (unscaled, an edge seen edge-on beat a corner 1.2 deg off - measured on IWER). A raw point lands on the grid when grid snapping is on. - chains with lengths / angles / total; a double tap (or the Measure ring's Keep) keeps the chain as a 48 B2 measurement ITEM (replicated, undoable, saved); the radial opens on the Measure ring while the tool is up: Keep / Undo point / Discard / Snap / Done (the sculpt ring's rule) - Selected > Edit measurement: a press near a point (by angle) picks it up, the next puts it down snapped, a press elsewhere adds one - measure.js's own edit step (measureEditStep, shared with the desktop click): one undo entry and one message per placed point. Edit mesh is no longer offered for a measurement item. - labels: VRMeasureLabels = troika text on a plate, facing the viewer, a constant ANGLE tall (0.03 rad) so they read the same near and far; measureCore.layoutLabels keeps them apart (every label is the same px tall). MeasureOverlay now draws items in a headset too. - hands: a pinch while measuring says "pick up a controller" and adds nothing (52 input rule) - Interact / leaving the headset / another tool ends it (a chain of 2+ points is kept, the desktop check rule) - tests: vitest vrMeasureCore (14); IWER suite vr-measure-iwer (T M L K N G E H F rows, two peers); vr-radial-menu's Tools list gains Measure. Frame cost on IWER: 0.16 ms/frame (hover search 0.24 ms at 30 Hz, labels 0.06 ms), 20 draw calls for a 4-point chain. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…- the desktop's results, one undo, peers see it
- Selected > Arrange (2+ objects): ONE ring, the radial's two-level rule - Axis (cycles X / Y / Z, value line),
Align min / centre / max, Even centres / Even gaps (3+, greyed below), Drop to ground, Align to (Selection /
Active, the Inspector's switch). Labels carry the axis ("Align X min").
- every sector is DATA (vr/arrangeCore.js, a leaf): sectorOp + arrangeCall name the SAME align.js call, with the
same arguments, that the multi-select Inspector's Arrange buttons and the object menu's Transform rows make -
so the move, the ONE transformSet undo entry and the replication (the move path) are the desktop's own.
- unit-level parity (tests/unit/vrArrangeParity, 23): the table per desktop control, and the REAL align.js run on
two identical scenes, one through the desktop call and one through the ring sector, lands every object in the
same place with one undo entry each (counterfactual: mapping gaps -> centres goes red on 4 checks).
- IWER row A: Selected > Arrange > Align X min picked with the stick (two peers), ONE Undo restores the set on
both, X even gaps, Drop to ground on A and B.
- icons: align-start/center/end-vertical, align-horizontal-space-around, arrow-down-to-line in the VR icon table.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s f-vr-place)
- placeDrag RAY mode: beginRayPlace / updateRayPlace / commitRayPlace lay the ghost out in the
objectsGroup's frame, so a moved or shrunk world rig (miniature, dollhouse) places at the
scene-unit spot under the laser; desktop pointer path unchanged (frame = identity)
- placeGhost.setGhostFrame + a frame for the pending drop boxes; explorerDrop.rayHit +
placeFromGhost(items, placements, {frame})
- prefabs get measured dims (their ghost was the 1 m unknown box and they landed half sunk)
- vr/placeRay.js: the VR glue — pointer laser per frame, stick flick turns 15 deg, the trigger
places (stays armed, keeps the turn), a trigger-drag drops on release, a grip cancels, no ghost
while the laser is on a panel
- the VR prefabs window's private ghost moved onto the shared one
- hooks: registerGripHooks (a feature may take a squeeze before anything grabs); the select
swallow learns which controller (IWER fires select BEFORE selectstart on press)
- suites: vr-asset-browser-iwer row G (new, IWER), vr-prefabs taught the shared ghost
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t in the desktop icon map - iconLint) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the rings with SHOTS=<dir> (vrEyes) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…CC0, your library) - vr/assetBrowser.js + assetBrowserDraw.js: one canvas on one plane (one draw call) on the OFF hand's wrist, facing the head; Add > Assets toggles it; steps aside while the radial is open on that hand; one-handed it opens pinned in front - tabs Packs (drill into a pack) / Free CC0 (Poly Haven models: a card downloads into the Library through the Explorer import, the card shows the progress, then it rides the laser) / My library (models + prefabs); search via the VR keyboard, filtering as you type; pages by the buttons or the stick while the laser is on the panel - a trigger-DRAG from a card drops the item where you let go (once); a CLICK arms it (the next trigger places, stays armed); the status line says what the trigger / grip do now - a GRIP lifts the panel off the wrist: let go in the air = pinned 1.6x, near the wrist = back on it; the Pin / To wrist button does both - a trigger on the panel is the panel's: never a select of what is behind it, never a placement (also under IWER's select-before-selectstart order) - suite vr-asset-browser-iwer row B (55 checks with G), evidence panel shots Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… precise tools ask for a controller
- a pinch IS the trigger: the panel and the laser ghost read the session's select events,
so a pinch presses a card, a pinch held while the hand moves drops the item where it lets
go, a pinch-click arms it and the next pinch places; the panel speaks hands ("pinch to
place", "let go over the world")
- vr/handInput.js: isTrackedHand + controllerRequired(tool, slot) - a tracked hand gets
"Pick up a controller to <tool>" on a small chip at that hand (one draw call) and the
tool stands down; wired into the VR sculpt brush, face edit and vertex edit; exported
from the vrControls facade for 52-measure / 52-paint
- the placing hint outranks a passing info line on the panel
- suite vr-asset-browser-iwer row H (IWER tracked hands; 70 checks with G + B)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…cost on the Quest 3 profile - the panel compares a few inputs per frame and builds its card list only when one moved (the Free tab is 400 rows; it used to be walked every frame); one laser raycast per slot - frame-hook and repaint counters on the panel and the laser ghost - suite row P (IWER metaQuest3, CPU x4): panel 0.59 ms/frame, a repaint 1.5 ms (only when the view changes), the ghost 1.17 ms/frame, +1 draw call for the panel, +2 for a box ghost Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…:50, Back to 1:1 - both grips = the world in Edit whatever the rays hit or the hands are inside (Open Brush / Gravity Sketch / ShapesXR); a first-hand grab is TENTATIVE (no selection, nothing sent) until it moves > 2 cm or 250 ms pass; an unmoved first grab joined by the second grip goes back to its start pose with no undo entry and the world grab starts - two-hand object scale (+ rotate with the hands' axis) only when the first hand already MOVED the object; a second hand INSIDE another small object takes that one instead (33 G4 by touch) - grip-by-contact skips objects that enclose the headset, are larger than 2 m, are terrain or are locked (vrGrip.contactGrabAllowed, smallest containing object wins); terrain never moves by grip - Settings > VR "Both grips scale the world: Always / Only in empty air" (vrBothGripsWorld) - detents 1:50 1:10 1:5 1:2 1:1 2x 5x 10x (WORLD_SCALE_MIN 0.05 -> 0.02), readout "1:10" below life size, the 53 comfort vignette hears the world gesture's scale/yaw rate, radial root "Back to 1:1" while the world is off life size - suites: vr-universal-world-grab (new, IWER; red on the 51 union in 1/3/4/5), vr-grips-by-mode, vr-two-hand-grab, vr-world-grab adapted; unit vrGrip / vrWorldSnap / gameShell Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…iniature too - Sculpt ring gains Paint ▸ (layers 1-4, terrain only) and Scatter ▸ (paint/erase/rotate/scale/relax + Source ▸) - Source ▸ wheel: current + in-use sources + primitives; stick Y = density while scatter-painting - haptic pulse on stroke start; tracked hands are asked to pick up a controller - brush label reads layer/tool, radius and strength or density; ring scales with the world (miniature) - wheel picks apply synchronously (primed sculpt module), no late dynamic import - e2e: vr-brushes-iwer (wheels, strokes, undo, miniature 1:10 placement), vr-brushes-perf (Quest profile, 2k instances) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- several callbacks share one XR frame and most are empty, so per-callback medians read 0 ms - lane run (--local, Quest profile, 2k instances): paint +0.2 ms, scatter +0.3 ms, rotate +3.0 ms median over idle Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # src/lib/vr/sculpt.js # src/lib/vrRadialMenu.js
…d ring (9 → 10) 52-browser registers Add ▸ Assets from its mount; the suite was not in that lane's battery. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…anding The impact rule was "pre-step downward velocity >= 1.2 m/s", so a ball thrown sideways at a wall or a target never fired On Impact. A contact now also counts when the two bodies close along the contact normal (rapier's manifold) at >= 1.2 m/s. The downward test stays as its own channel with its own cooldown: On Impact gains `direction: any | down` — new nodes are seeded 'any', a node saved without the key reads 'down' (param `legacy`, honoured by the Flow panel), so old scenes fire exactly as before; emitters' "On impact" keep the landing rule. impactSpeed.js is the leaf (closingSpeed / impactStrengthFor), unit-tested; e2e flow-impact-any (red on 4cd3daf: the 'any' nodes never fire on the wall/target hit). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… 150 % on the folded N6 Profile circle (53-int Q5): it opened on focus, so Tab walked the whole panel. Now the ARIA menu-button pattern: Tab passes it, Enter / Space / Down open it with focus on the first row, Escape closes and returns focus to the circle. Compact header (53-int Q4): at a large UI scale a WindowChrome header wrapped before it ellipsized (flex lines break on hypothetical sizes). The title enters line-breaking at its minimum, header buttons stop growing at 120 %, the panel icon and the back label's text step aside (still the accessible name). The object list header's shed thresholds follow the scale and its search narrows. Settings / Inspector / object list headers are one row at 150 % folded. Rows: a11y-axe (profile menu keys + axe on the open panel), a11y-scale (three one-row header checks on the folded N6 at 150 %). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…le, HUD Rows, HUD Set Input; HUD Timer honours start Orchestrator add (docs-1 Q1). flowSockets typed these inputs and the runtime read them, but the cards drew no socket, so they could not be wired by hand (hudActions' own 'trigger' wires had nowhere to land): Set Game State (trigger) and Set Variable (trigger, value) gain catalog `inputs`; HudNode draws HUD Rows (trigger, text) and HUD Set Input (trigger, value, options). HUD Timer drew `start` but triggerStampFor only counted an unnamed/`trigger` wire — it now takes `start` as an alias. The audit (scripts/graph-audit.cjs) gains an undrawn-input guard for the two generic cards; it found Device Param (value) and Note Trigger (trigger, note, velocity) with the same gap, fixed here too. Unit guard red on 257e8f6 with exactly those six; e2e flow-sockets-wire wires each by mouse and checks the effect (11 red on 257e8f6). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r start, not the clock's origin docs-1 CORE BUGS (verified): the rotor's ramp and an autostart HUD Timer both measured from t = 0 of the clock they are handed, and the synced clock is seconds since UTC midnight — every wheel was past its spin-up from the first frame (full speed at once) and an autostart timer read 0 the moment it appeared. The rotor now counts from the moment this peer saw it switch on (off -> rests and spins up again from rest); an autostart timer from when this peer first saw it. Both re-base when the clock goes back (midnight, a session clock adopted on join) instead of freezing. e2e flow-clock-origin: 4 red on 4cd3daf (rotor 4.70 of 5.22 rad/s in its first half-second; timer 0), branch: 0.55 vs 4.67 rad/s, timer 59 -> 55. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each checked against the node's catalog entry and runtime before rewriting: Sequence fires all four steps from one pulse (not one per pulse, no wrap); Move Input is WASD x/y only (no stick, no jump); All Players is a group gate, not an iterator; Character Controller sets the player's own movement model; Move Speed writes on `set`; Camera Follow has no distance/height; Gamepad Axis is the two sticks (L2/R2 are buttons); HUD Screen acts per pulse (show / hide / toggle); HUD List names the element, title and row cap; HUD Rows and Leaderboard write a HUD List element; HUD Timer a HUD Timer element; HUD Input also reads sliders, toggles, dropdowns and tabs. These lines feed the palette tooltip, the info pane and the docs-site node pages. Unit guard tests/unit/nodeDocsClaims.test.js: 6/6 red on 4cd3daf. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rd card width docs-1: at the 150 px card a "wait" word on every row plus a 48 px step label left each field's input 12 px wide — the values read as empty boxes. "wait (s)" is now said once as the column heading (each field keeps an accessible name, "wait before step N (seconds)"), the step label is a fixed 36 px so the field does not jump when the ● lights, and the field takes the rest: 68 px, "10.50" fits. e2e sequence-card: base 4cd3daf 12 px fields, all four values clipped. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The two pose tests called applyRotor once at t = 1 and expected the angle from the clock's origin; they now switch the wheel on at t = 0 first. New: a wheel switched on at t = 51 300 (mid-day on the synced clock) spins up from rest, off -> on spins up again, and a clock that goes back re-bases — red on 4cd3daf's motionNodes.js. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…1 s back) Follow-up to 10111ec: a frame of jitter before the switch-on time no longer moves the start; only a jump back of more than a second (midnight, a session clock adopted on join) re-bases. Unit test covers the jitter case. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1.44.0 # Conflicts: # src/lib/vrRadialMenu.js # tests/e2e/vr-radial-menu.test.cjs
- CHANGELOG 1.45.0 "Build in the headset" (52 V1-V5 + hands, fix-batch-1 #445 fixes). 1.44.0 went to roadmap 60, so roadmap 52 releases as the next minor. - tests/e2e/battery.json (written with battery.cjs's own writer, round-trip checked): suites vr-measure-iwer, vr-asset-browser-iwer, vr-universal-world-grab, vr-brushes-iwer (2x the 52-union desk times), flow-impact-any, flow-sockets-wire, flow-clock-origin, sequence-card (120 s, unmeasured); vr-brushes-perf excluded as a frame-rate measurement (--exclusive by hand). The batteryDesk coverage test was red on the union for exactly these nine. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ny drop The suite's shim now records, per page: every RTCPeerConnection's real ICE / connection state, every data channel's open/close with sent / delivered / held counts (a peerjs channel's label is its conn id), the selected ICE pair's candidate types, each DataConnection's sends and 'data' events by message type, and what the send / receive / role gates answer about the other peer. Printed with NETDROP_TRACE=1, or when B's edit never reaches A. A new premise checks that B's edit reaches A before any drop (the lock alone is a protocol-floor message). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
peerjs Peer.disconnect() - every drop of the signaling socket - sets peer.id to null until the socket re-registers. The cloud roles plugin reads its own id from getPeers().peer.id, so for the length of a drop it counted itself alone, wiped its role map and the host it joined; after the heal a joiner read itself as a VIEWER (the host still had it as an editor and, its open set unchanged, never re-sent the map), and core's creation gate kept every edit local. Only deployed builds load the plugin, which is why net-drop was green on a dev server and red on production 1.43.2 / 1.44.0 and every preview. peerDrop.keepPeerId gives the Peer an own id getter: the live id, else the id it was created with. peerjs bookkeeping (_id, _lastServerId, open, disconnected) is untouched. reconnectDial keeps its old no-dial-while-disconnected behaviour. net-drop: own id readable at the cut (red on v1.44.0), roles unchanged by the heal (with a plugin). Unit: keepPeerId against the real peerjs Peer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the world' row Re-recorded on the union (729ee67 snapshot) and checked to differ by exactly that row in 5 queries (Scale, xr, zoom, vr, ai) plus the VR section on 'zoom'; verify 11/0. Base v1.44.0 is green with the old lock, the union was red 6 (A/B ab1). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…7/39) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…h E2E_CLOUD_PLUGIN for the deployed condition Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… conn already closed A graceful ws.close() waits for the server's close frame, which offline emulation held for 3 s+ on a built app: B's socket sat CLOSING with peer.open still true, so the QUIET premise 'signaling down at the cut' was red on production and on any build, and a 3 s cut window let A's conn close before HARD reached it (script error). The shim now hands peerjs the 1006 close a dropped socket fires. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s too (room labels, recent toasts) A single sample at the cut raced peerjs's own retries, which put the id back between failed reconnects (v1.44.0 read null at 4.7 s / 5.3 s when sampled). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…outlives a signaling drop Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 26, 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.
Release 1.45.0 — Build in the headset: measure, arrange, paint, scatter, browse assets and shrink the world 🥽 (roadmap 52).
The 52 union (
feat/52): #422 52-measure (VR measure + Arrange), #432 52-paint (controller Paint/Scatter brushes),#423 52-browser (wrist Asset Browser, place-drag, hand tracking, universal world grab + detents), plus fix-batch-1 #445
(On Impact any side, sockets, clock origin, Sequence card) and the orchestrator add #478 (net-drop heal: our peer id
outlives a signaling drop, so a joiner no longer comes back View-only). Merged on v1.44.0 + origin/deps. Integration on
top (52-int):
Proof: gates (build, vitest 2842, svelte-check ratchet 287/39, tokens/storage/zindex, scene-lint 16 scenes 0 errors),
battery vs the v1.44.0 baseline (18 new reds, each A/B'd against pristine v1.44.0: lock-search was ours and is fixed;
the rest are red on both sides or load flakes), preview-1-45 proof (162 rows + L3 scene-load) and #478 on
preview-1-45b: net-drop vs production 1.44.0 alternating (production shows the View-only signature, the fix does
not), 25/0, 25/0, 24/1 on a quiet slot. Known and pre-existing on production 1.44.0 (not fixed here): the CSP blocks the
basis transcoder's eval (ktx2-twins, export-presets); export-zip's grep matches the CSP URL inside the bundled env
object; net-handshake 'C received the scene' under load.
🤖 Generated with Claude Code