space-game/src/levels/storage/ILevelStorageProvider.ts
Michael Mainguy 244a25fff5
All checks were successful
Build / build (push) Successful in 1m34s
Implement hybrid level storage system with JSON-based defaults and configurable orbit constraints
Major changes:
- Add LevelRegistry for managing default (JSON) and custom (localStorage) levels
- Default levels now load from /public/levels/*.json files
- Add 6 default level JSON files (rookie-training through final-challenge)
- Implement version-based automatic cache invalidation
- Add LevelVersionManager for tracking level updates
- Add LevelStatsManager for performance tracking (completion rate, best time, etc.)
- Add legacy migration tool for existing localStorage data
- Update level selector UI with stats display and version badges
- Add configurable orbit constraints per level (useOrbitConstraints flag)
- Hide copy button in level selector UI (TODO: re-enable later)
- Add extensive debug logging for velocity troubleshooting
- Add cloud sync infrastructure interfaces (future-ready)

Technical improvements:
- Hybrid storage: immutable defaults from JSON, editable custom levels in localStorage
- Automatic cache refresh when directory.json version changes
- Cache API for offline support
- Fresh start migration approach with export option
- Level loading now initializes before router starts

Physics configuration:
- Add useOrbitConstraints flag to LevelConfig
- Rookietraining.json uses constraints (velocities will create orbital motion)
- Debug logging added to verify velocity application

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 18:40:01 -06:00

242 lines
6.6 KiB
TypeScript

import {LevelConfig} from "../config/levelConfig";
/**
* Sync status for a level
*/
export enum SyncStatus {
NotSynced = 'not_synced',
Syncing = 'syncing',
Synced = 'synced',
Conflict = 'conflict',
Error = 'error'
}
/**
* Metadata for synced levels
*/
export interface SyncMetadata {
lastSyncedAt?: Date;
syncStatus: SyncStatus;
cloudVersion?: string;
localVersion?: string;
syncError?: string;
}
/**
* Interface for level storage providers (localStorage, cloud, etc.)
*/
export interface ILevelStorageProvider {
/**
* Get a level by ID
*/
getLevel(levelId: string): Promise<LevelConfig | null>;
/**
* Save a level
*/
saveLevel(levelId: string, config: LevelConfig): Promise<void>;
/**
* Delete a level
*/
deleteLevel(levelId: string): Promise<boolean>;
/**
* List all level IDs
*/
listLevels(): Promise<string[]>;
/**
* Check if provider is available/connected
*/
isAvailable(): Promise<boolean>;
/**
* Get sync metadata for a level (if supported)
*/
getSyncMetadata?(levelId: string): Promise<SyncMetadata | null>;
}
/**
* LocalStorage implementation of level storage provider
*/
export class LocalStorageProvider implements ILevelStorageProvider {
private storageKey: string;
constructor(storageKey: string = 'space-game-custom-levels') {
this.storageKey = storageKey;
}
async getLevel(levelId: string): Promise<LevelConfig | null> {
const stored = localStorage.getItem(this.storageKey);
if (!stored) {
return null;
}
try {
const levelsArray: [string, LevelConfig][] = JSON.parse(stored);
const found = levelsArray.find(([id]) => id === levelId);
return found ? found[1] : null;
} catch (error) {
console.error('Failed to get level from localStorage:', error);
return null;
}
}
async saveLevel(levelId: string, config: LevelConfig): Promise<void> {
const stored = localStorage.getItem(this.storageKey);
let levelsArray: [string, LevelConfig][] = [];
if (stored) {
try {
levelsArray = JSON.parse(stored);
} catch (error) {
console.error('Failed to parse localStorage data:', error);
}
}
// Update or add level
const existingIndex = levelsArray.findIndex(([id]) => id === levelId);
if (existingIndex >= 0) {
levelsArray[existingIndex] = [levelId, config];
} else {
levelsArray.push([levelId, config]);
}
localStorage.setItem(this.storageKey, JSON.stringify(levelsArray));
}
async deleteLevel(levelId: string): Promise<boolean> {
const stored = localStorage.getItem(this.storageKey);
if (!stored) {
return false;
}
try {
const levelsArray: [string, LevelConfig][] = JSON.parse(stored);
const newArray = levelsArray.filter(([id]) => id !== levelId);
if (newArray.length === levelsArray.length) {
return false; // Level not found
}
localStorage.setItem(this.storageKey, JSON.stringify(newArray));
return true;
} catch (error) {
console.error('Failed to delete level from localStorage:', error);
return false;
}
}
async listLevels(): Promise<string[]> {
const stored = localStorage.getItem(this.storageKey);
if (!stored) {
return [];
}
try {
const levelsArray: [string, LevelConfig][] = JSON.parse(stored);
return levelsArray.map(([id]) => id);
} catch (error) {
console.error('Failed to list levels from localStorage:', error);
return [];
}
}
async isAvailable(): Promise<boolean> {
try {
const testKey = '_storage_test_';
localStorage.setItem(testKey, 'test');
localStorage.removeItem(testKey);
return true;
} catch {
return false;
}
}
}
/**
* Cloud storage provider (stub for future implementation)
*
* Future implementation could use:
* - Firebase Firestore
* - AWS S3 + DynamoDB
* - Custom backend API
* - IPFS for decentralized storage
*/
export class CloudStorageProvider implements ILevelStorageProvider {
private apiEndpoint: string;
private authToken?: string;
constructor(apiEndpoint: string, authToken?: string) {
this.apiEndpoint = apiEndpoint;
this.authToken = authToken;
}
async getLevel(_levelId: string): Promise<LevelConfig | null> {
// TODO: Implement cloud fetch
throw new Error('Cloud storage not yet implemented');
}
async saveLevel(_levelId: string, _config: LevelConfig): Promise<void> {
// TODO: Implement cloud save
throw new Error('Cloud storage not yet implemented');
}
async deleteLevel(_levelId: string): Promise<boolean> {
// TODO: Implement cloud delete
throw new Error('Cloud storage not yet implemented');
}
async listLevels(): Promise<string[]> {
// TODO: Implement cloud list
throw new Error('Cloud storage not yet implemented');
}
async isAvailable(): Promise<boolean> {
// TODO: Implement cloud connectivity check
return false;
}
async getSyncMetadata(_levelId: string): Promise<SyncMetadata | null> {
// TODO: Implement sync metadata fetch
throw new Error('Cloud storage not yet implemented');
}
/**
* Authenticate with cloud service
*/
async authenticate(token: string): Promise<boolean> {
this.authToken = token;
// TODO: Implement authentication
return false;
}
/**
* Sync local level to cloud
*/
async syncToCloud(_levelId: string, _config: LevelConfig): Promise<SyncMetadata> {
// TODO: Implement sync to cloud
throw new Error('Cloud storage not yet implemented');
}
/**
* Sync cloud level to local
*/
async syncFromCloud(_levelId: string): Promise<LevelConfig> {
// TODO: Implement sync from cloud
throw new Error('Cloud storage not yet implemented');
}
/**
* Resolve sync conflicts
*/
async resolveConflict(
_levelId: string,
_strategy: 'use_local' | 'use_cloud' | 'merge'
): Promise<LevelConfig> {
// TODO: Implement conflict resolution
throw new Error('Cloud storage not yet implemented');
}
}