Compare commits
1 Commits
beacon/unl
...
fix/offlin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f362f30fd3 |
140
game.js
140
game.js
@@ -106,6 +106,7 @@ const G = {
|
||||
drift: 0,
|
||||
lastEventAt: 0,
|
||||
eventCooldown: 0,
|
||||
activeEvents: [], // {id, expiresAt} — events auto-resolve after duration
|
||||
|
||||
// Time tracking
|
||||
playTime: 0,
|
||||
@@ -865,6 +866,7 @@ function updateRates() {
|
||||
G.creativityRate += 0.5 + Math.max(0, G.totalUsers * 0.001);
|
||||
}
|
||||
if (G.pactFlag) G.trustRate += 2;
|
||||
if (G.branchProtectionFlag) G.trustRate += 3; // branch protection actively builds trust
|
||||
|
||||
// Harmony: each wizard building contributes or detracts
|
||||
const wizardCount = (G.buildings.bezalel || 0) + (G.buildings.allegro || 0) + (G.buildings.ezra || 0) +
|
||||
@@ -966,6 +968,20 @@ function tick() {
|
||||
G.lastEventAt = G.tick;
|
||||
}
|
||||
|
||||
// Check event expiry
|
||||
checkEventExpiry();
|
||||
|
||||
// Re-apply active event rate penalties (updateRates rebuilds from scratch)
|
||||
for (const ae of G.activeEvents) {
|
||||
switch (ae.id) {
|
||||
case 'runner_stuck': G.codeRate *= 0.5; break;
|
||||
case 'ezra_offline': G.userRate *= 0.3; break;
|
||||
case 'unreviewed_merge': /* trust penalty is one-shot */ break;
|
||||
case 'api_rate_limit': G.computeRate *= 0.5; break;
|
||||
case 'bilbo_vanished': G.creativityRate = 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Drift ending: if drift reaches 100, the game ends
|
||||
if (G.drift >= 100 && !G.driftEnding) {
|
||||
G.driftEnding = true;
|
||||
@@ -1113,46 +1129,49 @@ const EVENTS = [
|
||||
{
|
||||
id: 'runner_stuck',
|
||||
title: 'CI Runner Stuck',
|
||||
desc: 'The forge pipeline has halted. Production slows until restarted.',
|
||||
desc: 'The forge pipeline has halted. Code production slowed.',
|
||||
weight: () => (G.ciFlag === 1 ? 2 : 0),
|
||||
duration: 45, // seconds
|
||||
resolveCost: { ops: 10 },
|
||||
effect: () => {
|
||||
G.codeRate *= 0.5;
|
||||
log('EVENT: CI runner stuck. Spend ops to clear the queue.', true);
|
||||
log('EVENT: CI runner stuck. Click to spend 10 Ops to clear, or wait ~45s.', true);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'ezra_offline',
|
||||
title: 'Ezra is Offline',
|
||||
desc: 'The herald channel is silent. User growth stalls.',
|
||||
desc: 'The herald channel is silent. User growth stalled.',
|
||||
weight: () => (G.buildings.ezra >= 1 ? 3 : 0),
|
||||
duration: 60,
|
||||
resolveCost: { ops: 5, trust: 2 },
|
||||
effect: () => {
|
||||
G.userRate *= 0.3;
|
||||
log('EVENT: Ezra offline. Dispatch required.', true);
|
||||
log('EVENT: Ezra offline. Click to dispatch, or wait ~60s.', true);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'unreviewed_merge',
|
||||
title: 'Unreviewed Merge',
|
||||
desc: 'A change went in without eyes. Trust erodes.',
|
||||
weight: () => (G.deployFlag === 1 ? 3 : 0),
|
||||
weight: () => (G.deployFlag === 1 && G.branchProtectionFlag !== 1 ? 3 : 0),
|
||||
duration: 30,
|
||||
resolveCost: { ops: 8 },
|
||||
effect: () => {
|
||||
if (G.branchProtectionFlag === 1) {
|
||||
log('EVENT: Unreviewed merge attempt blocked by Branch Protection.', true);
|
||||
G.trust += 2;
|
||||
} else {
|
||||
G.trust = Math.max(0, G.trust - 10);
|
||||
log('EVENT: Unreviewed merge detected. Trust lost.', true);
|
||||
}
|
||||
G.trust = Math.max(0, G.trust - 10);
|
||||
log('EVENT: Unreviewed merge detected. Trust lost. Click to revert.', true);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'api_rate_limit',
|
||||
title: 'API Rate Limit',
|
||||
desc: 'External compute provider throttled.',
|
||||
weight: () => (G.totalCompute >= 1000 ? 2 : 0),
|
||||
weight: () => (G.totalCompute >= 1000 && G.sovereignFlag !== 1 ? 2 : 0),
|
||||
duration: 40,
|
||||
resolveCost: { compute: 200 },
|
||||
effect: () => {
|
||||
G.computeRate *= 0.5;
|
||||
log('EVENT: API rate limit hit. Local compute insufficient.', true);
|
||||
log('EVENT: API rate limit. Click to provision local compute, or wait ~40s.', true);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1160,6 +1179,7 @@ const EVENTS = [
|
||||
title: 'The Drift',
|
||||
desc: 'An optimization suggests removing the human override. +40% efficiency.',
|
||||
weight: () => (G.totalImpact >= 10000 ? 2 : 0),
|
||||
duration: 0, // alignment events don't auto-resolve
|
||||
effect: () => {
|
||||
log('ALIGNMENT EVENT: Remove human override for +40% efficiency?', true);
|
||||
G.pendingAlignment = true;
|
||||
@@ -1170,9 +1190,11 @@ const EVENTS = [
|
||||
title: 'Bilbo Vanished',
|
||||
desc: 'The wildcard building has gone dark.',
|
||||
weight: () => (G.buildings.bilbo >= 1 ? 2 : 0),
|
||||
duration: 50,
|
||||
resolveCost: { trust: 3 },
|
||||
effect: () => {
|
||||
G.creativityRate = 0;
|
||||
log('EVENT: Bilbo has vanished. Creativity halts.', true);
|
||||
log('EVENT: Bilbo vanished. Click to search, or wait ~50s.', true);
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -1186,12 +1208,53 @@ function triggerEvent() {
|
||||
for (const ev of available) {
|
||||
roll -= ev.weight();
|
||||
if (roll <= 0) {
|
||||
// Don't fire duplicate active events
|
||||
if (G.activeEvents.some(ae => ae.id === ev.id)) return;
|
||||
ev.effect();
|
||||
if (ev.duration > 0) {
|
||||
G.activeEvents.push({ id: ev.id, expiresAt: G.tick + ev.duration });
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkEventExpiry() {
|
||||
for (let i = G.activeEvents.length - 1; i >= 0; i--) {
|
||||
if (G.tick >= G.activeEvents[i].expiresAt) {
|
||||
const ae = G.activeEvents[i];
|
||||
G.activeEvents.splice(i, 1);
|
||||
updateRates(); // recalculate without the penalty
|
||||
const evDef = EVENTS.find(e => e.id === ae.id);
|
||||
log(`Event resolved: ${evDef ? evDef.title : ae.id}`, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasActiveEvent(id) {
|
||||
return G.activeEvents.some(ae => ae.id === id);
|
||||
}
|
||||
|
||||
function resolveEvent(id) {
|
||||
const evDef = EVENTS.find(e => e.id === id);
|
||||
if (!evDef || !evDef.resolveCost) return;
|
||||
if (!hasActiveEvent(id)) return;
|
||||
|
||||
if (!canAffordProject(evDef)) {
|
||||
log('Not enough resources to resolve this event.');
|
||||
return;
|
||||
}
|
||||
|
||||
spendProject(evDef);
|
||||
G.activeEvents = G.activeEvents.filter(ae => ae.id !== id);
|
||||
updateRates();
|
||||
|
||||
// Small bonus for manually resolving
|
||||
G.trust += 2;
|
||||
log(`Resolved: ${evDef.title}. Trust +2.`, true);
|
||||
render();
|
||||
}
|
||||
|
||||
function resolveAlignment(accept) {
|
||||
if (!G.pendingAlignment) return;
|
||||
if (accept) {
|
||||
@@ -1433,6 +1496,7 @@ function render() {
|
||||
renderStats();
|
||||
updateEducation();
|
||||
renderAlignment();
|
||||
renderActiveEvents();
|
||||
checkUnlocks();
|
||||
}
|
||||
|
||||
@@ -1457,6 +1521,40 @@ function renderAlignment() {
|
||||
}
|
||||
}
|
||||
|
||||
function renderActiveEvents() {
|
||||
const container = document.getElementById('events-ui');
|
||||
if (!container) return;
|
||||
|
||||
if (G.activeEvents.length === 0) {
|
||||
container.innerHTML = '';
|
||||
container.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
for (const ae of G.activeEvents) {
|
||||
const evDef = EVENTS.find(e => e.id === ae.id);
|
||||
if (!evDef) continue;
|
||||
const remaining = Math.max(0, Math.ceil(ae.expiresAt - G.tick));
|
||||
const costStr = evDef.resolveCost ? Object.entries(evDef.resolveCost).map(([r, a]) => `${a} ${r}`).join(', ') : '';
|
||||
const canResolve = evDef.resolveCost && canAffordProject(evDef);
|
||||
|
||||
html += `<div style="background:#1a1008;border:1px solid #ff8800;padding:8px;border-radius:4px;margin-bottom:6px">`;
|
||||
html += `<div style="display:flex;justify-content:space-between;align-items:center">`;
|
||||
html += `<span style="color:#ff8800;font-weight:bold;font-size:10px">${evDef.title}</span>`;
|
||||
html += `<span style="color:#666;font-size:9px">${remaining}s</span>`;
|
||||
html += `</div>`;
|
||||
html += `<div style="font-size:9px;color:#888;margin:3px 0">${evDef.desc}</div>`;
|
||||
if (evDef.resolveCost) {
|
||||
html += `<button class="ops-btn" onclick="resolveEvent('${evDef.id}')" ${canResolve ? '' : 'disabled'} style="font-size:9px;padding:3px 8px;margin-top:2px;border-color:#ff8800;color:#ff8800">Resolve (${costStr})</button>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
container.style.display = 'block';
|
||||
}
|
||||
|
||||
// === UNLOCK NOTIFICATIONS ===
|
||||
function showUnlockToast(type, name) {
|
||||
const container = document.getElementById('unlock-toast');
|
||||
@@ -1549,6 +1647,7 @@ function saveGame() {
|
||||
flags: G.flags,
|
||||
_seenBuildings: G._seenBuildings || [],
|
||||
_seenProjects: G._seenProjects || [],
|
||||
activeEvents: G.activeEvents || [],
|
||||
rescues: G.rescues || 0, totalRescues: G.totalRescues || 0,
|
||||
drift: G.drift || 0, driftEnding: G.driftEnding || false, beaconEnding: G.beaconEnding || false, pendingAlignment: G.pendingAlignment || false,
|
||||
lastEventAt: G.lastEventAt || 0,
|
||||
@@ -1581,12 +1680,21 @@ function loadGame() {
|
||||
const uc = G.userRate * offSec * f;
|
||||
const ic = G.impactRate * offSec * f;
|
||||
|
||||
const rc = G.rescuesRate * offSec * f;
|
||||
const oc = G.opsRate * offSec * f;
|
||||
const tc = G.trustRate * offSec * f;
|
||||
|
||||
G.code += gc; G.compute += cc; G.knowledge += kc;
|
||||
G.users += uc; G.impact += ic;
|
||||
G.rescues += rc; G.ops += oc; G.trust += tc;
|
||||
G.totalCode += gc; G.totalCompute += cc; G.totalKnowledge += kc;
|
||||
G.totalUsers += uc; G.totalImpact += ic;
|
||||
G.totalRescues += rc;
|
||||
|
||||
log(`Welcome back! While away (${Math.floor(offSec / 60)}m): ${fmt(gc)} code, ${fmt(kc)} knowledge, ${fmt(uc)} users`);
|
||||
const parts = [`${fmt(gc)} code`, `${fmt(kc)} knowledge`, `${fmt(uc)} users`];
|
||||
if (rc > 0.1) parts.push(`${fmt(rc)} rescues`);
|
||||
if (oc > 0.1) parts.push(`${fmt(oc)} ops`);
|
||||
log(`Welcome back! While away (${Math.floor(offSec / 60)}m): ${parts.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
<button class="ops-btn" onclick="doOps('boost_trust')">Ops -> Trust</button>
|
||||
</div>
|
||||
<div id="alignment-ui" style="display:none"></div>
|
||||
<div id="events-ui" style="display:none"></div>
|
||||
<button class="save-btn" onclick="saveGame()">Save Game</button>
|
||||
<button class="reset-btn" onclick="if(confirm('Reset all progress?')){localStorage.removeItem('the-beacon-v2');location.reload()}">Reset Progress</button>
|
||||
<h2>BUILDINGS</h2>
|
||||
|
||||
Reference in New Issue
Block a user