Bundle hiccup444's PRs #15–#19: XR session, player placement, save robustness, addon docs, menu panels #23

Merged
merith-tk merged 8 commits from muse-lantern/da-game:hiccup-bundle into main 2026-10-29 05:11:52 +00:00
Member

This PR bundles five open PRs by hiccup444 into one branch, applied manually with original authorship preserved, so the modularization work lands on top of them instead of conflicting with them. It supersedes the originals — please close #15, #16, #17, #18, #19 as superseded once this merges.

Original PRs (all by hiccup444):

  • #15 — XR session setup: StartXR, pause on focus loss, XR shaders — #15
  • #16 — Place the player, not the XR origin, at spawn, menu and save — #16
  • #17 — Save robustness: no soft lock on a missing level, safe writes, backup fallback — #17
  • #18 — Addon docs: imported assets need a Godot-exported pack — #18
  • #19 — Hidden menu panels stop catching pointers and rendering — #19

Base: main at f739d5b (post-#22 merge). Branch: muse-lantern/da-game:hiccup-bundle.

Conflicts resolved (all others cherry-picked clean): sibling test blocks in pause_check/save_check kept on both sides; game.tscn keeps main's scene uid plus #15's StartXR node; project.godot restores xr/openxr/default_action_map (lost from main during migration — the bundle's own smoke test asserts it) and enables xr/shaders/enabled, and drops the stale Vulkan driver lines. One judgment call needs reviewer eyes: player_rig.gd re-adds _process with only #16's _redo_untracked_placement() — update_arms was deliberately not restored because main's hands-only design keeps ArmsView dormant (see arms_view.gd); restoring it is a one-line change if reviewers disagree.

Verification (Godot 4.6.3 standard, headless): .godot wiped, --import run twice, both clean; all 12 headless suites PASS — smoke_menu, arms_check, addon_check, save_check, rig_motion_check, pause_check, input_check, skeleton_check, anchor_check, sandbox_gizmo_check, hand_launch_check, hands_check. Bridge e2e under Xvfb not run (known-flaky).

Still owed in-headset after merge (#16's checklist): stand off-centre facing a side wall and choose Return to Menu (panel straight ahead every time); launch with the headset on, off-centre and turned (first main menu straight ahead); New Game and Sandbox from off-centre and turned (land on spawn facing level forward, head clear of walls); save at one edge of the play area, walk to the other, Continue (come back where saved, facing the same way); an old save loads once near the saved spot, then saves exactly; pause while looking straight down or up (panel opens along body facing, not play-area forward).

This PR bundles five open PRs by hiccup444 into one branch, applied manually with original authorship preserved, so the modularization work lands on top of them instead of conflicting with them. It supersedes the originals — please close #15, #16, #17, #18, #19 as superseded once this merges. Original PRs (all by hiccup444): - #15 — XR session setup: StartXR, pause on focus loss, XR shaders — https://git.merith.xyz/dead-air/engine/pulls/15 - #16 — Place the player, not the XR origin, at spawn, menu and save — https://git.merith.xyz/dead-air/engine/pulls/16 - #17 — Save robustness: no soft lock on a missing level, safe writes, backup fallback — https://git.merith.xyz/dead-air/engine/pulls/17 - #18 — Addon docs: imported assets need a Godot-exported pack — https://git.merith.xyz/dead-air/engine/pulls/18 - #19 — Hidden menu panels stop catching pointers and rendering — https://git.merith.xyz/dead-air/engine/pulls/19 Base: main at f739d5b (post-#22 merge). Branch: muse-lantern/da-game:hiccup-bundle. Conflicts resolved (all others cherry-picked clean): sibling test blocks in pause_check/save_check kept on both sides; game.tscn keeps main's scene uid plus #15's StartXR node; project.godot restores xr/openxr/default_action_map (lost from main during migration — the bundle's own smoke test asserts it) and enables xr/shaders/enabled, and drops the stale Vulkan driver lines. One judgment call needs reviewer eyes: player_rig.gd re-adds _process with only #16's _redo_untracked_placement() — update_arms was deliberately not restored because main's hands-only design keeps ArmsView dormant (see arms_view.gd); restoring it is a one-line change if reviewers disagree. Verification (Godot 4.6.3 standard, headless): .godot wiped, --import run twice, both clean; all 12 headless suites PASS — smoke_menu, arms_check, addon_check, save_check, rig_motion_check, pause_check, input_check, skeleton_check, anchor_check, sandbox_gizmo_check, hand_launch_check, hands_check. Bridge e2e under Xvfb not run (known-flaky). Still owed in-headset after merge (#16's checklist): stand off-centre facing a side wall and choose Return to Menu (panel straight ahead every time); launch with the headset on, off-centre and turned (first main menu straight ahead); New Game and Sandbox from off-centre and turned (land on spawn facing level forward, head clear of walls); save at one edge of the play area, walk to the other, Continue (come back where saved, facing the same way); an old save loads once near the saved spot, then saves exactly; pause while looking straight down or up (panel opens along body facing, not play-area forward).
LevelLoader.load_level unloaded the current level before it checked
that the new one existed. Continue with a save naming a missing level
(an addon level whose addon was removed) left the player in an empty
world with no menu, and Game holding a freed level. From the pause
menu, Load unpaused first, so a failed load left the player unpaused
but still anchored.

The loader now unloads only once the new scene is loaded and its root
is a Level, so a broken addon scene cannot strand the player either.
continue_game checks the save's level is installed before touching
anything, keeps the save (inert, never deleted), warns, and returns
whether it worked. Pause Load runs while still paused and closes pause
only on success. Continue and pause Load light up only for a save that
reads and names an installed level, registered with its scene present.
That check reads the file through a new side-effect free
SaveGame.peek_state, so lighting a button emits no load signals.
The run save was documented as backup-first, but nothing ever read the
.bak, the backup step copied whatever was at the primary (a corrupt
file included, so the first save after a bad write replaced the last
good backup), the write went straight into the primary unchecked, and
has_state only checked that the file existed.

save_state now writes <path>.tmp, checks store_string and get_error,
and reads the file back before it replaces anything. The previous save
is rotated to .bak only if it still loads, then the temp file is
renamed into place. On 4.6.3 rename_absolute already replaces an
existing target (Windows deletes it first, Linux renames atomically),
so no explicit remove is needed. load_state falls back to .bak and
load_completed names the file actually used. has_state and peek_state
consider both files and never emit, so a truncated save with a good
backup still offers Continue, and two unreadable files offer nothing.
docs/addons.md told addon authors to replace the arms, or any model,
texture or sound, by putting the raw file at the same res:// path in
a zip. load() never opens that file: it follows the base game's
.import file to the imported copy, so the override silently does
nothing, and a raw model, texture or sound at a new path cannot be
loaded at all. Base scenes, resources and scripts are ignored the
same way in an exported game, where they load through .remap files.

The docs now keep plain zips for data and for new scenes and scripts,
and send new or replaced imported assets, and replaced converted
files, through a pack exported by Godot. The README no longer says
any pack can replace the arms, and
the AddonLoader header comment points at the same rule.

addon_check only covered files read with FileAccess. It now mounts
raw textures in a zip and checks that load() is unchanged at a base
path and finds nothing at a new one, then mounts a pck carrying
.import files and the data they point to and checks that load()
returns the override at both paths.
A hidden MenuPanel kept its collider on the pointer layer and its
SubViewport rendered every frame. After closing pause, the invisible
pause panel stayed where it was opened, so back in the menu it could
block the laser and take clicks meant for the main menu, including
Options changes that get saved.

The panel now follows its visibility in the tree: while hidden its
collider is off, its viewport stops updating and pointer events are
ignored. A press it was holding is let go by hiding and reshowing its
menu control, which ends the press without clicking a button or
moving a slider. Game ignores main menu actions outside MENU and pause
actions while not paused. Level.apply_mode also disables processing
for the hidden content groups, so physics objects and scripts added
there later go quiet with them.
Spawn, menu and save placement wrote the marker straight into the
XROrigin3D. With the default Stage reference space the origin is the
centre of the play area, so the player landed offset from every marker
by where they stood in their room, facing whichever way they faced,
and saves stored that origin as well.

PlayerRig.place_player now moves the origin so that the head, projected
to the floor, and its facing land on the target. place_at and
place_at_transform keep their names and go through it, so callers and
open branches keep working. Saves store player_floor_transform() under
a new player_transform key. A save that only has the old rig_transform
still loads through the same path, once off by at most where the player
stood when saving and facing the play area's forward, so the save
format stays at 1. The pause panel's fallback for looking straight up
or down takes its facing from the player instead of the origin.

rig_motion_check poses each camera off-centre and turned, including
steep and rolled looks up and down, and checks that the head lands on
the marker. save_check round-trips the player's floor transform with
the head off-centre and loads an old rig_transform save. pause_check
covers the panel fallback when looking straight down.
Game places the player at the menu anchor in _ready, before OpenXR has
reported a head pose. The XRCamera3D then still sits at its default
pose over the origin, so the boot menu was placed from the play-area
centre, and a player standing off-centre or facing a side wall found
the panel off to the side or behind them, as before the head placement
change.

A placement made in XR without a tracked head pose is now kept, and
the rig makes it again on the first frame the head is tracked.
Placements made with tracking live are not repeated.

rig_motion_check registers a stand-in head tracker to cover it.
Game only switched the viewport to XR. Physics stayed at the default
60 Hz while the headset renders at 72 to 120 Hz, so smooth locomotion
and turning judder (XR Tools moves the origin in _physics_process),
and V-Sync stayed on. Nothing reacted to the runtime taking focus
away, so the world kept running behind the dashboard.

The vendored StartXR, now the first child of Game, turns V-Sync off
as soon as the OpenXR interface is up, and sets the physics rate from
the runtime's refresh rate when the session begins. Its xr_ended
signal (dashboard open, headset off) now opens the pause menu. There
is no resume on xr_started: the player resumes from the panel instead
of landing mid-action.

Without a headset StartXR finds the OpenXR interface uninitialized,
its initialize() call fails, and it logs "OpenXR: Failed to
initialize"; _init_xr still puts the game in flat mode as before.
project: enable XR shaders, drop the stale driver keys
All checks were successful
tests / test (pull_request) Successful in 53s
0f271754d3
Godot's XR setup guide lists XR > Shaders as a required setting, and it
was off.

driver/driver_name.windows and .linuxbsd under [rendering] were added
to keep Forward+ off D3D12, where OpenXR could not create a session.
rendering/driver/driver_name is not a setting Godot 4.6 registers, and
the renderer is gl_compatibility now, so the two lines did nothing. If
the project moves to Mobile or Forward+, the 4.x key for the pin is
rendering/rendering_device/driver.windows.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
dead-air/engine!23
No description provided.