Skip to content

WebXR session re-entry not handled: app assumes fresh session state but navigator.xr.session is still active #136

Description

@XRDeltas

Body

## Description

In A-Blast (VR wave shooter game), when a player exits and re-enters a WebXR session without fully reloading the page (e.g., after game over or pressing the exit button), the game does not correctly detect that an `XRSession` object may still exist or be transitioning. The game proceeds as if starting a fresh session, leading to:

- Conflicting `requestAnimationFrame` callbacks from old and new sessions causing double rendering of enemies and projectiles
- Event listeners being registered twice on stale session objects (controller input, shooting mechanics)
- Incorrect state management (score, wave number, enemy count reset improperly)
- Game physics or AI loops not properly cleaned up, causing performance degradation

The root cause is that the game checks for session existence using a local variable rather than querying `navigator.xr.session`, or it does not properly handle the `end` event lifecycle before allowing re-entry. For a fast-paced shooter where performance is critical, this causes noticeable frame rate drops and input lag.

## Steps to Reproduce

1. Open the application and enter an immersive WebXR session
2. Exit the session (via browser UI or in-app exit button)
3. Re-enter an immersive session without page reload
4. Observe one or more of:
   - Duplicate animation frames / janky rendering
   - Input handling broken (no controller responses)
   - Console warnings about conflicting session references
   - `InvalidStateError` or similar session-related errors

## Expected Behavior

The application should correctly manage the XR session lifecycle by:

1. Tracking session state through `session.addEventListener('end', ...)` 
2. Clearing all session-local state (rAF handles, input sources, ref spaces) on end
3. Checking `navigator.xr.session` before calling `requestSession()`
4. Properly awaiting session end before allowing re-entry

## Actual Behavior

Application-level invariant violated: the app enters an inconsistent state where it believes it has a fresh session but the runtime still holds references to the previous session, or the app's internal state (input managers, render loops) is not reset.

## Environment

| Property | Value |
|----------|-------|
| Application | A-Blast (VR wave shooter game) |
| Browser | Chromium / Quest Browser / Pico Browser |
| Host | Physical device browsers (Quest 3, Pico 4) |
| WebXR API Version | Latest draft spec |
| Projection | Direct / Lifecycle |

## Evidence

**Exposing projection:** Lifecycle (Session state invariant)

**First divergent action:** Second `requestSession` call (re-entry after exit)

**Reproducible witness:** Consistently reproduced across device-browser hosts (Quest 3, Pico 4) where session lifecycle timing differs from desktop Chromium.

**Root cause:** Application-level assumption that session.end() synchronously clears all state, or missing cleanup in the `end` event handler before allowing `requestSession()` to be called again.

## Suggested Fix

```javascript
let currentSession = null;

async function startXR() {
    // Guard against re-entry while session exists
    if (currentSession && !currentSession.ended) {
        console.warn('Session already active, ending first');
        await currentSession.end();
    }
    
    currentSession = await navigator.xr.requestSession('immersive-vr');
    
    currentSession.addEventListener('end', () => {
        // Full cleanup
        currentSession = null;
        cancelAnimationFrame(rafHandle);
        inputSources.clear();
        refSpace = null;
    });
    
    // ... setup new session
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions