Skip to content

52-measure: measure in VR (V1) + align / distribute / drop from the VR radial (V2) - #422

Merged
AlexZ005 merged 4 commits into
feat/52from
feat/52-measure
Oct 23, 2026
Merged

AlexZ005 merged 4 commits into
feat/52from
feat/52-measure

Conversation

@AlexZ005

Copy link
Copy Markdown
Collaborator

52-measure — V1 measure in VR + V2 align / distribute / drop from the VR radial

Base: the 53 union (origin/feat/53-int, started early per the 52 rules); 52-int merges v1.43.0 into feat/52 first.

V1 — Measure in VR

  • Tools ▸ Measure (radial) arms a trigger tool. Each trigger press puts a point down, snapped through the same snap-engine search as the desktop (vertex / edge / face, the measure flags) inside a 3° 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° off (measured on IWER).
    • A raw point lands on the grid when grid snapping is on.
  • Chains: each segment shows its length, each joint its angle, and the chain a total. A double tap (or the Measure ring's Keep) keeps the chain as a 48 B2 measurement item (replicated, undoable, saved). While the tool is up, the radial opens on the Measure ring: Keep · Undo point · Discard · Snap · Done.
  • Selected ▸ Edit measurement: a press near a point (judged by angle) picks it up; the next press puts it down, snapped. Each placed point is one undo entry. This uses measureEditStep, which is now shared with the desktop click (desktop behaviour unchanged).
  • Labels: VRMeasureLabels.svelte draws troika text on a dark plate. Each label faces the viewer and is a constant 0.03 rad tall, so it reads the same near and far. layoutLabels keeps labels from overlapping.
  • Hands: a pinch says "pick up a controller" and adds nothing.

V2 — Arrange from the radial

  • Selected ▸ Arrange (2+ objects) is one ring, which keeps the radial's two-level rule. Sectors:
    • Axis: cycles X / Y / Z
    • Align min / centre / max
    • Even centres / Even gaps (3+ objects; greyed below that)
    • Drop to ground
    • Align to: Selection / Active
  • Every sector is data (vr/arrangeCore.js) naming the same align.js call the desktop makes. That gives the same results, one undo entry, and replication through the move path.

Proof

  • vitest vrMeasureCore (14) and vrArrangeParity (23). The parity test runs the real align.js on two identical scenes, one through the desktop call and one through the ring sector, and checks they land the same. Counterfactual: mapping gaps → centres turns 4 checks red.
  • IWER suite vr-measure-iwer (two peers): 71 checks green. Rows: Tools from the stick · corner snap (snap-off counterfactual in-suite) · labels facing and growing with distance · double-tap keep, synced to B · grid · edit + undo on both peers · hands · Arrange by stick with one undo on both peers · frame cost.
  • Frame cost (IWER, Quest 3 profile): 0.16 ms/frame (hover search 0.24 ms at 30 Hz, labels 0.06 ms/frame). The labels add 20 draw calls for a 4-point chain.
  • Regression battery green: measure-tool · align-arrange · vr-radial-menu · vr-multiselect-ring · vr-iwer · vr-ungroup · vr-props-panel · vr-box-select. svelte-check is at baseline (299/39), and full vitest is green.
  • Eye shots are in ~/.code/lanes-30/after-52/52-measure/.

Shared-file touch points (small additive edits)

  • vrRadialMenu.js: one import, one call in registerBuiltins, ringTitle('measure'), and Edit mesh hidden for a measurement item.
  • vr/iconData.js: six icons appended.
  • debugHooks.js: four rows.
  • MeasureOverlay.svelte: draws items in VR and mounts the VR labels.
  • measure.js: exports MEASURE_TARGETS / MEASURE_BIAS / RADIUS_MOUSE and measureEditStep.
  • tests/e2e/vr-radial-menu: the Tools list gains Measure.

Owed on a real Quest (int + device checklist)

Laser feel and the 3° cone, label legibility at the true eye resolution, haptic ticks, double-tap timing, and how the Arrange ring reads.

🤖 Generated with Claude Code

AlexZ005 and others added 4 commits October 23, 2026 00:05
…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>
…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>
@AlexZ005
AlexZ005 merged commit 389ce9f into feat/52 Oct 23, 2026
8 of 9 checks passed
@AlexZ005
AlexZ005 deleted the feat/52-measure branch October 23, 2026 21:26
@AlexZ005

Copy link
Copy Markdown
Collaborator Author

In main via 52-int: feat/52 was merged into feat/52-int and released in 1.45.0 (#482, tag v1.45.0 e59e96a, live on production).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant