Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions death-clock-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,81 @@ function sessionEquivalences(sessionTokens) {
// ACCELERATOR GAME — Pure Helpers
// ============================================================

// ── Company Roles ─────────────────────────────────────────────────────────────
// Human job roles that can be "replaced" by AI in the Doom Accelerator game.
// Firing each role grants passive tokens/sec and costs Doom Points.
// Sorted ascending by cost for display order.
const COMPANY_ROLES = [
{ id: 'social_media_mgr', icon: '📱', name: 'Social Media Manager', cost: 25, tps: 5_000, flavour: 'GPT handles the posts. And the engagement. And the existential dread.' },
{ id: 'copywriter', icon: '✍️', name: 'Copywriter', cost: 50, tps: 15_000, flavour: '"Content" flows at 10,000 words/minute. Zero of them are original.' },
{ id: 'data_analyst', icon: '📊', name: 'Data Analyst', cost: 100, tps: 40_000, flavour: 'The model found 47 correlations. All in the deck. None actionable.' },
{ id: 'junior_dev', icon: '💻', name: 'Junior Developer', cost: 150, tps: 80_000, flavour: 'Writes its own tests. They all pass. Nothing works.' },
{ id: 'support_team', icon: '🎧', name: 'Customer Support Team', cost: 250, tps: 150_000, flavour: 'Response time: 0 ms. Empathy: undefined.' },
{ id: 'hr_manager', icon: '📋', name: 'HR Manager', cost: 500, tps: 300_000, flavour: 'The AI now fires the AI. Recursive efficiency unlocked.' },
{ id: 'cfo', icon: '💰', name: 'Chief Financial Officer', cost: 1_500, tps: 1_000_000, flavour: 'Projections generated, reviewed, and approved by the same weights.' },
];

// ── AI Agents ─────────────────────────────────────────────────────────────────
// Passive token generators purchasable in the Doom Accelerator game.
// Multiple units of the same agent can be owned; counts stack linearly.
// Sorted ascending by cost for display order.
const AI_AGENTS = [
{ id: 'intern_bot', icon: '🤖', name: 'ChatBot Intern', cost: 5, tps: 1_000, flavour: 'Confidently wrong. Always available.' },
{ id: 'code_agent', icon: '🐒', name: 'Code Monkey Agent', cost: 30, tps: 8_000, flavour: "Opens issues about itself. Closes them. Does it again." },
{ id: 'content_farm', icon: '🏭', name: 'Content Farm Instance', cost: 150, tps: 50_000, flavour: '10,000 SEO articles/hr. Zero readers.' },
{ id: 'token_maxxer', icon: '📈', name: 'Token Maxxer v1', cost: 500, tps: 200_000, flavour: 'Optimises prompts to be longer, somehow.' },
{ id: 'vibe_coder', icon: '🎵', name: 'Vibe Coding Engine', cost: 2_000, tps: 900_000, flavour: "Doesn't compile. Vibes immaculate." },
{ id: 'ai_consultant', icon: '💼', name: 'AI Strategy Consultant', cost: 8_000, tps: 4_000_000, flavour: '200 slide decks/sec. 0 actionable insights.' },
];

// Company stage progression ordered by minimum workers replaced.
const COMPANY_STAGES = [
{ minReplaced: 0, name: 'Garage Startup', icon: '🌱' },
{ minReplaced: 1, name: 'AI-Curious Disruptor', icon: '🚀' },
{ minReplaced: 3, name: 'AI-First Pivot', icon: '🤖' },
{ minReplaced: 5, name: 'AI-Native Company', icon: '🏢' },
{ minReplaced: 7, name: 'Fully Automated Corp', icon: '☠️' },
];

/**
* Compute the total passive token generation rate (tokens/sec) from owned AI
* agents and fired (replaced) company roles.
*
* @param {Object} ownedAgents - { agentId: count } (non-integer counts are floored)
* @param {Object} replacedRoles - { roleId: true }
* @returns {number} tokens per second
*/
function computePassiveRate(ownedAgents, replacedRoles) {
const agents = (typeof ownedAgents === 'object' && ownedAgents !== null) ? ownedAgents : {};
const roles = (typeof replacedRoles === 'object' && replacedRoles !== null) ? replacedRoles : {};
let rate = 0;
AI_AGENTS.forEach((a) => {
const count = Number.isFinite(agents[a.id]) ? Math.max(0, Math.floor(agents[a.id])) : 0;
rate += count * a.tps;
});
COMPANY_ROLES.forEach((r) => {
if (roles[r.id]) rate += r.tps;
});
return rate;
}

/**
* Return the current company stage for the given number of replaced workers.
*
* @param {number} workersReplaced
* @returns {{ minReplaced: number, name: string, icon: string }}
*/
function getCompanyStage(workersReplaced) {
const count = (typeof workersReplaced === 'number' && isFinite(workersReplaced))
? Math.max(0, Math.floor(workersReplaced))
: 0;
let stage = COMPANY_STAGES[0];
for (const s of COMPANY_STAGES) {
if (count >= s.minReplaced) stage = s;
}
return stage;
}

/**
* All possible session challenge definitions.
* Each entry: { id, icon, label, desc, type, target, rewardDp }
Expand Down Expand Up @@ -606,6 +681,9 @@ const DeathClockCore = {
RATE_SCHEDULE,
SESSION_CHALLENGE_DEFS,
TOKEN_TIPS,
COMPANY_ROLES,
AI_AGENTS,
COMPANY_STAGES,
formatTokenCount,
formatTokenCountShort,
getTriggeredMilestones,
Expand All @@ -625,6 +703,8 @@ const DeathClockCore = {
computeComboMultiplier,
getSessionChallenges,
formatDoomPoints,
computePassiveRate,
getCompanyStage,
};

if (typeof module !== 'undefined' && module.exports) {
Expand Down
30 changes: 30 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,36 @@ <h2>🚀 Accelerate the Doom</h2>
<!-- populated by script.js -->
</div>

<!-- Company Stage Banner -->
<div class="accel-section-title">🏢 Build Your AI-Native Company</div>
<p class="lb-desc" style="margin-bottom:0.75rem;">
Fire your human workforce. Deploy AI agents. Token maxx your way to <em>Fully Automated Corp</em>
— all in the name of being AI-Native.
</p>
<div class="company-stage-banner" id="companyStage" aria-label="Current company stage">
<span class="company-stage-icon" id="companyStageIcon" aria-hidden="true">🌱</span>
<div>
<div class="company-stage-label">Company Stage</div>
<div class="company-stage-name" id="companyStageName">Garage Startup</div>
</div>
<div class="passive-rate-wrap">
<div class="passive-rate-label">⚙️ Passive</div>
<div class="passive-rate-value" id="passiveRateDisplay">0 tokens/sec</div>
</div>
</div>

<!-- Fire Your Human Workers -->
<div class="accel-section-title">👔 Replace Your Workforce</div>
<div id="workforcePanel" class="workforce-panel" aria-label="Workforce management — fire workers and replace with AI">
<!-- populated by script.js -->
</div>

<!-- AI Agents (passive generators) -->
<div class="accel-section-title">🤖 Deploy AI Agents</div>
<div id="agentShop" class="agent-shop" aria-label="AI agent deployment shop — passive token generation">
<!-- populated by script.js -->
</div>

<!-- Footer row: best score + share -->
<div class="accel-footer-row">
<div id="bestScoreRow" class="best-score-row">🏆 Best session: <span id="bestScoreValue">—</span></div>
Expand Down
181 changes: 180 additions & 1 deletion script.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
RATE_SCHEDULE,
SESSION_CHALLENGE_DEFS,
TOKEN_TIPS,
COMPANY_ROLES,
AI_AGENTS,
formatTokenCount,
formatTokenCountShort,
getTriggeredMilestones,
Expand All @@ -36,6 +38,8 @@
computeComboMultiplier,
getSessionChallenges,
formatDoomPoints,
computePassiveRate,
getCompanyStage,
} = window.DeathClockCore;

// ---- State -----------------------------------------------
Expand Down Expand Up @@ -1658,6 +1662,11 @@
{ id: 'first_blood', icon: '\uD83C\uDFC1', name: 'First Blood', desc: 'Personally triggered your first milestone.', type: 'manual' },
{ id: 'apex_accelerant', icon: '\u2620\uFE0F', name: 'Apex Accelerant', desc: 'Personally triggered 5 milestones.', type: 'manual' },
{ id: 'bragging_rights', icon: '\uD83D\uDCE4', name: 'Bragging Rights', desc: 'Shared your personal acceleration total.', type: 'manual' },
// AI-Native company badges
{ id: 'layoff_legend', icon: '📤', name: 'Layoff Legend', desc: 'Replaced your first human worker with AI.', type: 'manual' },
{ id: 'token_maxxer_badge', icon: '📈', name: 'Token Maxxer', desc: 'Deployed your first AI agent.', type: 'manual' },
{ id: 'ai_native_ceo', icon: '🏢', name: 'AI-Native CEO', desc: 'Reached AI-Native Company stage.', type: 'manual' },
{ id: 'lights_out', icon: '☠️', name: 'Lights Out', desc: 'Replaced every human worker. Fully automated.', type: 'manual' },
];

const LS_BADGES_KEY = 'tokenDeathclockBadges';
Expand Down Expand Up @@ -1812,6 +1821,7 @@

const LS_UPGRADES_KEY = 'tokenDeathclockUpgrades';
const LS_BESTSCORE_KEY = 'tokenDeathclockBestScore';
const LS_COMPANY_KEY = 'tokenDeathclockCompany';

// ---- State -----------------------------------------------

Expand All @@ -1834,6 +1844,10 @@
_comboResetTimer: null,
_speedSecond: { taps: 0, ts: 0 }, // taps in current 1-second bucket
_speedStreak: 0, // consecutive 1-sec buckets with ≥ 10 taps
// Company / AI-Native
replacedWorkers: {}, // roleId → true
ownedAgents: {}, // agentId → count
passiveRate: 0, // tokens/sec from passive generators
};

// ---- Persistence -----------------------------------------
Expand All @@ -1850,13 +1864,34 @@
const bs = parseFloat(localStorage.getItem(LS_BESTSCORE_KEY) || '0');
if (isFinite(bs) && bs > 0) acc.bestScore = bs;
} catch (_) { /* ignore */ }
// Recompute tap multiplier from owned upgrades
try {
const raw = localStorage.getItem(LS_COMPANY_KEY);
if (raw) {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') {
if (parsed.replacedWorkers && typeof parsed.replacedWorkers === 'object') {
acc.replacedWorkers = parsed.replacedWorkers;
}
if (parsed.ownedAgents && typeof parsed.ownedAgents === 'object') {
acc.ownedAgents = parsed.ownedAgents;
}
}
}
} catch (_) { /* ignore */ }
// Recompute tap multiplier and passive rate from persisted state
acc.tapMultiplier = currentTapMultiplier();
acc.passiveRate = computePassiveRate(acc.ownedAgents, acc.replacedWorkers);
}

