From 0b957c6f23d97f4c2ce33b1864ed7d794337186d Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Thu, 1 Oct 2026 20:44:26 +0300 Subject: [PATCH] [feat] 33 (G2/G3): the stick reels the world; api.claimInput('sticks'); every level + level tabs in the VR pause menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Untangle's Quest report (roadmap 33): "I can make bigger/smaller the globe but cannot use up/down on stick to move it further/closer ... just as in edit mode for objects", and "main menu do not show in VR levels to choose and tab to switch between flat/globe". G2 — the stick reels what you hold, the world included: - vrControls: the one-hand world PAN (a right grip on scenery/air in Edit, or in Interact when play.locomotion.worldGrab) now reels the world on that hand's stick Y with Edit's own object reel (grabStickAdjust): forward pushes it away along the hand's ray, back pulls it in, from the distance to the gripped spot (panReach). worldReelStep is pure + exported. - api.claimInput('sticks'): BOTH VR sticks' navigation stands down (left-stick move, right-stick snap/smooth turn + teleport) for a module that reads the sticks itself (Untangle's held globe: Y reels, X scales). claimInput now answers true for a scope this core knows (feature detect). G3 — the pause menu's Levels page in a headset: - shellPanelDraw drew list.slice(0, 20): Untangle's levels 21-30 were unreachable in VR. Now a 6-column grid as tall as fits (pages with arrows beside Back beyond that); <= 20 levels and no tabs keep the old 5 x 4. - addSetting({..., type: 'choice', onLevels: true}): the choice is also drawn as TABS above the Levels grid, desktop (GameShellMenu, role=tablist) and VR (shell:tab hits). Untangle registers its Board (Globe / 2D board) that way. Tests: - vitest shellPanelDraw (new, 6) + gameShell (+3): CF — the old slice(0, 20) restored: 2 red. - e2e vr-world-reel (new, 21/0 on 5278): pure step, Edit pan reel push/pull exactly by the reach growth, no drift at rest, Interact+worldGrab reel, the 'sticks' claim silencing snap turn and the left stick then releasing. fakeXR controllers do not follow the reference space, so the suite poses a spec-accurate right hand (re-derived on every setReferenceSpace). CF: on the feat/1.19 base the suite is red (no worldReelStep, claimInput answers undefined). - held: game-shell 66/0, vr-game-panel 36/0, vr-nav-suppression 5/0, vr-grips-by-mode 19/0, vr-laser-panels ALL PASS (SCENES_DIR = the preview-1-18 scenes). - svelte-check 333/47 (= baseline), vitest 387/387, check:storage clean. - modules flights on this branch: untangle-vr2 94/0 (incl. S: left X -> Levels -> Board tabs -> a level, through the panel's press path), untangle-vr 98/0, untangle 71/0, untangle-drag 54/0. Co-Authored-By: Claude Opus 5.5 (1M context) --- MODULES.md | 5 + src/components/hud/GameShellMenu.svelte | 47 ++++++ src/components/play/VRControls.svelte | 3 +- src/lib/gameSettings.js | 5 +- src/lib/gameShell.js | 46 +++++- src/lib/inputRuntime.js | 6 +- src/lib/moduleSDK.js | 20 ++- src/lib/shellPanelDraw.js | 72 +++++++-- src/lib/vrControls.js | 50 +++++- src/lib/vrGamePanel.js | 3 + tests/e2e/vr-world-reel.test.cjs | 193 ++++++++++++++++++++++++ tests/unit/gameShell.test.js | 34 ++++- tests/unit/shellPanelDraw.test.js | 67 ++++++++ 13 files changed, 517 insertions(+), 34 deletions(-) create mode 100644 tests/e2e/vr-world-reel.test.cjs create mode 100644 tests/unit/shellPanelDraw.test.js diff --git a/MODULES.md b/MODULES.md index 3c527c4d..ce956897 100644 --- a/MODULES.md +++ b/MODULES.md @@ -430,8 +430,11 @@ api.onInput((kind, code) => {}); // 'down'/'up' events; returns unsubscri // pause the HOST's use of an input scope while your module drives: // 'keys' — WASD camera fly + play-mode movement // 'locomotion' — VR left-stick locomotion +// 'sticks' — (1.19) BOTH VR sticks: left-stick move, right-stick snap/smooth turn + teleport +// (a held thing reeled on Y and scaled on X, the way an Edit grab does) api.claimInput('keys'); // ALWAYS release when your mode ends api.releaseInput('keys'); +const sticksOk = api.claimInput('sticks') === true; // true = this core knows the scope (older: undefined) // reading a keydown YOURSELF (a toolbox's own handler)? resolve the key the way the // editor does — `event.key` when it is an ASCII letter/digit, the physical @@ -693,6 +696,8 @@ api.game.addSetting?.({ onChange: (v) => setBoard(v) }); setBoard(api.game.setting?.('board') ?? 'globe'); // the stored choice at load +// (1.19) a CHOICE that decides which levels you see: `onLevels: true` also draws it as tabs +// above the Levels page's grid (desktop + headset) — Untangle's Board [Globe] [2D board] api.game.setSetting?.('board', '2d'); // your in-game button writes the same row api.game.onSettingsChange?.((values) => console.log(values.board, values.sfx)); ``` diff --git a/src/components/hud/GameShellMenu.svelte b/src/components/hud/GameShellMenu.svelte index b4614ca5..609c53ac 100644 --- a/src/components/hud/GameShellMenu.svelte +++ b/src/components/hud/GameShellMenu.svelte @@ -21,6 +21,7 @@ shellMenuAvailable, shellMenuItems, shellSettingViews, + shellLevelTabs, shellPageTitle, shellGameName, shellHelpLines, @@ -43,6 +44,9 @@ const items = $derived(itemsFor($gameLevels)); const viewsFor = (/** @type {any[]} */ ..._deps) => shellSettingViews({ vr: false }); const settings = $derived(viewsFor($gameSettingValues, $gameSettingRows)); + // 33 (G3): a game's level-picking choice (Untangle's Board) as tabs above the grid + const tabsFor = (/** @type {any[]} */ ..._deps) => shellLevelTabs(); + const levelTabs = $derived(tabsFor($gameSettingValues, $gameSettingRows)); const vrViewsFor = (/** @type {any[]} */ ..._deps) => shellSettingViews({ vr: true }).filter((/** @type {any} */ r) => r.vrOnly); const vrSettings = $derived(vrViewsFor($gameSettingValues)); const helpFor = (/** @type {any[]} */ ..._deps) => shellHelpLines({ vr: false }); @@ -150,6 +154,22 @@ {/each} {:else if page === 'levels'} + {#each levelTabs as tab (tab.id)} +
+ {tab.label} + {#each tab.options as option (option.value)} + + {/each} +
+ {/each}
{#each $gameLevels?.list ?? [] as level (level.id)}