Replace debugLog and console.* with loglevel logger

- Create centralized logger module (src/core/logger.ts)
- Replace all debugLog() calls with log.debug()
- Replace console.log() with log.info()
- Replace console.warn() with log.warn()
- Replace console.error() with log.error()
- Delete deprecated src/core/debug.ts
- Configure log levels: debug for dev, warn for production
- Add localStorage override for production debugging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-11-29 05:24:18 -06:00
parent e8ac3a8f0a
commit 123b341ed7
44 changed files with 575 additions and 572 deletions

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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}`);
}

View File

@ -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);
}
});
</script>

View File

@ -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 ==========');
});
</script>

View File

@ -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;

View File

@ -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);
}
}
});

View File

@ -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<void> {
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<void>((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();
}
}

View File

@ -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<void> {
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<void> {
try {
await DefaultScene.XR.baseExperience.exitXRAsync();
} catch (error) {
debugLog('[Main] Error exiting XR:', error);
log.debug('[Main] Error exiting XR:', error);
}
}
DefaultScene.XR = null;

View File

@ -1,9 +0,0 @@
import {GameConfig} from "./gameConfig";
const config = GameConfig.getInstance();
export default function debugLog(...params: any[]) {
if (config.debug) {
console.log(...params);
}
}

View File

@ -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);
}
}

View File

@ -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.');
}
};

15
src/core/logger.ts Normal file
View File

@ -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;

View File

@ -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<void> {
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<void> {
disableHandTracking: true,
disableDefaultUI: true
});
debugLog(WebXRFeaturesManager.GetAvailableFeatures());
log.debug(WebXRFeaturesManager.GetAvailableFeatures());
}
function registerXRStateHandler(): void {

View File

@ -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<void> {
debugLog("ExplosionManager initialized with MeshExploder");
log.debug("ExplosionManager initialized with MeshExploder");
}
/**
@ -69,7 +69,7 @@ export class ExplosionManager {
public async initAudio(audioEngine: AudioEngineV2): Promise<void> {
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");
}
}

View File

@ -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');
}
}
});

View File

@ -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`);
}
});
}

View File

@ -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);

View File

@ -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

View File

@ -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)`);
}
}

View File

@ -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;
}

View File

@ -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<void> {
// 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

View File

@ -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');
}
/**

View File

@ -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.');
}
}

View File

@ -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<void> {
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;

View File

@ -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<void> {
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<void> {
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<void> {
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;
}
}

View File

@ -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 [];
}

View File

@ -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<CloudLevelEntry[]> {
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<CloudLevelEntry[]> {
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<CloudLevelEntry | null> {
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<CloudLevelEntry | null> {
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<CloudLevelEntry | null> {
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<boolean> {
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<boolean> {
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<boolean> {
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<CloudLevelEntry[]> {
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<boolean> {
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<boolean> {
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<boolean> {
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;
}

View File

@ -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<boolean> {
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<boolean> {
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;
}
}

View File

@ -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);
}
}

View File

@ -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<SupabaseClient | null> {
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;

View File

@ -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);
}
}
}

View File

@ -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 };
}
}

View File

@ -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;

View File

@ -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<void> {
@ -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<void> {
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
);

View File

@ -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<void> {
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');
}
}

View File

@ -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<AuthState>(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 }));
},
};

View File

@ -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();

View File

@ -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);
}
},
};

View File

@ -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);
}
})();

View File

@ -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<void>, 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');
}
}

View File

@ -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');
}
/**

View File

@ -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);
}
}

View File

@ -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<LoadedAsset> {
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<string, AbstractMesh> = 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;
}
}