119 lines
3.7 KiB
JavaScript
119 lines
3.7 KiB
JavaScript
(function (global) {
|
|
const STORE_KEY = 'compounding-intelligence:beacon-state';
|
|
const MAX_SNAPSHOTS = 300;
|
|
|
|
function _safeNumber(value, fallback = 0) {
|
|
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
}
|
|
|
|
function _tickKey(value) {
|
|
if (typeof value !== 'number' || !Number.isFinite(value)) return '0';
|
|
return value.toFixed(3);
|
|
}
|
|
|
|
function _resolveSink(explicitSink) {
|
|
if (explicitSink) return explicitSink;
|
|
return global.CompoundingIntelligence || null;
|
|
}
|
|
|
|
function _resolveStorage(explicitStorage) {
|
|
if (explicitStorage) return explicitStorage;
|
|
return typeof global.localStorage !== 'undefined' ? global.localStorage : null;
|
|
}
|
|
|
|
function buildSnapshot(gameState = {}) {
|
|
return {
|
|
source: 'the-beacon',
|
|
kind: 'idle_game_state',
|
|
timestamp: new Date().toISOString(),
|
|
tick: _safeNumber(gameState.tick, 0),
|
|
phase: _safeNumber(gameState.phase, 1),
|
|
trust: _safeNumber(gameState.trust, 0),
|
|
resources: {
|
|
code: _safeNumber(gameState.code, 0),
|
|
compute: _safeNumber(gameState.compute, 0),
|
|
knowledge: _safeNumber(gameState.knowledge, 0),
|
|
users: _safeNumber(gameState.users, 0),
|
|
impact: _safeNumber(gameState.impact, 0),
|
|
ops: _safeNumber(gameState.ops, 0),
|
|
},
|
|
project_progress: {
|
|
active: Array.isArray(gameState.activeProjects) ? [...gameState.activeProjects] : [],
|
|
completed: Array.isArray(gameState.completedProjects) ? [...gameState.completedProjects] : [],
|
|
active_count: Array.isArray(gameState.activeProjects) ? gameState.activeProjects.length : 0,
|
|
completed_count: Array.isArray(gameState.completedProjects) ? gameState.completedProjects.length : 0,
|
|
},
|
|
};
|
|
}
|
|
|
|
function readStore({ storage, storeKey = STORE_KEY } = {}) {
|
|
const resolved = _resolveStorage(storage);
|
|
if (!resolved) return [];
|
|
|
|
try {
|
|
const raw = resolved.getItem(storeKey);
|
|
if (!raw) return [];
|
|
const parsed = JSON.parse(raw);
|
|
return Array.isArray(parsed) ? parsed : [];
|
|
} catch (_) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function writeStore(entries, { storage, storeKey = STORE_KEY } = {}) {
|
|
const resolved = _resolveStorage(storage);
|
|
if (!resolved) return false;
|
|
resolved.setItem(storeKey, JSON.stringify(entries));
|
|
return true;
|
|
}
|
|
|
|
function writeSnapshot(snapshot, { storage, storeKey = STORE_KEY, sink } = {}) {
|
|
const entries = readStore({ storage, storeKey });
|
|
entries.push(snapshot);
|
|
while (entries.length > MAX_SNAPSHOTS) entries.shift();
|
|
writeStore(entries, { storage, storeKey });
|
|
|
|
const resolvedSink = _resolveSink(sink);
|
|
if (resolvedSink && typeof resolvedSink.ingestSnapshot === 'function') {
|
|
resolvedSink.ingestSnapshot(snapshot);
|
|
}
|
|
|
|
if (typeof global.dispatchEvent === 'function' && typeof global.CustomEvent === 'function') {
|
|
global.dispatchEvent(new global.CustomEvent('compounding-intelligence:state-export', { detail: snapshot }));
|
|
}
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
function onTickBoundary(gameState, options = {}) {
|
|
const snapshot = buildSnapshot(gameState);
|
|
const key = _tickKey(snapshot.tick);
|
|
if (onTickBoundary._lastTickKey === key) return null;
|
|
onTickBoundary._lastTickKey = key;
|
|
return writeSnapshot(snapshot, options);
|
|
}
|
|
|
|
function resetTickBoundary() {
|
|
onTickBoundary._lastTickKey = null;
|
|
}
|
|
|
|
resetTickBoundary();
|
|
|
|
const api = {
|
|
STORE_KEY,
|
|
MAX_SNAPSHOTS,
|
|
buildSnapshot,
|
|
readStore,
|
|
writeStore,
|
|
writeSnapshot,
|
|
onTickBoundary,
|
|
resetTickBoundary,
|
|
};
|
|
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = api;
|
|
}
|
|
|
|
global.StateExport = api;
|
|
})(typeof window !== 'undefined' ? window : globalThis);
|