Add non-linear controller input curve and fix auth token error

- 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>
This commit is contained in:
Michael Mainguy 2025-12-01 19:24:57 -06:00
parent 749cc18211
commit 6655abeeec
5 changed files with 29 additions and 10 deletions

View File

@ -156,6 +156,20 @@ export class AuthService {
return await this._client.getTokenSilently(); return await this._client.getTokenSilently();
} catch (error) { } catch (error) {
log.error('Error getting access token:', error); log.error('Error getting access token:', error);
// If refresh token is missing/invalid, clear stale auth state
// User will appear logged out and can log in fresh
if (error instanceof Error && error.message.includes('Missing Refresh Token')) {
log.warn('[AuthService] Clearing stale auth session');
this._user = null;
// Clear Auth0 local storage cache without redirecting
try {
await this._client.logout({ openUrl: false });
} catch {
// Ignore logout errors - just clear local state
}
}
return undefined; return undefined;
} }
} }

View File

@ -144,8 +144,7 @@ export class GameResultsService {
levelName: string, levelName: string,
gameStats: GameStats, gameStats: GameStats,
totalAsteroids: number, totalAsteroids: number,
endReason: 'victory' | 'death' | 'stranded', endReason: 'victory' | 'death' | 'stranded'
parTime: number
): GameResult { ): GameResult {
// Get player name from auth service // Get player name from auth service
const authService = AuthService.getInstance(); const authService = AuthService.getInstance();

View File

@ -609,14 +609,20 @@ export class Ship {
rightStick: Vector2.Zero(), rightStick: Vector2.Zero(),
}; };
// Merge inputs with smooth deadzone scaling (controller takes priority if active, keyboard disabled in VR) // Merge inputs with non-linear curve (controller takes priority if active, keyboard disabled in VR)
// Deadzone: 0.1-0.15 range with linear scaling (avoids abrupt cliff effect) // Deadzone: 0.1, Max input: 0.9, Power curve for slow start, fast finish
const DEADZONE = 0.1;
const MAX_INPUT = 0.9;
const CURVE_EXPONENT = 2.5;
const leftMagnitude = controllerState.leftStick.length(); const leftMagnitude = controllerState.leftStick.length();
const rightMagnitude = controllerState.rightStick.length(); const rightMagnitude = controllerState.rightStick.length();
// Scale factor: 0% at 0.1, 100% at 0.15, linear interpolation between // Calculate curved scale: slow at low deflection, ramps up toward max at 0.9
const leftScale = Math.max(0, Math.min(1, (leftMagnitude - 0.1) / 0.05)); const leftScale = leftMagnitude <= DEADZONE ? 0 :
const rightScale = Math.max(0, Math.min(1, (rightMagnitude - 0.1) / 0.05)); Math.pow(Math.min(1, (leftMagnitude - DEADZONE) / (MAX_INPUT - DEADZONE)), CURVE_EXPONENT);
const rightScale = rightMagnitude <= DEADZONE ? 0 :
Math.pow(Math.min(1, (rightMagnitude - DEADZONE) / (MAX_INPUT - DEADZONE)), CURVE_EXPONENT);
const combinedInput = { const combinedInput = {
leftStick: leftStick:

View File

@ -20,6 +20,7 @@ import { GameStats } from "../game/gameStats";
import { Projectile } from "./projectile"; import { Projectile } from "./projectile";
import { RockFactory } from "../environment/asteroids/rockFactory"; import { RockFactory } from "../environment/asteroids/rockFactory";
import log from "../core/logger"; import log from "../core/logger";
import { ScoreEvent } from "../ui/hud/scoreboard";
/** /**
* Handles weapon firing and projectile lifecycle using shape casting * Handles weapon firing and projectile lifecycle using shape casting
@ -39,7 +40,7 @@ export class WeaponSystem {
private _havokPlugin: HavokPlugin | null = null; private _havokPlugin: HavokPlugin | null = null;
// Observable for score updates when asteroids are destroyed // Observable for score updates when asteroids are destroyed
private _scoreObservable: Observable<{score: number, remaining: number, message: string}> | null = null; private _scoreObservable: Observable<ScoreEvent> | null = null;
// Ship body to ignore in shape casts // Ship body to ignore in shape casts
private _shipBody: PhysicsBody | null = null; private _shipBody: PhysicsBody | null = null;

View File

@ -671,8 +671,7 @@ export class StatusScreen {
this._currentLevelName, this._currentLevelName,
this._gameStats, this._gameStats,
this._totalAsteroids, this._totalAsteroids,
endReason, endReason
this._parTime
); );
log.info('[StatusScreen] Built result:', result); log.info('[StatusScreen] Built result:', result);