Skip to content

1.18.0: Menus, levels and smooth frames in a headset (roadmap 31 + #254) - #256

Merged
AlexZ005 merged 41 commits into
mainfrom
feat/1.18
Oct 1, 2026
Merged

AlexZ005 merged 41 commits into
mainfrom
feat/1.18

Conversation

@AlexZ005

@AlexZ005 AlexZ005 commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

1.18.0 — Menus, levels and smooth frames in a headset

Roadmap 31, the second Quest round, integrated on feat/1.18 (lane 31-integrate):

Proof: svelte-check 333/47, vitest 378, build green; the held battery + modules flights green on the union; preview
proof pass on https://preview-1-18.theprototype.pages.dev. Details: the 31-integrate handover.

🤖 Generated with Claude Code

AlexZ005 and others added 30 commits October 1, 2026 00:23
- src/lib/gameSettings.js: the ONE settings store every game shares - the core rows
  (music/volume, sfx/volume, haptics, show FPS, VR turning + snap angle, comfort
  vignette, quality preset) plus game rows declared by api.game.addSetting or a flow
  node (registerGameSetting(row, owner) -> off). LOCAL per device via safeStorage,
  per game id: core rows in tp:game:<id>:shell, a game row in tp:game:<id>:<row id>.
- the game id = slug of the last scene FILE opened (sessions.applySession notes the
  payload name, so a Games-tab load of Towers is 'towers'), else the saved scene name,
  else 'untitled'.
- live resolvers core reads: sfxLevel/musicLevel/hapticsAllowed/resolveTurning.
- vitest gameSettings (14): per-game isolation (SFX off in Towers, on in Waves), row
  coercion, reserved ids, a game row surviving re-registration.
  Counterfactual: drop the gid from shellKey -> "SFX off in game A does not reach
  game B" goes red.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- scripts/perf-games.cjs: per game, a fresh page installs the game's modules from their
  real zips, loads its real .tpscene through the Games-tab path (importSessionZip +
  requestLoadSession) from the scenes repo at a git ref (PERF_SCENES_REF, default
  preview-1-17) or a directory, presses Play and the game's own HUD Play/Start, then
  measures 10 s at CPU throttle x4: draw calls + triangles per display frame (every
  render() pass summed), geometries/textures, a texture-MB estimate, lights and
  shadow-casting lights/meshes, meshes/instanced/unculled, p50/p95/p99 frame ms and the
  heap delta. Markdown + JSON into after-31/31-perf/.
- --vr walks a fake XR session's left stick forward the whole window (fakeXR.cjs);
  --profile adds CDP CPU self-time and allocation sampling (collected objects included)
  per function, top 30 each.
- Baseline on 1.17 (Radeon 890M): Waves p50 700 ms, Dungeon Realms ~10 MB/s heap churn,
  Football 336 calls + 7 lights, Jam Room 910 calls.

No behaviour change (a script), so no counterfactual; the table is the artefact.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… trigger picks it

The Quest report: "In VR, when I open radial menu, i do not see highlights when i move
joystick, fix navigation." The input was never the problem - vrControls set vrHovered to
the right sector every frame (measured through fakeXR: 8/8 directions). VRMenu.svelte is a
LEGACY-mode component, where `color={sectorColor(s.entry)}` compiles to
untrack(() => sectorColor(...)) depending on `s` alone, so the $vrHovered read INSIDE the
helper registered nothing: no sector mesh ever repainted, under the stick or the ray (the
hub, which reads the store inline, did).

- VRMenu: sectorColor/labelColor take the hovered id as a PARAMETER ($vrHovered passed in
  the template), so the colour depends on it.
- vrControls: a stick-lit sector is remembered per frame (radialStickSelection); Scene's
  trigger picks it when its own ray misses the ring (before, only a ray hit could be
  triggered and a stick-lit sector fell through to an object select). Stick-click and the
  hold-mode release are unchanged.
- each stick is read from its OWN inputSource found by handedness (the pointer stick used
  sources[<controller slot>], the 194/210 divergence after a hands<->controllers swap);
  a greyed (disabled) sector is never lit by the stick.
- one haptic tick per sector change stays behind hapticPulse's C4 gate (Interact ticks,
  Edit is silent - the user's earlier rule; recorded in QUESTIONS).

Suite vr-radial-stick (12, fakeXR through the real per-frame path + Scene's select handler):
8 directions light exactly their sector MESH, centred = nothing, 3 ticks for 3 changes,
trigger picks the stick-lit Add, stick-click picks Scene, the other hand's stick, the ray
lights what it hits, a greyed sector stays dark.
Counterfactuals (one batch, all restored): colour helper without the hovered argument ->
mesh-repaint, other-hand, ray and both greyed checks red; trigger without
radialStickSelection -> "the trigger opens the stick-lit sector" red; disabled guard
removed -> "a greyed sector is not lit" red. 6 red in total.
svelte-check 333/47 (base 333/47, identical list modulo line shifts).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… core obeys, show FPS, levels

G4 THE PAUSE MENU (gameShell.js + GameShellMenu.svelte + the VR board):
- Resume · Restart · Levels (when registered) · Settings · How to play · Main menu, plus a
  quiet desktop-only "Back to editor". ONE model (shellMenuItems / shellSettingViews / ...)
  drawn by the desktop card and, in a headset, on the VR game panel (shellPanelDraw.js,
  kind 'shell' hits pressed through the panel's one press path).
- Desktop: Escape in a GAME opens it (a lost pointer lock pauses into it; Escape again =
  Resume, re-locking through the retry path); the corner Menu button; desktop Interact
  takes Escape in the component. A scene that is not a game keeps Escape = exit.
- VR: the LEFT X button (the free face button), a Menu button on the board footer and on
  the wrist card.
- HudLayer stays the ONE writer of playPointerFree (folds shellMenu.open in) and its ring
  stands down while the menu owns the keys.
- Main menu: music stops, a headset session ends, Interact -> Edit, play exits (editor
  camera via the camera-follows-isLocked effect), the templates modal opens on Games
  (templatesModalTab, new). Restart: requestResetGame (host/alone), module onRestart hooks,
  respawn, screen overrides cleared. Seams registered by gameShellWire.js.

G1/G2 PER-GAME SETTINGS OBEYED LIVE:
- the per-game Music / Sound effects levels land on the audio BUSES (audioEngine
  setBusLevel, built-later buses start at the level): api.playSound, api.music, flow sound
  nodes, pings and the scene track all obey; playGameSound starts nothing when off.
- haptics: the hapticPulse funnel also gates on this game's "Controller vibration".
- VR turning: snap / smooth (90 deg/s, deadzone) / off + the snap angle, per game while
  playing, the device's snap angle otherwise (vrControls.turningInForce).
- comfort vignette (comfortVignette.js): a head-locked ring closing on stick locomotion and
  smooth turning, one mesh, no per-frame allocation.
- Quality: qualityGovernor.applyGameQuality pins High 0 / Medium 3 / Low 7 while playing,
  Auto releases to the governor (split agreed with 31-perf).
- the game id also follows the saved scene name (levels.currentLevel) after the file name.

G3 SHOW FPS: fpsMeter.js (desktop = sceneBudget's frame observer, VR = the panel's per-XR-
frame call), FpsCounter.svelte (fps · ms · calls · tris), the VR strip + wrist lead line.

P3 SDK (moduleSDK api.game): levels, addSetting, setting, setSetting, onSettingsChange,
setHelp, onRestart, openMenu/closeMenu/menuOpen — all journalled, feature-detectable.

Tests: e2e game-shell (70 checks, desktop + VR emulated via fakeXR), vitest gameShell (10),
gameSettings (14). svelte-check 333/47 (baseline held).
Counterfactuals (each run red with the change reverted, then restored):
- drop the PLC Escape -> openShellMenu line: 1.2-1.5 red (Escape exits play again)
- drop setBusLevel('sfx', ...): 3.3 / 3.6 red
- drop the left-X toggle in vrControls: 8.2 / 8.3 / 8.5 / 8.6 red
- drop hapticsAllowed() from the funnel: 10.1 red (off:2)
- levels off() compared object identity: vitest "a pick reaches onPick" red (fixed with a token)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…art, menu

A section per call with a short example; the core rows core obeys and how a module with
its own WebAudio graph obeys the sfx row. (The docs-site copy is on the docs repo branch
docs-31-game-shell.)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…, a filled hover, module menus as beam ends

The Quest report: "i want to be able to see from controller ray where i point in menu (now
its hard to navigate)". The beam and reticle existed; four things made them hard to use.

- vrControls: the beam is NORMAL-blended (the additive glow vanished against a bright sky
  - the documented additive-burst trap) and a touch wider; the reticle ring gains a solid
  DOT at the exact hit point (the ring left the point itself empty, which is where a small
  button is).
- vrControls.beamTarget: a module's INTERACTIVE scene-root content ends the beam too (it
  lives under module-world-root since 30b P5, outside objectsGroup, so the laser passed
  through a module's own menu/board with no dot). The name list is cached on the registry
  revision - no sort or allocation per frame.
- vrGamePanel: the button under the laser is FILLED (+ a thicker ring), the footer buttons
  too - a 4 px outline on a board 1.2 m away was too faint to tell which one the ray was on.
- vrGameInput.interactiveGroupOf: accept a group under module-world-root. The old
  `node.parent === scene` test matched nothing after 30b P5 re-homed module groups, so a
  module's buttons never resolved to a click target for the laser hover or the sweep.

Suite vr-laser-panels (23, fakeXR + the REAL Towers, Stars Room and Jam Room templates
from scenes preview-1-17 via SCENES_DIR): per game the beam ends on the menu button
(length = distance), reticle AND dot on its centre (< 1 cm), the board's hover id is that
button and a pixel inside it changes (filled); the beam is normal-blended; an inline
module's interactive group is re-homed, ends the beam at 1.49 m and resolves to its
group; the reticle sits on a radial sector.
Counterfactuals (one batch, restored): no dot -> 3 red; no fill -> 3 red; additive beam ->
1 red; no module roots in beamTarget -> beam 2.90 m red; parent === scene -> group null
red. 9 red total.
svelte-check 333/47 (identical list modulo line shifts).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Quest note on Towers: "you can't take object from specific distance and need to jump on
other objects". Both rules are core and opt-in, so any game can use them.

- playReach.js (pure leaf, vitest 12): reach is the distance from the aimed point to the
  player's BODY (the segment feet..eye), not the eye - a crate at your feet is in reach, one
  on a 3 m ledge is not until you stand on something or jump. carryLimit keeps a carry on
  the reach boundary along any aim.
- scenePhysics `play.reach` (metres; OMITTED when absent, so older scenes are byte-identical)
  + playSettings `reach` (a publisher's userData.play.reach overrides, field by field).
- desktop Play (playInteract): a grabbable body out of reach reads 'toofar' (the crosshair:
  "Too far - get closer"), a press on it carries nothing, and a carry is held inside the reach
  whatever the wheel asks. The editor's Interact cursor carry (no body) is unaffected.
- VR Interact/Play (gripTargetOf): a grip on a piece out of reach holds nothing and buzzes
  `fail`; the hand-inside path takes the same rule.
- VR jump: the Character Controller node's jumpHeight now jumps in VR on right A while it
  declares walk mode and Interact walks (vrJumpHeight); A stays push-to-talk otherwise, and a
  switch mid-hold cannot strand the mic open. vrWalkStep takes jumpHeight.
- Configure Scene > Physics > Play mode: "Limit grab reach" + a Reach (m) row.

Suite play-reach-jump (33): desktop reach + carry limit, desktop jump ONTO a 0.6 m crate (the
capsule stands on a dynamic piece and does not shove it), VR grip reach + the buzz through the
real per-frame path (fakeXR), VR jump on A through the real per-frame path, the Inspector row.
Each in-run counterfactual included (no reach -> the far crate is grabbable; jumpHeight 0 / no
controller -> no jump).

Counterfactuals (source reverted, suite re-run, restored):
- playInteract inReach -> always true: 7 red ("a crate 2.8 m away reads TOO FAR", "a press on
  it carries nothing", ...).
- vrControls gripTargetOf reach -> null: "VR: a grip on the far crate takes nothing" and "the
  squeeze holds nothing" red (it grabbed the far crate).
- vrControls vrJumpHeight -> 0: "VR: ... can jump" and "pressing A lifts the head" red
  (peak 1.61).

svelte-check 333/47, the message list identical to the base.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Measured with scripts/perf-games.cjs --profile (desktop, CPU x4, Radeon 890M), 1.17 ->
this commit: Waves p50 699.9 -> 16.7 ms, Dungeon Realms p99 116.7 -> 16.8 ms and heap
churn +97 MB/10 s -> none, Football p50 49.9 -> 33.4 ms.

- ModuleContent.svelte: rows (each carrying its THREE group) lived in a deep $state and
  `use:rowMenu={row}` made svelte deep_read the action parameter - the WHOLE scene graph
  through `parent`, every typed-array index, on every refresh (1 s + every pokeScene),
  mounted in every mode incl. VR. 88% of Waves' CPU, ~200 MB/s garbage; Dungeon Realms'
  1 Hz hitch. $state.raw, and the 1 s catch-up skips a hidden list.
- dungeonPlay.dungeonData: a whole-scene getObjectByName per frame from the walker,
  flier and VR walk in EVERY game (a miss walks everything). Hit cached + re-validated by
  parent chain, miss remembered 250 ms; walkable() stops allocating arrays per call.
- flowRuntime: resolveInputs / evalNode input() scanned every edge and, per edge, every
  node (O(consumers x edges x nodes) per tick). Indexed per graph array (nodeById,
  edgesInto), rebuilt only when a graph changes.
- moveSmoothing: a watching peer searched the tree per eased body and pokeScene'd (every
  $objectsGroup subscriber) EVERY frame while a remote physics stream eased. The ease keeps
  its object; the UI hears the motion at 10 Hz and once on landing.
- VRObjectsPanel: always mounted, and rebuilt its row list on every scene poke while
  closed. Only while open.
- perf-games.cjs: --profile names the nearest app frame calling the heaviest functions;
  --vr measures with post/AO off (the headset's direct render), documented as one eye.

Suite perf-stutter (new), one counterfactual guard per fix - with the five fixes reverted:
  deep-reads 0 -> 9 getter reads; edge reads 401 -> 20451; dungeon traversals 1/1 ->
  100/100; eased frames <=1 search + <=1 poke -> 59 + 30; closed-panel rebuilds 0 -> 8.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- src/lib/lod.js: dense meshes (>= 3000 tris) in objectsGroup and under the module world
  root get meshoptimizer-simplified levels (ratios 0.5 / 0.25 / 0.1), built ONCE per asset
  in the existing decimation worker (keyed by a content signature, so fifty placements of
  one pack piece or ten parses of one GLB share one set). THE TREE IS NEVER TOUCHED: the
  swap happens in scene.onBeforeRender / onAfterRender, inside renderer.render() only, so
  every serializer, picking, BVH, physics hull and mesh tool sees the source geometry.
  Distances are in world RADII of the mesh with a hysteresis band; a `lodBias` store pulls
  them in (the quality governor writes it in P3). A geometry swap or an in-place edit
  stands the entry down at once and rebuilds after it has held still 1.5 s. Skinned
  meshes, morph targets and (in auto mode) instanced meshes are left alone;
  `userData.lod = false` opts a mesh out. LOCAL, never replicated.
- src/lib/lodCore.js: the pure rule (pickLevel with hysteresis, levelsToBuild,
  normalizeLodOptions, lodBiasFor, geometrySignature) - vitest.
- decimateCore: an optional per-job `errorCap` (absent = the import gate's 0.05,
  byte-identical); a far LOD level asks for the count, not a surface error.
- api.lod(object, {ratios, distances, minTriangles}) -> {meshes, ready, remove}, journalled.
- Settings > "Simplify distant models" (lodEnabled, LOCAL, default on); App boots it and
  the debug hook gains `lod` (the three tails 219/219/219).

Suite lod (new, 20 checks): levels built (9024 -> 4511 -> 2256 -> 902), the frame's
triangles from 400 m drop 18110 -> 1866 and a middle distance draws a middle level, the
mesh holds its source geometry after a render and toJSON writes only it, a geometry swap
draws whole at once, twins share one cache entry, the bias pulls the edge in, api.lod on
scene-root module content + teardown on deactivate.
COUNTERFACTUAL in-suite: with LOD switched off the far render draws the full 18110.
vitest lodCore (14): hysteresis both ways, radii not metres, bias, floors, signature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ty (P3, K4)

Until now the adaptive quality governor never ran in VR: its frames came from
sceneBudget's window rAF, which an immersive session suspends, so a Quest sat at full
quality however badly it judged.
- FRAMES: the XR session's own requestAnimationFrame (a second callback beside three's)
  feeds every frame interval, deciding every 250 ms.
- THRESHOLDS from the headset's refresh rate (xrThresholds: over = 1.3 budgets, under =
  1.12 budgets; 72 Hz -> 18.1 / 15.6 ms), re-read on frameratechange, through a new
  governor.setThresholds(). The static VR pair (13.9 / 11.1) could never recover at 72 Hz:
  a healthy frame reads 13.9.
- ENTRY FLOOR: auto mode enters a session at level 1 (shadows off, the Quest budget) and
  holds it as a recovery floor (governor.setFloor) for the session; exit gives the level
  back. The opt-out holds: with auto quality off nothing changes a level.
- RESOLUTION: an XR framebuffer's size is fixed at entry and three already runs maximum
  foveation, so the scale the session needed is applied to the NEXT entry
  (xrScaleAfter -> setFramebufferScaleFactor at sessionend).
- LOD: every published level sets lod.lodBias = 0.85^level (floor 0.35).
- api.quality = {level (getter, 0 best), max, labels, vr, onChange(fn) -> off}; the
  listener fires on level CHANGES only and is released with the module.
- A headset is always "heavy" (governed); the sampler's last reading is a desktop one.
MERGE NOTE (31-game-shell edits the same file): their applyGameQuality pin must win over
the entry floor - `if (gameForcedLevel !== null) return` at the top of startXRQuality's
floor block. Hunks were kept apart from theirs (decideNow / after autoQuality.subscribe).

perf-governor section 6b (8 checks): a session starts at level 1 with shadows off even on
a light scene, judges by 72 Hz, missed XR frames take the next step and pull the LOD bias
in, on-time frames recover to the floor and never past it, exit restores level 0 and the
desktop thresholds, the next-entry scale is kept. COUNTERFACTUAL in-suite: with auto quality
off a session changes no level. vitest: the 72 Hz counterfactual (static pair stuck at
level 1, xrThresholds recovers), missed-frame step + recovery, scale rule.
lod suite 8.3/8.4: api.quality's shape and onChange hearing 2 then 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…et, the probe (P4)

- api.quality / api.lod with a worked example (cut your own effects on onChange; levels
  for dense module geometry; userData.lod = false opts a mesh out).
- The Quest budget (<= 150 calls, <= 300k tris, <= 2 lights shadows off, no per-frame
  allocations) and how to measure a game with scripts/perf-games.cjs.
- The four lessons this round measured: no THREE object inside a Svelte $state (the deep
  read was 88% of a game's frame), no per-frame whole-scene lookups, no per-frame
  allocation, lights/shadow casters/transmission are draw calls.

Before/after (all seven games, desktop + VR-emulated) is in the lane handover and
after-31/31-perf/perf-{baseline-1.17,after-31,vr-baseline-1.17,vr-after-31}.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-towers P2+P3)

The Quest note: "after finishing basic level nothing happens, just like hooray. I want to use
different levels and different shapes... logical things such as you can't take object from
specific distance and need to jump on other objects" (T1, T2).

- src/modules/towers: a CORE module (the pong precedent), dormant unless the scene holds the
  `Towers game` marker. levels.js is the pure leaf (vitest 17): 12 levels - Stack, Planks,
  Climb, Wedges, Barrels, Arches, Narrow base, Gusts, Balls, Wobble, Outline, Summit - each
  with its own shape set dealt onto racks, a goal, par pieces + par time (1-3 stars), a time
  limit and an allowance of lost pieces; the unlock chain (a star opens the next level); the
  rack layout; the tower measure; the judge (win = the goal HELD 3 s with nothing held; lose =
  time, a collapse, too many pieces lost outside the yard, the star lost); the gust schedule;
  the outline cells.
- module.js: ONE authority (the physics initiator, else the lowest peer id) deals the pieces as
  TRANSIENT duplicates of templates parked under the floor, judges ~10x/s, pushes gusts and
  writes tw* game variables; every peer derives the HUD words (the `towersinfo` value node into
  HUD Text's format), the moments (intro banner, gust warning, lost-piece poof, landing knocks,
  win confetti/fanfare/buzz, fail sound) and saves stars with api.storage. HUD presses carry no
  presser, so every peer watches the hudbutton stamps and only the authority acts. `towerswobble`
  rocks the level-10 plate as a module EFFECT (a kinematic body). The game shell's
  api.game.levels / setHelp / onRestart are feature-detected (feat/31-game-shell's shape).
- The Towers def: the arena (pad, pedestal, wobble plate, two racks, a high ledge above your
  reach, a star perch, the goal/yard/ghost markers), ten piece templates (cube, plank, beam,
  wedge, barrel, arch + L with compound colliders, ball, heavy base, star), the level-select
  menu (12 buttons + stars), the in-game HUD, results (Next / Retry / Levels), a walk + 1 m
  jump Character Controller and `play.reach: 1.3`. No module download any more.
- Author kit: a `block` object type (the app's Wedge/Arch/Corner/Stairs) and `thumb.dress`
  (card-only objects: the card shows a round in progress).

Suites: towers-levels (NEW, 55/55 on the staged scene: level select + locked refusal, level 1
dealt + HUD + goal ring, a scripted WIN of level 1 (stars saved), the unlock PERSISTS across a
page reload, level 2 dealt + a scripted win, Next -> level 3 with its ledge pieces out of
reach, a LOSS by pieces lost outside the yard, Retry, P Restart/Levels, the empty arena on the
level select, gusts warned + pushing, the wobble plate rocking as a kinematic body, the outline
won, the summit won with the star on top). game-towers rewritten (21/21: the arena, the
templates, reach + spawn, the walk+jump body, the level select, the music, a readable frame).

Counterfactuals:
- the judge's hold removed (won on first contact): vitest "must HOLD for three seconds" and
  "outline and deliver goals" red.
- the wobble effect reading `base.rotation` (the runtime hands `{rot: [...]}`): "in level 10 the
  plate ROCKS" red (tilts 0.000 x6) - the bug this suite found.
- gusts at strength 1.1: "the gust PUSHED the top of the tower" red (3 cm).
- toMenu without clearPieces / a test writing progress behind api.storage: the level-select
  sections red (the findings that added setProgress and the empty-arena clear).
The e2e counterfactual run for the unlock check and the lost-piece rule is scripted
(scratchpad cf-p2.py) and still OWED - the slot wait timed out before it ran.

svelte-check 333/47, message list identical to the base.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CLAUDE.md: the ROADMAP 31 Towers entry (playReach, VR jump, the core towers module, the
module-effect base-is-arrays trap). MODULES.md: a walkthrough of the towers module as the
pattern for a game's rules (a pure leaf, one authority, HUD presses without a presser,
api.storage progress, the shell seams feature-detected). The docs-site pages (towers.md + a
physics.md snippet) are staged for the integrator in cloud-lane-30-staging/31-towers/docs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… through the pause menu

- game-shell-games (63 checks): Towers, Stars Room, Football, Jam Room, Dungeon Realms,
  Untangle, Waves from their REAL .tpscene + module zips — each: the game id is its slug,
  Escape mid-game opens the pause menu, Resume returns, Main menu leaves Play to the Games
  tab with the music off; then VR (fakeXR): left X opens it on the board, Resume closes,
  Main menu leaves the game. ALL PASS (SHELL_SCENES_DIR = the preview-1-17 scenes).
  Evidence: /home/deck/.code/lanes-30/after-31/31-game-shell/menu-desktop-<game>.png.
- game-shell: EVIDENCE_DIR also writes the VR board's four pages as PNGs.
- game-editor-flow asserted "Escape returns to the editor" in a game, which K3 changes on
  purpose (QUESTIONS-31-game-shell fork 1): it leaves through the menu's Back to editor now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s to its template

Roadmap 31 (Stars Room S2/S3/S4), the core half:
- clapGesture.js (pure leaf): two hands within 10 cm, held 0.25 s, re-armed only by
  parting past 20 cm, one per second. vitest (9).
- clap.js: the runtime, fed by the knock's hand seam from Scene (never imports
  vrControls), hands carried into the objects group's frame; gated on the game (Interact
  or Play) and on a listening On Clap node. feedClap() is the test hook.
- On Clap node (Game): pulse + `point` + `byMe` outputs, an `enabled` input, `who`
  anyone|me. `anyone` replicates as the ordinary nodetrigger, which now CARRIES the point
  (optional `at`, validated in wireValidate; absent = every pre-31 pulse), so the
  initiator's spawner and every peer's burst agree on where. `me` stays local (a buzz).
- Spawn gains a `position` input (a place in the objects group's frame; wins over the
  offset); Effect Burst / Game Sound accept a place for `at`.
- Point Grab node + pointGrab.js leaf: while `enabled` reads off, the VR grip RAY
  (gripTargetOf) and the desktop carry (crosshair and Interact cursor) take nothing; a hand
  INSIDE an object still holds it, the knock is untouched, Edit is never affected.
- Game Setting node: declares a row through 31-game-shell's gameSettings.js
  (registerGameSetting, owner node:<id>, dropped with the node) and outputs this device's
  value.
- a spawned transient copy remembers its template (transientObjects.spawnedFromOf, derived
  on both sides of `duplicate`, nothing new on the wire) and answers to the template's
  Object Selector for events, so an On Hit written for the template counts every copy.
- normalizeLocomotion keeps a boolean `worldGrab` (K1; 31-vr-core implements it).
- gameKit exports clap / clapGesture / pointGrab / gameSettings for the suites.

Suite stars-clap (NEW, 33/33). Counterfactuals, one run with six guards reverted:
- grip ray gate removed (byRay = true) -> 8.4 red (the ray took the ball with pointing off)
- desktop cursor gate removed -> 8.6 red
- spawn ignores the wired place -> 2.2 red (copy at the template's offset, [0,1,0]; 6.2 with it)
- nodetrigger sent without `at` -> 2.8 red
- template-selector match for copies removed -> 6.3 red (count 0 -> 0)
- fireClap's own `enabled` check removed -> 4.1 stayed GREEN: clap.js asks clapWanted()
  first, which already refuses; the inner check is the second layer for a graph holding one
  enabled and one disabled On Clap node (not separately proven).
All restored; worktree clean.
svelte-check 336/47, message list identical to base 40e07fb; vitest 300 green.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… clapped stars; Jam Room: fly, world grab, teleport

Roadmap 31 items S1-S4 and J1 (the user's Quest 3 feedback, 2026-10-01).
- Stars Room: play.locomotion.teleport + play.bounds = the inside of the glass (walls at
  +-5.75); two Game Setting rows, "Point to move stars" (Point Grab node) and "Make stars
  with a clap" (both On Clap nodes); a clap spawns ONE star from the template at the point
  (maxAlive 40, oldest recycled) with a sparkle + sparks burst and a portal sound there, and
  a `me` buzz in both hands; the template's On Hit (anyone: a ring; me: a touch) makes a
  clapped (or More-stars) star count as one of your touches. Copy: how to play mentions the
  clap and teleport.
- Jam Room: play.locomotion {fly, worldGrab, teleport} + play.bounds inside the studio.
  The flags take effect once 31-vr-core lands (K1); they survive the save now.
- game-stars-room +13 checks (S1-S4 on the real scene: bounds, the two rows, a clap makes a
  crystal dynamic star with its burst/sound/buzz, a push flies it and counts a touch, clap
  off makes nothing, pointing off: ray refuses, touch holds) -> 70/70.
- game-jam-room +5 checks (flags + bounds survive the load; in a world grown 1.5x by the
  Edit gesture's own maths a held sweep plays three piano keys and flips three drum steps)
  -> 44/44. music-template 11/11, author-kit 59/59.
Counterfactual: normalizeLocomotion without the worldGrab line -> vitest locomotionPolicy
red (1 failed / 13 passed), restored green.
Staged: cloud-lane-30-staging/31-stars-jam/games/{stars-room 52729 B, jam-room 86324 B}.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… arrows

"Device default" overflowed the 110 px value slot between the ‹ › buttons on the VR board
(seen in the evidence capture). The two choice rows say "Default" now, and the board
shrinks a value's font to fit its slot, so a game row's long option label cannot overlap
its arrows either. game-shell 65/0, game-editor-flow 41/0, vr-game-panel 35/0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…them) + api.vrPanel

The Quest report: "menu buttons during game covered by scene objects below untangle (like
base/floor objects)". A VR panel was a mesh like any other, so whatever stood between the
eyes and it covered its buttons - while the laser, which prefers a panel, still pressed them.

- vrPanelOverlay.js (leaf, THREE only): every open panel joins the transparent list at
  PANEL_ORDER after ONE depth clear (a colourless, never-culled sentinel whose
  onBeforeRender clears depth), so panels draw over the finished scene and still depth-test
  against EACH OTHER (a panel's own layers keep their order; a nearer panel covers a
  farther one - which a blanket depthTest:false gets wrong). Both eyes: without multiview
  three renders one eye completely, then the other, so the clear touches a finished half;
  with multiview both views clear together. Raycasts read geometry, so hit tests are
  untouched.
- vrControls: core panels re-marked each frame (idempotent, allocation-free); the sentinel
  is in the scene only while a panel is open (a frame without one is the old frame); a
  panel the ray reaches ENDS THE BEAM even with a floor in front (the press already
  preferred the panel - now the beam agrees); beam + reticle draw at BEAM_ORDER while on a
  panel. registerOverlayPanel / panelOverlayDebug exported.
- moduleSDK: api.vrPanel(object) -> undo (journalled with the module) for a module's own VR
  menu / level bar - Untangle's bar is the case the user saw.
- vrGamePanel surfaces sit at PANEL_ORDER (they already ignored depth); splineEdit's handle
  group opts out (userData.vrOverlay = false - world handles, not a panel).

Suite vr-panels-on-top (12, fakeXR, PIXELS with a red unlit blocker in objectsGroup between
the camera and each panel): premise - the blocker hides an ordinary mesh (0.00 not red);
the radial ring (1.00), the game board's Start (1.00) and an api.vrPanel module bar (1.00)
draw over it; the beam passes the blocker and ends on the board (1.400 m) and on the module
bar (0.900 m) at BEAM_ORDER; the trigger presses Start through the blocker; the depth clear
ran and stands down with no panel open.
Counterfactuals (restored after each): overlay disabled -> ring 0.00, module bar 0.00,
clears 0 (3 red); panel hits compared by distance -> beam 0.660 m / 0.475 m and order 0
(3 red).
svelte-check 333/47 (identical list).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…world gestures; play.bounds

The Quest reports: "Entangle game does not allow me to scale scene with grips within game,
only in edit mode" and "In Jam Room I would like to be able to fly around and also scale the
entire environment with grips and move around same as in edit mode".

- locomotionPolicy: `play.locomotion.worldGrab: true` -> Interact's worldGestures; the
  normaliser keeps exactly the typed booleans teleport / fly / worldGrab (empty -> absent,
  so a scene that never used them saves byte-identically).
- vrGrip.gripMovesWorld(mode, worldGrab): Edit always, Interact only with the flag. vrControls'
  empty-air grip asks it with the live policy - so two grips scale/rotate the world, the right
  grip alone pans it, and a grip on a grabbable body (dynamic, interaction 'grab') still
  takes the BODY (pickGripTarget runs first, unchanged).
- teleportRules.js (new pure leaf, P4's rules) - here for normalizeBounds: `play.bounds
  {min:[x,y,z], max:[x,y,z]}` is a SIBLING of locomotion; six finite numbers |v| <= 1e5, each
  axis ordered, else dropped. scenePhysics keeps it in the play block (omitted when absent);
  resolvePlaySettings resolves it field-by-field like spawn, a publisher's in ITS local frame
  (`boundsOwner`). Vitest teleportRules (18): bounds, shrink, slab test, raster segment, the
  verdict order.
- moduleSDK: `api.locomotion = {boundedTeleport: true, worldGrab: true}` (frozen) - the
  feature probe the game lanes publish against (agreed with 31-fb-dungeon and 31-stars-jam).
- wireValidate: no change - scenephysics has no shape there, and the singleton's own
  normaliser is its boundary.

Suite vr-locomotion-flags (13, fakeXR through the real grip path): the normaliser (typed flags
only, bounds ordered, bad bounds dropped, untouched scene writes neither key); WITHOUT the
flag Interact's two grips start nothing and the world stays 1:1; WITH it two grips start the
world grab and a spread scales x2.00, a right grip on the floor pans, a grip on the dynamic
cube holds the cube; api.locomotion probe; a module publisher's userData.play turns it on.
Vitest +5 (locomotionPolicy 2, vrGrip 1, + the teleportRules file): 296 green (base 275).
Counterfactuals (one batch, restored): gripMovesWorld(mode) -> 5 red; worldGrab dropped by
the normaliser -> 1 red; bounds not normalised -> 1 red. 7 red.
svelte-check 333/47 (identical list).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he play area, never through a wall; red when refused

The Quest reports: "Dungeon Realms ... I would like to be able to teleport but not outside the
dungeon walls" and "In Stars room I would like to be able to teleport but not outside the
scene". Edit's teleport lands anywhere the arc does; a game's (Interact/Play with
`play.locomotion.teleport: true`) asks teleportRules for a verdict now.

- computeTeleportArc(origin, dir, group, {bounded, roots}): a bounded arc stops at the FIRST
  surface it meets, of any slope (a wall face ends it instead of the arc passing through the
  wall to the floor behind), over objectsGroup AND registered module content (a dungeon's
  walls live under module-world-root), and reports the landing's world normal.y.
- teleportVerdict(from, to, normalY) (exported; the dungeon lane's e2e entry point), the rules
  in order, each in ITS OWN frame so a world grab never skews one:
  1 walkable: normal.y >= 0.7 (or the floor plane) else 'steep';
  2 inside `play.bounds` (scene frame, or the publisher group's local frame) - else the
    scene's content box pulled in 0.3 m on x/z (measured once per aim) - else 'outside';
  3 a dungeon raster (dungeonPlay): the target cell is floor ('off-floor') and no wall cell lies
    on the straight line ('wall-cell');
  4 nothing solid crossed 1.1 m above both ends: a publisher's `play.colliders` AABBs, then a
    mesh raycast ('blocked').
- updateTeleport: the arc is GREEN when valid and RED when refused (teleportPreview() reads
  it); release teleports only on a valid landing - a red release does nothing.
- executeTeleport(target, bounded): a game's teleport stands your FEET on the landing (a step
  onto a platform is a step up); Edit keeps its head-height move exactly as before.

Suite vr-bounded-teleport (19, a walled test room through fakeXR's real right-stick path):
the verdict (floor ok, behind the wall blocked, beyond the bounds outside, a wall face steep;
no bounds -> the content box shrunk 0.3 m); the arc short = green on the floor, the release
moves you there; onto a 0.5 m platform (a sim running) = green, head 0.5 m higher; over the
wall = RED behind it, release does nothing; at the wall = ends on the face, steep; a raster
wall cell on the way refused; Interact without the flag = no arc; Edit = the over-wall
landing green and taken.
Counterfactuals: mesh probe / raster / content bounds / bounded arc removed (one batch) ->
6 red; feet not placed (y: 0) -> "stands you ON it" 1.60 red. Both restored.
svelte-check 333/47 (identical list); vitest 296.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… play.bounds, the bounded teleport, api.vrPanel)

- New section "Moving around in a game: locomotion, bounds, VR panels (1.18, roadmap 31)":
  `locomotion.teleport` (the bounded rules, red arc, feet on the landing), `locomotion.worldGrab`,
  `locomotion.fly`, `play.bounds` (a sibling of locomotion; a module's in its group's local
  frame), `play.colliders`, the `api.locomotion` feature probe, the teleportVerdict /
  teleportPreview debug entry points, and `api.vrPanel(group)` for a module's own VR menu
  (drawn over the scene, ends the laser).
- static/llms-full.txt regenerated (npm run sync-llms).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
31-vr-core asked consumers to take its normalizer: locomotionPolicy resolved to theirs
(identical worldGrab line, mine dropped); vrControls = the union of the two import blocks
(the pointGrab gate in gripTargetOf auto-merged). The two held suites gain the bounded-
teleport verdicts (feature-detected: SKIP without vr-core).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… Stars Room's floating stars)

Found by 31-stars-jam on the union: in the Stars Room, teleportVerdict from the spawn to the
floor across the room returned 'blocked'. The wall probe runs 1.1 m above both ends, which is
the stars' layer (24 dynamic icosahedra at 0.8-2.6 m), so any floating star in the line
refused every landing. meshBetween now skips hits whose top-level object is a DYNAMIC physics
body or a spawner's transient copy - a body you can knock aside is not a wall. Static and
pick-through solids (the room's glass) still block.

vr-bounded-teleport 21/21 (+2: a dynamic box at 1.1 m in the line -> ok; the same box static
-> blocked). Counterfactual: the filter removed -> the dynamic check red.
svelte-check 333/47 (identical list).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… room past the stars

On the union with 31-vr-core: teleportVerdict takes FEET, so the check stands on the spawn
floor; a new check teleports across the room through the floating-star layer, which was
refused (reason blocked) until vr-core 8189daa stopped dynamic bodies counting as walls.
game-stars-room 72/72 on the union.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…frame (rays carry the camera)

Found by 31-untangle: since P1 the VR rays reach module content (the beam ends on interactive
groups; P4's arc and wall probe raycast registered module groups). three's Sprite.raycast
needs `raycaster.camera` - it warns and THROWS on a null camera ("Cannot read properties of
null (reading 'matrixWorld')"). One Sprite in a module group (Untangle's globe-mode canvas HUD)
threw inside updateVRControls every frame and aborted it before onSqueezeStart: worldGrab
silently dead in the globe, fine on the 2D board.

- vrControls.withRayCamera(ray): every VR ray carries the camera the viewer sees through (the
  XR camera in a session, the editor camera otherwise) - controllerRay, pointerHandRay
  (api.pointerRay), the teleport arc and wall probe; vrGameInput.controllerRayOf too.
- safeIntersect: a raycast INTO module content is guarded - a module mesh whose raycast
  throws logs once per call and yields no hit instead of taking the frame down.

vr-laser-panels 25/25 (+2: a Sprite on the module board -> no page error, the beam ends on
it at 1.200 m). Counterfactual (camera + guard removed): the exact reported pageerror, the
beam passes to 1.490 m, and the radial reticle after it is off (3 red).
Re-run on this commit: vr-bounded-teleport 21/0, vr-panels-on-top 12/0, vr-game-panel 35/0,
vr-sweep 25/0 (with JAM_ROOM_TPSCENE = the preview-1-17 Jam Room - the scenes checkout on
main is older and has no mixer mute buttons, which reads as a 10.4 failure).
svelte-check 333/47 (identical list).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…aware governor + api.quality, core stutter fixes
AlexZ005 and others added 11 commits October 1, 2026 04:41
…h, VR jump

# Conflicts:
#	src/lib/playSettings.js
#	src/lib/scenePhysics.js
#	src/lib/vrControls.js
…tar; Jam Room fly + world grab + teleport

# Conflicts:
#	src/lib/gameKit.js
#	src/lib/playInteract.js
#	src/lib/vrControls.js
…ULES_REPO for every suite

- 31 K3 made Escape in a game open the shared pause menu, so game-untangle /
  game-dungeon-realms / game-waves left the HOST in Play before the late joiner
  dialled, and the Approve card could not be clicked (connect died 3/3).
  helpers.leavePlay(peer) steps out through Back to editor (fallback
  gameShell.leaveToEditor) and the three suites assert it as a premise
- helpers.cjs honours env MODULES_REPO (an integrator's union checkout of zips);
  `a ?? b + '/'` parenthesised (the first cut dropped the trailing slash and
  every suite reported "no zip")
- game-shell-games: Towers needs no module now (31-towers); game-waves' routed
  index row names waves 2.2.0 (the union's version)
- union: game-untangle 51/0, game-dungeon-realms 66/0, game-waves 41/0,
  game-shell-games 71/0 (seven games), game-jam-room 45/0, vr-sweep 25/0

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the governor merge note

- CHANGELOG `## 1.18.0 — Menus, levels and smooth frames in a headset` from the
  nine lanes' integrator notes (menus, VR pointing/moving, the games, frames, authors)
- CLAUDE.md: the ROADMAP 31 architecture entry (K1 teleportRules/worldGrab, K2
  vrPanelOverlay, K3 gameShell family, K4 lod + XR governor + perf-games, Stars
  Room clap/pointGrab), four gotchas (e2e-slot fd inheritance, legacy-mode
  untracked helper reads, deep $state over THREE objects, `a ?? b + '/'`), R31 SDK
- the qualityGovernor merge (2a037db): a game's pinned Quality outranks the
  headset entry floor. perf-governor §6c; counterfactual (963e4da's governor):
  6c.1-6c.3 red; union 50/0

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d at the gate)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bumps the minor-and-patch group with 14 updates:

| Package | From | To |
| --- | --- | --- |
| [@lucide/svelte](https://github.com/lucide-icons/lucide/tree/HEAD/packages/svelte) | `1.35.0` | `1.48.0` |
| [@threlte/core](https://github.com/threlte/threlte/tree/HEAD/packages/core) | `8.6.0` | `8.6.1` |
| [@threlte/extras](https://github.com/threlte/threlte/tree/HEAD/packages/extras) | `9.21.1` | `9.22.0` |
| [@xyflow/svelte](https://github.com/xyflow/xyflow/tree/HEAD/packages/svelte) | `1.6.5` | `1.7.0` |
| [meshoptimizer](https://github.com/zeux/meshoptimizer) | `1.1.1` | `1.3.0` |
| [postprocessing](https://github.com/pmndrs/postprocessing) | `6.39.4` | `6.39.5` |
| [three](https://github.com/mrdoob/three.js) | `0.185.1` | `0.186.1` |
| [three-mesh-bvh](https://github.com/gkjohnson/three-mesh-bvh) | `0.9.14` | `0.9.15` |
| [@sveltejs/vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte/tree/HEAD/packages/vite-plugin-svelte) | `7.3.0` | `7.3.1` |
| [playwright](https://github.com/microsoft/playwright) | `1.62.1` | `1.63.0` |
| [prettier](https://github.com/prettier/prettier) | `3.9.6` | `3.9.9` |
| [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) | `5.57.0` | `5.57.1` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.2.2` | `8.3.1` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `5.0.0` | `5.0.2` |


Updates `@lucide/svelte` from 1.35.0 to 1.48.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.48.0/packages/svelte)

Updates `@threlte/core` from 8.6.0 to 8.6.1
- [Release notes](https://github.com/threlte/threlte/releases)
- [Changelog](https://github.com/threlte/threlte/blob/main/packages/core/CHANGELOG.md)
- [Commits](https://github.com/threlte/threlte/commits/@threlte/core@8.6.1/packages/core)

Updates `@threlte/extras` from 9.21.1 to 9.22.0
- [Release notes](https://github.com/threlte/threlte/releases)
- [Changelog](https://github.com/threlte/threlte/blob/main/packages/extras/CHANGELOG.md)
- [Commits](https://github.com/threlte/threlte/commits/@threlte/extras@9.22.0/packages/extras)

Updates `@xyflow/svelte` from 1.6.5 to 1.7.0
- [Release notes](https://github.com/xyflow/xyflow/releases)
- [Changelog](https://github.com/xyflow/xyflow/blob/main/packages/svelte/CHANGELOG.md)
- [Commits](https://github.com/xyflow/xyflow/commits/@xyflow/svelte@1.7.0/packages/svelte)

Updates `meshoptimizer` from 1.1.1 to 1.3.0
- [Release notes](https://github.com/zeux/meshoptimizer/releases)
- [Commits](zeux/meshoptimizer@v1.1.1...v1.3)

Updates `postprocessing` from 6.39.4 to 6.39.5
- [Release notes](https://github.com/pmndrs/postprocessing/releases)
- [Commits](pmndrs/postprocessing@v6.39.4...v6.39.5)

Updates `three` from 0.185.1 to 0.186.1
- [Release notes](https://github.com/mrdoob/three.js/releases)
- [Commits](https://github.com/mrdoob/three.js/commits)

Updates `three-mesh-bvh` from 0.9.14 to 0.9.15
- [Release notes](https://github.com/gkjohnson/three-mesh-bvh/releases)
- [Changelog](https://github.com/gkjohnson/three-mesh-bvh/blob/master/CHANGELOG.md)
- [Commits](gkjohnson/three-mesh-bvh@v0.9.14...v0.9.15)

Updates `@sveltejs/vite-plugin-svelte` from 7.3.0 to 7.3.1
- [Release notes](https://github.com/sveltejs/vite-plugin-svelte/releases)
- [Changelog](https://github.com/sveltejs/vite-plugin-svelte/blob/main/packages/vite-plugin-svelte/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/vite-plugin-svelte/commits/@sveltejs/vite-plugin-svelte@7.3.1/packages/vite-plugin-svelte)

Updates `playwright` from 1.62.1 to 1.63.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](microsoft/playwright@v1.62.1...v1.63.0)

Updates `prettier` from 3.9.6 to 3.9.9
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](prettier/prettier@3.9.6...3.9.9)

Updates `svelte` from 5.57.0 to 5.57.1
- [Release notes](https://github.com/sveltejs/svelte/releases)
- [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.57.1/packages/svelte)

Updates `vite` from 8.2.2 to 8.3.1
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.3.1/packages/vite)

Updates `vitest` from 5.0.0 to 5.0.2
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.2/packages/vitest)

---
updated-dependencies:
- dependency-name: "@lucide/svelte"
  dependency-version: 1.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: "@threlte/core"
  dependency-version: 8.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: "@threlte/extras"
  dependency-version: 9.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: "@xyflow/svelte"
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: meshoptimizer
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: postprocessing
  dependency-version: 6.39.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: three
  dependency-version: 0.186.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: three-mesh-bvh
  dependency-version: 0.9.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: "@sveltejs/vite-plugin-svelte"
  dependency-version: 7.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: playwright
  dependency-version: 1.63.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: prettier
  dependency-version: 3.9.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: svelte
  dependency-version: 5.57.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: vite
  dependency-version: 8.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: vitest
  dependency-version: 5.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…ered

- the owner's Quest report on preview-1-18: "ALL text in VR menus became blurry since
  round 31". Cause: 31-perf P3 carried the governor's RESOLUTION steps into the headset
  through setFramebufferScaleFactor for the NEXT entry, so any session that reached
  level 2+ (a load hitch, or a game's Medium/Low Quality pin) left every later entry in
  that tab at 0.85..0.5 of the eye buffer and the compositor stretched it back up. The
  panel textures (canvas size, anisotropy) and the K2 overlay (a depth clear, no render
  target) were measured unchanged from 1.17
- qualityGovernorCore: XR_FRAMEBUFFER_SCALE = 1 replaces xrScaleAfter; endXRQuality hands
  three 1 at every session end. The fps work stands (per-frame fixes, the shadows-off
  entry floor, particles/presence steps, the module lanes)
- NEW suite vr-panel-sharpness: an overloaded XR session (level 7), the scale three is
  handed for the next entry, and the VR pause board rendered from the head at that eye
  buffer + upscaled like the compositor, Laplacian edge energy vs a full-scale frame:
  6/0 (ratio 1.000). Counterfactual (the old next-entry scale): 2 red — three handed
  [0.5], ratio 0.064 (34.0 vs 532.5)
- perf-governor §6b flipped (nextScale === 1), 50/0; vitest qualityGovernor 25

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he Quest ran 2.1.0)

- the owner's preview-1-18 report "Waves: as soon as I hit an enemy, ALL enemies return to
  their start position" is the 1.17 Waves bug, fixed in waves 2.2.0 (31-waves W1) and not
  reproducible on it (modules flight waves-hit-reset: 2.2.0 green; 2.1.0 RED — the other
  walker back at its portal, the wave's start +5 s). The headset kept its installed 2.1.0:
  a user module is installed once per device, and a scene asking for a NEWER version only
  got an advisory "(you have 2.1.0)" on its Games-tab card
- moduleRequirements.classifyRequirements: `outdated` = installed but older than the scene
  asks (moduleGallery.versionNewer); sessions.confirmModuleRequirements names it ("Older
  version installed: waves v2.1.0 (this scene needs v2.2.0)") and the install button
  becomes Update / Install + update — installRequired installs the gallery's copy over
  the old one, live (installUrl). Load anyway stays; an OLDER asked version stays advisory
- NEW suite module-update-on-load (gallery routed to a modules checkout, Waves 2.1.0
  installed, the 1.18 Waves scene imported): 7/0. Counterfactual (no `outdated`): 3 red —
  no prompt, Waves stays 2.1.0

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ree 0.186.1, vite 8.3.1, svelte 5.57.1, playwright 1.63.0, ...)
…el button starts the game)

- 31-towers replaced "Start round" with level buttons ("1 · Stack"); the suite's premises
  looked for a Start button and the first battery ran it against the OLD Towers (no
  TOWERS_TPSCENE). START = /Start round|1 · Stack/; ALL PASS on the union Towers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e deps bump

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AlexZ005
AlexZ005 merged commit df2563d into main Oct 1, 2026
4 checks passed
@AlexZ005
AlexZ005 deleted the feat/1.18 branch October 1, 2026 22:00
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