- Set renderingGroupId=2 for game objects (ship, asteroids, base, sun, planets)
- Set camera maxZ=100000 for FreeCamera and XR camera (distant planets)
- Add static sphere physics bodies to planets
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create LevelEditor.svelte with permission-gated level list
- Create LevelEditForm.svelte for editing level metadata
- Add getAllLevelsForAdmin() and updateLevelAsAdmin() to CloudLevelService
- Add /editor/:levelId route to App.svelte
- Show Level Editor link in header only for admins with canManageOfficial
- Add migration to use internal UUID for admins table
- Fix auth0_sub -> auth0_id column name in supabaseService
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace linear controller scaling with power curve (exp 2.5) for
smoother VR controls - less sensitive at low deflections, full
power at 90% stick travel
- Fix Missing Refresh Token error by clearing stale auth state
automatically instead of leaving users in broken state
- Fix build errors: use ScoreEvent type in weaponSystem, remove
unused parTime parameter from buildResult
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace multiplier-based scoring with additive system
- Score builds from asteroid destruction based on size and timing
- Small asteroids (<10 scale): 1000 pts, Medium (10-20): 500 pts, Large (>20): 250 pts
- Timing multiplier: 3x in first 1/3 of par time, 2x in middle, 1x in last third
- End-game bonuses only applied at game end (hull, fuel, accuracy)
- Add scale property to ScoreEvent for point calculation
- Update status screen to show "CURRENT SCORE" during play, "FINAL SCORE" at end
- Refactor star ratings display into individual columns
- Fix button clipping on hover with clipChildren = false
- Add reusable button hover effects utility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add 'mission_brief_shown' event type to HintEntry interface
- Add triggerMissionBriefShown() method to LevelHintSystem
- Call hint trigger when mission brief is displayed in Level1
- Remove old missionBriefAudio playback from MissionBrief class
This enables database-configurable audio hints (like welcome_rookie)
to play when the mission brief is shown.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace automatic XR entry with user-triggered ENTER XR button
- Display level name, difficulty, and mission brief during loading
- Add VR availability check with "VR not available" error for desktop
- Add deep link protection - redirect locked levels to level select
- Extract XR entry logic to xrEntryHandler.ts for code organization
- Refactor levelSelectedHandler.ts from 206 to 150 lines
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements a hint system that plays audio clips when specific game events occur:
- Ship status changes (fuel/hull/ammo thresholds)
- Asteroid destruction counts
- Ship collisions
Hints are stored in database with configurable play modes (once/always).
Also lowers background music volume from 0.5 to 0.2.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add mission_brief_audio field to CloudLevelEntry interface
- Update missionBrief.ts to use AudioEngineV2.createSoundAsync()
instead of legacy Sound class (fixes audio not playing)
- Pass audioEngine to MissionBrief.initialize() from Level1
- Add welcome_rookie.mp3 audio file
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Pass initial position to ship.initialize() to set position BEFORE
creating physics body, preventing collision race condition on reload
- Use get_or_create_user_id RPC (security definer) to bypass RLS
for user profile sync in both authService and cloudLeaderboardService
- Sync user to Supabase on Auth0 login to ensure profile exists
- Add Supabase schema.sql and policies.sql for documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Use InputControlManager.shipControlsEnabled instead of local flag
- Remove unused _controlsEnabled field from Ship class
- Fixes trigger press not dismissing mission brief screen
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Use setTransformationFromNonVRCamera to set XR reference space
to ship cockpit position before entering immersive mode. This
prevents camera jumping on Quest when the 2D preloader disappears.
Also includes:
- Ship physics tuning (reduced force multipliers and fuel consumption)
- Fix reverse thrust direction in shipPhysics.ts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Calculate velocity at bullet spawn point (8.4 units from center) instead
of ship center. Accounts for tangential velocity from angular rotation
using cross product: velocity_at_spawn = linear + (angular × offset).
This fixes bullets appearing to drift when ship is moving and rotating.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix duplicate render loops causing 50% FPS drop (70→40)
- Add stopRenderLoop() before runRenderLoop() in level1.ts and levelSelectedHandler.ts
- Add ?loglevel=debug|info|warn|error query parameter
- Add Y button to toggle inspector in XR
- Throttle scoreboard updates to every 10 frames
- Throttle game-end condition checks to every 30 frames
- Remove per-frame logging from explosion animations
- Reduce background stars from 5000 to 2500
- Freeze asteroid material after loading
- Reduce physics substeps from 5 to 2
- Disable autoClear for Quest 2 performance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Dispose old scene before creating new one (cleans up physics)
- Stop existing render loop before starting new one
- Dynamically import @babylonjs/inspector on 'i' key press
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Hide #gameCanvas by default in CSS (display: none)
- Show canvas in setupXRCamera() after camera is parented to ship
- Show canvas in flat mode fallback paths
- Fix preloader to append to document.body (was hidden with #levelSelect)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- 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>
- Extract cleanup logic to src/core/cleanup.ts
- Extract XR setup to src/core/xrSetup.ts
- Extract scene/physics/audio setup to src/core/sceneSetup.ts
- Remove unused GameState enum and _gameState field
- main.ts reduced from 192 to 91 lines
- All methods now under 20 lines
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Extract analytics init to src/analytics/initAnalytics.ts
- Extract level selection handler to src/core/handlers/levelSelectedHandler.ts
- Extract replay handler to src/core/handlers/viewReplaysHandler.ts
- Extract app initialization to src/core/appInitializer.ts
- Remove unused DemoScene and demo.ts
- Remove dead code: DEBUG_CONTROLLERS, webGpu, TestLevel handler
- Add BabylonJS shader pre-bundling to fix Vite dev server issues
- Reduce main.ts from 885 lines to 211 lines
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add ESLint with typescript-eslint for unused code detection
- Fix 33 unused variable/import warnings across codebase
- Remove player_name from leaderboard insert (normalized design)
- Add ensureUserProfile() to upsert user display_name to users table
- Update leaderboard queries to join with users(display_name)
- Add getDisplayName() helper for leaderboard entries
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Made 75+ types internal (removed export keyword) across 24 files:
- Analytics event types (kept GameEventMap, GameEventName, GameEventProperties)
- Level config types (QuaternionArray, MaterialConfig, etc.)
- Ship types (SightConfig, InputState, etc.)
- Store state types (AuthState, GameConfigData, etc.)
- Various config interfaces
These types are still used internally but were never imported elsewhere.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove all local level storage concepts and load levels exclusively from
Supabase cloud. Simplifies LevelRegistry from 380+ lines to ~50 lines.
Uses CloudLevelEntry directly throughout the codebase instead of wrapper
types like LevelDirectoryEntry.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create consolidated setupXRCamera() method in Level1 to handle all XR
camera initialization in one place
- Use intermediate TransformNode (xrCameraRig) for camera rotation since
WebXR camera only uses rotationQuaternion which XR frame updates overwrite
- Fix pointer selection feature registration timing - must register after
XR session starts, not during initialize()
- Move pointer registration to onStateChangedObservable and setupXRCamera()
- Don't stop render loop before entering XR as it may prevent observables
from firing properly
- Fix audio paths in shipAudio.ts to use correct asset locations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add pagination support to CloudLeaderboardService with offset parameter
- Implement infinite scroll in Leaderboard.svelte using IntersectionObserver
- Update seed script to use actual game scoring formulas (time, accuracy, fuel, hull multipliers)
- Add level-specific asteroid counts and par times to seed data
- Create BUGS.md to track known issues
- Partial work on XR camera orientation (documented in BUGS.md)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add Supabase service and cloud leaderboard service for score submission
- Fix Auth0 JWT by adding audience parameter for Supabase RLS compatibility
- Fix BabylonJS shader loading by adding @babylonjs/materials to Vite pre-bundle
- Update CI workflow with Supabase and Auth0 audience secrets
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Shows a button that uses Meta's Web Launch to send the current URL
to the user's Quest headset. Hidden when already browsing on Quest.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add new asteroid-mania level to directory and DEFAULT_LEVEL_ORDER
- Remove level caching entirely (always fetch fresh from network)
- Delete legacy router.ts, levelSelector.ts, and levelVersionManager.ts
- Remove unused router handlers from main.ts (~120 lines)
- Fix projectile curving by cloning velocity vector in weaponSystem.ts
- Update LevelSelect.svelte to include asteroid-mania
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create GameResultsService for storing game results in localStorage
- Create gameResultsStore Svelte store for reactive data access
- Add Leaderboard component showing top 20 scores
- Add leaderboard route and navigation link
- Record game results on victory/death/stranded (not manual exits)
- Fix header visibility when exiting game
- Fix camera error by stopping render loop after cleanup
- Clear canvas after cleanup to prevent last frame showing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Switch from svelte-spa-router to svelte-routing for clean URLs without hashes
- Fix relative asset paths to absolute paths (prevents 404s on nested routes)
- Fix physics engine disposal using scene.disablePhysicsEngine()
- Fix Ship observer cleanup to prevent stale callbacks after level disposal
- Add _gameplayStarted flag to prevent false game-end triggers during init
- Add hasAsteroidsToDestroy check to prevent false victory on restart
- Add RockFactory.reset() to properly reinitialize asteroid mesh between games
- Add null safety checks throughout RockFactory for static properties
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Resolved issue where VR laser pointers could not click mission brief buttons. Root cause was scene.pointerMovePredicate filtering out GUI meshes before pointer events could reach AdvancedDynamicTexture.
Changes:
- Commented out restrictive pointerMovePredicate that blocked GUI mesh picking
- Temporarily disabled renderingGroupId=3 on mission brief for VR compatibility
- Adjusted ship physics: reduced angular force multiplier (1.5→0.5) and increased damping (0.5→0.6)
Technical details:
- WebXRControllerPointerSelection uses scene.pointerMovePredicate during pickWithRay()
- If predicate returns false, pickInfo.hit=false and GUI events never fire
- AdvancedDynamicTexture requires pickInfo.pickedMesh === mesh to process events
- Removing predicate allows default behavior (all isPickable meshes are candidates)
TODO: Re-implement predicate using renderingGroupId === 3 check for production
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit resolves several physics-related issues that were causing
unexpected behavior in ship and asteroid movement:
**Physics Sleep System**
- Fixed abrupt stops by preventing Havok from putting bodies to sleep
- Added PhysicsActivationControl.ALWAYS_ACTIVE for ship and asteroids
- Made ship sleep behavior configurable via shipPhysics.alwaysActive
- Sleep was causing sudden velocity zeroing at low speeds
**Center of Mass Issues**
- Discovered mesh-based physics calculated offset CoM: (0, -0.38, 0.37)
- Override ship center of mass to (0, 0, 0) to prevent thrust torque
- Applying force at offset CoM was creating unwanted pitch rotation
- Added debug logging to track mass properties
**Input Deadzone Improvements**
- Implemented smooth deadzone scaling (0.1-0.15 range)
- Replaced hard threshold cliff with linear interpolation
- Prevents abrupt control cutoff during gentle inputs
- Added VR mode check to disable keyboard fallback in VR
**Configuration System**
- Added DEFAULT_SHIP_PHYSICS constant as single source of truth
- Added tunable parameters: linearDamping, angularDamping, alwaysActive
- Added fuel consumption rates: linearFuelConsumptionRate, angularFuelConsumptionRate
- Tuned for 1 minute linear thrust, 2 minutes angular thrust at 60Hz
- All physics parameters now persist to localStorage
**Other Fixes**
- Changed orbit center to STATIC motion type (was ANIMATED)
- Fixed linear force application point (removed offset)
- Added ship initial velocity support from level config
- Changed physics update from every 10 frames to every physics tick
- Increased linear input threshold from 0.1 to 0.15
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add linear-clamped scoring system that rewards speed, accuracy, fuel
efficiency, and hull integrity. Scores are always positive with a 0.5x
multiplier floor for refueling/repairs.
Scoring Components:
- Create scoreCalculator module with configurable scoring logic
- Time multiplier: Exponential decay from par time (0.1x to 3.0x)
- Accuracy multiplier: Linear 1.0x to 2.0x based on hit percentage
- Fuel efficiency: Linear with 0.5x floor (handles refueling >100%)
- Hull integrity: Linear with 0.5x floor (handles deaths/repairs >100%)
- Star rating system: 0-3 stars per category (12 stars max)
Integration:
- Add calculateFinalScore() to GameStats
- Support parTime in level config metadata
- Auto-calculate par time from difficulty level in Level1
- Recruit: 300s, Pilot: 180s, Captain: 120s, Commander: 90s, Test: 60s
- Display comprehensive score breakdown on status screen
Status Screen Updates:
- Increase mesh size from 1.5x1.0m to 1.5x2.25m (portrait orientation)
- Increase texture from 1024x768 to 1024x1536 (fit all content)
- Add score display section with:
- Final score in gold with thousand separators
- Score multiplier breakdown for each category
- Unicode star ratings (★★★) per category
- Total stars earned (X/12)
Formula:
finalScore = 10,000 × time × accuracy × fuel × hull
All multipliers ≥ 0.5, ensuring scores are never negative even with
multiple refuels/deaths. System rewards balanced excellence across all
performance metrics.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create InputControlManager singleton for centralized ship controls and pointer selection management
- Last-wins behavior for state changes
- Mutually exclusive ship controls and VR pointer selection
- Observable events for state changes with requester tracking
- Enables debugging and prevents conflicts between UI components
- Refactor Ship class to use InputControlManager
- Remove disableControls() and enableControls() methods
- Register input systems with InputControlManager on initialization
- Simplify control state management throughout ship lifecycle
- Update StatusScreen to use InputControlManager
- Remove manual pointer selection enable/disable methods
- Delegate control management to InputControlManager
- Automatic laser pointer enabling when screen shows
- Update Level1 mission brief to use InputControlManager
- Consistent control management for mission brief display
- Proper pointer selection during mission brief interaction
- Fix controller input trigger blocking bug
- Triggers now properly blocked when controls disabled
- Prevents shooting when status screen or mission brief is visible
- Only X-button (status screen toggle) allowed when disabled
- Add START MISSION button to mission brief
- Replace "Pull trigger to start" text with clickable button
- Green styled button matching StatusScreen design
- Works with VR laser pointer interaction
- Trigger pull still works as fallback
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major refactoring of the UI layer to use Svelte components:
- Replace inline HTML with modular Svelte components
- Add authentication system with UserProfile component
- Implement navigation store for view management
- Create comprehensive settings and controls screens
- Add level editor with JSON validation
- Implement progression tracking system
- Update level configurations and base station model
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement comprehensive controller remapping UI that allows users to customize
VR controller button and stick mappings with per-axis granularity and inversion
controls. Configuration persists to localStorage and applies on level start.
## Features
- Full-page controller remapping UI at #/controls
- Per-axis stick mapping (4 dropdowns: leftX, leftY, rightX, rightY)
- Individual axis inversion toggles (8 total invert options)
- Button remapping (6 buttons: trigger, A, B, X, Y, squeeze)
- Available actions: yaw, pitch, roll, forward thrust, camera, status screen
- Configuration validation with warnings for duplicates/missing controls
- Preview/test functionality to review current mapping
- Reset to default option
- localStorage persistence with backward compatibility
## Implementation
- ControllerMappingConfig singleton manages configuration and validation
- ControlsScreen handles UI logic and form manipulation
- ControllerInput applies mapping by translating raw input to actions
- Actions mapped back to virtual stick positions for ShipPhysics
- No changes needed to ShipPhysics - receives correctly mapped values
## User Flow
1. Navigate to Controls via header menu
2. Select action for each stick axis (yaw/pitch/roll/forward/none)
3. Toggle invert checkboxes as needed
4. Assign button actions (fire/camera/status/none)
5. Save configuration
6. Changes apply when starting new level
## Technical Details
- Storage key: 'space-game-controller-mapping'
- Raw stick values stored, mapping applied in getInputState()
- Supports future actions without code changes
- Validation ensures critical controls (fire, forward) are mapped
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>