function saveAcceleratorState() {
try { localStorage.setItem(LS_UPGRADES_KEY, JSON.stringify(acc.ownedUpgrades)); } catch (_) { /* ignore */ }
try { localStorage.setItem(LS_BESTSCORE_KEY, String(acc.bestScore)); } catch (_) { /* ignore */ }
try {
localStorage.setItem(LS_COMPANY_KEY, JSON.stringify({
replacedWorkers: acc.replacedWorkers,
ownedAgents: acc.ownedAgents,
}));
} catch (_) { /* ignore */ }
}

function currentTapMultiplier() {
Expand Down Expand Up @@ -2045,10 +2080,16 @@
: impact.waterL.toFixed(2) + ' L';
setAccelText('accelWater', waterVal);

// Passive rate display
setAccelText('passiveRateDisplay', formatTokenCount(acc.passiveRate) + ' tokens/sec');

updateComboDisplay();
updateMilestoneRace();
updateBestScore();
renderUpgradeShop();
renderWorkforcePanel();
renderAgentShop();
updateCompanyStage();

// Show/hide share button
const shareBtn = document.getElementById('shareAccelerationBtn');
Expand Down Expand Up @@ -2193,6 +2234,139 @@
});
}

// ---- Workforce panel (fire workers) ----------------------

