diff --git a/src/analytics/adapters/newRelicAdapter.ts b/src/analytics/adapters/newRelicAdapter.ts index d819eea..019e3f8 100644 --- a/src/analytics/adapters/newRelicAdapter.ts +++ b/src/analytics/adapters/newRelicAdapter.ts @@ -1,5 +1,6 @@ import { AnalyticsAdapter, AnalyticsEvent } from './analyticsAdapter'; import { BrowserAgent } from '@newrelic/browser-agent/loaders/browser-agent'; +import log from '../../core/logger'; interface NewRelicAdapterConfig { /** Maximum events to batch before auto-flush */ @@ -137,7 +138,7 @@ export class NewRelicAdapter implements AnalyticsAdapter { this.agent.addPageAction(event.name, payload); this.log('Event sent:', event.name, payload); } catch (error) { - console.error('Failed to send New Relic event:', error); + log.error('Failed to send New Relic event:', error); } } @@ -163,7 +164,7 @@ export class NewRelicAdapter implements AnalyticsAdapter { this.agent.addPageAction(`${eventName}_batch`, batchPayload); this.log(`Batched ${events.length} events:`, eventName, batchPayload); } catch (error) { - console.error('Failed to send batched New Relic event:', error); + log.error('Failed to send batched New Relic event:', error); } } @@ -237,7 +238,7 @@ export class NewRelicAdapter implements AnalyticsAdapter { private log(message: string, ...args: any[]): void { if (this.config.debug) { - console.log(`[NewRelicAdapter] ${message}`, ...args); + log.info(`[NewRelicAdapter] ${message}`, ...args); } } } diff --git a/src/analytics/analyticsService.ts b/src/analytics/analyticsService.ts index d35300a..5daa602 100644 --- a/src/analytics/analyticsService.ts +++ b/src/analytics/analyticsService.ts @@ -1,5 +1,6 @@ import { AnalyticsAdapter, AnalyticsConfig, EventMetadata, EventOptions } from './adapters/analyticsAdapter'; import { GameEventName, GameEventProperties } from './events/gameEvents'; +import log from '../core/logger'; /** * Central analytics service with pluggable adapters @@ -36,7 +37,7 @@ export class AnalyticsService { static initialize(config?: AnalyticsConfig): AnalyticsService { if (AnalyticsService.instance) { - console.warn('AnalyticsService already initialized'); + log.warn('AnalyticsService already initialized'); return AnalyticsService.instance; } @@ -108,7 +109,7 @@ export class AnalyticsService { try { adapter.track(event); } catch (error) { - console.error(`Adapter ${adapter.name} failed to track event:`, error); + log.error(`Adapter ${adapter.name} failed to track event:`, error); } } } @@ -140,7 +141,7 @@ export class AnalyticsService { try { adapter.track(event); } catch (error) { - console.error(`Adapter ${adapter.name} failed to track custom event:`, error); + log.error(`Adapter ${adapter.name} failed to track custom event:`, error); } } } @@ -170,7 +171,7 @@ export class AnalyticsService { try { adapter.flush(); } catch (error) { - console.error(`Adapter ${adapter.name} failed to flush:`, error); + log.error(`Adapter ${adapter.name} failed to flush:`, error); } } } @@ -184,7 +185,7 @@ export class AnalyticsService { try { adapter.shutdown(); } catch (error) { - console.error(`Adapter ${adapter.name} failed to shutdown:`, error); + log.error(`Adapter ${adapter.name} failed to shutdown:`, error); } } AnalyticsService.instance = null; @@ -239,7 +240,7 @@ export class AnalyticsService { private log(message: string, ...args: any[]): void { if (this.config.debug) { - console.log(`[AnalyticsService] ${message}`, ...args); + log.info(`[AnalyticsService] ${message}`, ...args); } } } diff --git a/src/components/game/LevelCard.svelte b/src/components/game/LevelCard.svelte index 5fcffeb..7a7355a 100644 --- a/src/components/game/LevelCard.svelte +++ b/src/components/game/LevelCard.svelte @@ -5,12 +5,13 @@ import { progressionStore } from '../../stores/progression'; import { gameConfigStore } from '../../stores/gameConfig'; import Button from '../shared/Button.svelte'; + import log from '../../core/logger'; export let levelId: string; export let levelEntry: CloudLevelEntry; async function handleLevelClick() { - console.log('[LevelCard] Level clicked:', { + log.info('[LevelCard] Level clicked:', { levelId, levelName: levelEntry.name, isUnlocked, @@ -20,24 +21,24 @@ // If level is locked and user not authenticated, prompt to sign in if (!isUnlocked && !$authStore.isAuthenticated) { - console.log('[LevelCard] Locked level clicked - prompting for sign in'); + log.info('[LevelCard] Locked level clicked - prompting for sign in'); try { await authStore.login(); - console.log('[LevelCard] Login completed'); + log.info('[LevelCard] Login completed'); } catch (error) { - console.error('[LevelCard] Login failed:', error); + log.error('[LevelCard] Login failed:', error); } return; } // If level is still locked (progression), don't allow play if (!isUnlocked) { - console.log('[LevelCard] Level still locked after auth check (progression lock)'); + log.info('[LevelCard] Level still locked after auth check (progression lock)'); return; } // Navigate to level play route - console.log('[LevelCard] Level unlocked, navigating to /play/' + levelId); + log.info('[LevelCard] Level unlocked, navigating to /play/' + levelId); navigate(`/play/${levelId}`); } diff --git a/src/components/game/PlayLevel.svelte b/src/components/game/PlayLevel.svelte index f5cd2c6..3ebb591 100644 --- a/src/components/game/PlayLevel.svelte +++ b/src/components/game/PlayLevel.svelte @@ -4,7 +4,7 @@ import { Main } from '../../main'; import type { LevelConfig } from '../../levels/config/levelConfig'; import { LevelRegistry } from '../../levels/storage/levelRegistry'; - import debugLog from '../../core/debug'; + import log from '../../core/logger'; import { DefaultScene } from '../../core/defaultScene'; // svelte-routing passes params as an object with route params @@ -34,17 +34,17 @@ // Handle popstate (browser back/forward buttons) function handlePopState() { if (isInitialized && !isExiting && !window.location.pathname.startsWith('/play/')) { - debugLog('[PlayLevel] Navigation detected via popstate, starting cleanup'); + log.debug('[PlayLevel] Navigation detected via popstate, starting cleanup'); isExiting = true; } } onMount(async () => { - console.log('[PlayLevel] Component mounted'); - console.log('[PlayLevel] params:', params); - console.log('[PlayLevel] levelId prop:', levelId); - console.log('[PlayLevel] actualLevelId:', actualLevelId); - console.log('[PlayLevel] window.location.pathname:', window.location.pathname); + log.info('[PlayLevel] Component mounted'); + log.info('[PlayLevel] params:', params); + log.info('[PlayLevel] levelId prop:', levelId); + log.info('[PlayLevel] actualLevelId:', actualLevelId); + log.info('[PlayLevel] window.location.pathname:', window.location.pathname); // Try to extract levelId from URL if props don't have it let extractedLevelId = actualLevelId; @@ -52,19 +52,19 @@ const match = window.location.pathname.match(/\/play\/(.+)/); if (match) { extractedLevelId = match[1]; - console.log('[PlayLevel] Extracted levelId from URL:', extractedLevelId); + log.info('[PlayLevel] Extracted levelId from URL:', extractedLevelId); } } levelName = extractedLevelId; if (!levelName) { - console.error('[PlayLevel] No levelId found!'); + log.error('[PlayLevel] No levelId found!'); error = 'No level specified'; return; } - console.log('[PlayLevel] Using levelName:', levelName); + log.info('[PlayLevel] Using levelName:', levelName); // Add event listeners window.addEventListener('beforeunload', handleBeforeUnload); @@ -75,7 +75,7 @@ const appElement = document.getElementById('app'); if (appElement) { appElement.style.display = 'none'; - debugLog('[PlayLevel] App UI hidden'); + log.debug('[PlayLevel] App UI hidden'); } // Get the main instance (should already exist from app initialization) @@ -93,7 +93,7 @@ throw new Error(`Level "${levelName}" not found`); } - debugLog('[PlayLevel] Level config loaded:', levelEntry); + log.debug('[PlayLevel] Level config loaded:', levelEntry); // Dispatch the levelSelected event (existing system expects this) // We'll refactor this later to call Main methods directly @@ -107,10 +107,10 @@ window.dispatchEvent(event); isInitialized = true; - debugLog('[PlayLevel] Level initialization started'); + log.debug('[PlayLevel] Level initialization started'); } catch (err) { - console.error('[PlayLevel] Error initializing level:', err); + log.error('[PlayLevel] Error initializing level:', err); error = err instanceof Error ? err.message : 'Unknown error'; // Show UI again on error @@ -127,8 +127,8 @@ }); onDestroy(async () => { - console.log('[PlayLevel] Component unmounting - cleaning up'); - debugLog('[PlayLevel] Component unmounting - cleaning up'); + log.info('[PlayLevel] Component unmounting - cleaning up'); + log.debug('[PlayLevel] Component unmounting - cleaning up'); // Remove event listeners window.removeEventListener('beforeunload', handleBeforeUnload); @@ -138,8 +138,8 @@ const appElement = document.getElementById('app'); if (appElement) { appElement.style.display = 'block'; - console.log('[PlayLevel] App UI restored'); - debugLog('[PlayLevel] App UI restored'); + log.info('[PlayLevel] App UI restored'); + log.debug('[PlayLevel] App UI restored'); } try { @@ -148,7 +148,7 @@ await mainInstance.cleanupAndExit(); } } catch (err) { - console.error('[PlayLevel] Error during cleanup:', err); + log.error('[PlayLevel] Error during cleanup:', err); } }); diff --git a/src/components/layouts/App.svelte b/src/components/layouts/App.svelte index 4ddf9be..c0a3ecf 100644 --- a/src/components/layouts/App.svelte +++ b/src/components/layouts/App.svelte @@ -5,6 +5,7 @@ import { navigationStore } from '../../stores/navigation'; import { AuthService } from '../../services/authService'; import { authStore } from '../../stores/auth'; + import log from '../../core/logger'; // Import game views import LevelSelect from '../game/LevelSelect.svelte'; @@ -16,21 +17,21 @@ // Initialize Auth0 when component mounts onMount(async () => { - console.log('[App] ========== APP MOUNTED - INITIALIZING AUTH0 =========='); + log.info('[App] ========== APP MOUNTED - INITIALIZING AUTH0 =========='); try { const authService = AuthService.getInstance(); await authService.initialize(); - console.log('[App] Auth0 initialized successfully'); + log.info('[App] Auth0 initialized successfully'); // Refresh auth store to update UI with current auth state - console.log('[App] Refreshing auth store...'); + log.info('[App] Refreshing auth store...'); await authStore.refresh(); - console.log('[App] Auth store refreshed'); + log.info('[App] Auth store refreshed'); } catch (error) { - console.error('[App] !!!!! AUTH0 INITIALIZATION FAILED !!!!!', error); - console.error('[App] Error details:', error?.message, error?.stack); + log.error('[App] !!!!! AUTH0 INITIALIZATION FAILED !!!!!', error); + log.error('[App] Error details:', error?.message, error?.stack); } - console.log('[App] ========== AUTH0 INITIALIZATION COMPLETE =========='); + log.info('[App] ========== AUTH0 INITIALIZATION COMPLETE =========='); }); diff --git a/src/components/leaderboard/Leaderboard.svelte b/src/components/leaderboard/Leaderboard.svelte index f8d89c1..6b04930 100644 --- a/src/components/leaderboard/Leaderboard.svelte +++ b/src/components/leaderboard/Leaderboard.svelte @@ -5,6 +5,7 @@ import type { GameResult } from '../../services/gameResultsService'; import { CloudLeaderboardService, type CloudLeaderboardEntry, getDisplayName } from '../../services/cloudLeaderboardService'; import { formatStars } from '../../game/scoreCalculator'; + import log from '../../core/logger'; // View toggle: 'local' or 'cloud' let activeView: 'local' | 'cloud' = 'cloud'; @@ -52,7 +53,7 @@ } } catch (error) { cloudError = 'Failed to load cloud leaderboard'; - console.error('[Leaderboard] Cloud load error:', error); + log.error('[Leaderboard] Cloud load error:', error); } finally { cloudLoading = false; cloudLoadingMore = false; diff --git a/src/components/settings/SettingsScreen.svelte b/src/components/settings/SettingsScreen.svelte index e2524f2..29659e5 100644 --- a/src/components/settings/SettingsScreen.svelte +++ b/src/components/settings/SettingsScreen.svelte @@ -8,6 +8,7 @@ import Checkbox from '../shared/Checkbox.svelte'; import NumberInput from '../shared/NumberInput.svelte'; import InfoBox from '../shared/InfoBox.svelte'; + import log from '../../core/logger'; let message = ''; let messageType: 'success' | 'error' | 'warning' = 'success'; @@ -21,7 +22,7 @@ const config = JSON.parse(stored); gameConfigStore.set(config); } catch (error) { - console.warn('[SettingsScreen] Failed to reload config:', error); + log.warn('[SettingsScreen] Failed to reload config:', error); } } }); diff --git a/src/core/appInitializer.ts b/src/core/appInitializer.ts index 97bf1f8..28e1caa 100644 --- a/src/core/appInitializer.ts +++ b/src/core/appInitializer.ts @@ -2,118 +2,95 @@ import { mount } from 'svelte'; import App from '../components/layouts/App.svelte'; import { LegacyMigration } from '../levels/migration/legacyMigration'; import { LevelRegistry } from '../levels/storage/levelRegistry'; -import debugLog from './debug'; +import log from './logger'; // Type for Main class - imported dynamically to avoid circular dependency type MainClass = new (progressCallback?: (percent: number, message: string) => void) => any; /** * Initialize the application - * - Check for legacy data migration - * - Initialize level registry - * - Mount Svelte app - * - Create Main instance */ export async function initializeApp(MainConstructor: MainClass): Promise { - console.log('[Main] ========================================'); - console.log('[Main] initializeApp() STARTED at', new Date().toISOString()); - console.log('[Main] ========================================'); + log.info('[Main] ========================================'); + log.info('[Main] initializeApp() STARTED at', new Date().toISOString()); + log.info('[Main] ========================================'); - // Check for legacy data migration const needsMigration = LegacyMigration.needsMigration(); - console.log('[Main] Needs migration check:', needsMigration); + log.info('[Main] Needs migration check:', needsMigration); if (needsMigration) { - debugLog('[Main] Legacy data detected - showing migration modal'); + log.debug('[Main] Legacy data detected - showing migration modal'); return new Promise((resolve) => { LegacyMigration.showMigrationModal(async (result) => { - debugLog('[Main] Migration completed:', result); - // Initialize the new registry system + log.debug('[Main] Migration completed:', result); try { - console.log('[Main] About to call LevelRegistry.getInstance().initialize() [AFTER MIGRATION]'); + log.info('[Main] Initializing LevelRegistry [AFTER MIGRATION]'); await LevelRegistry.getInstance().initialize(); - console.log('[Main] LevelRegistry.initialize() completed successfully [AFTER MIGRATION]'); - debugLog('[Main] LevelRegistry initialized after migration'); - - // Mount Svelte app and create Main + log.info('[Main] LevelRegistry initialized [AFTER MIGRATION]'); mountAppAndCreateMain(MainConstructor); resolve(); } catch (error) { - console.error('[Main] Failed to initialize LevelRegistry after migration:', error); + log.error('[Main] Failed to initialize LevelRegistry after migration:', error); resolve(); } }); }); - } else { - console.log('[Main] No migration needed - proceeding to initialize registry'); - // Initialize the new registry system - try { - console.log('[Main] About to call LevelRegistry.getInstance().initialize()'); - console.log('[Main] Timestamp before initialize:', Date.now()); - await LevelRegistry.getInstance().initialize(); - console.log('[Main] Timestamp after initialize:', Date.now()); - console.log('[Main] LevelRegistry.initialize() completed successfully'); - debugLog('[Main] LevelRegistry initialized'); - - // Expose registry to window for debugging (dev mode) - const isDev = window.location.hostname === 'localhost' || - window.location.hostname.includes('dev.') || - window.location.port !== ''; - if (isDev) { - (window as any).__levelRegistry = LevelRegistry.getInstance(); - console.log('[Main] LevelRegistry exposed to window.__levelRegistry for debugging'); - console.log('[Main] To clear caches: window.__levelRegistry.reset(); location.reload()'); - } - } catch (error) { - console.error('[Main] !!!!! EXCEPTION in LevelRegistry initialization !!!!!'); - console.error('[Main] Failed to initialize LevelRegistry:', error); - console.error('[Main] Error stack:', (error as Error)?.stack); - } } - // Mount Svelte app and create Main - mountAppAndCreateMain(MainConstructor); + log.info('[Main] No migration needed - proceeding to initialize registry'); + try { + log.info('[Main] Initializing LevelRegistry'); + await LevelRegistry.getInstance().initialize(); + log.info('[Main] LevelRegistry initialized successfully'); - console.log('[Main] initializeApp() FINISHED at', new Date().toISOString()); + // Expose registry to window for debugging (dev mode) + const isDev = window.location.hostname === 'localhost' || + window.location.hostname.includes('dev.') || + window.location.port !== ''; + if (isDev) { + (window as any).__levelRegistry = LevelRegistry.getInstance(); + log.info('[Main] LevelRegistry exposed to window.__levelRegistry'); + } + } catch (error) { + log.error('[Main] Failed to initialize LevelRegistry:', error); + } + + mountAppAndCreateMain(MainConstructor); + log.info('[Main] initializeApp() FINISHED'); } /** * Mount the Svelte app and create Main instance */ function mountAppAndCreateMain(MainConstructor: MainClass): void { - console.log('[Main] Mounting Svelte app'); + log.info('[Main] Mounting Svelte app'); const appElement = document.getElementById('app'); if (appElement) { - mount(App, { - target: appElement - }); - console.log('[Main] Svelte app mounted successfully'); + mount(App, { target: appElement }); + log.info('[Main] Svelte app mounted successfully'); - // Create Main instance lazily only if it doesn't exist if (!(window as any).__mainInstance) { - debugLog('[Main] Creating Main instance (not initialized)'); + log.debug('[Main] Creating Main instance'); const main = new MainConstructor(); (window as any).__mainInstance = main; } } else { - console.error('[Main] Failed to mount Svelte app - #app element not found'); + log.error('[Main] Failed to mount Svelte app - #app element not found'); } } /** * Set up global error handler for shader loading errors - * Suppress non-critical BabylonJS shader loading errors during development */ export function setupErrorHandler(): void { window.addEventListener('unhandledrejection', (event) => { const error = event.reason; - if (error && error.message) { - // Only suppress specific shader-related errors, not asset loading errors + if (error?.message) { if (error.message.includes('rgbdDecode.fragment') || error.message.includes('procedural.vertex') || (error.message.includes('Failed to fetch dynamically imported module') && (error.message.includes('rgbdDecode') || error.message.includes('procedural')))) { - debugLog('[Main] Suppressed shader loading error (should be fixed by Vite pre-bundling):', error.message); + log.debug('[Main] Suppressed shader loading error:', error.message); event.preventDefault(); } } diff --git a/src/core/cleanup.ts b/src/core/cleanup.ts index 823940d..3311bf9 100644 --- a/src/core/cleanup.ts +++ b/src/core/cleanup.ts @@ -1,7 +1,7 @@ import { Engine } from "@babylonjs/core"; import { DefaultScene } from "./defaultScene"; import { RockFactory } from "../environment/asteroids/rockFactory"; -import debugLog from './debug'; +import log from './logger'; import Level from "../levels/level"; export interface CleanupContext { @@ -18,7 +18,7 @@ export async function cleanupAndExit( context: CleanupContext, canvas: HTMLCanvasElement ): Promise { - debugLog('[Main] cleanupAndExit() called - starting graceful shutdown'); + log.debug('[Main] cleanupAndExit() called - starting graceful shutdown'); try { context.getEngine().stopRenderLoop(); disposeCurrentLevel(context); @@ -29,7 +29,7 @@ export async function cleanupAndExit( context.resetState(); clearCanvas(canvas); } catch (error) { - console.error('[Main] Cleanup failed:', error); + log.error('[Main] Cleanup failed:', error); window.location.reload(); } } @@ -47,7 +47,7 @@ async function exitXRSession(): Promise { try { await DefaultScene.XR.baseExperience.exitXRAsync(); } catch (error) { - debugLog('[Main] Error exiting XR:', error); + log.debug('[Main] Error exiting XR:', error); } } DefaultScene.XR = null; diff --git a/src/core/debug.ts b/src/core/debug.ts deleted file mode 100644 index 5832562..0000000 --- a/src/core/debug.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {GameConfig} from "./gameConfig"; - -const config = GameConfig.getInstance(); - -export default function debugLog(...params: any[]) { - if (config.debug) { - console.log(...params); - } -} \ No newline at end of file diff --git a/src/core/gameConfig.ts b/src/core/gameConfig.ts index ed4f4d8..0f8c1ee 100644 --- a/src/core/gameConfig.ts +++ b/src/core/gameConfig.ts @@ -1,3 +1,5 @@ +import log from './logger'; + /** * Default ship physics configuration */ @@ -93,7 +95,7 @@ export class GameConfig { this.save(); } } catch (error) { - console.warn('Failed to load game config from localStorage:', error); + log.warn('Failed to load game config from localStorage:', error); } } diff --git a/src/core/handlers/levelSelectedHandler.ts b/src/core/handlers/levelSelectedHandler.ts index c25843b..7f93914 100644 --- a/src/core/handlers/levelSelectedHandler.ts +++ b/src/core/handlers/levelSelectedHandler.ts @@ -5,7 +5,7 @@ import Level from "../../levels/level"; import { RockFactory } from "../../environment/asteroids/rockFactory"; import { LevelConfig } from "../../levels/config/levelConfig"; import { Preloader } from "../../ui/screens/preloader"; -import debugLog from '../debug'; +import log from '../logger'; /** * Interface for Main class methods needed by the level selected handler @@ -35,7 +35,7 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C context.setStarted(true); const { levelName, config } = e.detail as { levelName: string, config: LevelConfig }; - debugLog(`[Main] Starting level: ${levelName}`); + log.debug(`[Main] Starting level: ${levelName}`); // Hide all UI elements const levelSelect = document.querySelector('#levelSelect') as HTMLElement; @@ -57,7 +57,7 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C try { // Initialize engine if this is first time if (!context.isInitialized()) { - debugLog('[Main] First level selected - initializing engine'); + log.debug('[Main] First level selected - initializing engine'); preloader.updateProgress(0, 'Initializing game engine...'); await context.initializeEngine(); } @@ -65,14 +65,14 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C // Load assets if this is the first level being played if (!context.areAssetsLoaded()) { preloader.updateProgress(40, 'Loading 3D models and textures...'); - debugLog('[Main] Loading assets for first time'); + log.debug('[Main] Loading assets for first time'); // Load visual assets (meshes, particles) ParticleHelper.BaseAssetsUrl = window.location.href; await RockFactory.init(); context.setAssetsLoaded(true); - debugLog('[Main] Assets loaded successfully'); + log.debug('[Main] Assets loaded successfully'); preloader.updateProgress(60, 'Assets loaded'); } @@ -88,9 +88,9 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C try { preloader.updateProgress(75, 'Entering VR...'); xrSession = await DefaultScene.XR.baseExperience.enterXRAsync('immersive-vr', 'local-floor'); - debugLog('XR session started successfully (render loop paused until camera is ready)'); + log.debug('XR session started successfully (render loop paused until camera is ready)'); } catch (error) { - debugLog('Failed to enter XR, will fall back to flat mode:', error); + log.debug('Failed to enter XR, will fall back to flat mode:', error); DefaultScene.XR = null; engine.runRenderLoop(() => { DefaultScene.MainScene.render(); @@ -112,9 +112,9 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C const camera = DefaultScene.XR?.baseExperience?.camera || DefaultScene.MainScene.activeCamera; if (camera && audioEngine.listener) { audioEngine.listener.attach(camera); - debugLog('[Main] Audio listener attached to camera for spatial audio'); + log.debug('[Main] Audio listener attached to camera for spatial audio'); } else { - debugLog('[Main] WARNING: Could not attach audio listener - camera or listener not available'); + log.warn('[Main] Could not attach audio listener - camera or listener not available'); } preloader.updateProgress(90, 'Creating level...'); @@ -134,26 +134,26 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C // Listen for replay requests from the ship if (ship) { ship.onReplayRequestObservable.add(() => { - debugLog('Replay requested - reloading page'); + log.debug('Replay requested - reloading page'); window.location.reload(); }); } // If we entered XR before level creation, manually setup camera parenting - console.log('[Main] ========== CHECKING XR STATE =========='); - console.log('[Main] DefaultScene.XR exists:', !!DefaultScene.XR); - console.log('[Main] xrSession exists:', !!xrSession); + log.info('[Main] ========== CHECKING XR STATE =========='); + log.info('[Main] DefaultScene.XR exists:', !!DefaultScene.XR); + log.info('[Main] xrSession exists:', !!xrSession); if (DefaultScene.XR) { - console.log('[Main] XR base experience state:', DefaultScene.XR.baseExperience.state); + log.info('[Main] XR base experience state:', DefaultScene.XR.baseExperience.state); } if (DefaultScene.XR && xrSession && DefaultScene.XR.baseExperience.state === 2) { - debugLog('[Main] XR already active - using consolidated setupXRCamera()'); + log.debug('[Main] XR already active - using consolidated setupXRCamera()'); level1.setupXRCamera(); await level1.showMissionBrief(); - debugLog('[Main] XR setup and mission brief complete'); + log.debug('[Main] XR setup and mission brief complete'); } else { - console.log('[Main] XR not active yet - will use onInitialXRPoseSetObservable instead'); + log.info('[Main] XR not active yet - will use onInitialXRPoseSetObservable instead'); engine.runRenderLoop(() => { DefaultScene.MainScene.render(); }); @@ -166,20 +166,20 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C }, 500); // Hide UI (no longer remove from DOM - let Svelte routing handle it) - console.log('[Main] ========== HIDING UI FOR GAMEPLAY =========='); - console.log('[Main] Timestamp:', Date.now()); + log.info('[Main] ========== HIDING UI FOR GAMEPLAY =========='); + log.info('[Main] Timestamp:', Date.now()); // Start the game - console.log('[Main] About to call context.play()'); + log.info('[Main] About to call context.play()'); await context.play(); - console.log('[Main] context.play() completed'); + log.info('[Main] context.play() completed'); }); // Now initialize the level (after observable is registered) await currentLevel.initialize(); } catch (error) { - console.error('[Main] Level initialization failed:', error); + log.error('[Main] Level initialization failed:', error); preloader.updateProgress(0, 'Failed to load level. Please refresh and try again.'); } }; diff --git a/src/core/logger.ts b/src/core/logger.ts new file mode 100644 index 0000000..88a2b25 --- /dev/null +++ b/src/core/logger.ts @@ -0,0 +1,15 @@ +import log from 'loglevel'; + +// Check localStorage for custom level (enables production debugging) +const storedLevel = localStorage.getItem('log-level'); + +// Set level: localStorage override > environment default +if (storedLevel) { + log.setLevel(storedLevel as log.LogLevelDesc); +} else { + const isDev = window.location.hostname === 'localhost' || + window.location.hostname.includes('dev.'); + log.setLevel(isDev ? 'debug' : 'warn'); +} + +export default log; diff --git a/src/core/xrSetup.ts b/src/core/xrSetup.ts index 2cacef9..ce878d1 100644 --- a/src/core/xrSetup.ts +++ b/src/core/xrSetup.ts @@ -1,7 +1,7 @@ import { WebXRDefaultExperience, WebXRFeaturesManager } from "@babylonjs/core"; import { DefaultScene } from "./defaultScene"; import { InputControlManager } from "../ship/input/inputControlManager"; -import debugLog from './debug'; +import log from './logger'; export interface ProgressReporter { reportProgress(percent: number, message: string): void; @@ -24,7 +24,7 @@ export async function initializeXR(reporter: ProgressReporter): Promise { registerXRStateHandler(); reporter.reportProgress(40, 'VR support enabled'); } catch (error) { - debugLog("WebXR initialization failed:", error); + log.debug("WebXR initialization failed:", error); DefaultScene.XR = null; reporter.reportProgress(40, 'Desktop mode'); } @@ -37,7 +37,7 @@ async function createXRExperience(): Promise { disableHandTracking: true, disableDefaultUI: true }); - debugLog(WebXRFeaturesManager.GetAvailableFeatures()); + log.debug(WebXRFeaturesManager.GetAvailableFeatures()); } function registerXRStateHandler(): void { diff --git a/src/environment/asteroids/explosionManager.ts b/src/environment/asteroids/explosionManager.ts index 13848b1..bbc2629 100644 --- a/src/environment/asteroids/explosionManager.ts +++ b/src/environment/asteroids/explosionManager.ts @@ -7,7 +7,7 @@ import { Vector3 } from "@babylonjs/core"; import {DefaultScene} from "../../core/defaultScene"; -import debugLog from '../../core/debug'; +import log from '../../core/logger'; /** * Configuration for explosion effects @@ -42,7 +42,7 @@ export class ExplosionManager { constructor(scene: Scene, config?: ExplosionConfig) { this.scene = scene; this.config = { ...ExplosionManager.DEFAULT_CONFIG, ...config }; - debugLog(this.config); + log.debug(this.config); this._debrisBaseMesh = MeshBuilder.CreateIcoSphere( 'debrisBase', { @@ -60,7 +60,7 @@ export class ExplosionManager { * Initialize the explosion manager (no longer needed for MeshExploder, but kept for API compatibility) */ public async initialize(): Promise { - debugLog("ExplosionManager initialized with MeshExploder"); + log.debug("ExplosionManager initialized with MeshExploder"); } /** @@ -69,7 +69,7 @@ export class ExplosionManager { public async initAudio(audioEngine: AudioEngineV2): Promise { this.audioEngine = audioEngine; - debugLog(`ExplosionManager: Initializing audio with pool size ${this.soundPoolSize}`); + log.debug(`ExplosionManager: Initializing audio with pool size ${this.soundPoolSize}`); // Create sound pool for concurrent explosions for (let i = 0; i < this.soundPoolSize; i++) { @@ -89,7 +89,7 @@ export class ExplosionManager { this.explosionSounds.push(sound); } - debugLog(`ExplosionManager: Loaded ${this.explosionSounds.length} explosion sounds`); + log.debug(`ExplosionManager: Loaded ${this.explosionSounds.length} explosion sounds`); } /** @@ -104,7 +104,7 @@ export class ExplosionManager { } // If all sounds are playing, reuse the first one (will cut off the oldest) - debugLog("ExplosionManager: All sounds in pool are playing, reusing sound 0"); + log.debug("ExplosionManager: All sounds in pool are playing, reusing sound 0"); return this.explosionSounds[0] || null; } @@ -119,7 +119,7 @@ export class ExplosionManager { const sound = this.getAvailableSound(); if (!sound) { - debugLog("ExplosionManager: No sound available in pool"); + log.debug("ExplosionManager: No sound available in pool"); return; } @@ -139,7 +139,7 @@ export class ExplosionManager { } catch (error) { - debugLog("ExplosionManager: Error playing explosion audio", error); + log.debug("ExplosionManager: Error playing explosion audio", error); explosionNode.dispose(); } } @@ -152,7 +152,7 @@ export class ExplosionManager { * @returns Array of sphere mesh objects */ private splitIntoSeparateMeshes(position: Vector3, pieces: number = 32): InstancedMesh[] { - debugLog(`[ExplosionManager] Creating ${pieces} sphere debris pieces`); + log.debug(`[ExplosionManager] Creating ${pieces} sphere debris pieces`); const meshPieces: InstancedMesh[] = []; @@ -182,13 +182,13 @@ export class ExplosionManager { sphere.setEnabled(true); meshPieces.push(sphere); } catch (error) { - console.error(`[ExplosionManager] ERROR creating debris piece ${i}:`, error); + log.error(`[ExplosionManager] ERROR creating debris piece ${i}:`, error); } } - debugLog(`[ExplosionManager] Successfully created ${meshPieces.length}/${pieces} sphere debris pieces`); + log.debug(`[ExplosionManager] Successfully created ${meshPieces.length}/${pieces} sphere debris pieces`); if (meshPieces.length > 0) { - debugLog('[ExplosionManager] First piece sample:', { + log.debug('[ExplosionManager] First piece sample:', { name: meshPieces[0].name, position: meshPieces[0].position.toString(), isVisible: meshPieces[0].isVisible, @@ -203,8 +203,8 @@ export class ExplosionManager { * @param mesh The mesh to explode (will be cloned internally) */ public playExplosion(mesh: AbstractMesh): void { - debugLog('[ExplosionManager] playExplosion called'); - debugLog('[ExplosionManager] Input mesh:', { + log.debug('[ExplosionManager] playExplosion called'); + log.debug('[ExplosionManager] Input mesh:', { name: mesh.name, id: mesh.id, isInstancedMesh: !!(mesh as any).sourceMesh, @@ -220,14 +220,14 @@ export class ExplosionManager { let sourceMesh: Mesh; if ((mesh as any).sourceMesh) { sourceMesh = (mesh as any).sourceMesh as Mesh; - debugLog('[ExplosionManager] Using source mesh from instance:', sourceMesh.name); + log.debug('[ExplosionManager] Using source mesh from instance:', sourceMesh.name); } else { sourceMesh = mesh as Mesh; - debugLog('[ExplosionManager] Using mesh directly (not instanced)'); + log.debug('[ExplosionManager] Using mesh directly (not instanced)'); } // Clone the source mesh so we don't affect the original - debugLog('[ExplosionManager] Cloning mesh...'); + log.debug('[ExplosionManager] Cloning mesh...'); mesh.computeWorldMatrix(true); // Apply the instance's transformation to the cloned mesh const position = mesh.getAbsolutePosition().clone(); @@ -237,34 +237,34 @@ export class ExplosionManager { // Check if mesh has proper geometry if (!mesh.getTotalVertices || mesh.getTotalVertices() === 0) { - console.error('[ExplosionManager] ERROR: Mesh has no vertices, cannot explode'); + log.error('[ExplosionManager] ERROR: Mesh has no vertices, cannot explode'); mesh.dispose(); return; } // Split the mesh into separate mesh objects (MeshExploder requirement) - debugLog('[ExplosionManager] Splitting mesh into pieces...'); + log.debug('[ExplosionManager] Splitting mesh into pieces...'); const meshPieces = this.splitIntoSeparateMeshes(position, 12); if (meshPieces.length === 0) { - console.error('[ExplosionManager] ERROR: Failed to split mesh into pieces'); + log.error('[ExplosionManager] ERROR: Failed to split mesh into pieces'); mesh.dispose(); return; } // Original mesh is no longer needed - the pieces replace it - debugLog('[ExplosionManager] Disposing original cloned mesh'); + log.debug('[ExplosionManager] Disposing original cloned mesh'); mesh.dispose(); // Create the exploder with the array of separate meshes // The second parameter is optional - it's the center mesh to explode from // If not provided, MeshExploder will auto-calculate the center - debugLog('[ExplosionManager] Creating MeshExploder...'); + log.debug('[ExplosionManager] Creating MeshExploder...'); try { const exploder = new MeshExploder((meshPieces as unknown) as Mesh[]); - debugLog('[ExplosionManager] MeshExploder created successfully'); + log.debug('[ExplosionManager] MeshExploder created successfully'); - debugLog(`[ExplosionManager] Starting explosion animation:`, { + log.debug(`[ExplosionManager] Starting explosion animation:`, { pieceCount: meshPieces.length, duration: this.config.duration, maxForce: this.config.explosionForce @@ -286,7 +286,7 @@ export class ExplosionManager { try { exploder.explode(currentValue); } catch (error) { - console.error('[ExplosionManager] ERROR in explode():', error); + log.error('[ExplosionManager] ERROR in explode():', error); } // Animate debris size to zero (1.0 to 0.0) @@ -301,7 +301,7 @@ export class ExplosionManager { // Log every 15 frames (approximately every 250ms at 60fps) if (frameCount % 15 === 0 || frameCount === 1) { - debugLog(`[ExplosionManager] Animation frame ${frameCount}:`, { + log.debug(`[ExplosionManager] Animation frame ${frameCount}:`, { elapsed: `${elapsed}ms`, progress: progress.toFixed(3), currentValue: currentValue.toFixed(2), @@ -313,16 +313,16 @@ export class ExplosionManager { // Continue animation if not complete if (progress >= 1.0) { // Animation complete - remove observer and clean up - debugLog(`[ExplosionManager] Animation complete after ${frameCount} frames, cleaning up`); + log.debug(`[ExplosionManager] Animation complete after ${frameCount} frames, cleaning up`); this.scene.onBeforeRenderObservable.remove(animationObserver); this.cleanupExplosion(meshPieces); } }); // Log that animation loop is registered - debugLog('[ExplosionManager] Starting animation loop...'); + log.debug('[ExplosionManager] Starting animation loop...'); } catch (error) { - console.error('[ExplosionManager] ERROR creating MeshExploder:', error); + log.error('[ExplosionManager] ERROR creating MeshExploder:', error); // Clean up pieces if exploder failed meshPieces.forEach(piece => { if (piece && !piece.isDisposed()) { @@ -336,7 +336,7 @@ export class ExplosionManager { * Clean up explosion meshes */ private cleanupExplosion(meshPieces: InstancedMesh[]): void { - debugLog('[ExplosionManager] Starting cleanup of explosion meshes...'); + log.debug('[ExplosionManager] Starting cleanup of explosion meshes...'); let disposedCount = 0; // Dispose all the mesh pieces @@ -346,12 +346,12 @@ export class ExplosionManager { mesh.dispose(); disposedCount++; } catch (error) { - console.error(`[ExplosionManager] ERROR disposing piece ${index}:`, error); + log.error(`[ExplosionManager] ERROR disposing piece ${index}:`, error); } } }); - debugLog(`[ExplosionManager] Cleanup complete - disposed ${disposedCount}/${meshPieces.length} pieces`); + log.debug(`[ExplosionManager] Cleanup complete - disposed ${disposedCount}/${meshPieces.length} pieces`); } /** @@ -360,6 +360,6 @@ export class ExplosionManager { public dispose(): void { this._debrisBaseMesh.dispose(false, true); // Nothing to dispose with MeshExploder approach - debugLog("ExplosionManager disposed"); + log.debug("ExplosionManager disposed"); } } diff --git a/src/environment/asteroids/rockFactory.ts b/src/environment/asteroids/rockFactory.ts index bad9de9..1ada1a7 100644 --- a/src/environment/asteroids/rockFactory.ts +++ b/src/environment/asteroids/rockFactory.ts @@ -18,7 +18,7 @@ import {DefaultScene} from "../../core/defaultScene"; import {ScoreEvent} from "../../ui/hud/scoreboard"; import {GameConfig} from "../../core/gameConfig"; import {ExplosionManager} from "./explosionManager"; -import debugLog from '../../core/debug'; +import log from '../../core/logger'; import loadAsset from "../../utils/loadAsset"; export class Rock { @@ -66,7 +66,7 @@ export class RockFactory { * Reset static state - call during game cleanup */ public static reset(): void { - debugLog('[RockFactory] Resetting static state'); + log.debug('[RockFactory] Resetting static state'); this._asteroidMesh = null; if (this._explosionManager) { this._explosionManager.dispose(); @@ -83,20 +83,20 @@ export class RockFactory { * Call this AFTER audio engine is unlocked */ public static async initAudio(audioEngine: AudioEngineV2) { - debugLog('[RockFactory] Initializing audio via ExplosionManager'); + log.debug('[RockFactory] Initializing audio via ExplosionManager'); if (this._explosionManager) { await this._explosionManager.initAudio(audioEngine); } - debugLog('[RockFactory] Audio initialization complete'); + log.debug('[RockFactory] Audio initialization complete'); } private static async loadMesh() { - debugLog('loading mesh'); + log.debug('loading mesh'); const asset = await loadAsset("asteroid.glb"); this._asteroidMesh = asset.meshes.get('Asteroid') || null; if (this._asteroidMesh) { this._asteroidMesh.setEnabled(false); } - debugLog(this._asteroidMesh); + log.debug(this._asteroidMesh); } public static async createRock(i: number, position: Vector3, scale: number, @@ -108,7 +108,7 @@ export class RockFactory { } const rock = new InstancedMesh("asteroid-" +i, this._asteroidMesh as Mesh); - debugLog(rock.id); + log.debug(rock.id); rock.scaling = new Vector3(scale, scale, scale); rock.position = position; //rock.material = this._rockMaterial; @@ -135,11 +135,11 @@ export class RockFactory { // Only apply orbit constraint if enabled for this level and orbit center exists if (useOrbitConstraint && this._orbitCenter) { - debugLog(`[RockFactory] Applying orbit constraint for ${rock.name}`); + log.debug(`[RockFactory] Applying orbit constraint for ${rock.name}`); const constraint = new DistanceConstraint(Vector3.Distance(position, this._orbitCenter.body.transformNode.position), DefaultScene.MainScene); body.addConstraint(this._orbitCenter.body, constraint); } else { - debugLog(`[RockFactory] Orbit constraint disabled for ${rock.name} - asteroid will move freely`); + log.debug(`[RockFactory] Orbit constraint disabled for ${rock.name} - asteroid will move freely`); } body.setLinearDamping(0) @@ -152,9 +152,9 @@ export class RockFactory { physicsPlugin.setActivationControl(body, PhysicsActivationControl.ALWAYS_ACTIVE); } - debugLog(`[RockFactory] Setting velocities for ${rock.name}:`); - debugLog(`[RockFactory] Linear velocity input: ${linearVelocitry.toString()}`); - debugLog(`[RockFactory] Angular velocity input: ${angularVelocity.toString()}`); + log.debug(`[RockFactory] Setting velocities for ${rock.name}:`); + log.debug(`[RockFactory] Linear velocity input: ${linearVelocitry.toString()}`); + log.debug(`[RockFactory] Angular velocity input: ${angularVelocity.toString()}`); body.setLinearVelocity(linearVelocitry); body.setAngularVelocity(angularVelocity); @@ -162,24 +162,24 @@ export class RockFactory { // Verify velocities were set const setLinear = body.getLinearVelocity(); const setAngular = body.getAngularVelocity(); - debugLog(`[RockFactory] Linear velocity after set: ${setLinear.toString()}`); - debugLog(`[RockFactory] Angular velocity after set: ${setAngular.toString()}`); + log.debug(`[RockFactory] Linear velocity after set: ${setLinear.toString()}`); + log.debug(`[RockFactory] Angular velocity after set: ${setAngular.toString()}`); body.getCollisionObservable().add((eventData) => { if (eventData.type == 'COLLISION_STARTED') { if ( eventData.collidedAgainst.transformNode.id == 'ammo') { - debugLog('[RockFactory] ASTEROID HIT! Triggering explosion...'); + log.debug('[RockFactory] ASTEROID HIT! Triggering explosion...'); score.notifyObservers({score: 1, remaining: -1, message: "Asteroid Destroyed"}); // Get the asteroid mesh before disposing const asteroidMesh = eventData.collider.transformNode as AbstractMesh; - debugLog('[RockFactory] Asteroid mesh to explode:', { + log.debug('[RockFactory] Asteroid mesh to explode:', { name: asteroidMesh.name, id: asteroidMesh.id, position: asteroidMesh.getAbsolutePosition().toString() }); // Dispose asteroid physics objects BEFORE explosion (to prevent double-disposal) - debugLog('[RockFactory] Disposing asteroid physics objects...'); + log.debug('[RockFactory] Disposing asteroid physics objects...'); if (eventData.collider.shape) { eventData.collider.shape.dispose(); } @@ -194,7 +194,7 @@ export class RockFactory { } // Dispose projectile physics objects - debugLog('[RockFactory] Disposing projectile physics objects...'); + log.debug('[RockFactory] Disposing projectile physics objects...'); if (eventData.collidedAgainst.shape) { eventData.collidedAgainst.shape.dispose(); } @@ -204,7 +204,7 @@ export class RockFactory { if (eventData.collidedAgainst) { eventData.collidedAgainst.dispose(); } - debugLog('[RockFactory] Disposal complete'); + log.debug('[RockFactory] Disposal complete'); } } }); diff --git a/src/environment/background/backgroundStars.ts b/src/environment/background/backgroundStars.ts index e32e6ac..8b98d33 100644 --- a/src/environment/background/backgroundStars.ts +++ b/src/environment/background/backgroundStars.ts @@ -1,5 +1,5 @@ import {Color3, Color4, PointsCloudSystem, Scene, StandardMaterial, Vector3} from "@babylonjs/core"; -import debugLog from '../../core/debug'; +import log from '../../core/logger'; /** * Configuration options for background stars @@ -109,7 +109,7 @@ export class BackgroundStars { // Make stars always render behind everything else mesh.isPickable = false; - debugLog(`Created ${this.config.count} background stars`); + log.debug(`Created ${this.config.count} background stars`); } }); } diff --git a/src/environment/stations/starBase.ts b/src/environment/stations/starBase.ts index 6ef6121..f73c707 100644 --- a/src/environment/stations/starBase.ts +++ b/src/environment/stations/starBase.ts @@ -7,7 +7,7 @@ import { } from "@babylonjs/core"; import {DefaultScene} from "../../core/defaultScene"; import {GameConfig} from "../../core/gameConfig"; -import debugLog from "../../core/debug"; +import log from "../../core/logger"; import loadAsset from "../../utils/loadAsset"; import {Vector3Array} from "../../levels/config/levelConfig"; @@ -50,7 +50,7 @@ export default class StarBase { agg2.body.setMotionType(PhysicsMotionType.ANIMATED); agg2.body.getCollisionObservable().add((collidedBody) => { - debugLog('collidedBody', collidedBody); + log.debug('collidedBody', collidedBody); }) landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH); diff --git a/src/game/gameStats.ts b/src/game/gameStats.ts index 6f6b0fd..e3c0179 100644 --- a/src/game/gameStats.ts +++ b/src/game/gameStats.ts @@ -1,5 +1,5 @@ import { getAnalytics } from "../analytics"; -import debugLog from "../core/debug"; +import log from "../core/logger"; import { calculateScore, ScoreCalculation } from "./scoreCalculator"; /** @@ -64,7 +64,7 @@ export class GameStats { hullDamage: this._hullDamageTaken }, { sampleRate: 0.5 }); // 50% sampling for performance snapshots } catch (error) { - debugLog('Performance snapshot failed:', error); + log.debug('Performance snapshot failed:', error); } } @@ -159,7 +159,7 @@ export class GameStats { totalAsteroidsDestroyed: this._asteroidsDestroyed }, { immediate: true }); // Send immediately } catch (error) { - debugLog('Session end tracking failed:', error); + log.debug('Session end tracking failed:', error); } // Stop performance tracking diff --git a/src/game/progression.ts b/src/game/progression.ts index 76ce9ea..a450ea4 100644 --- a/src/game/progression.ts +++ b/src/game/progression.ts @@ -1,3 +1,5 @@ +import log from '../core/logger'; + /** * Progression tracking system for level completion and feature unlocks */ @@ -62,7 +64,7 @@ export class ProgressionManager { }; } } catch (error) { - console.error('Error loading progression data:', error); + log.error('Error loading progression data:', error); } // Return fresh progression data @@ -87,7 +89,7 @@ export class ProgressionManager { }; localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave)); } catch (error) { - console.error('Error saving progression data:', error); + log.error('Error saving progression data:', error); } } @@ -202,7 +204,7 @@ export class ProgressionManager { const completedCount = this.getCompletedDefaultLevels().length; if (completedCount >= EDITOR_UNLOCK_REQUIREMENT && !this._data.editorUnlocked) { this._data.editorUnlocked = true; - console.log(`🎉 Editor unlocked! (${completedCount} levels completed)`); + log.info(`🎉 Editor unlocked! (${completedCount} levels completed)`); } } diff --git a/src/levels/config/levelDeserializer.ts b/src/levels/config/levelDeserializer.ts index 5bcee9a..62a960a 100644 --- a/src/levels/config/levelDeserializer.ts +++ b/src/levels/config/levelDeserializer.ts @@ -19,7 +19,7 @@ import { } from "./levelConfig"; import { FireProceduralTexture } from "@babylonjs/procedural-textures"; import { createSphereLightmap } from "../../environment/celestial/sphereLightmap"; -import debugLog from '../../core/debug'; +import log from '../../core/logger'; import StarBase from "../../environment/stations/starBase"; import {LevelRegistry} from "../storage/levelRegistry"; @@ -61,7 +61,7 @@ export class LevelDeserializer { planets: AbstractMesh[]; asteroids: AbstractMesh[]; }> { - debugLog('Deserializing level:', this.config.difficulty); + log.debug('Deserializing level:', this.config.difficulty); const baseResult = await this.createStartBase(); const sun = this.createSun(); @@ -158,7 +158,7 @@ export class LevelDeserializer { planets.push(planet); } - debugLog(`Created ${planets.length} planets from config`); + log.debug(`Created ${planets.length} planets from config`); return planets; } @@ -173,15 +173,15 @@ export class LevelDeserializer { for (let i = 0; i < this.config.asteroids.length; i++) { const asteroidConfig = this.config.asteroids[i]; - debugLog(`[LevelDeserializer] Creating asteroid ${i} (${asteroidConfig.id}):`); - debugLog(`[LevelDeserializer] Position: [${asteroidConfig.position.join(', ')}]`); - debugLog(`[LevelDeserializer] Scale: ${asteroidConfig.scale}`); - debugLog(`[LevelDeserializer] Linear velocity: [${asteroidConfig.linearVelocity.join(', ')}]`); - debugLog(`[LevelDeserializer] Angular velocity: [${asteroidConfig.angularVelocity.join(', ')}]`); + log.debug(`[LevelDeserializer] Creating asteroid ${i} (${asteroidConfig.id}):`); + log.debug(`[LevelDeserializer] Position: [${asteroidConfig.position.join(', ')}]`); + log.debug(`[LevelDeserializer] Scale: ${asteroidConfig.scale}`); + log.debug(`[LevelDeserializer] Linear velocity: [${asteroidConfig.linearVelocity.join(', ')}]`); + log.debug(`[LevelDeserializer] Angular velocity: [${asteroidConfig.angularVelocity.join(', ')}]`); // Use orbit constraints by default (true if not specified) const useOrbitConstraints = this.config.useOrbitConstraints !== false; - debugLog(`[LevelDeserializer] Use orbit constraints: ${useOrbitConstraints}`); + log.debug(`[LevelDeserializer] Use orbit constraints: ${useOrbitConstraints}`); // Use RockFactory to create the asteroid const _rock = await RockFactory.createRock( @@ -202,7 +202,7 @@ export class LevelDeserializer { } } - debugLog(`Created ${asteroids.length} asteroids from config`); + log.debug(`Created ${asteroids.length} asteroids from config`); return asteroids; } diff --git a/src/levels/level1.ts b/src/levels/level1.ts index a6f4513..8f9e20f 100644 --- a/src/levels/level1.ts +++ b/src/levels/level1.ts @@ -14,7 +14,7 @@ import setLoadingMessage from "../utils/setLoadingMessage"; import {LevelConfig} from "./config/levelConfig"; import {LevelDeserializer} from "./config/levelDeserializer"; import {BackgroundStars} from "../environment/background/backgroundStars"; -import debugLog from '../core/debug'; +import log from '../core/logger'; import {getAnalytics} from "../analytics"; import {MissionBrief} from "../ui/hud/missionBrief"; import {LevelRegistry} from "./storage/levelRegistry"; @@ -52,18 +52,18 @@ export class Level1 implements Level { if (!isReplayMode && DefaultScene.XR) { const xr = DefaultScene.XR; - debugLog('Level1 constructor - Setting up XR observables'); - debugLog('XR input exists:', !!xr.input); - debugLog('onControllerAddedObservable exists:', !!xr.input?.onControllerAddedObservable); + log.debug('Level1 constructor - Setting up XR observables'); + log.debug('XR input exists:', !!xr.input); + log.debug('onControllerAddedObservable exists:', !!xr.input?.onControllerAddedObservable); xr.baseExperience.onInitialXRPoseSetObservable.add(() => { - debugLog('[Level1] onInitialXRPoseSetObservable fired'); + log.debug('[Level1] onInitialXRPoseSetObservable fired'); // Use consolidated XR camera setup this.setupXRCamera(); // Show mission brief after camera setup - debugLog('[Level1] Showing mission brief on XR entry'); + log.debug('[Level1] Showing mission brief on XR entry'); this.showMissionBrief(); }); } @@ -82,16 +82,16 @@ export class Level1 implements Level { public setupXRCamera(): void { const xr = DefaultScene.XR; if (!xr) { - debugLog('[Level1] setupXRCamera: No XR experience available'); + log.debug('[Level1] setupXRCamera: No XR experience available'); return; } if (!this._ship?.transformNode) { - console.error('[Level1] setupXRCamera: Ship or transformNode not available'); + log.error('[Level1] setupXRCamera: Ship or transformNode not available'); return; } - debugLog('[Level1] ========== setupXRCamera START =========='); + log.debug('[Level1] ========== setupXRCamera START =========='); // Create intermediate TransformNode for camera rotation // WebXR camera only uses rotationQuaternion (not .rotation), and XR frame updates overwrite it @@ -99,24 +99,24 @@ export class Level1 implements Level { const cameraRig = new TransformNode("xrCameraRig", DefaultScene.MainScene); cameraRig.parent = this._ship.transformNode; cameraRig.rotation = new Vector3(0, 0, 0); // Rotate 180° to face forward - debugLog('[Level1] Created cameraRig TransformNode, rotated 180°'); + log.debug('[Level1] Created cameraRig TransformNode, rotated 180°'); // Parent XR camera to the rig xr.baseExperience.camera.parent = cameraRig; xr.baseExperience.camera.position = new Vector3(0, .8, 0); - debugLog('[Level1] XR camera parented to cameraRig at position (0, 1.2, 0)'); + log.debug('[Level1] XR camera parented to cameraRig at position (0, 1.2, 0)'); // Ensure render loop is running const engine = DefaultScene.MainScene.getEngine(); engine.runRenderLoop(() => { DefaultScene.MainScene.render(); }); - debugLog('[Level1] Render loop started/resumed'); + log.debug('[Level1] Render loop started/resumed'); // Disable keyboard input in VR mode to prevent interference if (this._ship.keyboardInput) { this._ship.keyboardInput.setEnabled(false); - debugLog('[Level1] Keyboard input disabled for VR mode'); + log.debug('[Level1] Keyboard input disabled for VR mode'); } // Register pointer selection feature @@ -126,9 +126,9 @@ export class Level1 implements Level { if (pointerFeature) { const inputManager = InputControlManager.getInstance(); inputManager.registerPointerFeature(pointerFeature); - debugLog('[Level1] Pointer selection feature registered'); + log.debug('[Level1] Pointer selection feature registered'); } else { - debugLog('[Level1] WARNING: Pointer selection feature not available'); + log.debug('[Level1] WARNING: Pointer selection feature not available'); } // Track WebXR session start @@ -139,16 +139,16 @@ export class Level1 implements Level { isImmersive: true }); } catch (error) { - debugLog('[Level1] Analytics tracking failed:', error); + log.debug('[Level1] Analytics tracking failed:', error); } // Setup controller observer xr.input.onControllerAddedObservable.add((controller) => { - debugLog('[Level1] 🎮 Controller added:', controller.inputSource.handedness); + log.debug('[Level1] 🎮 Controller added:', controller.inputSource.handedness); this._ship.addController(controller); }); - debugLog('[Level1] ========== setupXRCamera COMPLETE =========='); + log.debug('[Level1] ========== setupXRCamera COMPLETE =========='); } /** @@ -158,12 +158,12 @@ export class Level1 implements Level { public async showMissionBrief(): Promise { // Prevent showing twice if (this._missionBriefShown) { - console.log('[Level1] Mission brief already shown, skipping'); + log.info('[Level1] Mission brief already shown, skipping'); return; } this._missionBriefShown = true; - console.log('[Level1] showMissionBrief() called'); + log.info('[Level1] showMissionBrief() called'); let directoryEntry: CloudLevelEntry | null = null; @@ -171,18 +171,18 @@ export class Level1 implements Level { if (this._levelId) { try { const registry = LevelRegistry.getInstance(); - console.log('[Level1] ======================================'); - console.log('[Level1] Getting all levels from registry...'); + log.info('[Level1] ======================================'); + log.info('[Level1] Getting all levels from registry...'); const allLevels = registry.getAllLevels(); - console.log('[Level1] Total levels in registry:', allLevels.size); - console.log('[Level1] Looking for level ID:', this._levelId); + log.info('[Level1] Total levels in registry:', allLevels.size); + log.info('[Level1] Looking for level ID:', this._levelId); const registryEntry = allLevels.get(this._levelId); - console.log('[Level1] Registry entry found:', !!registryEntry); + log.info('[Level1] Registry entry found:', !!registryEntry); if (registryEntry) { directoryEntry = registryEntry; - console.log('[Level1] Level entry data:', { + log.info('[Level1] Level entry data:', { id: directoryEntry?.id, slug: directoryEntry?.slug, name: directoryEntry?.name, @@ -193,39 +193,39 @@ export class Level1 implements Level { }); if (directoryEntry?.missionBrief) { - console.log('[Level1] Mission brief objectives:'); + log.info('[Level1] Mission brief objectives:'); directoryEntry.missionBrief.forEach((item, i) => { - console.log(` ${i + 1}. ${item}`); + log.info(` ${i + 1}. ${item}`); }); } else { - console.warn('[Level1] ⚠️ No missionBrief found in level entry!'); + log.warn('[Level1] ⚠️ No missionBrief found in level entry!'); } } else { - console.error('[Level1] ❌ No registry entry found for level ID:', this._levelId); - console.log('[Level1] Available level IDs:', Array.from(allLevels.keys())); + log.error('[Level1] ❌ No registry entry found for level ID:', this._levelId); + log.info('[Level1] Available level IDs:', Array.from(allLevels.keys())); } - console.log('[Level1] ======================================'); + log.info('[Level1] ======================================'); - debugLog('[Level1] Retrieved directory entry for level:', this._levelId, directoryEntry); + log.debug('[Level1] Retrieved directory entry for level:', this._levelId, directoryEntry); } catch (error) { - console.error('[Level1] ❌ Exception while getting directory entry:', error); - debugLog('[Level1] Failed to get directory entry:', error); + log.error('[Level1] ❌ Exception while getting directory entry:', error); + log.debug('[Level1] Failed to get directory entry:', error); } } else { - console.warn('[Level1] ⚠️ No level ID available, using config-only mission brief'); - debugLog('[Level1] No level ID available, using config-only mission brief'); + log.warn('[Level1] ⚠️ No level ID available, using config-only mission brief'); + log.debug('[Level1] No level ID available, using config-only mission brief'); } - console.log('[Level1] About to show mission brief. Has directoryEntry:', !!directoryEntry); + log.info('[Level1] About to show mission brief. Has directoryEntry:', !!directoryEntry); // Disable ship controls while mission brief is showing - debugLog('[Level1] Disabling ship controls for mission brief'); + log.debug('[Level1] Disabling ship controls for mission brief'); const inputManager = InputControlManager.getInstance(); inputManager.disableShipControls("MissionBrief"); // Show mission brief with trigger observable this._missionBrief.show(this._levelConfig, directoryEntry, this._ship.onMissionBriefTriggerObservable, () => { - debugLog('[Level1] Mission brief dismissed - enabling controls and starting game'); + log.debug('[Level1] Mission brief dismissed - enabling controls and starting game'); inputManager.enableShipControls("MissionBrief"); this.startGameplay(); }); @@ -237,19 +237,19 @@ export class Level1 implements Level { */ private startGameplay(): void { if (this._gameStarted) { - debugLog('[Level1] startGameplay called but game already started'); + log.debug('[Level1] startGameplay called but game already started'); return; } this._gameStarted = true; - debugLog('[Level1] Starting gameplay'); + log.debug('[Level1] Starting gameplay'); // Enable game end condition checking on ship this._ship.startGameplay(); // Start game timer this._ship.gameStats.startTimer(); - debugLog('Game timer started'); + log.debug('Game timer started'); } public async play() { @@ -266,55 +266,55 @@ export class Level1 implements Level { playCount: 1 // TODO: Get actual play count from progression system }); } catch (error) { - debugLog('Analytics tracking failed:', error); + log.debug('Analytics tracking failed:', error); } // Play background music (already loaded during initialization) if (this._backgroundMusic) { this._backgroundMusic.play(); - debugLog('Started playing background music'); + log.debug('Started playing background music'); } // If XR is available and session is active, mission brief will handle starting gameplay if (DefaultScene.XR && DefaultScene.XR.baseExperience.state === WebXRState.IN_XR) { // XR session already active, mission brief is showing or has been dismissed - debugLog('XR session already active, checking for controllers. Count:', DefaultScene.XR.input.controllers.length); + log.debug('XR session already active, checking for controllers. Count:', DefaultScene.XR.input.controllers.length); DefaultScene.XR.input.controllers.forEach((controller, index) => { - debugLog(`Controller ${index} - handedness: ${controller.inputSource.handedness}`); + log.debug(`Controller ${index} - handedness: ${controller.inputSource.handedness}`); this._ship.addController(controller); }); // Wait and check again after a delay (controllers might connect later) - debugLog('Waiting 2 seconds to check for controllers again...'); + log.debug('Waiting 2 seconds to check for controllers again...'); setTimeout(() => { - debugLog('After 2 second delay - controller count:', DefaultScene.XR.input.controllers.length); + log.debug('After 2 second delay - controller count:', DefaultScene.XR.input.controllers.length); DefaultScene.XR.input.controllers.forEach((controller, index) => { - debugLog(` Late controller ${index} - handedness: ${controller.inputSource.handedness}`); + log.debug(` Late controller ${index} - handedness: ${controller.inputSource.handedness}`); }); }, 2000); // Note: Mission brief will call startGameplay() when start button is clicked - debugLog('XR mode: Mission brief will control game start'); + log.debug('XR mode: Mission brief will control game start'); } else if (DefaultScene.XR) { // XR available but not entered yet, try to enter try { const _xr = await DefaultScene.XR.baseExperience.enterXRAsync('immersive-vr', 'local-floor'); - debugLog('Entered XR mode from play()'); + log.debug('Entered XR mode from play()'); // Check for controllers DefaultScene.XR.input.controllers.forEach((controller, index) => { - debugLog(`Controller ${index} - handedness: ${controller.inputSource.handedness}`); + log.debug(`Controller ${index} - handedness: ${controller.inputSource.handedness}`); this._ship.addController(controller); }); // Mission brief will show and handle starting gameplay - debugLog('XR mode entered: Mission brief will control game start'); + log.debug('XR mode entered: Mission brief will control game start'); } catch (error) { - debugLog('Failed to enter XR from play(), falling back to flat mode:', error); + log.debug('Failed to enter XR from play(), falling back to flat mode:', error); // Start flat mode immediately this.startGameplay(); } } else { // Flat camera mode - start game timer and physics recording immediately - debugLog('Playing in flat camera mode (no XR)'); + log.debug('Playing in flat camera mode (no XR)'); this.startGameplay(); } } @@ -341,9 +341,9 @@ export class Level1 implements Level { } public async initialize() { - debugLog('Initializing level from config:', this._levelConfig.difficulty); + log.debug('Initializing level from config:', this._levelConfig.difficulty); if (this._initialized) { - console.error('Initialize called twice'); + log.error('Initialize called twice'); return; } await this._ship.initialize(); @@ -380,7 +380,7 @@ export class Level1 implements Level { // Initialize scoreboard with total asteroid count this._ship.scoreboard.setRemainingCount(entities.asteroids.length); - debugLog(`Initialized scoreboard with ${entities.asteroids.length} asteroids`); + log.debug(`Initialized scoreboard with ${entities.asteroids.length} asteroids`); // Create background starfield setLoadingMessage("Creating starfield..."); @@ -407,7 +407,7 @@ export class Level1 implements Level { if (!this._isReplayMode) { setLoadingMessage("Initializing physics recorder..."); //this._physicsRecorder = new PhysicsRecorder(DefaultScene.MainScene, this._levelConfig); - debugLog('Physics recorder initialized (will start on XR pose)'); + log.debug('Physics recorder initialized (will start on XR pose)'); } // Load background music before marking as ready @@ -417,39 +417,39 @@ export class Level1 implements Level { loop: true, volume: 0.5 }); - debugLog('Background music loaded successfully'); + log.debug('Background music loaded successfully'); } // Initialize mission brief (will be shown when entering XR) setLoadingMessage("Initializing mission brief..."); - console.log('[Level1] ========== ABOUT TO INITIALIZE MISSION BRIEF =========='); - console.log('[Level1] _missionBrief object:', this._missionBrief); - console.log('[Level1] Ship exists:', !!this._ship); - console.log('[Level1] Ship ID in scene:', DefaultScene.MainScene.getNodeById('Ship') !== null); + log.info('[Level1] ========== ABOUT TO INITIALIZE MISSION BRIEF =========='); + log.info('[Level1] _missionBrief object:', this._missionBrief); + log.info('[Level1] Ship exists:', !!this._ship); + log.info('[Level1] Ship ID in scene:', DefaultScene.MainScene.getNodeById('Ship') !== null); this._missionBrief.initialize(); - console.log('[Level1] ========== MISSION BRIEF INITIALIZATION COMPLETE =========='); - debugLog('Mission brief initialized'); + log.info('[Level1] ========== MISSION BRIEF INITIALIZATION COMPLETE =========='); + log.debug('Mission brief initialized'); this._initialized = true; // Set par time and level info for score calculation and results recording const parTime = this.getParTimeForDifficulty(this._levelConfig.difficulty); const statusScreen = this._ship.statusScreen; - console.log('[Level1] StatusScreen reference:', statusScreen); - console.log('[Level1] Level config metadata:', this._levelConfig.metadata); - console.log('[Level1] Asteroids count:', entities.asteroids.length); + log.info('[Level1] StatusScreen reference:', statusScreen); + log.info('[Level1] Level config metadata:', this._levelConfig.metadata); + log.info('[Level1] Asteroids count:', entities.asteroids.length); if (statusScreen) { statusScreen.setParTime(parTime); - console.log(`[Level1] Set par time to ${parTime}s for difficulty: ${this._levelConfig.difficulty}`); + log.info(`[Level1] Set par time to ${parTime}s for difficulty: ${this._levelConfig.difficulty}`); // Set level info for game results recording const levelId = this._levelId || 'unknown'; const levelName = this._levelConfig.metadata?.description || 'Unknown Level'; - console.log('[Level1] About to call setCurrentLevel with:', { levelId, levelName, asteroidCount: entities.asteroids.length }); + log.info('[Level1] About to call setCurrentLevel with:', { levelId, levelName, asteroidCount: entities.asteroids.length }); statusScreen.setCurrentLevel(levelId, levelName, entities.asteroids.length); - console.log('[Level1] setCurrentLevel called successfully'); + log.info('[Level1] setCurrentLevel called successfully'); } else { - console.error('[Level1] StatusScreen is null/undefined!'); + log.error('[Level1] StatusScreen is null/undefined!'); } // Notify that initialization is complete diff --git a/src/levels/migration/legacyMigration.ts b/src/levels/migration/legacyMigration.ts index 739e3c4..eab1388 100644 --- a/src/levels/migration/legacyMigration.ts +++ b/src/levels/migration/legacyMigration.ts @@ -1,4 +1,5 @@ import {LevelConfig} from "../config/levelConfig"; +import log from "../../core/logger"; const LEGACY_STORAGE_KEY = 'space-game-levels'; const ARCHIVE_STORAGE_KEY = 'space-game-levels-archive'; @@ -64,7 +65,7 @@ export class LegacyMigration { } return status; } catch (error) { - console.error('Failed to parse migration status:', error); + log.error('Failed to parse migration status:', error); return null; } } @@ -127,10 +128,10 @@ export class LegacyMigration { result.success = true; - console.log('Migration completed:', result); + log.info('Migration completed:', result); } catch (error) { result.error = error instanceof Error ? error.message : 'Unknown error'; - console.error('Migration failed:', error); + log.error('Migration failed:', error); } return result; @@ -162,7 +163,7 @@ export class LegacyMigration { const archive = JSON.parse(stored); return archive.data || null; } catch (error) { - console.error('Failed to parse archived data:', error); + log.error('Failed to parse archived data:', error); return null; } } @@ -186,7 +187,7 @@ export class LegacyMigration { return JSON.stringify(exportData, null, 2); } catch (error) { - console.error('Failed to export legacy data:', error); + log.error('Failed to export legacy data:', error); return null; } } @@ -197,7 +198,7 @@ export class LegacyMigration { public static downloadLegacyData(): void { const jsonString = this.exportLegacyData(); if (!jsonString) { - console.warn('No legacy data to download'); + log.warn('No legacy data to download'); return; } @@ -224,7 +225,7 @@ export class LegacyMigration { */ public static resetMigration(): void { localStorage.removeItem(MIGRATION_STATUS_KEY); - console.log('Migration status reset'); + log.info('Migration status reset'); } /** @@ -235,7 +236,7 @@ export class LegacyMigration { localStorage.removeItem(ARCHIVE_STORAGE_KEY); localStorage.removeItem(CUSTOM_LEVELS_KEY); localStorage.removeItem(MIGRATION_STATUS_KEY); - console.log('Full migration reset completed'); + log.info('Full migration reset completed'); } /** diff --git a/src/levels/storage/levelRegistry.ts b/src/levels/storage/levelRegistry.ts index 20e4c11..3ff61cf 100644 --- a/src/levels/storage/levelRegistry.ts +++ b/src/levels/storage/levelRegistry.ts @@ -1,5 +1,6 @@ import { LevelConfig } from "../config/levelConfig"; import { CloudLevelService, CloudLevelEntry } from "../../services/cloudLevelService"; +import log from "../../core/logger"; /** * Singleton registry for managing levels from cloud (Supabase) @@ -36,7 +37,7 @@ export class LevelRegistry { } this.initialized = true; - console.log('[LevelRegistry] Loaded', this.levels.size, 'levels from cloud:', + log.info('[LevelRegistry] Loaded', this.levels.size, 'levels from cloud:', Array.from(this.levels.keys())); } @@ -74,6 +75,6 @@ export class LevelRegistry { public reset(): void { this.levels.clear(); this.initialized = false; - console.log('[LevelRegistry] Reset complete. Call initialize() to reload.'); + log.info('[LevelRegistry] Reset complete. Call initialize() to reload.'); } } diff --git a/src/main.ts b/src/main.ts index 41b79e4..387e25c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import '@babylonjs/loaders'; import { DefaultScene } from "./core/defaultScene"; import Level from "./levels/level"; -import debugLog from './core/debug'; +import log from './core/logger'; import { initializeAnalytics } from './analytics/initAnalytics'; import { createLevelSelectedHandler, LevelSelectedContext } from './core/handlers/levelSelectedHandler'; @@ -59,7 +59,7 @@ export class Main implements LevelSelectedContext, CleanupContext { public async initializeEngine(): Promise { if (this._initialized) return; - debugLog('[Main] Starting engine initialization'); + log.debug('[Main] Starting engine initialization'); this.reportProgress(0, 'Initializing 3D engine...'); const result = await setupScene(canvas, this); this._engine = result.engine; diff --git a/src/services/authService.ts b/src/services/authService.ts index 48f8bc6..a6d8f85 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -1,4 +1,5 @@ import { createAuth0Client, Auth0Client, User } from '@auth0/auth0-spa-js'; +import log from '../core/logger'; /** * Singleton service for managing Auth0 authentication @@ -26,22 +27,22 @@ export class AuthService { * Call this early in the application lifecycle */ public async initialize(): Promise { - console.log('[AuthService] ========== INITIALIZE CALLED =========='); + log.info('[AuthService] ========== INITIALIZE CALLED =========='); const domain = import.meta.env.VITE_AUTH0_DOMAIN; const clientId = import.meta.env.VITE_AUTH0_CLIENT_ID; - console.log('[AuthService] Config:', { + log.info('[AuthService] Config:', { domain, clientId: clientId ? clientId.substring(0, 10) + '...' : 'missing', redirectUri: window.location.origin }); if (!domain || !clientId || domain.trim() === '') { - console.warn('[AuthService] Auth0 not configured - authentication features will be disabled'); + log.warn('[AuthService] Auth0 not configured - authentication features will be disabled'); return; } - console.log('[AuthService] Creating Auth0 client...'); + log.info('[AuthService] Creating Auth0 client...'); const audience = import.meta.env.VITE_AUTH0_AUDIENCE; this._client = await createAuth0Client({ domain, @@ -53,58 +54,58 @@ export class AuthService { cacheLocation: 'localstorage', // Persist tokens across page reloads useRefreshTokens: true // Enable silent token refresh }); - console.log('[AuthService] Auth0 client created successfully'); + log.info('[AuthService] Auth0 client created successfully'); // Handle redirect callback after login const hasCallback = window.location.search.includes('code=') || window.location.search.includes('state='); - console.log('[AuthService] Checking for Auth0 callback:', hasCallback); - console.log('[AuthService] Current URL:', window.location.href); + log.info('[AuthService] Checking for Auth0 callback:', hasCallback); + log.info('[AuthService] Current URL:', window.location.href); if (hasCallback) { - console.log('[AuthService] ========== PROCESSING AUTH0 CALLBACK =========='); + log.info('[AuthService] ========== PROCESSING AUTH0 CALLBACK =========='); try { const result = await this._client.handleRedirectCallback(); - console.log('[AuthService] Callback handled successfully:', result); + log.info('[AuthService] Callback handled successfully:', result); // Clean up the URL after handling callback window.history.replaceState({}, document.title, '/'); - console.log('[AuthService] URL cleaned, redirected to home'); + log.info('[AuthService] URL cleaned, redirected to home'); } catch (error) { - console.error('[AuthService] !!!!! CALLBACK ERROR !!!!!', error); - console.error('[AuthService] Error details:', error?.message, error?.stack); + log.error('[AuthService] !!!!! CALLBACK ERROR !!!!!', error); + log.error('[AuthService] Error details:', error?.message, error?.stack); } } // Check if user is authenticated and load user info - console.log('[AuthService] Checking authentication status...'); + log.info('[AuthService] Checking authentication status...'); const isAuth = await this._client.isAuthenticated(); - console.log('[AuthService] Is authenticated:', isAuth); + log.info('[AuthService] Is authenticated:', isAuth); if (isAuth) { - console.log('[AuthService] Loading user info...'); + log.info('[AuthService] Loading user info...'); this._user = await this._client.getUser() ?? null; - console.log('[AuthService] User loaded:', { + log.info('[AuthService] User loaded:', { name: this._user?.name, email: this._user?.email, sub: this._user?.sub }); } else { - console.log('[AuthService] User not authenticated'); + log.info('[AuthService] User not authenticated'); } - console.log('[AuthService] ========== INITIALIZATION COMPLETE =========='); + log.info('[AuthService] ========== INITIALIZATION COMPLETE =========='); } /** * Redirect to Auth0 login page */ public async login(): Promise { - console.log('[AuthService] ========== LOGIN CALLED =========='); + log.info('[AuthService] ========== LOGIN CALLED =========='); if (!this._client) { - console.error('[AuthService] !!!!! CLIENT NOT INITIALIZED !!!!!'); + log.error('[AuthService] !!!!! CLIENT NOT INITIALIZED !!!!!'); throw new Error('Auth client not initialized. Call initialize() first.'); } - console.log('[AuthService] Redirecting to Auth0 login...'); + log.info('[AuthService] Redirecting to Auth0 login...'); await this._client.loginWithRedirect(); } @@ -112,13 +113,13 @@ export class AuthService { * Log out the current user and redirect to home */ public async logout(): Promise { - console.log('[AuthService] ========== LOGOUT CALLED =========='); + log.info('[AuthService] ========== LOGOUT CALLED =========='); if (!this._client) { - console.error('[AuthService] !!!!! CLIENT NOT INITIALIZED !!!!!'); + log.error('[AuthService] !!!!! CLIENT NOT INITIALIZED !!!!!'); throw new Error('Auth client not initialized. Call initialize() first.'); } this._user = null; - console.log('[AuthService] Logging out and redirecting to:', window.location.origin); + log.info('[AuthService] Logging out and redirecting to:', window.location.origin); await this._client.logout({ logoutParams: { returnTo: window.location.origin @@ -150,7 +151,7 @@ export class AuthService { try { return await this._client.getTokenSilently(); } catch (error) { - console.error('Error getting access token:', error); + log.error('Error getting access token:', error); return undefined; } } diff --git a/src/services/cloudLeaderboardService.ts b/src/services/cloudLeaderboardService.ts index b8cf26e..0188b1e 100644 --- a/src/services/cloudLeaderboardService.ts +++ b/src/services/cloudLeaderboardService.ts @@ -1,6 +1,7 @@ import { SupabaseService } from './supabaseService'; import { AuthService } from './authService'; import type { GameResult } from './gameResultsService'; +import log from '../core/logger'; /** * Represents a leaderboard entry from Supabase @@ -69,7 +70,7 @@ export class CloudLeaderboardService { const client = await supabase.getAuthenticatedClient(); if (!client) { - console.warn('[CloudLeaderboardService] Not authenticated - cannot sync user'); + log.warn('[CloudLeaderboardService] Not authenticated - cannot sync user'); return false; } @@ -84,11 +85,11 @@ export class CloudLeaderboardService { }); if (error) { - console.error('[CloudLeaderboardService] Failed to sync user:', error); + log.error('[CloudLeaderboardService] Failed to sync user:', error); return false; } - console.log('[CloudLeaderboardService] User synced:', userId); + log.info('[CloudLeaderboardService] User synced:', userId); return true; } @@ -100,7 +101,7 @@ export class CloudLeaderboardService { const supabase = SupabaseService.getInstance(); if (!supabase.isConfigured()) { - console.warn('[CloudLeaderboardService] Supabase not configured'); + log.warn('[CloudLeaderboardService] Supabase not configured'); return false; } @@ -109,16 +110,16 @@ export class CloudLeaderboardService { const user = authService.getUser(); if (!user?.sub) { - console.warn('[CloudLeaderboardService] No user sub claim - user not logged in'); + log.warn('[CloudLeaderboardService] No user sub claim - user not logged in'); return false; } - console.log('[CloudLeaderboardService] Submitting score for user:', user.sub); + log.info('[CloudLeaderboardService] Submitting score for user:', user.sub); // Get authenticated client for insert (requires RLS) const client = await supabase.getAuthenticatedClient(); if (!client) { - console.warn('[CloudLeaderboardService] Not authenticated - cannot submit score'); + log.warn('[CloudLeaderboardService] Not authenticated - cannot submit score'); return false; } @@ -141,7 +142,7 @@ export class CloudLeaderboardService { star_rating: result.starRating }; - console.log('[CloudLeaderboardService] Inserting entry:', entry); + log.info('[CloudLeaderboardService] Inserting entry:', entry); const { data, error } = await client .from('leaderboard') @@ -149,12 +150,12 @@ export class CloudLeaderboardService { .select(); if (error) { - console.error('[CloudLeaderboardService] Failed to submit score:', error); - console.error('[CloudLeaderboardService] Error details:', JSON.stringify(error, null, 2)); + log.error('[CloudLeaderboardService] Failed to submit score:', error); + log.error('[CloudLeaderboardService] Error details:', JSON.stringify(error, null, 2)); return false; } - console.log('[CloudLeaderboardService] Score submitted successfully:', data); + log.info('[CloudLeaderboardService] Score submitted successfully:', data); return true; } @@ -167,7 +168,7 @@ export class CloudLeaderboardService { const client = supabase.getClient(); if (!client) { - console.warn('[CloudLeaderboardService] Supabase not configured'); + log.warn('[CloudLeaderboardService] Supabase not configured'); return []; } @@ -178,7 +179,7 @@ export class CloudLeaderboardService { .range(offset, offset + limit - 1); if (error) { - console.error('[CloudLeaderboardService] Failed to fetch leaderboard:', error); + log.error('[CloudLeaderboardService] Failed to fetch leaderboard:', error); return []; } @@ -193,7 +194,7 @@ export class CloudLeaderboardService { const client = supabase.getClient(); if (!client) { - console.warn('[CloudLeaderboardService] Supabase not configured'); + log.warn('[CloudLeaderboardService] Supabase not configured'); return []; } @@ -205,7 +206,7 @@ export class CloudLeaderboardService { .limit(limit); if (error) { - console.error('[CloudLeaderboardService] Failed to fetch user scores:', error); + log.error('[CloudLeaderboardService] Failed to fetch user scores:', error); return []; } @@ -220,7 +221,7 @@ export class CloudLeaderboardService { const client = supabase.getClient(); if (!client) { - console.warn('[CloudLeaderboardService] Supabase not configured'); + log.warn('[CloudLeaderboardService] Supabase not configured'); return []; } @@ -232,7 +233,7 @@ export class CloudLeaderboardService { .limit(limit); if (error) { - console.error('[CloudLeaderboardService] Failed to fetch level leaderboard:', error); + log.error('[CloudLeaderboardService] Failed to fetch level leaderboard:', error); return []; } diff --git a/src/services/cloudLevelService.ts b/src/services/cloudLevelService.ts index 00416ec..c602720 100644 --- a/src/services/cloudLevelService.ts +++ b/src/services/cloudLevelService.ts @@ -1,6 +1,7 @@ import { SupabaseService } from './supabaseService'; import { AuthService } from './authService'; import type { LevelConfig } from '../levels/config/levelConfig'; +import log from '../core/logger'; /** * Level entry from the cloud database @@ -66,7 +67,7 @@ function rowToEntry(row: LevelRow): CloudLevelEntry { try { config = JSON.parse(row.config); } catch (e) { - console.error('[CloudLevelService] Failed to parse config string:', e); + log.error('[CloudLevelService] Failed to parse config string:', e); } } @@ -130,24 +131,24 @@ export class CloudLevelService { public async getOfficialLevels(): Promise { const client = SupabaseService.getInstance().getClient(); if (!client) { - console.warn('[CloudLevelService] Supabase not configured'); + log.warn('[CloudLevelService] Supabase not configured'); return []; } - console.log('[CloudLevelService] Fetching official levels...'); + log.info('[CloudLevelService] Fetching official levels...'); const { data, error } = await client .from('levels') .select('*') .eq('level_type', 'official') .order('sort_order', { ascending: true }); - console.log('[CloudLevelService] Query result - data:', data?.length, 'rows, error:', error); + log.info('[CloudLevelService] Query result - data:', data?.length, 'rows, error:', error); if (data) { - console.log('[CloudLevelService] Raw rows:', JSON.stringify(data, null, 2)); + log.info('[CloudLevelService] Raw rows:', JSON.stringify(data, null, 2)); } if (error) { - console.error('[CloudLevelService] Failed to fetch official levels:', error); + log.error('[CloudLevelService] Failed to fetch official levels:', error); return []; } @@ -160,7 +161,7 @@ export class CloudLevelService { public async getPublishedLevels(limit: number = 20, offset: number = 0): Promise { const client = SupabaseService.getInstance().getClient(); if (!client) { - console.warn('[CloudLevelService] Supabase not configured'); + log.warn('[CloudLevelService] Supabase not configured'); return []; } @@ -172,7 +173,7 @@ export class CloudLevelService { .range(offset, offset + limit - 1); if (error) { - console.error('[CloudLevelService] Failed to fetch published levels:', error); + log.error('[CloudLevelService] Failed to fetch published levels:', error); return []; } @@ -186,14 +187,14 @@ export class CloudLevelService { const supabaseService = SupabaseService.getInstance(); const client = await supabaseService.getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return []; } // Get internal user ID (UUID) const internalUserId = await supabaseService.ensureUserExists(); if (!internalUserId) { - console.warn('[CloudLevelService] No internal user ID available'); + log.warn('[CloudLevelService] No internal user ID available'); return []; } @@ -204,7 +205,7 @@ export class CloudLevelService { .order('updated_at', { ascending: false }); if (error) { - console.error('[CloudLevelService] Failed to fetch user levels:', error); + log.error('[CloudLevelService] Failed to fetch user levels:', error); return []; } @@ -217,7 +218,7 @@ export class CloudLevelService { public async getLevelById(id: string): Promise { const client = SupabaseService.getInstance().getClient(); if (!client) { - console.warn('[CloudLevelService] Supabase not configured'); + log.warn('[CloudLevelService] Supabase not configured'); return null; } @@ -229,7 +230,7 @@ export class CloudLevelService { if (error) { if (error.code !== 'PGRST116') { // Not found is not an error - console.error('[CloudLevelService] Failed to fetch level:', error); + log.error('[CloudLevelService] Failed to fetch level:', error); } return null; } @@ -243,7 +244,7 @@ export class CloudLevelService { public async getLevelBySlug(slug: string): Promise { const client = SupabaseService.getInstance().getClient(); if (!client) { - console.warn('[CloudLevelService] Supabase not configured'); + log.warn('[CloudLevelService] Supabase not configured'); return null; } @@ -255,7 +256,7 @@ export class CloudLevelService { if (error) { if (error.code !== 'PGRST116') { - console.error('[CloudLevelService] Failed to fetch level by slug:', error); + log.error('[CloudLevelService] Failed to fetch level by slug:', error); } return null; } @@ -279,7 +280,7 @@ export class CloudLevelService { }); if (error) { - console.error('[CloudLevelService] Failed to check slug availability:', error); + log.error('[CloudLevelService] Failed to check slug availability:', error); return false; } @@ -307,14 +308,14 @@ export class CloudLevelService { const supabaseService = SupabaseService.getInstance(); const client = await supabaseService.getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return null; } // Get internal user ID (UUID) const internalUserId = await supabaseService.ensureUserExists(); if (!internalUserId) { - console.warn('[CloudLevelService] No internal user ID available'); + log.warn('[CloudLevelService] No internal user ID available'); return null; } @@ -335,7 +336,7 @@ export class CloudLevelService { .single(); if (error) { - console.error('[CloudLevelService] Failed to create level:', error); + log.error('[CloudLevelService] Failed to create level:', error); return null; } @@ -359,7 +360,7 @@ export class CloudLevelService { ): Promise { const client = await SupabaseService.getInstance().getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return null; } @@ -380,7 +381,7 @@ export class CloudLevelService { .single(); if (error) { - console.error('[CloudLevelService] Failed to update level:', error); + log.error('[CloudLevelService] Failed to update level:', error); return null; } @@ -393,7 +394,7 @@ export class CloudLevelService { public async deleteLevel(id: string): Promise { const client = await SupabaseService.getInstance().getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return false; } @@ -403,7 +404,7 @@ export class CloudLevelService { .eq('id', id); if (error) { - console.error('[CloudLevelService] Failed to delete level:', error); + log.error('[CloudLevelService] Failed to delete level:', error); return false; } @@ -420,7 +421,7 @@ export class CloudLevelService { public async submitForReview(id: string): Promise { const client = await SupabaseService.getInstance().getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return false; } @@ -429,7 +430,7 @@ export class CloudLevelService { }); if (error) { - console.error('[CloudLevelService] Failed to submit for review:', error); + log.error('[CloudLevelService] Failed to submit for review:', error); return false; } @@ -442,7 +443,7 @@ export class CloudLevelService { public async withdrawSubmission(id: string): Promise { const client = await SupabaseService.getInstance().getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return false; } @@ -453,7 +454,7 @@ export class CloudLevelService { .eq('level_type', 'pending_review'); if (error) { - console.error('[CloudLevelService] Failed to withdraw submission:', error); + log.error('[CloudLevelService] Failed to withdraw submission:', error); return false; } @@ -470,7 +471,7 @@ export class CloudLevelService { public async getPendingReviews(): Promise { const client = await SupabaseService.getInstance().getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return []; } @@ -481,7 +482,7 @@ export class CloudLevelService { .order('submitted_at', { ascending: true }); if (error) { - console.error('[CloudLevelService] Failed to fetch pending reviews:', error); + log.error('[CloudLevelService] Failed to fetch pending reviews:', error); return []; } @@ -494,7 +495,7 @@ export class CloudLevelService { public async approveLevel(id: string, notes?: string): Promise { const client = await SupabaseService.getInstance().getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return false; } @@ -504,7 +505,7 @@ export class CloudLevelService { }); if (error) { - console.error('[CloudLevelService] Failed to approve level:', error); + log.error('[CloudLevelService] Failed to approve level:', error); return false; } @@ -517,7 +518,7 @@ export class CloudLevelService { public async rejectLevel(id: string, notes: string): Promise { const client = await SupabaseService.getInstance().getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return false; } @@ -527,7 +528,7 @@ export class CloudLevelService { }); if (error) { - console.error('[CloudLevelService] Failed to reject level:', error); + log.error('[CloudLevelService] Failed to reject level:', error); return false; } @@ -550,7 +551,7 @@ export class CloudLevelService { }); if (error) { - console.error('[CloudLevelService] Failed to increment play count:', error); + log.error('[CloudLevelService] Failed to increment play count:', error); } } @@ -566,7 +567,7 @@ export class CloudLevelService { }); if (error) { - console.error('[CloudLevelService] Failed to increment completion count:', error); + log.error('[CloudLevelService] Failed to increment completion count:', error); } } @@ -575,21 +576,21 @@ export class CloudLevelService { */ public async rateLevel(levelId: string, rating: number): Promise { if (rating < 1 || rating > 5) { - console.error('[CloudLevelService] Rating must be between 1 and 5'); + log.error('[CloudLevelService] Rating must be between 1 and 5'); return false; } const supabaseService = SupabaseService.getInstance(); const client = await supabaseService.getAuthenticatedClient(); if (!client) { - console.warn('[CloudLevelService] Not authenticated'); + log.warn('[CloudLevelService] Not authenticated'); return false; } // Get internal user ID (UUID) const internalUserId = await supabaseService.ensureUserExists(); if (!internalUserId) { - console.warn('[CloudLevelService] No internal user ID available'); + log.warn('[CloudLevelService] No internal user ID available'); return false; } @@ -604,7 +605,7 @@ export class CloudLevelService { }); if (error) { - console.error('[CloudLevelService] Failed to rate level:', error); + log.error('[CloudLevelService] Failed to rate level:', error); return false; } diff --git a/src/services/facebookShare.ts b/src/services/facebookShare.ts index 2b0e9a4..1ecdc91 100644 --- a/src/services/facebookShare.ts +++ b/src/services/facebookShare.ts @@ -1,3 +1,5 @@ +import log from '../core/logger'; + /** * Facebook Share Integration * Handles sharing game results to Facebook when user is authenticated via Facebook @@ -37,7 +39,7 @@ export class FacebookShare { } if (!this._appId) { - console.warn('Facebook App ID not configured'); + log.warn('Facebook App ID not configured'); return false; } @@ -82,7 +84,7 @@ export class FacebookShare { }; script.onerror = () => { - console.error('Failed to load Facebook SDK'); + log.error('Failed to load Facebook SDK'); resolve(false); }; @@ -103,13 +105,13 @@ export class FacebookShare { */ public async shareResults(shareData: ShareData): Promise { if (!this._fbInitialized) { - console.warn('Facebook SDK not initialized'); + log.warn('Facebook SDK not initialized'); return false; } const FB = (window as any).FB; if (!FB) { - console.error('Facebook SDK not available'); + log.error('Facebook SDK not available'); return false; } @@ -126,10 +128,10 @@ export class FacebookShare { hashtag: '#SpaceCombatVR' }, (response: any) => { if (response && !response.error_message) { - console.log('Successfully shared to Facebook'); + log.info('Successfully shared to Facebook'); resolve(true); } else { - console.error('Error sharing to Facebook:', response?.error_message || 'Unknown error'); + log.error('Error sharing to Facebook:', response?.error_message || 'Unknown error'); resolve(false); } }); @@ -142,7 +144,7 @@ export class FacebookShare { */ public async shareWithWebAPI(shareData: ShareData): Promise { if (!navigator.share) { - console.warn('Web Share API not supported'); + log.warn('Web Share API not supported'); return false; } @@ -158,7 +160,7 @@ export class FacebookShare { return true; } catch (error) { // User cancelled or error occurred - console.log('Share cancelled or failed:', error); + log.info('Share cancelled or failed:', error); return false; } } @@ -200,7 +202,7 @@ export class FacebookShare { await navigator.clipboard.writeText(message); return true; } catch (error) { - console.error('Failed to copy to clipboard:', error); + log.error('Failed to copy to clipboard:', error); return false; } } diff --git a/src/services/gameResultsService.ts b/src/services/gameResultsService.ts index 9cbf04b..6a68470 100644 --- a/src/services/gameResultsService.ts +++ b/src/services/gameResultsService.ts @@ -1,7 +1,7 @@ import { AuthService } from './authService'; import { CloudLeaderboardService } from './cloudLeaderboardService'; import { GameStats } from '../game/gameStats'; -import debugLog from '../core/debug'; +import log from '../core/logger'; /** * Represents a completed game session result @@ -53,13 +53,12 @@ export class GameResultsService { * Save a game result to storage (local + cloud) */ public saveResult(result: GameResult): void { - console.log('[GameResultsService] saveResult called with:', result); + log.info('[GameResultsService] saveResult called with:', result); const results = this.getAllResults(); - console.log('[GameResultsService] Existing results count:', results.length); + log.info('[GameResultsService] Existing results count:', results.length); results.push(result); this.saveToStorage(results); - console.log('[GameResultsService] Saved result:', result.id, result.finalScore); - debugLog('[GameResultsService] Saved result:', result.id, result.finalScore); + log.debug('[GameResultsService] Saved result:', result.id, result.finalScore); // Submit to cloud leaderboard (non-blocking) this.submitToCloud(result); @@ -74,14 +73,14 @@ export class GameResultsService { if (cloudService.isAvailable()) { const success = await cloudService.submitScore(result); if (success) { - console.log('[GameResultsService] Cloud submission successful'); + log.info('[GameResultsService] Cloud submission successful'); } else { - console.log('[GameResultsService] Cloud submission skipped (not authenticated or failed)'); + log.info('[GameResultsService] Cloud submission skipped (not authenticated or failed)'); } } } catch (error) { // Don't let cloud failures affect local save - console.warn('[GameResultsService] Cloud submission error:', error); + log.warn('[GameResultsService] Cloud submission error:', error); } } @@ -96,7 +95,7 @@ export class GameResultsService { } return JSON.parse(data) as GameResult[]; } catch (error) { - debugLog('[GameResultsService] Error loading results:', error); + log.debug('[GameResultsService] Error loading results:', error); return []; } } @@ -116,7 +115,7 @@ export class GameResultsService { */ public clearAll(): void { localStorage.removeItem(STORAGE_KEY); - debugLog('[GameResultsService] Cleared all results'); + log.debug('[GameResultsService] Cleared all results'); } /** @@ -125,15 +124,14 @@ export class GameResultsService { private saveToStorage(results: GameResult[]): void { try { const json = JSON.stringify(results); - console.log('[GameResultsService] Saving to localStorage, key:', STORAGE_KEY, 'size:', json.length); + log.info('[GameResultsService] Saving to localStorage, key:', STORAGE_KEY, 'size:', json.length); localStorage.setItem(STORAGE_KEY, json); - console.log('[GameResultsService] Successfully saved to localStorage'); + log.info('[GameResultsService] Successfully saved to localStorage'); // Verify it was saved const verify = localStorage.getItem(STORAGE_KEY); - console.log('[GameResultsService] Verification - stored data exists:', !!verify); + log.info('[GameResultsService] Verification - stored data exists:', !!verify); } catch (error) { - console.error('[GameResultsService] Error saving results:', error); - debugLog('[GameResultsService] Error saving results:', error); + log.error('[GameResultsService] Error saving results:', error); } } diff --git a/src/services/supabaseService.ts b/src/services/supabaseService.ts index 28750cb..dee6491 100644 --- a/src/services/supabaseService.ts +++ b/src/services/supabaseService.ts @@ -1,5 +1,6 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js'; import { AuthService } from './authService'; +import log from '../core/logger'; const SUPABASE_URL = import.meta.env.VITE_SUPABASE_PROJECT; const SUPABASE_ANON_KEY = import.meta.env.VITE_SUPABASE_KEY; @@ -17,7 +18,7 @@ export class SupabaseService { if (SUPABASE_URL && SUPABASE_ANON_KEY) { this._client = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); } else { - console.warn('[SupabaseService] Supabase not configured - cloud features disabled'); + log.warn('[SupabaseService] Supabase not configured - cloud features disabled'); } } @@ -51,7 +52,7 @@ export class SupabaseService { */ public async getAuthenticatedClient(): Promise { if (!SUPABASE_URL || !SUPABASE_ANON_KEY) { - console.warn('[SupabaseService] Missing Supabase URL or key'); + log.warn('[SupabaseService] Missing Supabase URL or key'); return null; } @@ -59,16 +60,16 @@ export class SupabaseService { const token = await authService.getAccessToken(); if (!token) { - console.warn('[SupabaseService] No auth token available'); + log.warn('[SupabaseService] No auth token available'); return null; } - console.log('[SupabaseService] Got Auth0 token, length:', token.length); + log.info('[SupabaseService] Got Auth0 token, length:', token.length); // Debug: decode JWT to see claims (without verification) try { const payload = JSON.parse(atob(token.split('.')[1])); - console.log('[SupabaseService] Token claims:', { + log.info('[SupabaseService] Token claims:', { iss: payload.iss, sub: payload.sub, aud: payload.aud, @@ -76,7 +77,7 @@ export class SupabaseService { role: payload.role }); } catch (_e) { - console.warn('[SupabaseService] Could not decode token'); + log.warn('[SupabaseService] Could not decode token'); } // Create a new client with the Auth0 token for RLS @@ -105,7 +106,7 @@ export class SupabaseService { const authService = AuthService.getInstance(); const user = authService.getUser(); if (!user?.sub) { - console.warn('[SupabaseService] No user sub available'); + log.warn('[SupabaseService] No user sub available'); return null; } @@ -132,7 +133,7 @@ export class SupabaseService { .single(); if (insertError) { - console.error('[SupabaseService] Failed to create user:', insertError); + log.error('[SupabaseService] Failed to create user:', insertError); return null; } @@ -140,7 +141,7 @@ export class SupabaseService { } if (fetchError) { - console.error('[SupabaseService] Failed to fetch user:', fetchError); + log.error('[SupabaseService] Failed to fetch user:', fetchError); } return null; diff --git a/src/ship/input/controllerInput.ts b/src/ship/input/controllerInput.ts index 2020963..49ab241 100644 --- a/src/ship/input/controllerInput.ts +++ b/src/ship/input/controllerInput.ts @@ -5,7 +5,7 @@ import { WebXRControllerComponent, WebXRInputSource, } from "@babylonjs/core"; -import debugLog from "../../core/debug"; +import log from "../../core/logger"; import { ControllerMappingConfig, StickAction } from "./controllerMapping"; const controllerComponents = [ @@ -159,17 +159,17 @@ export class ControllerInput { * Add a VR controller to the input system */ public addController(controller: WebXRInputSource): void { - debugLog( + log.debug( "ControllerInput.addController called for:", controller.inputSource.handedness ); if (controller.inputSource.handedness === "left") { - debugLog("Adding left controller"); + log.debug("Adding left controller"); this._leftInputSource = controller; this._leftInputSource.onMotionControllerInitObservable.add( (motionController) => { - debugLog( + log.debug( "Left motion controller initialized:", motionController.handness ); @@ -179,17 +179,17 @@ export class ControllerInput { // Check if motion controller is already initialized if (controller.motionController) { - debugLog("Left motion controller already initialized, mapping now"); + log.debug("Left motion controller already initialized, mapping now"); this.mapMotionController(controller.motionController); } } if (controller.inputSource.handedness === "right") { - debugLog("Adding right controller"); + log.debug("Adding right controller"); this._rightInputSource = controller; this._rightInputSource.onMotionControllerInitObservable.add( (motionController) => { - debugLog( + log.debug( "Right motion controller initialized:", motionController.handness ); @@ -199,7 +199,7 @@ export class ControllerInput { // Check if motion controller is already initialized if (controller.motionController) { - debugLog("Right motion controller already initialized, mapping now"); + log.debug("Right motion controller already initialized, mapping now"); this.mapMotionController(controller.motionController); } } @@ -211,7 +211,7 @@ export class ControllerInput { private mapMotionController( controller: WebXRAbstractMotionController ): void { - debugLog( + log.debug( "Mapping motion controller:", controller.handness, "Profile:", @@ -222,13 +222,13 @@ export class ControllerInput { const comp = controller.components[component]; if (!comp) { - debugLog( + log.debug( ` Component ${component} not found on ${controller.handness} controller` ); return; } - debugLog( + log.debug( ` Found component ${component} on ${controller.handness} controller` ); const observable = this._controllerObservable; @@ -329,7 +329,7 @@ export class ControllerInput { this._onStatusScreenToggleObservable.notifyObservers(); } } - console.log(controllerEvent); + log.info(controllerEvent); } } } diff --git a/src/ship/input/controllerMapping.ts b/src/ship/input/controllerMapping.ts index fff5b0d..354e5ca 100644 --- a/src/ship/input/controllerMapping.ts +++ b/src/ship/input/controllerMapping.ts @@ -1,4 +1,4 @@ -import debugLog from '../../core/debug'; +import log from '../../core/logger'; const STORAGE_KEY = 'space-game-controller-mapping'; @@ -107,7 +107,7 @@ export class ControllerMappingConfig { */ public setMapping(mapping: ControllerMapping): void { this._mapping = { ...mapping }; - debugLog('[ControllerMapping] Configuration updated:', this._mapping); + log.debug('[ControllerMapping] Configuration updated:', this._mapping); } /** @@ -115,7 +115,7 @@ export class ControllerMappingConfig { */ public resetToDefault(): void { this._mapping = { ...ControllerMappingConfig.DEFAULT_MAPPING }; - debugLog('[ControllerMapping] Reset to default configuration'); + log.debug('[ControllerMapping] Reset to default configuration'); } /** @@ -125,9 +125,9 @@ export class ControllerMappingConfig { try { const json = JSON.stringify(this._mapping); localStorage.setItem(STORAGE_KEY, json); - debugLog('[ControllerMapping] Saved to localStorage'); + log.debug('[ControllerMapping] Saved to localStorage'); } catch (error) { - console.error('[ControllerMapping] Failed to save to localStorage:', error); + log.error('[ControllerMapping] Failed to save to localStorage:', error); } } @@ -146,12 +146,12 @@ export class ControllerMappingConfig { ...parsed, }; - debugLog('[ControllerMapping] Loaded from localStorage:', this._mapping); + log.debug('[ControllerMapping] Loaded from localStorage:', this._mapping); } else { - debugLog('[ControllerMapping] No saved configuration, using defaults'); + log.debug('[ControllerMapping] No saved configuration, using defaults'); } } catch (error) { - console.warn('[ControllerMapping] Failed to load from localStorage, using defaults:', error); + log.warn('[ControllerMapping] Failed to load from localStorage, using defaults:', error); this._mapping = { ...ControllerMappingConfig.DEFAULT_MAPPING }; } } diff --git a/src/ship/input/inputControlManager.ts b/src/ship/input/inputControlManager.ts index 707736f..37070c6 100644 --- a/src/ship/input/inputControlManager.ts +++ b/src/ship/input/inputControlManager.ts @@ -1,7 +1,7 @@ import { Observable } from "@babylonjs/core"; import { KeyboardInput } from "./keyboardInput"; import { ControllerInput } from "./controllerInput"; -import debugLog from "../../core/debug"; +import log from "../../core/logger"; /** * State change event emitted when ship controls or pointer selection state changes @@ -42,7 +42,7 @@ export class InputControlManager { * Private constructor for singleton pattern */ private constructor() { - debugLog('[InputControlManager] Instance created'); + log.debug('[InputControlManager] Instance created'); } /** @@ -59,7 +59,7 @@ export class InputControlManager { * Register input systems (called by Ship during initialization) */ public registerInputSystems(keyboard: KeyboardInput | null, controller: ControllerInput | null): void { - debugLog('[InputControlManager] Registering input systems', { keyboard: !!keyboard, controller: !!controller }); + log.debug('[InputControlManager] Registering input systems', { keyboard: !!keyboard, controller: !!controller }); this._keyboardInput = keyboard; this._controllerInput = controller; } @@ -68,7 +68,7 @@ export class InputControlManager { * Register XR pointer feature (called by main.ts during XR setup) */ public registerPointerFeature(pointerFeature: any): void { - debugLog('[InputControlManager] Registering XR pointer feature'); + log.debug('[InputControlManager] Registering XR pointer feature'); this._xrPointerFeature = pointerFeature; // Apply current state to the newly registered pointer feature @@ -79,7 +79,7 @@ export class InputControlManager { * Enable ship controls, disable pointer selection */ public enableShipControls(requester: string): void { - debugLog(`[InputControlManager] Enabling ship controls (requester: ${requester})`); + log.debug(`[InputControlManager] Enabling ship controls (requester: ${requester})`); // Update state this._shipControlsEnabled = true; @@ -104,7 +104,7 @@ export class InputControlManager { * Disable ship controls, enable pointer selection */ public disableShipControls(requester: string): void { - debugLog(`[InputControlManager] Disabling ship controls (requester: ${requester})`); + log.debug(`[InputControlManager] Disabling ship controls (requester: ${requester})`); // Update state this._shipControlsEnabled = false; @@ -119,12 +119,12 @@ export class InputControlManager { } // Enable pointer selection - console.log(`[InputControlManager] About to update pointer feature...`); + log.info(`[InputControlManager] About to update pointer feature...`); this.updatePointerFeature(); // Emit state change event this.emitStateChange(requester); - console.log(`[InputControlManager] ===== Ship controls disabled =====`); + log.info(`[InputControlManager] ===== Ship controls disabled =====`); } /** @@ -139,14 +139,14 @@ export class InputControlManager { if (this._pointerSelectionEnabled) { // Enable pointer selection (attach feature) this._xrPointerFeature.attach(); - debugLog('[InputControlManager] Pointer selection enabled'); + log.debug('[InputControlManager] Pointer selection enabled'); } else { // Disable pointer selection (detach feature) this._xrPointerFeature.detach(); - debugLog('[InputControlManager] Pointer selection disabled'); + log.debug('[InputControlManager] Pointer selection disabled'); } } catch (error) { - console.warn('[InputControlManager] Failed to update pointer feature:', error); + log.warn('[InputControlManager] Failed to update pointer feature:', error); } } @@ -163,7 +163,7 @@ export class InputControlManager { this._onStateChangedObservable.notifyObservers(stateChange); - debugLog('[InputControlManager] State changed:', stateChange); + log.debug('[InputControlManager] State changed:', stateChange); } /** @@ -191,7 +191,7 @@ export class InputControlManager { * Cleanup (for testing or hot reload) */ public dispose(): void { - debugLog('[InputControlManager] Disposing'); + log.debug('[InputControlManager] Disposing'); this._onStateChangedObservable.clear(); this._keyboardInput = null; this._controllerInput = null; diff --git a/src/ship/ship.ts b/src/ship/ship.ts index b4eb6c7..baf1778 100644 --- a/src/ship/ship.ts +++ b/src/ship/ship.ts @@ -17,7 +17,7 @@ import type { AudioEngineV2 } from "@babylonjs/core"; import { DefaultScene } from "../core/defaultScene"; import { GameConfig } from "../core/gameConfig"; import { Sight } from "./sight"; -import debugLog from "../core/debug"; +import log from "../core/logger"; import { Scoreboard } from "../ui/hud/scoreboard"; import loadAsset from "../utils/loadAsset"; import { KeyboardInput } from "./input/keyboardInput"; @@ -107,7 +107,7 @@ export class Ship { */ public startGameplay(): void { this._gameplayStarted = true; - debugLog('[Ship] Gameplay started - game end conditions now active'); + log.debug('[Ship] Gameplay started - game end conditions now active'); } public get onMissionBriefTriggerObservable(): Observable { @@ -164,7 +164,7 @@ export class Ship { // Create physics if enabled const config = GameConfig.getInstance(); if (config.physicsEnabled) { - console.log("Physics Enabled for Ship"); + log.info("Physics Enabled for Ship"); if (this._ship) { const agg = new PhysicsAggregate( this._ship, @@ -184,9 +184,9 @@ export class Ship { // Debug: Log center of mass before override const massProps = agg.body.getMassProperties(); - console.log(`[Ship] Original center of mass (local): ${massProps.centerOfMass.toString()}`); - console.log(`[Ship] Mass: ${massProps.mass}`); - console.log(`[Ship] Inertia: ${massProps.inertia.toString()}`); + log.info(`[Ship] Original center of mass (local): ${massProps.centerOfMass.toString()}`); + log.info(`[Ship] Mass: ${massProps.mass}`); + log.info(`[Ship] Inertia: ${massProps.inertia.toString()}`); // Override center of mass to origin to prevent thrust from causing torque // (mesh-based physics was calculating offset center of mass from geometry) @@ -197,7 +197,7 @@ export class Ship { inertiaOrientation: massProps.inertiaOrientation }); - console.log(`[Ship] Center of mass overridden to: ${agg.body.getMassProperties().centerOfMass.toString()}`); + log.info(`[Ship] Center of mass overridden to: ${agg.body.getMassProperties().centerOfMass.toString()}`); // Configure physics sleep behavior from config // (disabling sleep prevents abrupt stops at zero linear velocity) @@ -243,7 +243,7 @@ export class Ship { // Apply damage if above minimum threshold if (this._scoreboard?.shipStatus && damage > 0.001) { this._scoreboard.shipStatus.damageHull(damage); - debugLog(`Collision damage: ${damage.toFixed(4)} (energy: ${kineticEnergy.toFixed(1)}, speed: ${relativeSpeed.toFixed(1)} m/s)`); + log.debug(`Collision damage: ${damage.toFixed(4)} (energy: ${kineticEnergy.toFixed(1)}, speed: ${relativeSpeed.toFixed(1)} m/s)`); // Play collision sound if (this._audio) { @@ -253,7 +253,7 @@ export class Ship { } }); } else { - console.warn("No geometry mesh found, cannot create physics"); + log.warn("No geometry mesh found, cannot create physics"); } } @@ -361,7 +361,7 @@ export class Ship { if (!DefaultScene.XR && !this._isReplayMode) { DefaultScene.MainScene.activeCamera = this._camera; //this._camera.attachControl(DefaultScene.MainScene.getEngine().getRenderingCanvas(), true); - debugLog('Flat camera set as active camera'); + log.debug('Flat camera set as active camera'); } // Create sight reticle @@ -394,7 +394,7 @@ export class Ship { }, { sampleRate: 0.2 }); // Sample 20% of asteroid events to reduce data } catch (error) { // Analytics not initialized or failed - don't break gameplay - debugLog('Analytics tracking failed:', error); + log.debug('Analytics tracking failed:', error); } }); @@ -415,7 +415,7 @@ export class Ship { source: 'asteroid_collision' // Default assumption }); } catch (error) { - debugLog('Analytics tracking failed:', error); + log.debug('Analytics tracking failed:', error); } } }); @@ -436,7 +436,7 @@ export class Ship { * Handle replay button click from status screen */ private handleReplayRequest(): void { - debugLog('Replay button clicked - notifying observers'); + log.debug('Replay button clicked - notifying observers'); this.onReplayRequestObservable.notifyObservers(); } @@ -444,7 +444,7 @@ export class Ship { * Handle exit VR button click from status screen */ private async handleExitVR(): Promise { - debugLog('Exit VR button clicked - navigating to home'); + log.debug('Exit VR button clicked - navigating to home'); try { // Ensure the app UI is visible before navigating (safety net) @@ -463,7 +463,7 @@ export class Ship { const { navigate } = await import('svelte-routing'); navigate('/', { replace: true }); } catch (error) { - console.error('Failed to navigate, falling back to reload:', error); + log.error('Failed to navigate, falling back to reload:', error); window.location.reload(); } } @@ -472,7 +472,7 @@ export class Ship { * Handle resume button click from status screen */ private handleResume(): void { - debugLog('Resume button clicked - hiding status screen'); + log.debug('Resume button clicked - hiding status screen'); // InputControlManager will handle re-enabling controls when status screen hides this._statusScreen.hide(); } @@ -481,7 +481,7 @@ export class Ship { * Handle next level button click from status screen */ private handleNextLevel(): void { - debugLog('Next Level button clicked - navigating to level selector'); + log.debug('Next Level button clicked - navigating to level selector'); // Navigate back to level selector (root route) window.location.hash = '#/'; window.location.reload(); @@ -521,7 +521,7 @@ export class Ship { // Check condition 1: Death by hull damage (outside landing zone) if (!this._isInLandingZone && hull < 0.01) { - debugLog('Game end condition met: Hull critical outside landing zone'); + log.debug('Game end condition met: Hull critical outside landing zone'); this._statusScreen.show(true, false, 'death'); // Game ended, not victory, death reason // InputControlManager will handle disabling controls when status screen shows this._statusScreenAutoShown = true; @@ -530,7 +530,7 @@ export class Ship { // Check condition 2: Stranded (outside landing zone, no fuel, low velocity) if (!this._isInLandingZone && fuel < 0.01 && totalVelocity < 5) { - debugLog('Game end condition met: Stranded (no fuel, low velocity)'); + log.debug('Game end condition met: Stranded (no fuel, low velocity)'); this._statusScreen.show(true, false, 'stranded'); // Game ended, not victory, stranded reason // InputControlManager will handle disabling controls when status screen shows this._statusScreenAutoShown = true; @@ -540,7 +540,7 @@ export class Ship { // Check condition 3: Victory (all asteroids destroyed, inside landing zone) // Must have had asteroids to destroy in the first place (prevents false victory on init) if (asteroidsRemaining <= 0 && this._isInLandingZone && this._scoreboard.hasAsteroidsToDestroy) { - debugLog('Game end condition met: Victory (all asteroids destroyed)'); + log.debug('Game end condition met: Victory (all asteroids destroyed)'); this._statusScreen.show(true, true, 'victory'); // Game ended, VICTORY! // InputControlManager will handle disabling controls when status screen shows this._statusScreenAutoShown = true; @@ -633,9 +633,9 @@ export class Ship { // Log zone transitions if (this._isInLandingZone && !wasInZone) { - debugLog("Ship entered landing zone - resupply active"); + log.debug("Ship entered landing zone - resupply active"); } else if (!this._isInLandingZone && wasInZone) { - debugLog("Ship exited landing zone - resupply inactive"); + log.debug("Ship exited landing zone - resupply inactive"); } // Resupply at 0.1 per second if in zone @@ -669,7 +669,7 @@ export class Ship { private handleShoot(): void { // If controls are disabled, fire mission brief trigger observable instead of shooting if (!this._controlsEnabled) { - debugLog('[Ship] Controls disabled - firing mission brief trigger observable'); + log.debug('[Ship] Controls disabled - firing mission brief trigger observable'); this._onMissionBriefTriggerObservable.notifyObservers(); return; } @@ -703,7 +703,7 @@ export class Ship { landingAggregate.body.getCollisionObservable().add((collisionEvent) => { // Check if the collision is with our ship if (collisionEvent.collider === this._ship.physicsBody) { - debugLog("Physics trigger fired for landing zone"); + log.debug("Physics trigger fired for landing zone"); } }); } @@ -712,7 +712,7 @@ export class Ship { * Add a VR controller to the input system */ public addController(controller: WebXRInputSource) { - debugLog( + log.debug( "Ship.addController called for:", controller.inputSource.handedness ); diff --git a/src/ship/voiceAudioSystem.ts b/src/ship/voiceAudioSystem.ts index e99dea0..0d4cc30 100644 --- a/src/ship/voiceAudioSystem.ts +++ b/src/ship/voiceAudioSystem.ts @@ -1,5 +1,5 @@ import { AudioEngineV2, StaticSound, SoundState } from "@babylonjs/core"; -import debugLog from "../core/debug"; +import log from "../core/logger"; import { ShipStatus, ShipStatusChangeEvent } from "./shipStatus"; /** @@ -66,7 +66,7 @@ export class VoiceAudioSystem { public async initialize(audioEngine: AudioEngineV2): Promise { this._audioEngine = audioEngine; - debugLog('VoiceAudioSystem: Loading voice clips...'); + log.debug('VoiceAudioSystem: Loading voice clips...'); // Load all voice files as non-spatial sounds for (const fileName of this.VOICE_FILES) { @@ -82,11 +82,11 @@ export class VoiceAudioSystem { ); this._sounds.set(fileName, sound); } catch (error) { - debugLog(`VoiceAudioSystem: Failed to load ${fileName}.mp3`, error); + log.debug(`VoiceAudioSystem: Failed to load ${fileName}.mp3`, error); } } - debugLog(`VoiceAudioSystem: Loaded ${this._sounds.size}/${this.VOICE_FILES.length} voice clips`); + log.debug(`VoiceAudioSystem: Loaded ${this._sounds.size}/${this.VOICE_FILES.length} voice clips`); } /** @@ -98,7 +98,7 @@ export class VoiceAudioSystem { this.handleStatusChange(event); }); - debugLog('VoiceAudioSystem: Subscribed to game events'); + log.debug('VoiceAudioSystem: Subscribed to game events'); } /** @@ -108,7 +108,7 @@ export class VoiceAudioSystem { const { statusType, newValue, delta } = event; const maxValue = 1; const percentage = maxValue > 0 ? newValue / maxValue : 0; - debugLog(event); + log.debug(event); // Clear warning states if resources increase above thresholds if (delta > 0) { @@ -126,7 +126,7 @@ export class VoiceAudioSystem { if (percentage < 0.2 && !this._warningStates.has(`danger_${statusType}`)) { - debugLog(`VoiceAudioSystem: DANGER warning triggered for ${statusType} (${(percentage * 100).toFixed(1)}%)`); + log.debug(`VoiceAudioSystem: DANGER warning triggered for ${statusType} (${(percentage * 100).toFixed(1)}%)`); this._warningStates.add(`danger_${statusType}`); // Clear warning state if it exists (danger supersedes warning) this.clearWarningState(`warning_${statusType}`); @@ -134,13 +134,13 @@ export class VoiceAudioSystem { } // Warning (10% <= x < 30%) - repeat every 4 seconds ONLY if not in danger else if (percentage >= 0.2 && percentage < 0.5 && !this._warningStates.has(`warning_${statusType}`) && !this._warningStates.has(`danger_${statusType}`)) { - debugLog(`VoiceAudioSystem: Warning triggered for ${statusType} (${(percentage * 100).toFixed(1)}%)`); + log.debug(`VoiceAudioSystem: Warning triggered for ${statusType} (${(percentage * 100).toFixed(1)}%)`); this._warningStates.add(`warning_${statusType}`); this.queueMessage(['warning', statusType], VoiceMessagePriority.NORMAL, false, 4000, `warning_${statusType}`); } // Empty (= 0) - no repeat else if (newValue === 0 && !this._warningStates.has(`empty_${statusType}`)) { - debugLog(`VoiceAudioSystem: EMPTY warning triggered for ${statusType}`); + log.debug(`VoiceAudioSystem: EMPTY warning triggered for ${statusType}`); this._warningStates.add(`empty_${statusType}`); this.queueMessage([statusType, 'empty'], VoiceMessagePriority.HIGH, false, 0, `empty_${statusType}`); } @@ -158,7 +158,7 @@ export class VoiceAudioSystem { stateKey?: string ): void { if (!this._audioEngine) { - debugLog('VoiceAudioSystem: Cannot queue message - audio not initialized'); + log.debug('VoiceAudioSystem: Cannot queue message - audio not initialized'); return; } @@ -185,7 +185,7 @@ export class VoiceAudioSystem { } const repeatInfo = repeatInterval > 0 ? ` (repeat every ${repeatInterval}ms)` : ''; - debugLog(`VoiceAudioSystem: Queued message [${sounds.join(', ')}] with priority ${priority}${repeatInfo}`); + log.debug(`VoiceAudioSystem: Queued message [${sounds.join(', ')}] with priority ${priority}${repeatInfo}`); } /** @@ -222,7 +222,7 @@ export class VoiceAudioSystem { this.playCurrentSound(); } else { // Sequence complete - debugLog('VoiceAudioSystem: Sequence complete'); + log.debug('VoiceAudioSystem: Sequence complete'); // Check if this message should repeat if (this._currentMessage.repeatInterval && this._currentMessage.repeatInterval > 0) { @@ -233,9 +233,9 @@ export class VoiceAudioSystem { // Re-queue the message for repeat this._queue.push({ ...this._currentMessage }); - debugLog(`VoiceAudioSystem: Message re-queued for repeat in ${this._currentMessage.repeatInterval}ms`); + log.debug(`VoiceAudioSystem: Message re-queued for repeat in ${this._currentMessage.repeatInterval}ms`); } else { - debugLog(`VoiceAudioSystem: Message NOT re-queued - warning state '${this._currentMessage.stateKey}' cleared`); + log.debug(`VoiceAudioSystem: Message NOT re-queued - warning state '${this._currentMessage.stateKey}' cleared`); } } @@ -265,7 +265,7 @@ export class VoiceAudioSystem { this._currentMessage = this._queue.shift()!; this._currentSoundIndex = 0; this._isPlaying = true; - debugLog(`VoiceAudioSystem: Starting sequence [${this._currentMessage.sounds.join(' → ')}]`); + log.debug(`VoiceAudioSystem: Starting sequence [${this._currentMessage.sounds.join(' → ')}]`); this.playCurrentSound(); } } @@ -283,9 +283,9 @@ export class VoiceAudioSystem { if (sound) { sound.play(); - debugLog(`VoiceAudioSystem: Playing ${soundName} (${this._currentSoundIndex + 1}/${this._currentMessage.sounds.length})`); + log.debug(`VoiceAudioSystem: Playing ${soundName} (${this._currentSoundIndex + 1}/${this._currentMessage.sounds.length})`); } else { - debugLog(`VoiceAudioSystem: Sound ${soundName} not found, skipping`); + log.debug(`VoiceAudioSystem: Sound ${soundName} not found, skipping`); // Skip to next sound this._currentSoundIndex++; } @@ -314,7 +314,7 @@ export class VoiceAudioSystem { */ public clearQueue(): void { this._queue = []; - debugLog('VoiceAudioSystem: Queue cleared'); + log.debug('VoiceAudioSystem: Queue cleared'); } /** @@ -330,9 +330,9 @@ export class VoiceAudioSystem { const removed = originalLength - this._queue.length; if (removed > 0) { - debugLog(`VoiceAudioSystem: Cleared warning state '${key}' and purged ${removed} queued message(s)`); + log.debug(`VoiceAudioSystem: Cleared warning state '${key}' and purged ${removed} queued message(s)`); } else { - debugLog(`VoiceAudioSystem: Cleared warning state '${key}'`); + log.debug(`VoiceAudioSystem: Cleared warning state '${key}'`); } } } @@ -342,7 +342,7 @@ export class VoiceAudioSystem { */ public resetWarningStates(): void { this._warningStates.clear(); - debugLog('VoiceAudioSystem: Warning states reset'); + log.debug('VoiceAudioSystem: Warning states reset'); } /** @@ -353,6 +353,6 @@ export class VoiceAudioSystem { this.clearQueue(); this._sounds.clear(); this._warningStates.clear(); - debugLog('VoiceAudioSystem: Disposed'); + log.debug('VoiceAudioSystem: Disposed'); } } diff --git a/src/stores/auth.ts b/src/stores/auth.ts index 330db2c..99ea9c0 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -1,5 +1,6 @@ import { writable } from 'svelte/store'; import { AuthService } from '../services/authService'; +import log from '../core/logger'; interface AuthState { isAuthenticated: boolean; @@ -18,34 +19,34 @@ function createAuthStore() { const { subscribe, set, update } = writable(initial); - console.log('[AuthStore] Store created with initial state:', initial); + log.info('[AuthStore] Store created with initial state:', initial); // Initialize auth state - will be properly initialized after AuthService.initialize() is called (async () => { - console.log('[AuthStore] Checking initial auth state...'); + log.info('[AuthStore] Checking initial auth state...'); const isAuth = await authService.isAuthenticated(); const user = authService.getUser(); - console.log('[AuthStore] Initial auth check:', { isAuth, user: user?.name || user?.email || null }); + log.info('[AuthStore] Initial auth check:', { isAuth, user: user?.name || user?.email || null }); set({ isAuthenticated: isAuth, user, isLoading: false }); })(); return { subscribe, login: async () => { - console.log('[AuthStore] login() called'); + log.info('[AuthStore] login() called'); await authService.login(); // After redirect, page will reload and auth state will be refreshed }, logout: async () => { - console.log('[AuthStore] logout() called'); + log.info('[AuthStore] logout() called'); await authService.logout(); // After logout redirect, page will reload }, refresh: async () => { - console.log('[AuthStore] refresh() called'); + log.info('[AuthStore] refresh() called'); const isAuth = await authService.isAuthenticated(); const user = authService.getUser(); - console.log('[AuthStore] Refreshed auth state:', { isAuth, user: user?.name || user?.email || null }); + log.info('[AuthStore] Refreshed auth state:', { isAuth, user: user?.name || user?.email || null }); update(state => ({ ...state, isAuthenticated: isAuth, user })); }, }; diff --git a/src/stores/controllerMapping.ts b/src/stores/controllerMapping.ts index 84762c3..43a16a9 100644 --- a/src/stores/controllerMapping.ts +++ b/src/stores/controllerMapping.ts @@ -1,6 +1,7 @@ import { writable, get } from 'svelte/store'; import type { ControllerMapping } from '../ship/input/controllerMapping'; import { ControllerMappingConfig } from '../ship/input/controllerMapping'; +import log from '../core/logger'; const _STORAGE_KEY = 'space-game-controller-mapping'; @@ -21,13 +22,13 @@ function createControllerMappingStore() { const mapping = get(controllerMappingStore); config.setMapping(mapping); config.save(); - console.log('[ControllerMapping Store] Saved'); + log.info('[ControllerMapping Store] Saved'); }, reset: () => { config.resetToDefault(); config.save(); set(config.getMapping()); - console.log('[ControllerMapping Store] Reset to defaults'); + log.info('[ControllerMapping Store] Reset to defaults'); }, validate: () => { return config.validate(); diff --git a/src/stores/gameConfig.ts b/src/stores/gameConfig.ts index b98313c..2872dc5 100644 --- a/src/stores/gameConfig.ts +++ b/src/stores/gameConfig.ts @@ -1,4 +1,5 @@ import { writable, get } from 'svelte/store'; +import log from '../core/logger'; const STORAGE_KEY = 'game-config'; @@ -34,7 +35,7 @@ function loadFromStorage(): GameConfigData { return { ...defaultConfig, ...parsed }; } } catch (error) { - console.warn('[GameConfig Store] Failed to load from localStorage:', error); + log.warn('[GameConfig Store] Failed to load from localStorage:', error); } return { ...defaultConfig }; } @@ -51,18 +52,18 @@ function createGameConfigStore() { const config = get(gameConfigStore); try { localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); - console.log('[GameConfig Store] Saved to localStorage'); + log.info('[GameConfig Store] Saved to localStorage'); } catch (error) { - console.error('[GameConfig Store] Failed to save:', error); + log.error('[GameConfig Store] Failed to save:', error); } }, reset: () => { set({ ...defaultConfig }); try { localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultConfig)); - console.log('[GameConfig Store] Reset to defaults'); + log.info('[GameConfig Store] Reset to defaults'); } catch (error) { - console.error('[GameConfig Store] Failed to save defaults:', error); + log.error('[GameConfig Store] Failed to save defaults:', error); } }, }; diff --git a/src/stores/levelRegistry.ts b/src/stores/levelRegistry.ts index c5a57c5..358f45e 100644 --- a/src/stores/levelRegistry.ts +++ b/src/stores/levelRegistry.ts @@ -2,6 +2,7 @@ import { writable } from 'svelte/store'; import { LevelRegistry } from '../levels/storage/levelRegistry'; import type { LevelConfig } from '../levels/config/levelConfig'; import type { CloudLevelEntry } from '../services/cloudLevelService'; +import log from '../core/logger'; interface LevelRegistryState { isInitialized: boolean; @@ -28,7 +29,7 @@ function createLevelRegistryStore() { levels: registry.getAllLevels(), })); } catch (error) { - console.error('[LevelRegistryStore] Failed to initialize:', error); + log.error('[LevelRegistryStore] Failed to initialize:', error); } })(); diff --git a/src/ui/hud/missionBrief.ts b/src/ui/hud/missionBrief.ts index 8a08772..c5daa9e 100644 --- a/src/ui/hud/missionBrief.ts +++ b/src/ui/hud/missionBrief.ts @@ -8,7 +8,7 @@ import { } from "@babylonjs/gui"; import { DefaultScene } from "../../core/defaultScene"; import {MeshBuilder, Vector3, Observable, Observer} from "@babylonjs/core"; -import debugLog from '../../core/debug'; +import log from '../../core/logger'; import { LevelConfig } from "../../levels/config/levelConfig"; import { CloudLevelEntry } from "../../services/cloudLevelService"; @@ -27,20 +27,20 @@ export class MissionBrief { * Initialize the mission brief as a fullscreen overlay */ public initialize(): void { - console.log('[MissionBrief] ========== INITIALIZE CALLED =========='); + log.info('[MissionBrief] ========== INITIALIZE CALLED =========='); const scene = DefaultScene.MainScene; - console.log('[MissionBrief] Scene exists:', !!scene); + log.info('[MissionBrief] Scene exists:', !!scene); try { - console.log('[MissionBrief] Initializing as fullscreen overlay'); + log.info('[MissionBrief] Initializing as fullscreen overlay'); const mesh = MeshBuilder.CreatePlane('brief', {size: 2}); - console.log('[MissionBrief] Mesh created:', mesh.name, 'ID:', mesh.id); + log.info('[MissionBrief] Mesh created:', mesh.name, 'ID:', mesh.id); const ship = scene.getNodeById('Ship'); - console.log('[MissionBrief] Ship node found:', !!ship); + log.info('[MissionBrief] Ship node found:', !!ship); if (!ship) { - console.error('[MissionBrief] ERROR: Ship node not found! Cannot parent mission brief mesh.'); + log.error('[MissionBrief] ERROR: Ship node not found! Cannot parent mission brief mesh.'); return; } @@ -49,18 +49,18 @@ export class MissionBrief { mesh.rotation = new Vector3(0, 0, 0); mesh.renderingGroupId = 3; // Same as status screen for consistent rendering mesh.metadata = { uiPickable: true }; // TAG: VR UI - allow pointer selection - console.log('[MissionBrief] Mesh parented to ship at position:', mesh.position); - console.log('[MissionBrief] Mesh absolute position:', mesh.getAbsolutePosition()); - console.log('[MissionBrief] Mesh scaling:', mesh.scaling); - console.log('[MissionBrief] Mesh isEnabled:', mesh.isEnabled()); - console.log('[MissionBrief] Mesh isVisible:', mesh.isVisible); + log.info('[MissionBrief] Mesh parented to ship at position:', mesh.position); + log.info('[MissionBrief] Mesh absolute position:', mesh.getAbsolutePosition()); + log.info('[MissionBrief] Mesh scaling:', mesh.scaling); + log.info('[MissionBrief] Mesh isEnabled:', mesh.isEnabled()); + log.info('[MissionBrief] Mesh isVisible:', mesh.isVisible); // Create fullscreen advanced texture (not attached to mesh) this._advancedTexture = AdvancedDynamicTexture.CreateForMesh(mesh); - console.log('[MissionBrief] AdvancedDynamicTexture created for mesh'); - console.log('[MissionBrief] Texture dimensions:', this._advancedTexture.getSize()); + log.info('[MissionBrief] AdvancedDynamicTexture created for mesh'); + log.info('[MissionBrief] Texture dimensions:', this._advancedTexture.getSize()); - console.log('[MissionBrief] Fullscreen UI created'); + log.info('[MissionBrief] Fullscreen UI created'); // Create main container - centered overlay this._container = new Rectangle("missionBriefContainer"); @@ -73,16 +73,16 @@ export class MissionBrief { this._container.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER; this._container.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER; this._advancedTexture.addControl(this._container); - console.log('[MissionBrief] Container created and added to texture'); + log.info('[MissionBrief] Container created and added to texture'); // Initially hidden this._container.isVisible = false; - console.log('[MissionBrief] Container initially hidden'); + log.info('[MissionBrief] Container initially hidden'); - console.log('[MissionBrief] ========== INITIALIZATION COMPLETE =========='); + log.info('[MissionBrief] ========== INITIALIZATION COMPLETE =========='); } catch (error) { - console.error('[MissionBrief] !!!!! INITIALIZATION FAILED !!!!!', error); - console.error('[MissionBrief] Error stack:', error?.stack); + log.error('[MissionBrief] !!!!! INITIALIZATION FAILED !!!!!', error); + log.error('[MissionBrief] Error stack:', error?.stack); } } @@ -94,18 +94,18 @@ export class MissionBrief { * @param onStart - Callback when start button is pressed */ public show(levelConfig: LevelConfig, directoryEntry: CloudLevelEntry | null, triggerObservable: Observable, onStart: () => void): void { - console.log('[MissionBrief] ========== SHOW() CALLED =========='); - console.log('[MissionBrief] Container exists:', !!this._container); - console.log('[MissionBrief] AdvancedTexture exists:', !!this._advancedTexture); + log.info('[MissionBrief] ========== SHOW() CALLED =========='); + log.info('[MissionBrief] Container exists:', !!this._container); + log.info('[MissionBrief] AdvancedTexture exists:', !!this._advancedTexture); if (!this._container || !this._advancedTexture) { - console.error('[MissionBrief] !!!!! CANNOT SHOW - NOT INITIALIZED !!!!!'); - console.error('[MissionBrief] Container:', this._container); - console.error('[MissionBrief] AdvancedTexture:', this._advancedTexture); + log.error('[MissionBrief] !!!!! CANNOT SHOW - NOT INITIALIZED !!!!!'); + log.error('[MissionBrief] Container:', this._container); + log.error('[MissionBrief] AdvancedTexture:', this._advancedTexture); return; } - console.log('[MissionBrief] Showing with config:', { + log.info('[MissionBrief] Showing with config:', { difficulty: levelConfig.difficulty, description: levelConfig.metadata?.description, asteroidCount: levelConfig.asteroids?.length, @@ -117,7 +117,7 @@ export class MissionBrief { // Listen for trigger pulls to dismiss the mission brief this._triggerObserver = triggerObservable.add(() => { - debugLog('[MissionBrief] Trigger pulled - dismissing mission brief'); + log.debug('[MissionBrief] Trigger pulled - dismissing mission brief'); this.hide(); if (this._onStartCallback) { this._onStartCallback(); @@ -215,7 +215,7 @@ export class MissionBrief { startButton.fontWeight = "bold"; startButton.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER; startButton.onPointerClickObservable.add(() => { - debugLog('[MissionBrief] START button clicked - dismissing mission brief'); + log.debug('[MissionBrief] START button clicked - dismissing mission brief'); this.hide(); if (this._onStartCallback) { this._onStartCallback(); @@ -232,12 +232,12 @@ export class MissionBrief { this._container.isVisible = true; this._isVisible = true; - console.log('[MissionBrief] ========== CONTAINER NOW VISIBLE =========='); - console.log('[MissionBrief] Container.isVisible:', this._container.isVisible); - console.log('[MissionBrief] _isVisible flag:', this._isVisible); - console.log('[MissionBrief] Container children count:', this._container.children.length); - console.log('[MissionBrief] AdvancedTexture control count:', this._advancedTexture.rootContainer.children.length); - console.log('[MissionBrief] ========== MISSION BRIEF DISPLAY COMPLETE =========='); + log.info('[MissionBrief] ========== CONTAINER NOW VISIBLE =========='); + log.info('[MissionBrief] Container.isVisible:', this._container.isVisible); + log.info('[MissionBrief] _isVisible flag:', this._isVisible); + log.info('[MissionBrief] Container children count:', this._container.children.length); + log.info('[MissionBrief] AdvancedTexture control count:', this._advancedTexture.rootContainer.children.length); + log.info('[MissionBrief] ========== MISSION BRIEF DISPLAY COMPLETE =========='); } /** @@ -247,7 +247,7 @@ export class MissionBrief { if (this._container) { this._container.isVisible = false; this._isVisible = false; - debugLog('[MissionBrief] Mission brief hidden'); + log.debug('[MissionBrief] Mission brief hidden'); } } @@ -306,6 +306,6 @@ export class MissionBrief { this._onStartCallback = null; this._triggerObserver = null; this._isVisible = false; - debugLog('[MissionBrief] Disposed'); + log.debug('[MissionBrief] Disposed'); } } diff --git a/src/ui/hud/scoreboard.ts b/src/ui/hud/scoreboard.ts index 510891e..f71a84e 100644 --- a/src/ui/hud/scoreboard.ts +++ b/src/ui/hud/scoreboard.ts @@ -6,7 +6,7 @@ import { Observable, Vector3, } from "@babylonjs/core"; -import debugLog from '../../core/debug'; +import log from '../../core/logger'; import { ShipStatus } from '../../ship/shipStatus'; export type ScoreEvent = { @@ -101,8 +101,8 @@ export class Scoreboard { const scene = DefaultScene.MainScene; const parent = scene.getNodeById('ship'); - debugLog('Scoreboard parent:', parent); - debugLog('Initializing scoreboard'); + log.debug('Scoreboard parent:', parent); + log.debug('Initializing scoreboard'); let scoreboard: Mesh | null = null; // Retrieve and setup screen mesh from the loaded GLB @@ -137,7 +137,7 @@ export class Scoreboard { // Fallback: create a plane if screen mesh not found if (!scoreboard) { - console.error('Screen mesh not found, creating fallback plane'); + log.error('Screen mesh not found, creating fallback plane'); scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene); scoreboard.parent = parent; @@ -249,7 +249,7 @@ export class Scoreboard { oldMaterial.dispose(true, true); } - debugLog('Gauges texture created, material:', gaugesMesh.material?.name); + log.debug('Gauges texture created, material:', gaugesMesh.material?.name); // Create a vertical stack panel for the gauges const panel = new StackPanel('GaugesPanel'); @@ -274,7 +274,7 @@ export class Scoreboard { this._shipStatus.setHull(1); this._shipStatus.setAmmo(1); - debugLog('Gauges display created with initial test values'); + log.debug('Gauges display created with initial test values'); } /** diff --git a/src/ui/hud/statusScreen.ts b/src/ui/hud/statusScreen.ts index 5c5b4d8..aab3340 100644 --- a/src/ui/hud/statusScreen.ts +++ b/src/ui/hud/statusScreen.ts @@ -22,7 +22,7 @@ import { FacebookShare, ShareData } from "../../services/facebookShare"; import { InputControlManager } from "../../ship/input/inputControlManager"; import { formatStars } from "../../game/scoreCalculator"; import { GameResultsService } from "../../services/gameResultsService"; -import debugLog from "../../core/debug"; +import log from "../../core/logger"; /** * Status screen that displays game statistics @@ -374,7 +374,7 @@ export class StatusScreen { * Set the current level info for progression tracking and results */ public setCurrentLevel(levelId: string, levelName: string, totalAsteroids: number): void { - console.log('[StatusScreen] setCurrentLevel called:', { levelId, levelName, totalAsteroids }); + log.info('[StatusScreen] setCurrentLevel called:', { levelId, levelName, totalAsteroids }); this._currentLevelId = levelId; this._currentLevelName = levelName; this._totalAsteroids = totalAsteroids; @@ -445,7 +445,7 @@ export class StatusScreen { if (this._shareButton.isVisible) { const fbShare = FacebookShare.getInstance(); fbShare.initialize().catch(error => { - console.error('Failed to initialize Facebook SDK:', error); + log.error('Failed to initialize Facebook SDK:', error); }); } } @@ -564,7 +564,7 @@ export class StatusScreen { const copied = await fbShare.copyToClipboard(shareData); if (copied) { // Show notification (you could add a toast notification here) - console.log('Results copied to clipboard!'); + log.info('Results copied to clipboard!'); // Update button text temporarily to show feedback if (this._shareButton) { @@ -605,8 +605,8 @@ export class StatusScreen { * Record game result to the results service */ private recordGameResult(endReason: 'victory' | 'death' | 'stranded'): void { - console.log('[StatusScreen] recordGameResult called with endReason:', endReason); - console.log('[StatusScreen] Level info:', { + log.info('[StatusScreen] recordGameResult called with endReason:', endReason); + log.info('[StatusScreen] Level info:', { levelId: this._currentLevelId, levelName: this._currentLevelName, totalAsteroids: this._totalAsteroids, @@ -615,8 +615,8 @@ export class StatusScreen { // Only record if we have level info if (!this._currentLevelId || !this._currentLevelName) { - console.warn('[StatusScreen] Cannot record result - missing level info'); - debugLog('[StatusScreen] Cannot record result - missing level info'); + log.warn('[StatusScreen] Cannot record result - missing level info'); + log.debug('[StatusScreen] Cannot record result - missing level info'); return; } @@ -630,15 +630,15 @@ export class StatusScreen { this._parTime ); - console.log('[StatusScreen] Built result:', result); + log.info('[StatusScreen] Built result:', result); const service = GameResultsService.getInstance(); service.saveResult(result); - console.log('[StatusScreen] Game result saved successfully'); - debugLog('[StatusScreen] Game result recorded:', result.id, result.finalScore, result.endReason); + log.info('[StatusScreen] Game result saved successfully'); + log.debug('[StatusScreen] Game result recorded:', result.id, result.finalScore, result.endReason); } catch (error) { - console.error('[StatusScreen] Failed to record game result:', error); - debugLog('[StatusScreen] Failed to record game result:', error); + log.error('[StatusScreen] Failed to record game result:', error); + log.debug('[StatusScreen] Failed to record game result:', error); } } diff --git a/src/utils/loadAsset.ts b/src/utils/loadAsset.ts index 4a05496..bb382c2 100644 --- a/src/utils/loadAsset.ts +++ b/src/utils/loadAsset.ts @@ -1,6 +1,6 @@ import {DefaultScene} from "../core/defaultScene"; import {AbstractMesh, AssetContainer, LoadAssetContainerAsync} from "@babylonjs/core"; -import debugLog from "../core/debug"; +import log from "../core/logger"; type LoadedAsset = { container: AssetContainer, @@ -8,23 +8,23 @@ type LoadedAsset = { } export default async function loadAsset(file: string, theme: string = "default"): Promise { const assetPath = `/assets/themes/${theme}/models/${file}`; - debugLog(`[loadAsset] Loading: ${assetPath}`); + log.debug(`[loadAsset] Loading: ${assetPath}`); try { const container = await LoadAssetContainerAsync(assetPath, DefaultScene.MainScene); - debugLog(`[loadAsset] ✓ Container loaded for ${file}`); + log.debug(`[loadAsset] ✓ Container loaded for ${file}`); const map: Map = new Map(); container.addAllToScene(); - debugLog(`[loadAsset] Root nodes count: ${container.rootNodes.length}`); + log.debug(`[loadAsset] Root nodes count: ${container.rootNodes.length}`); if (container.rootNodes.length === 0) { - console.error(`[loadAsset] ERROR: No root nodes found in ${file}`); + log.error(`[loadAsset] ERROR: No root nodes found in ${file}`); return {container: container, meshes: map}; } for (const mesh of container.rootNodes[0].getChildMeshes(false)) { - console.log(mesh.id, mesh); + log.info(mesh.id, mesh); // Ensure mesh is visible and enabled mesh.isVisible = true; mesh.setEnabled(true); @@ -42,10 +42,10 @@ export default async function loadAsset(file: string, theme: string = "default") map.set(mesh.id, mesh); } - debugLog(`[loadAsset] ✓ Loaded ${map.size} meshes from ${file}`); + log.debug(`[loadAsset] ✓ Loaded ${map.size} meshes from ${file}`); return {container: container, meshes: map}; } catch (error) { - console.error(`[loadAsset] FAILED to load ${assetPath}:`, error); + log.error(`[loadAsset] FAILED to load ${assetPath}:`, error); throw error; } } \ No newline at end of file