Skip to content

[31 game-shell] K3: one pause menu, levels, per-game settings and FPS in every game - #251

Merged
AlexZ005 merged 5 commits into
feat/1.18from
feat/31-game-shell
Oct 1, 2026
Merged

AlexZ005 merged 5 commits into
feat/1.18from
feat/31-game-shell

Conversation

@AlexZ005

@AlexZ005 AlexZ005 commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

31 game-shell (K3) — one pause menu, levels, per-game settings and FPS in every game

The Quest feedback items this lane owns (cloud plans/core/lanes/31/user-feedback-2026-10-01.md):

# item status
G4 enter the MAIN MENU during any game done — Resume · Restart · Levels · Settings · How to play · Main menu (+ desktop "Back to editor"); desktop Esc / corner Menu, VR left X + Menu on the board and wrist
G1 sound / sfx / music off per game done — per-game levels on the sfx / music audio buses + a playGameSound gate
G2 the usual game settings done — vibration, VR turning snap/smooth/off + snap angle, comfort vignette, quality preset
G3 show FPS on demand done — desktop counter (fps · ms · calls · tris) + VR strip/wrist line
U5 levels in the VR menu (core half) done — api.game.levels → desktop + VR picker
S2 settings plumbing done — src/lib/gameSettings.js (31-stars-jam registers its rows into it)
P1 the Quality row done — qualityGovernor.applyGameQuality pins High 0 / Medium 3 / Low 7 while playing (split agreed with 31-perf)

What changed

  • gameSettings.js (leaf): core rows + game rows, LOCAL per device and per game (tp:game:<id>:shell / tp:game:<id>:<row>); game id = the opened scene file's name slug.
  • gameShell.js (leaf) + gameShellWire.js: menu state, levels, help, restart hooks, Main menu (music stops, XR session ends, play exits, Templates opens on Games).
  • GameShellMenu.svelte, FpsCounter.svelte; shellPanelDraw.js paints the same pages on the VR game panel (content only — no edits to 31-vr-core's rendering/laser/hit-test functions).
  • PointerLockControls: in a GAME, Escape / a lost lock opens the menu; a non-game scene keeps Escape = exit. HudLayer stays the single writer of playPointerFree.
  • audioEngine.setBusLevel, haptics gate in the hapticPulse funnel, turningInForce + smooth turning in vrControls, comfortVignette.js, fpsMeter.js.
  • SDK: api.game.levels / addSetting / setting / setSetting / onSettingsChange / setHelp / onRestart / openMenu / closeMenu / menuOpen; MODULES.md section (docs site: theprototype-app/docs PR from docs-31-game-shell).

Tests

  • e2e game-shell 65/0 (desktop + VR via fakeXR), game-shell-games — the SEVEN real games (scene + module zips), desktop + VR: ALL PASS.
  • held: hud-actions 65/0, game-editor-flow 41/0 (updated: leaves play through the menu), sdk-game-seams 78/0, editor-modes 45/0, vr-game-panel 35/0.
  • vitest 299/299 (new gameSettings 14, gameShell 10); svelte-check 333/47 (baseline); check:storage clean; build green.
  • Counterfactuals (each red with the change reverted): Escape hook → 1.2–1.5; setBusLevel('sfx') → 3.3/3.6; left-X toggle → 8.2/8.3/8.5/8.6; hapticsAllowed() → 10.1; levels off() identity → vitest.

Decisions (QUESTIONS-31-game-shell.md)

Escape in a game = the menu (leaving is Main menu / Back to editor) · the menu does not pause the shared world · Restart = the shell's reset rule + module hooks + respawn · per-game volumes multiply the device volumes on the buses · VR menu button = left X.

Merge notes

  • Touches qualityGovernor.js (decideNow early return + applyGameQuality block) — 31-perf keeps out of those hunks; its XR session-start floor must stand down while gameForcedLevel !== null.
  • Touches vrControls.js (haptics gate, turningInForce/smooth turning in updateSnapTurn, left X) and vrGamePanel.js (footer extraction, shell branch) — expect textual overlap with 31-vr-core; both sides compose.
  • debug names ride __stores.gameKit (no App.svelte tail change).

OWED on device (Quest)

left X opening the menu · board readability at arm's length · smooth-turn speed + vignette feel · an FPS reading on the strip · Main menu ending the session cleanly.

🤖 Generated with Claude Code

AlexZ005 and others added 5 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>
… 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>
… 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>
… 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>
@AlexZ005
AlexZ005 merged commit be2fe86 into feat/1.18 Oct 1, 2026
4 checks passed
@AlexZ005
AlexZ005 deleted the feat/31-game-shell branch October 1, 2026 02:40
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