Initial commit — Meridian OS browser desktop demo

This commit is contained in:
2026-04-19 23:18:55 +00:00
commit 8e30dd3597
30 changed files with 6277 additions and 0 deletions

128
js/achievements/engine.js Normal file
View File

@@ -0,0 +1,128 @@
// Achievements engine — subscribes to bus events, unlocks achievements.
// On unlock: pushes id into State, emits 'achievement:unlocked' for the widget.
import Bus from '../core/bus.js';
import State from '../core/state.js';
import catalog from './catalog.js';
// In-memory counters per achievement id
const counters = {};
// For uniqueField tracking: Set of seen values per achievement id
const uniqueSets = {};
// For speedrun: map of windowId -> open timestamp
const openTimestamps = {};
let initialized = false;
function hasUnlocked(id) {
const unlocked = State.get('achievements');
return unlocked && unlocked.includes(id);
}
function unlock(ach) {
if (hasUnlocked(ach.id)) return;
State.addAchievement(ach.id);
Bus.emit('achievement:unlocked', {
id: ach.id,
title: ach.title,
desc: ach.desc,
game: ach.game
});
}
function handleEvent(ach, data) {
const trigger = ach.trigger;
// Event+id specific trigger (easter egg matches)
if (trigger.id) {
if (data && data.id === trigger.id) {
unlock(ach);
}
return;
}
// Simple event trigger (no count, no accumulate) — unlock on first fire
if (!trigger.count && !trigger.accumulate && !trigger.uniqueField) {
unlock(ach);
return;
}
// Count-based trigger
if (trigger.count) {
counters[ach.id] = (counters[ach.id] || 0) + 1;
if (counters[ach.id] >= trigger.count) {
unlock(ach);
}
return;
}
// UniqueField trigger
if (trigger.uniqueField) {
if (!uniqueSets[ach.id]) {
uniqueSets[ach.id] = new Set();
}
const val = data && data[trigger.uniqueField];
if (val) {
uniqueSets[ach.id].add(val);
if (uniqueSets[ach.id].size >= trigger.count) {
unlock(ach);
}
}
return;
}
// Accumulate trigger
if (trigger.accumulate) {
const key = trigger.accumulate;
const value = data && data[key];
if (typeof value === 'number') {
counters[ach.id] = (counters[ach.id] || 0) + value;
if (counters[ach.id] >= trigger.threshold) {
unlock(ach);
}
}
return;
}
}
function init() {
if (initialized) return;
initialized = true;
// Pre-initialize counters for all achievements
catalog.forEach(ach => {
counters[ach.id] = 0;
uniqueSets[ach.id] = ach.trigger.uniqueField ? new Set() : undefined;
});
// Subscribe to each achievement's trigger event
catalog.forEach(ach => {
const trigger = ach.trigger;
if (trigger.custom === 'speedrun') {
// Speedrun: listen to app:open and app:close on wm.js
Bus.on('app:open', (data) => {
if (data && data.id) {
openTimestamps[data.id] = Date.now();
}
});
Bus.on('app:close', (data) => {
if (data && data.id && openTimestamps[data.id]) {
const delta = Date.now() - openTimestamps[data.id];
if (delta < 2000) {
unlock(ach);
}
delete openTimestamps[data.id];
}
});
return;
}
if (trigger.event) {
Bus.on(trigger.event, (data) => {
handleEvent(ach, data);
});
}
});
}
export default { init };