function fireWorker(id) {
const role = COMPANY_ROLES.find((r) => r.id === id);
if (!role || acc.replacedWorkers[id]) return;
if (acc.doomPoints < role.cost) return;
acc.doomPoints -= role.cost;
acc.replacedWorkers[id] = true;
acc.passiveRate = computePassiveRate(acc.ownedAgents, acc.replacedWorkers);
saveAcceleratorState();
queueToast({
icon: role.icon,
name: 'Role Automated: ' + role.name,
desc: role.flavour + ' (+' + formatTokenCount(role.tps) + '/sec)',
});
updateAcceleratorUI();
renderWorkforcePanel();
checkCompanyAchievements();
updateChallengeProgress();
}

function renderWorkforcePanel() {
const panel = document.getElementById('workforcePanel');
if (!panel) return;
panel.innerHTML = '';
COMPANY_ROLES.forEach((r) => {
const fired = !!acc.replacedWorkers[r.id];
const affordable = !fired && acc.doomPoints >= r.cost;
const card = document.createElement('button');
card.className = 'worker-card' +
(fired ? ' fired' : '') +
(!fired && !affordable ? ' unaffordable' : '');
card.setAttribute('aria-label', fired
? r.name + ' (automated)'
: r.name + ' — fire for ' + r.cost + ' DP'
);
if (fired) card.setAttribute('aria-disabled', 'true');
card.innerHTML = `
<span class="worker-card-icon" aria-hidden="true">${escHtml(r.icon)}</span>
<div class="worker-card-name">${escHtml(r.name)}</div>
<div class="worker-card-flavour">${escHtml(r.flavour)}</div>
<div class="worker-card-tps">+${escHtml(formatTokenCount(r.tps))}/sec</div>
<div class="worker-card-cost">${fired ? '🤖 Automated' : escHtml(String(r.cost)) + ' DP'}</div>`;
if (!fired) {
card.addEventListener('click', () => fireWorker(r.id));
}
panel.appendChild(card);
});
}

