feat: add Worlds 2-3, patch persistence, and zoom controls
- World 2 (Filtros): 8 levels teaching filters, resonance, LFO modulation, acid bass - World 3 (Envelopes): 8 levels teaching VCA, ADSR, pluck, tremolo, full synth lead - Star-based world unlock system (12 stars for W2, 24 for W3) - Level patch persistence: auto-saves player patches, restores on revisit - Google Maps-style zoom controls (+/−/reset) in both puzzle and sandbox views - Multi-world navigation in GameApp and WorldMap - Target audio now supports filter chain for World 2 levels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
/**
|
||||
* gameState.js — Game progress persistence
|
||||
* Tracks completed levels, stars earned, and unlocks
|
||||
* Tracks completed levels, stars earned, unlocks, and saved patches per level
|
||||
*/
|
||||
|
||||
const STORAGE_KEY = 'synthquest-progress';
|
||||
const PATCHES_KEY = 'synthquest-patches';
|
||||
|
||||
const defaultProgress = {
|
||||
currentWorld: 'w1',
|
||||
completedLevels: {}, // { levelId: { stars: 3, bestTime: 12.5 } }
|
||||
completedLevels: {}, // { levelId: { stars: 3 } }
|
||||
unlockedWorlds: ['w1'],
|
||||
totalStars: 0,
|
||||
};
|
||||
|
||||
let _progress = null;
|
||||
let _patches = null; // { levelId: { modules, connections } }
|
||||
|
||||
export function loadProgress() {
|
||||
if (_progress) return _progress;
|
||||
@@ -50,15 +52,56 @@ export function getLevelProgress(levelId) {
|
||||
|
||||
export function isLevelUnlocked(levelId, worldLevels) {
|
||||
const p = loadProgress();
|
||||
// First level is always unlocked
|
||||
const idx = worldLevels.findIndex(l => l.id === levelId);
|
||||
if (idx === 0) return true;
|
||||
// Previous level must have at least 1 star
|
||||
const prevId = worldLevels[idx - 1]?.id;
|
||||
return prevId && p.completedLevels[prevId]?.stars >= 1;
|
||||
}
|
||||
|
||||
export function resetProgress() {
|
||||
_progress = { ...defaultProgress };
|
||||
_patches = {};
|
||||
saveProgress();
|
||||
savePatches();
|
||||
}
|
||||
|
||||
// ==================== Level patch persistence ====================
|
||||
|
||||
function loadPatches() {
|
||||
if (_patches) return _patches;
|
||||
try {
|
||||
const raw = localStorage.getItem(PATCHES_KEY);
|
||||
_patches = raw ? JSON.parse(raw) : {};
|
||||
} catch {
|
||||
_patches = {};
|
||||
}
|
||||
return _patches;
|
||||
}
|
||||
|
||||
function savePatches() {
|
||||
if (!_patches) return;
|
||||
try {
|
||||
localStorage.setItem(PATCHES_KEY, JSON.stringify(_patches));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function saveLevelPatch(levelId, modules, connections) {
|
||||
const patches = loadPatches();
|
||||
patches[levelId] = {
|
||||
modules: modules.map(m => ({ id: m.id, type: m.type, x: m.x, y: m.y, params: { ...m.params } })),
|
||||
connections: connections.map(c => ({ ...c })),
|
||||
savedAt: Date.now(),
|
||||
};
|
||||
savePatches();
|
||||
}
|
||||
|
||||
export function getLevelPatch(levelId) {
|
||||
const patches = loadPatches();
|
||||
return patches[levelId] || null;
|
||||
}
|
||||
|
||||
export function clearLevelPatch(levelId) {
|
||||
const patches = loadPatches();
|
||||
delete patches[levelId];
|
||||
savePatches();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user