beacon: add ops overflow auto-conversion to code
When Operations exceed 80% of max capacity, excess ops automatically drain into Code at 2 ops/sec (10:1 ratio with code boost). This prevents ops from sitting idle at the cap and gives the early game smoother flow. Visual indicator shows 'overflow -> code' in the ops rate display when active. No log spam - just works silently in the background.
This commit is contained in:
132
game.js
132
game.js
@@ -118,6 +118,14 @@ const G = {
|
||||
// Bulk buy multiplier (1, 10, or -1 for max)
|
||||
buyAmount: 1,
|
||||
|
||||
// Code Sprint ability
|
||||
sprintActive: false,
|
||||
sprintTimer: 0, // seconds remaining on active sprint
|
||||
sprintCooldown: 0, // seconds until sprint available again
|
||||
sprintDuration: 10, // seconds of boost
|
||||
sprintCooldownMax: 60,// seconds cooldown
|
||||
sprintMult: 10, // code multiplier during sprint
|
||||
|
||||
// Time tracking
|
||||
playTime: 0,
|
||||
startTime: 0
|
||||
@@ -1027,8 +1035,24 @@ function tick() {
|
||||
G.creativity += G.creativityRate * dt;
|
||||
}
|
||||
|
||||
// Ops overflow: auto-convert excess ops to code when near cap
|
||||
// Prevents ops from sitting idle at max — every operation becomes code
|
||||
if (G.ops > G.maxOps * 0.8) {
|
||||
const overflowDrain = Math.min(2 * dt, G.ops - G.maxOps * 0.8);
|
||||
G.ops -= overflowDrain;
|
||||
const codeGain = overflowDrain * 10 * G.codeBoost;
|
||||
G.code += codeGain;
|
||||
G.totalCode += codeGain;
|
||||
G.opsOverflowActive = true;
|
||||
} else {
|
||||
G.opsOverflowActive = false;
|
||||
}
|
||||
|
||||
G.tick += dt;
|
||||
|
||||
// Sprint ability
|
||||
tickSprint(dt);
|
||||
|
||||
// Combo decay
|
||||
if (G.comboCount > 0) {
|
||||
G.comboTimer -= dt;
|
||||
@@ -1479,6 +1503,33 @@ function doOps(action) {
|
||||
render();
|
||||
}
|
||||
|
||||
function activateSprint() {
|
||||
if (G.sprintActive || G.sprintCooldown > 0) return;
|
||||
G.sprintActive = true;
|
||||
G.sprintTimer = G.sprintDuration;
|
||||
G.codeBoost *= G.sprintMult;
|
||||
updateRates();
|
||||
log('CODE SPRINT! 10x code production for 10 seconds!', true);
|
||||
render();
|
||||
}
|
||||
|
||||
function tickSprint(dt) {
|
||||
if (G.sprintActive) {
|
||||
G.sprintTimer -= dt;
|
||||
if (G.sprintTimer <= 0) {
|
||||
G.sprintActive = false;
|
||||
G.sprintTimer = 0;
|
||||
G.sprintCooldown = G.sprintCooldownMax;
|
||||
G.codeBoost /= G.sprintMult;
|
||||
updateRates();
|
||||
log('Sprint ended. Cooling down...');
|
||||
}
|
||||
} else if (G.sprintCooldown > 0) {
|
||||
G.sprintCooldown -= dt;
|
||||
if (G.sprintCooldown < 0) G.sprintCooldown = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// === RENDERING ===
|
||||
function renderResources() {
|
||||
const set = (id, val, rate) => {
|
||||
@@ -1494,6 +1545,11 @@ function renderResources() {
|
||||
set('r-users', G.users, G.userRate);
|
||||
set('r-impact', G.impact, G.impactRate);
|
||||
set('r-ops', G.ops, G.opsRate);
|
||||
// Show ops overflow indicator
|
||||
const opsRateEl = document.getElementById('r-ops-rate');
|
||||
if (opsRateEl && G.opsOverflowActive) {
|
||||
opsRateEl.innerHTML = `<span style="color:#ff8c00">▲ overflow → code</span>`;
|
||||
}
|
||||
set('r-trust', G.trust, G.trustRate);
|
||||
set('r-harmony', G.harmony, G.harmonyRate);
|
||||
|
||||
@@ -1891,6 +1947,53 @@ function renderDebuffs() {
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderSprint() {
|
||||
const container = document.getElementById('sprint-container');
|
||||
const btn = document.getElementById('sprint-btn');
|
||||
const barWrap = document.getElementById('sprint-bar-wrap');
|
||||
const bar = document.getElementById('sprint-bar');
|
||||
const label = document.getElementById('sprint-label');
|
||||
if (!container || !btn) return;
|
||||
|
||||
// Show sprint UI once player has at least 1 autocoder
|
||||
if (G.buildings.autocoder < 1) {
|
||||
container.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
container.style.display = 'block';
|
||||
|
||||
if (G.sprintActive) {
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.6';
|
||||
btn.textContent = `⚡ SPRINTING — ${Math.ceil(G.sprintTimer)}s`;
|
||||
btn.style.borderColor = '#ff8c00';
|
||||
btn.style.color = '#ff8c00';
|
||||
barWrap.style.display = 'block';
|
||||
bar.style.width = (G.sprintTimer / G.sprintDuration * 100) + '%';
|
||||
label.textContent = `10x CODE • ${fmt(G.codeRate)}/s`;
|
||||
label.style.color = '#ff8c00';
|
||||
} else if (G.sprintCooldown > 0) {
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.4';
|
||||
btn.textContent = `⚡ COOLING DOWN — ${Math.ceil(G.sprintCooldown)}s`;
|
||||
btn.style.borderColor = '#555';
|
||||
btn.style.color = '#555';
|
||||
barWrap.style.display = 'block';
|
||||
bar.style.width = ((G.sprintCooldownMax - G.sprintCooldown) / G.sprintCooldownMax * 100) + '%';
|
||||
label.textContent = 'Ready soon...';
|
||||
label.style.color = '#555';
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
btn.textContent = '⚡ CODE SPRINT — 10x Code for 10s';
|
||||
btn.style.borderColor = '#ffd700';
|
||||
btn.style.color = '#ffd700';
|
||||
barWrap.style.display = 'none';
|
||||
label.textContent = 'Press S or click to activate';
|
||||
label.style.color = '#666';
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderResources();
|
||||
renderPhase();
|
||||
@@ -1902,6 +2005,7 @@ function render() {
|
||||
renderProgress();
|
||||
renderCombo();
|
||||
renderDebuffs();
|
||||
renderSprint();
|
||||
}
|
||||
|
||||
function renderAlignment() {
|
||||
@@ -1995,6 +2099,9 @@ function saveGame() {
|
||||
activeDebuffIds: debuffIds,
|
||||
totalEventsResolved: G.totalEventsResolved || 0,
|
||||
buyAmount: G.buyAmount || 1,
|
||||
sprintActive: G.sprintActive || false,
|
||||
sprintTimer: G.sprintTimer || 0,
|
||||
sprintCooldown: G.sprintCooldown || 0,
|
||||
savedAt: Date.now()
|
||||
};
|
||||
|
||||
@@ -2010,6 +2117,28 @@ function loadGame() {
|
||||
const data = JSON.parse(raw);
|
||||
Object.assign(G, data);
|
||||
|
||||
// Restore sprint state properly
|
||||
// codeBoost was saved with the sprint multiplier baked in
|
||||
if (data.sprintActive) {
|
||||
// Sprint was active when saved — check if it expired during offline time
|
||||
const offSec = data.savedAt ? (Date.now() - data.savedAt) / 1000 : 0;
|
||||
const remaining = (data.sprintTimer || 0) - offSec;
|
||||
if (remaining > 0) {
|
||||
// Sprint still going — keep boost, update timer
|
||||
G.sprintActive = true;
|
||||
G.sprintTimer = remaining;
|
||||
G.sprintCooldown = 0;
|
||||
} else {
|
||||
// Sprint expired during offline — remove boost, start cooldown
|
||||
G.sprintActive = false;
|
||||
G.sprintTimer = 0;
|
||||
G.codeBoost /= G.sprintMult;
|
||||
const cdRemaining = G.sprintCooldownMax + remaining; // remaining is negative
|
||||
G.sprintCooldown = Math.max(0, cdRemaining);
|
||||
}
|
||||
}
|
||||
// If not sprintActive at save time, codeBoost is correct as-is
|
||||
|
||||
// Reconstitute active debuffs from saved IDs (functions can't be JSON-parsed)
|
||||
if (data.activeDebuffIds && data.activeDebuffIds.length > 0) {
|
||||
G.activeDebuffs = [];
|
||||
@@ -2109,7 +2238,7 @@ function initGame() {
|
||||
log('Click WRITE CODE or press SPACE to start.');
|
||||
log('Build AutoCode for passive production.');
|
||||
log('Watch for Research Projects to appear.');
|
||||
log('Keys: SPACE=Code 1=Ops->Code 2=Ops->Compute 3=Ops->Knowledge 4=Ops->Trust B=Buy x1/x10/MAX');
|
||||
log('Keys: SPACE=Code S=Code Sprint 1=Ops->Code 2=Ops->Compute 3=Ops->Knowledge 4=Ops->Trust B=Buy x1/x10/MAX');
|
||||
}
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
@@ -2156,4 +2285,5 @@ window.addEventListener('keydown', function (e) {
|
||||
else if (G.buyAmount === 10) setBuyAmount(-1);
|
||||
else setBuyAmount(1);
|
||||
}
|
||||
if (e.code === 'KeyS') activateSprint();
|
||||
});
|
||||
|
||||
@@ -124,6 +124,11 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
<button class="ops-btn" onclick="doOps('boost_knowledge')">Ops -> Knowledge</button>
|
||||
<button class="ops-btn" onclick="doOps('boost_trust')">Ops -> Trust</button>
|
||||
</div>
|
||||
<div id="sprint-container" style="display:none;margin-top:6px">
|
||||
<button id="sprint-btn" class="main-btn" onclick="activateSprint()" style="font-size:11px;padding:8px 10px;border-color:#ffd700;color:#ffd700;width:100%">⚡ CODE SPRINT — 10x Code for 10s</button>
|
||||
<div id="sprint-bar-wrap" style="display:none;margin-top:4px;height:4px;background:#111;border-radius:2px;overflow:hidden"><div id="sprint-bar" style="height:100%;background:linear-gradient(90deg,#ffd700,#ff8c00);border-radius:2px;transition:width 0.1s"></div></div>
|
||||
<div id="sprint-label" style="font-size:9px;color:#666;margin-top:2px;text-align:center"></div>
|
||||
</div>
|
||||
<div id="alignment-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>
|
||||
|
||||
Reference in New Issue
Block a user