// ---- AI Agent shop (passive generators) ------------------

function purchaseAgent(id) {
const agent = AI_AGENTS.find((a) => a.id === id);
if (!agent) return;
if (acc.doomPoints < agent.cost) return;
acc.doomPoints -= agent.cost;
acc.ownedAgents[id] = (acc.ownedAgents[id] || 0) + 1;
acc.passiveRate = computePassiveRate(acc.ownedAgents, acc.replacedWorkers);
saveAcceleratorState();
updateAcceleratorUI();
renderAgentShop();
checkCompanyAchievements();
updateChallengeProgress();
}

function renderAgentShop() {
const shop = document.getElementById('agentShop');
if (!shop) return;
shop.innerHTML = '';
AI_AGENTS.forEach((a) => {
const count = acc.ownedAgents[a.id] || 0;
const affordable = acc.doomPoints >= a.cost;
const card = document.createElement('button');
card.className = 'agent-card' + (!affordable ? ' unaffordable' : '');
card.setAttribute('aria-label',
a.name + (count ? ' (×' + count + ' owned)' : '') + ' — costs ' + a.cost + ' DP'
);
card.innerHTML = `
<span class="agent-card-icon" aria-hidden="true">${escHtml(a.icon)}</span>
<div class="agent-card-name">${escHtml(a.name)}</div>
<div class="agent-card-flavour">${escHtml(a.flavour)}</div>
<div class="agent-card-tps">+${escHtml(formatTokenCount(a.tps))}/sec each</div>
<div class="agent-card-cost">${escHtml(String(a.cost))} DP</div>
${count ? `<div class="agent-card-owned">\u00D7${count} deployed</div>` : ''}`;
card.addEventListener('click', () => purchaseAgent(a.id));
shop.appendChild(card);
});
}

// ---- Company stage display --------------------------------

function updateCompanyStage() {
const replaced = Object.keys(acc.replacedWorkers).length;
const stage = getCompanyStage(replaced);
const iconEl = document.getElementById('companyStageIcon');
const nameEl = document.getElementById('companyStageName');
if (iconEl) iconEl.textContent = stage.icon;
if (nameEl) nameEl.textContent = stage.name;
}

// ---- Company achievements --------------------------------

function checkCompanyAchievements() {
const replaced = Object.keys(acc.replacedWorkers).length;
if (replaced >= 1) awardBadge('layoff_legend');
if (replaced >= 5) awardBadge('ai_native_ceo');
if (replaced >= COMPANY_ROLES.length) awardBadge('lights_out');
const hasAgent = AI_AGENTS.some((a) => (acc.ownedAgents[a.id] || 0) > 0);
if (hasAgent) awardBadge('token_maxxer_badge');
}

// ---- Passive token generation loop -----------------------

function startPassiveLoop() {
// Tick every 200 ms — add passiveRate × 0.2 tokens per tick.
// Only update the minimal counter elements here; full UI re-renders
// (shop affordability, challenge progress bars) happen via handleTap() and
// purchase actions so we avoid heavy DOM churn every 200 ms.
setInterval(() => {
if (acc.passiveRate <= 0) return;
const tokensAdded = acc.passiveRate * 0.2;
acc.personalTokens += tokensAdded;
acc.doomPoints += tokensAdded * ACC_DP_PER_TOKEN;
// Update only the lightweight numeric displays
setAccelText('accelTokens', formatTokenCount(acc.personalTokens));
setAccelText('accelDp', formatDoomPoints(acc.doomPoints));
updateMilestoneRace();
updateBestScore();
updateChallengeProgress();
}, 200);
}

function renderChallenges() {
const row = document.getElementById('challengeRow');
if (!row) return;
Expand Down Expand Up @@ -2277,6 +2451,9 @@
initChallengeProgress();
renderChallenges();
renderUpgradeShop();
renderWorkforcePanel();
renderAgentShop();
updateCompanyStage();
updateAcceleratorUI();
// Show best score from storage
const valueEl = document.getElementById('bestScoreValue');
Expand Down Expand Up @@ -2307,6 +2484,8 @@

// Combo reset timer
startComboResetLoop();
// Passive token generation loop
startPassiveLoop();
}

// ---- Bootstrap ------------------------------------------
Expand Down
Loading
Loading