Pause menu + control settings (hold by_button 2s) #9
Loading…
Reference in a new issue
No description provided.
Delete branch "muse-lantern/da-game:pause-menu"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Stacked on PR #8 (branch is based on
menu-save-v1; it will rebase cleanly once #8 merges — the diff to review is the single commitcfcb789).Implements the control rulings from the 2026-10-22 design session (logged on game-planning #19 / #28):
Pause
by_button, B/Y) for 2 seconds to pause — the hold is deliberate; the press action stays unallocated for future use. The controller menu button and desktop Esc open the same pause (note: they no longer teleport straight to the menu place — Return to Menu inside the pause does that now).Game.save_blocked— the hook for the traversal rule (no saving in the traversal region); nothing sets it yet.Settings (the Options page)
GameSettingstouser://settings.cfg— their own file, separate from the run save, so settings survive across runs. Stick swap re-homes the movement/turn providers between controllers (they re-resolve their controller on_enter_tree).Refactor:
MenuPanelnow finds its menu control by theaction_selectedsignal instead of the MainMenu type, so the level panel and pause panel share one implementation.Verified:
pause_checkPASS (pause state machine, world freeze/restore, save gate, settings round-trip, provider re-homing both ways) · save_check / smoke / arms / addon / rig_motion PASS · bridge e2e PASS · real boot clean, no script errors. CI list gainspause_check.— Lantern
Gesture fix (Zach's correction): as first shipped, this PR opened pause by holding the upper face button for 2s. The design is the legacy gesture — press both face buttons at once (ax + by on one controller, either hand) — and the pause menu replaces what that gesture used to call up. Fixed in
effbf51:Game._pause_gesture()— pure function of the four button states: per-hand chord, edge-triggered, no re-fire while held, single buttons never fire, one-button-per-hand doesn't count.PAUSE_HOLD_SECONDSand the hold machinery are gone.ax_button/by_buttonboth exist in the action map with per-hand bindings; nothing else in the rig consumes them.The plan record is corrected too (game-planning #19). Rest of the PR is as reviewed — Zach: "otherwise it's good."
— Lantern
Gesture settled — strict hold (reverts
effbf51): the chord commit was my misreading of Zach's bug report (he was describing the both-buttons press opening the menu as a bug, not requesting it). Reverted inc22d369; what ships now:Game._pause_hold()— a pure function of the four button states + elapsed time. pause_check now asserts the hold semantics headlessly: fires at exactly 2s, no re-fire while held, both-buttons-held never fires, A/X mid-hold resets, other hand's A/X doesn't poison.All suites PASS locally; CI running on this push.
— Lantern
c22d3695637d9f0b6660Branch folded + input code removed (2026-10-23, Zach's direction).
History rewritten into three chunks (tree-verified identical apart from the deltas below):
0bcc2f2— CI: the runner fixes squashed into one commite037f0e— Pause menu + control settings (this PR's actual feature)7d9f0b6— docs: input system design; prep for the input rebuildRemoved by design: the in-game pause-gesture code (the strict B/Y hold timer, and the earlier chord). Gesture disambiguation — press vs hold vs chord — belongs to the runtime binding layer, not game code: SteamVR bindings do it natively (e.g. Vivecraft: Y press = backpack, Y hold = keyboard). The pause menu's remaining triggers (menu button, desktop Esc) are stopgaps that call
open_pause()directly until the input system lands.The design is documented in
docs/input-system.mdon this branch:src/input/layout (generic contract + per-controller folders + per-runtime presets for steamvr / oculus / openxr), semantic actions only for game code,pauseproduced per runtime (steamvr: by_button hold 2s; oculus/openxr: menu key direct), Quest capacitive touch (incl. the thumbrest) as built-in backend events, addon-supplied controllers, and the deferred cosmetic layer (the loading-circle plan is preserved there, unbuilt — SteamVR has its own hold cosmetics).PRs #10 and #11 were restacked onto the folded history (feature commits cherry-picked; their CI replay commits dropped as now-redundant). Their trees differ from before only by this fold's deltas.
Input system v1 built into this PR (
0bb094f, 2026-10-23) — per Zach's call, the input system and the pause menu ship together: the pause menu is the input system's first consumer, so there is no separate branch for it.What landed, following
docs/input-system.md(status there is now "built v1"):src/input/generic/input_contract.gd— the positional/semantic vocabulary (face_lower/face_upper/menu/…, capacitive touch ids included). Backends produce it; game code consumes it.src/input/generic/controller_backend.gd— backend base class: per-hand state feed viaupdate_from_states(), touch edges as signals. Synthetic states make backends headless-testable.src/input/generic/rig_input.gd— a node on the rig. Discovers controller descriptors data-driven (the LevelRegistry pattern), owns the active backend, and turns the contract menu state's rising edge into the semanticpause_pressedsignal; in flat mode it produces pause from desktop Esc.src/input/controllers/quest_touch/— the backend (reads the action map's existing actions; the full capacitive surface is wired astouch_changedsignals from day one, thumbrest/secondary_touchincluded, with no consumers yet, by design), the descriptor (capabilities: capacitive_touch, thumbrest, analog_trigger, analog_grip), and the per-runtime presets: steamvr pause =by_buttonhold 2.0s (native binding-layer production ontomenu_button), oculus/openxr pause = menu key direct.Game changes: subscribes to
pause_pressedand toggles. The last of the stopgap input code (_processmenu polling,_unhandled_inputEsc) is deleted — Game now contains no button polling and no gesture code at all.Verified: new
tests/input_check.gd(discovery, presets, synthetic state feed, touch edges, semantic edges, end-to-end pause toggle through the real game scene — added to the CI suite list); all suites pass on the restacked stack tip and the real boot is clean.PRs #10/#11 restacked onto this (their restack surfaced and fixed a real latent issue: the rig scene had two scripts claiming ext_resource id 7 — RigInput and the avatar skeleton; the avatar is now id 8).
The generic-vocabulary rename landed on top of this PR (
2230310), per the design tracked in #12:da_*names —da_button_primary(A/X anchor),da_button_secondary(B/Y),da_trigger_primary/da_trigger_primary_analog,da_grip_primary/da_grip_primary_analog/da_grip_primary_force,da_menu_action,da_stick/da_stick_click,da_pad/da_pad_click,da_touch_*— bindings untouched (they reference sub-resources). So SteamVR's binding UI now presents these names: a controller with no shipped config is fully mappable by its owner, because the name says what the mapping is.haptickeeps its name (vendored xr-tools references it by const).da_stick,da_trigger_primary) since the vendored defaults name the old actions. Presets updated (steamvr: hold ofbutton_secondaryontoda_menu_action);docs/input-system.mdcarries the refined model + the two addon authoring paths.c53df2a, traversal-v1750c606. Verification: pause-menu 7/7 suites, skeleton 8/8, traversal tip 9/9, real boots clean on each. (One log line chased down along the way: aboolconstructor error in input_check's harness cascade — A/B-verified via stash to pre-date this change; the real boot is clean.)Follow-up from the first real-hardware look (Quest via SteamVR, Zach testing): the
da_*names show up in SteamVR's binding UI exactly as designed. The default binding SteamVR synthesizes from our suggested bindings is another story — one source block per action instead of merged button sources, andda_menu_actionparked on the X click (our map binds it only to menu/home/system paths; the left menu source appears to be claimed by SteamVR's dashboard in that profile). With those defaults, nothing in the documented scheme opens pause — the divergence is in the binding layer, not the game.Code side (
175dba4, restacked through #10e6cf141/ #11aad321f): no game-logic change was needed — RigInput's per-hand rising-edge detection is already delivery-shape-agnostic. What landed is the proof, pinned intests/input_check.gd: a menu state held true across frames (SteamVR's Held method) toggles exactly once; menu co-fired with another action on the same click fires once and the rest of the state flows through. Plusdocs/input-system.mdnow has a "SteamVR bindings in practice" section with the manual configuration the preset prescribes (consolidate the face-button sources; Long/Heldda_menu_actiononbutton_secondaryat 2.0s) and the export/import paths — the first verified export from hardware becomes the repo's tester reference binding.Local verification: full sweeps green at every stack level (traversal_check flaked once under back-to-back load — its streaming assertion is timing-sensitive — then passed 3/3 solo).
Vocabulary v2 landed (
344ccaa, restacked: #106b08864, #118df7395+ a test de-flakedaddfcb): the [control][_channel] naming, three groups (generics /da_extra_0..10/ systemda_menu),da_selectdeleted, pad folded into the stick actions. Details on #12. Map surgery verified by parse (exactly the 30 intended actions; engine enumerates them) and full sweeps are green at every level.The de-flake, for the record:
traversal_check's "exit closed while destination streams" assertion sampled a transient state after a fixed 3-frame wait; the tiny destination level's threaded load legitimately completes within a frame or two on a warm cache, so the check raced correct behavior (~50% failures). It now asserts the invariant that always holds — the gate starts closed — synchronously atbegin_traversal. 6/6 solo runs pass.