## 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
}
Body