diff --git a/src/core/handlers/levelSelectedHandler.ts b/src/core/handlers/levelSelectedHandler.ts index 381ed75..e01a5b9 100644 --- a/src/core/handlers/levelSelectedHandler.ts +++ b/src/core/handlers/levelSelectedHandler.ts @@ -97,6 +97,7 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C if (canvas) { canvas.style.display = 'block'; } + engine.stopRenderLoop(); engine.runRenderLoop(() => { DefaultScene.MainScene.render(); }); @@ -164,6 +165,7 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C if (canvas) { canvas.style.display = 'block'; } + engine.stopRenderLoop(); engine.runRenderLoop(() => { DefaultScene.MainScene.render(); }); diff --git a/src/core/logger.ts b/src/core/logger.ts index 88a2b25..d74e9fb 100644 --- a/src/core/logger.ts +++ b/src/core/logger.ts @@ -1,5 +1,12 @@ import log from 'loglevel'; +// Check URL query parameter for log level override +const urlParams = new URLSearchParams(window.location.search); +const queryLevel = urlParams.get('loglevel'); +if (queryLevel && ['debug', 'info', 'warn', 'error'].includes(queryLevel)) { + localStorage.setItem('log-level', queryLevel); +} + // Check localStorage for custom level (enables production debugging) const storedLevel = localStorage.getItem('log-level'); diff --git a/src/core/sceneSetup.ts b/src/core/sceneSetup.ts index b22a7b8..1eb9a78 100644 --- a/src/core/sceneSetup.ts +++ b/src/core/sceneSetup.ts @@ -59,6 +59,11 @@ function createMainScene(engine: Engine): void { DefaultScene.MainScene = new Scene(engine); DefaultScene.MainScene.ambientColor = new Color3(.2, .2, .2); DefaultScene.MainScene.clearColor = new Color3(0, 0, 0).toColor4(); + + // Performance optimizations for Quest 2 + //DefaultScene.MainScene.performancePriority = ScenePerformancePriority.Intermediate; + DefaultScene.MainScene.autoClear = false; + DefaultScene.MainScene.autoClearDepthAndStencil = false; } async function setupPhysics(): Promise { @@ -66,7 +71,7 @@ async function setupPhysics(): Promise { const havokPlugin = new HavokPlugin(true, havok); DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin); DefaultScene.MainScene.getPhysicsEngine()!.setTimeStep(1/60); - DefaultScene.MainScene.getPhysicsEngine()!.setSubTimeStep(5); + DefaultScene.MainScene.getPhysicsEngine()!.setSubTimeStep(2); DefaultScene.MainScene.collisionsEnabled = true; } diff --git a/src/environment/asteroids/explosionManager.ts b/src/environment/asteroids/explosionManager.ts index bbc2629..dfe0fea 100644 --- a/src/environment/asteroids/explosionManager.ts +++ b/src/environment/asteroids/explosionManager.ts @@ -299,17 +299,6 @@ export class ExplosionManager { frameCount++; - // Log every 15 frames (approximately every 250ms at 60fps) - if (frameCount % 15 === 0 || frameCount === 1) { - log.debug(`[ExplosionManager] Animation frame ${frameCount}:`, { - elapsed: `${elapsed}ms`, - progress: progress.toFixed(3), - currentValue: currentValue.toFixed(2), - scale: scale.toFixed(3), - piecesAlive: meshPieces.filter(p => !p.isDisposed()).length - }); - } - // Continue animation if not complete if (progress >= 1.0) { // Animation complete - remove observer and clean up diff --git a/src/environment/asteroids/rockFactory.ts b/src/environment/asteroids/rockFactory.ts index 1ada1a7..cec2502 100644 --- a/src/environment/asteroids/rockFactory.ts +++ b/src/environment/asteroids/rockFactory.ts @@ -93,6 +93,8 @@ export class RockFactory { log.debug('loading mesh'); const asset = await loadAsset("asteroid.glb"); this._asteroidMesh = asset.meshes.get('Asteroid') || null; + this._asteroidMesh.material.backFaceCulling = true; + this._asteroidMesh.material.freeze(); if (this._asteroidMesh) { this._asteroidMesh.setEnabled(false); } diff --git a/src/environment/background/backgroundStars.ts b/src/environment/background/backgroundStars.ts index 8b98d33..cdb7b14 100644 --- a/src/environment/background/backgroundStars.ts +++ b/src/environment/background/backgroundStars.ts @@ -27,9 +27,9 @@ export class BackgroundStars { private scene: Scene; private config: Required; - // Default configuration + // Default configuration (reduced from 5000 for Quest 2 performance) private static readonly DEFAULT_CONFIG: Required = { - count: 5000, + count: 2500, radius: 5000, minBrightness: 0.3, maxBrightness: 1.0, diff --git a/src/levels/level1.ts b/src/levels/level1.ts index 4a1e5b0..c77bf5d 100644 --- a/src/levels/level1.ts +++ b/src/levels/level1.ts @@ -112,8 +112,9 @@ export class Level1 implements Level { canvas.style.display = 'block'; } - // Ensure render loop is running + // Ensure render loop is running (stop first to prevent duplicates) const engine = DefaultScene.MainScene.getEngine(); + engine.stopRenderLoop(); engine.runRenderLoop(() => { DefaultScene.MainScene.render(); }); diff --git a/src/ship/input/controllerInput.ts b/src/ship/input/controllerInput.ts index 49ab241..2960091 100644 --- a/src/ship/input/controllerInput.ts +++ b/src/ship/input/controllerInput.ts @@ -58,6 +58,7 @@ export class ControllerInput { private _onCameraAdjustObservable: Observable = new Observable(); private _onStatusScreenToggleObservable: Observable = new Observable(); + private _onInspectorToggleObservable: Observable = new Observable(); private _enabled: boolean = true; private _mappingConfig: ControllerMappingConfig; @@ -87,6 +88,13 @@ export class ControllerInput { return this._onStatusScreenToggleObservable; } + /** + * Get observable that fires when Y button is pressed on left controller + */ + public get onInspectorToggleObservable(): Observable { + return this._onInspectorToggleObservable; + } + /** * Get current input state (stick positions) * Applies controller mapping configuration to translate raw input to actions @@ -329,6 +337,12 @@ export class ControllerInput { this._onStatusScreenToggleObservable.notifyObservers(); } } + if (controllerEvent.component.id === "y-button" && controllerEvent.hand === "left") { + // Only trigger on button press, not release + if (controllerEvent.pressed) { + this._onInspectorToggleObservable.notifyObservers(); + } + } log.info(controllerEvent); } } @@ -341,5 +355,6 @@ export class ControllerInput { this._controllerObservable.clear(); this._onShootObservable.clear(); this._onCameraAdjustObservable.clear(); + this._onInspectorToggleObservable.clear(); } } diff --git a/src/ship/ship.ts b/src/ship/ship.ts index baf1778..8680ac0 100644 --- a/src/ship/ship.ts +++ b/src/ship/ship.ts @@ -309,6 +309,18 @@ export class Ship { } }); + // Wire up inspector toggle event (Y button) + this._controllerInput.onInspectorToggleObservable.add(() => { + import('@babylonjs/inspector').then(() => { + const scene = DefaultScene.MainScene; + if (scene.debugLayer.isVisible()) { + scene.debugLayer.hide(); + } else { + scene.debugLayer.show({ overlay: true, showExplorer: true }); + } + }); + }); + // Wire up camera adjustment events this._keyboardInput.onCameraChangeObservable.add((cameraKey) => { if (cameraKey === 1) { @@ -335,16 +347,22 @@ export class Ship { this._physics.setGameStats(this._gameStats); // Setup physics update loop (every 10 frames) + let p = 0; this._physicsObserver = DefaultScene.MainScene.onAfterPhysicsObservable.add(() => { - this.updatePhysics(); + + this.updatePhysics(); + }); + let renderFrameCount = 0; this._renderObserver = DefaultScene.MainScene.onAfterRenderObservable.add(() => { // Update voice audio system (checks for completed sounds and plays next in queue) if (this._voiceAudio) { this._voiceAudio.update(); } - // Check game end conditions every frame (but only acts once) - this.checkGameEndConditions(); + // Check game end conditions every 30 frames (~0.5 sec at 60fps) + if (renderFrameCount++ % 30 === 0) { + this.checkGameEndConditions(); + } }); // Setup camera diff --git a/src/ui/hud/scoreboard.ts b/src/ui/hud/scoreboard.ts index f71a84e..90f3ba7 100644 --- a/src/ui/hud/scoreboard.ts +++ b/src/ui/hud/scoreboard.ts @@ -189,6 +189,8 @@ export class Scoreboard { advancedTexture.addControl(panel); let i = 0; const _afterRender = scene.onAfterRenderObservable.add(() => { + if (i++ % 10 !== 0) return; + scoreText.text = `Score: ${this.calculateScore()}`; remainingText.text = `Remaining: ${this._remaining}`; @@ -201,7 +203,7 @@ export class Scoreboard { } const elapsed = Date.now() - this._startTime; - if (this._active && i++%30 == 0) { + if (this._active) { timeRemainingText.text = `Time: ${Math.floor(elapsed/60000).toString().padStart(2,"0")}:${(Math.floor(elapsed/1000)%60).toString().padStart(2,"0")}`; fpsText.text = `FPS: ${Math.floor(scene.getEngine().getFps())}`; }