Skip to content

Commit 707cb67

Browse files
authored
Merge pull request #28 from nitrocode/copilot/ai-native-incorporate-token-maxxing
feat: AI-Native company building mini-game in Doom Accelerator
2 parents a8b6d32 + 595d062 commit 707cb67

5 files changed

Lines changed: 700 additions & 2 deletions

File tree

‎death-clock-core.js‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,81 @@ function sessionEquivalences(sessionTokens) {
524524
// ACCELERATOR GAME — Pure Helpers
525525
// ============================================================
526526

527+
// ── Company Roles ─────────────────────────────────────────────────────────────
528+
// Human job roles that can be "replaced" by AI in the Doom Accelerator game.
529+
// Firing each role grants passive tokens/sec and costs Doom Points.
530+
// Sorted ascending by cost for display order.
531+
const COMPANY_ROLES = [
532+
{ 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.' },
533+
{ id: 'copywriter', icon: '✍️', name: 'Copywriter', cost: 50, tps: 15_000, flavour: '"Content" flows at 10,000 words/minute. Zero of them are original.' },
534+
{ id: 'data_analyst', icon: '📊', name: 'Data Analyst', cost: 100, tps: 40_000, flavour: 'The model found 47 correlations. All in the deck. None actionable.' },
535+
{ id: 'junior_dev', icon: '💻', name: 'Junior Developer', cost: 150, tps: 80_000, flavour: 'Writes its own tests. They all pass. Nothing works.' },
536+
{ id: 'support_team', icon: '🎧', name: 'Customer Support Team', cost: 250, tps: 150_000, flavour: 'Response time: 0 ms. Empathy: undefined.' },
537+
{ id: 'hr_manager', icon: '📋', name: 'HR Manager', cost: 500, tps: 300_000, flavour: 'The AI now fires the AI. Recursive efficiency unlocked.' },
538+
{ id: 'cfo', icon: '💰', name: 'Chief Financial Officer', cost: 1_500, tps: 1_000_000, flavour: 'Projections generated, reviewed, and approved by the same weights.' },
539+
];
540+
541+
// ── AI Agents ─────────────────────────────────────────────────────────────────
542+
// Passive token generators purchasable in the Doom Accelerator game.
543+
// Multiple units of the same agent can be owned; counts stack linearly.
544+
// Sorted ascending by cost for display order.
545+
const AI_AGENTS = [
546+
{ id: 'intern_bot', icon: '🤖', name: 'ChatBot Intern', cost: 5, tps: 1_000, flavour: 'Confidently wrong. Always available.' },
547+
{ id: 'code_agent', icon: '🐒', name: 'Code Monkey Agent', cost: 30, tps: 8_000, flavour: "Opens issues about itself. Closes them. Does it again." },
548+
{ id: 'content_farm', icon: '🏭', name: 'Content Farm Instance', cost: 150, tps: 50_000, flavour: '10,000 SEO articles/hr. Zero readers.' },
549+
{ id: 'token_maxxer', icon: '📈', name: 'Token Maxxer v1', cost: 500, tps: 200_000, flavour: 'Optimises prompts to be longer, somehow.' },
550+
{ id: 'vibe_coder', icon: '🎵', name: 'Vibe Coding Engine', cost: 2_000, tps: 900_000, flavour: "Doesn't compile. Vibes immaculate." },
551+
{ id: 'ai_consultant', icon: '💼', name: 'AI Strategy Consultant', cost: 8_000, tps: 4_000_000, flavour: '200 slide decks/sec. 0 actionable insights.' },
552+
];
553+
554+
// Company stage progression ordered by minimum workers replaced.
555+
const COMPANY_STAGES = [
556+
{ minReplaced: 0, name: 'Garage Startup', icon: '🌱' },
557+
{ minReplaced: 1, name: 'AI-Curious Disruptor', icon: '🚀' },
558+
{ minReplaced: 3, name: 'AI-First Pivot', icon: '🤖' },
559+
{ minReplaced: 5, name: 'AI-Native Company', icon: '🏢' },
560+
{ minReplaced: 7, name: 'Fully Automated Corp', icon: '☠️' },
561+
];
562+
563+
/**
564+
* Compute the total passive token generation rate (tokens/sec) from owned AI
565+
* agents and fired (replaced) company roles.
566+
*
567+
* @param {Object} ownedAgents - { agentId: count } (non-integer counts are floored)
568+
* @param {Object} replacedRoles - { roleId: true }
569+
* @returns {number} tokens per second
570+
*/
571+
function computePassiveRate(ownedAgents, replacedRoles) {
572+
const agents = (typeof ownedAgents === 'object' && ownedAgents !== null) ? ownedAgents : {};
573+
const roles = (typeof replacedRoles === 'object' && replacedRoles !== null) ? replacedRoles : {};
574+
let rate = 0;
575+
AI_AGENTS.forEach((a) => {
576+
const count = Number.isFinite(agents[a.id]) ? Math.max(0, Math.floor(agents[a.id])) : 0;
577+
rate += count * a.tps;
578+
});
579+
COMPANY_ROLES.forEach((r) => {
580+
if (roles[r.id]) rate += r.tps;
581+
});
582+
return rate;
583+
}
584+
585+
/**
586+
* Return the current company stage for the given number of replaced workers.
587+
*
588+
* @param {number} workersReplaced
589+
* @returns {{ minReplaced: number, name: string, icon: string }}
590+
*/
591+
function getCompanyStage(workersReplaced) {
592+
const count = (typeof workersReplaced === 'number' && isFinite(workersReplaced))
593+
? Math.max(0, Math.floor(workersReplaced))
594+
: 0;
595+
let stage = COMPANY_STAGES[0];
596+
for (const s of COMPANY_STAGES) {
597+
if (count >= s.minReplaced) stage = s;
598+
}
599+
return stage;
600+
}
601+
527602
/**
528603
* All possible session challenge definitions.
529604
* Each entry: { id, icon, label, desc, type, target, rewardDp }
@@ -606,6 +681,9 @@ const DeathClockCore = {
606681
RATE_SCHEDULE,
607682
SESSION_CHALLENGE_DEFS,
608683
TOKEN_TIPS,
684+
COMPANY_ROLES,
685+
AI_AGENTS,
686+
COMPANY_STAGES,
609687
formatTokenCount,
610688
formatTokenCountShort,
611689
getTriggeredMilestones,
@@ -625,6 +703,8 @@ const DeathClockCore = {
625703
computeComboMultiplier,
626704
getSessionChallenges,
627705
formatDoomPoints,
706+
computePassiveRate,
707+
getCompanyStage,
628708
};
629709

630710
if (typeof module !== 'undefined' && module.exports) {

‎index.html‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,36 @@ <h2>🚀 Accelerate the Doom</h2>
371371
<!-- populated by script.js -->
372372
</div>
373373

374+
<!-- Company Stage Banner -->
375+
<div class="accel-section-title">🏢 Build Your AI-Native Company</div>
376+
<p class="lb-desc" style="margin-bottom:0.75rem;">
377+
Fire your human workforce. Deploy AI agents. Token maxx your way to <em>Fully Automated Corp</em>
378+
— all in the name of being AI-Native.
379+
</p>
380+
<div class="company-stage-banner" id="companyStage" aria-label="Current company stage">
381+
<span class="company-stage-icon" id="companyStageIcon" aria-hidden="true">🌱</span>
382+
<div>
383+
<div class="company-stage-label">Company Stage</div>
384+
<div class="company-stage-name" id="companyStageName">Garage Startup</div>
385+
</div>
386+
<div class="passive-rate-wrap">
387+
<div class="passive-rate-label">⚙️ Passive</div>
388+
<div class="passive-rate-value" id="passiveRateDisplay">0 tokens/sec</div>
389+
</div>
390+
</div>
391+
392+
<!-- Fire Your Human Workers -->
393+
<div class="accel-section-title">👔 Replace Your Workforce</div>
394+
<div id="workforcePanel" class="workforce-panel" aria-label="Workforce management — fire workers and replace with AI">
395+
<!-- populated by script.js -->
396+
</div>
397+
398+
<!-- AI Agents (passive generators) -->
399+
<div class="accel-section-title">🤖 Deploy AI Agents</div>
400+
<div id="agentShop" class="agent-shop" aria-label="AI agent deployment shop — passive token generation">
401+
<!-- populated by script.js -->
402+
</div>
403+
374404
<!-- Footer row: best score + share -->
375405
<div class="accel-footer-row">
376406
<div id="bestScoreRow" class="best-score-row">🏆 Best session: <span id="bestScoreValue">—</span></div>

‎script.js‎

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
RATE_SCHEDULE,
1818
SESSION_CHALLENGE_DEFS,
1919
TOKEN_TIPS,
20+
COMPANY_ROLES,
21+
AI_AGENTS,
2022
formatTokenCount,
2123
formatTokenCountShort,
2224
getTriggeredMilestones,
@@ -36,6 +38,8 @@
3638
computeComboMultiplier,
3739
getSessionChallenges,
3840
formatDoomPoints,
41+
computePassiveRate,
42+
getCompanyStage,
3943
} = window.DeathClockCore;
4044

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

16631672
const LS_BADGES_KEY = 'tokenDeathclockBadges';
@@ -1812,6 +1821,7 @@
18121821

18131822
const LS_UPGRADES_KEY = 'tokenDeathclockUpgrades';
18141823
const LS_BESTSCORE_KEY = 'tokenDeathclockBestScore';
1824+
const LS_COMPANY_KEY = 'tokenDeathclockCompany';
18151825

18161826
// ---- State -----------------------------------------------
18171827

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

18391853
// ---- Persistence -----------------------------------------
@@ -1850,13 +1864,34 @@
18501864
const bs = parseFloat(localStorage.getItem(LS_BESTSCORE_KEY) || '0');
18511865
if (isFinite(bs) && bs > 0) acc.bestScore = bs;
18521866
} catch (_) { /* ignore */ }
1853-
// Recompute tap multiplier from owned upgrades
1867+
try {
1868+
const raw = localStorage.getItem(LS_COMPANY_KEY);
1869+
if (raw) {
1870+
const parsed = JSON.parse(raw);
1871+
if (parsed && typeof parsed === 'object') {
1872+
if (parsed.replacedWorkers && typeof parsed.replacedWorkers === 'object') {
1873+
acc.replacedWorkers = parsed.replacedWorkers;
1874+
}
1875+
if (parsed.ownedAgents && typeof parsed.ownedAgents === 'object') {
1876+
acc.ownedAgents = parsed.ownedAgents;
1877+
}
1878+
}
1879+
}
1880+
} catch (_) { /* ignore */ }
1881+
// Recompute tap multiplier and passive rate from persisted state
18541882
acc.tapMultiplier = currentTapMultiplier();
1883+
acc.passiveRate = computePassiveRate(acc.ownedAgents, acc.replacedWorkers);
18551884
}
18561885

18571886
function saveAcceleratorState() {
18581887
try { localStorage.setItem(LS_UPGRADES_KEY, JSON.stringify(acc.ownedUpgrades)); } catch (_) { /* ignore */ }
18591888
try { localStorage.setItem(LS_BESTSCORE_KEY, String(acc.bestScore)); } catch (_) { /* ignore */ }
1889+
try {
1890+
localStorage.setItem(LS_COMPANY_KEY, JSON.stringify({
1891+
replacedWorkers: acc.replacedWorkers,
1892+
ownedAgents: acc.ownedAgents,
1893+
}));
1894+
} catch (_) { /* ignore */ }
18601895
}
18611896

18621897
function currentTapMultiplier() {
@@ -2045,10 +2080,16 @@
20452080
: impact.waterL.toFixed(2) + ' L';
20462081
setAccelText('accelWater', waterVal);
20472082

2083+
// Passive rate display
2084+
setAccelText('passiveRateDisplay', formatTokenCount(acc.passiveRate) + ' tokens/sec');
2085+
20482086
updateComboDisplay();
20492087
updateMilestoneRace();
20502088
updateBestScore();
20512089
renderUpgradeShop();
2090+
renderWorkforcePanel();
2091+
renderAgentShop();
2092+
updateCompanyStage();
20522093

20532094
// Show/hide share button
20542095
const shareBtn = document.getElementById('shareAccelerationBtn');
@@ -2193,6 +2234,139 @@
21932234
});
21942235
}
21952236

2237+
// ---- Workforce panel (fire workers) ----------------------
2238+
2239+
function fireWorker(id) {
2240+
const role = COMPANY_ROLES.find((r) => r.id === id);
2241+
if (!role || acc.replacedWorkers[id]) return;
2242+
if (acc.doomPoints < role.cost) return;
2243+
acc.doomPoints -= role.cost;
2244+
acc.replacedWorkers[id] = true;
2245+
acc.passiveRate = computePassiveRate(acc.ownedAgents, acc.replacedWorkers);
2246+
saveAcceleratorState();
2247+
queueToast({
2248+
icon: role.icon,
2249+
name: 'Role Automated: ' + role.name,
2250+
desc: role.flavour + ' (+' + formatTokenCount(role.tps) + '/sec)',
2251+
});
2252+
updateAcceleratorUI();
2253+
renderWorkforcePanel();
2254+
checkCompanyAchievements();
2255+
updateChallengeProgress();
2256+
}
2257+
2258+
function renderWorkforcePanel() {
2259+
const panel = document.getElementById('workforcePanel');
2260+
if (!panel) return;
2261+
panel.innerHTML = '';
2262+
COMPANY_ROLES.forEach((r) => {
2263+
const fired = !!acc.replacedWorkers[r.id];
2264+
const affordable = !fired && acc.doomPoints >= r.cost;
2265+
const card = document.createElement('button');
2266+
card.className = 'worker-card' +
2267+
(fired ? ' fired' : '') +
2268+
(!fired && !affordable ? ' unaffordable' : '');
2269+
card.setAttribute('aria-label', fired
2270+
? r.name + ' (automated)'
2271+
: r.name + ' — fire for ' + r.cost + ' DP'
2272+
);
2273+
if (fired) card.setAttribute('aria-disabled', 'true');
2274+
card.innerHTML = `
2275+
<span class="worker-card-icon" aria-hidden="true">${escHtml(r.icon)}</span>
2276+
<div class="worker-card-name">${escHtml(r.name)}</div>
2277+
<div class="worker-card-flavour">${escHtml(r.flavour)}</div>
2278+
<div class="worker-card-tps">+${escHtml(formatTokenCount(r.tps))}/sec</div>
2279+
<div class="worker-card-cost">${fired ? '🤖 Automated' : escHtml(String(r.cost)) + ' DP'}</div>`;
2280+
if (!fired) {
2281+
card.addEventListener('click', () => fireWorker(r.id));
2282+
}
2283+
panel.appendChild(card);
2284+
});
2285+
}
2286+
2287+
// ---- AI Agent shop (passive generators) ------------------
2288+
2289+
function purchaseAgent(id) {
2290+
const agent = AI_AGENTS.find((a) => a.id === id);
2291+
if (!agent) return;
2292+
if (acc.doomPoints < agent.cost) return;
2293+
acc.doomPoints -= agent.cost;
2294+
acc.ownedAgents[id] = (acc.ownedAgents[id] || 0) + 1;
2295+
acc.passiveRate = computePassiveRate(acc.ownedAgents, acc.replacedWorkers);
2296+
saveAcceleratorState();
2297+
updateAcceleratorUI();
2298+
renderAgentShop();
2299+
checkCompanyAchievements();
2300+
updateChallengeProgress();
2301+
}
2302+
2303+
function renderAgentShop() {
2304+
const shop = document.getElementById('agentShop');
2305+
if (!shop) return;
2306+
shop.innerHTML = '';
2307+
AI_AGENTS.forEach((a) => {
2308+
const count = acc.ownedAgents[a.id] || 0;
2309+
const affordable = acc.doomPoints >= a.cost;
2310+
const card = document.createElement('button');
2311+
card.className = 'agent-card' + (!affordable ? ' unaffordable' : '');
2312+
card.setAttribute('aria-label',
2313+
a.name + (count ? ' (×' + count + ' owned)' : '') + ' — costs ' + a.cost + ' DP'
2314+
);
2315+
card.innerHTML = `
2316+
<span class="agent-card-icon" aria-hidden="true">${escHtml(a.icon)}</span>
2317+
<div class="agent-card-name">${escHtml(a.name)}</div>
2318+
<div class="agent-card-flavour">${escHtml(a.flavour)}</div>
2319+
<div class="agent-card-tps">+${escHtml(formatTokenCount(a.tps))}/sec each</div>
2320+
<div class="agent-card-cost">${escHtml(String(a.cost))} DP</div>
2321+
${count ? `<div class="agent-card-owned">\u00D7${count} deployed</div>` : ''}`;
2322+
card.addEventListener('click', () => purchaseAgent(a.id));
2323+
shop.appendChild(card);
2324+
});
2325+
}
2326+
2327+
// ---- Company stage display --------------------------------
2328+
2329+
function updateCompanyStage() {
2330+
const replaced = Object.keys(acc.replacedWorkers).length;
2331+
const stage = getCompanyStage(replaced);
2332+
const iconEl = document.getElementById('companyStageIcon');
2333+
const nameEl = document.getElementById('companyStageName');
2334+
if (iconEl) iconEl.textContent = stage.icon;
2335+
if (nameEl) nameEl.textContent = stage.name;
2336+
}
2337+
2338+
// ---- Company achievements --------------------------------
2339+
2340+
function checkCompanyAchievements() {
2341+
const replaced = Object.keys(acc.replacedWorkers).length;
2342+
if (replaced >= 1) awardBadge('layoff_legend');
2343+
if (replaced >= 5) awardBadge('ai_native_ceo');
2344+
if (replaced >= COMPANY_ROLES.length) awardBadge('lights_out');
2345+
const hasAgent = AI_AGENTS.some((a) => (acc.ownedAgents[a.id] || 0) > 0);
2346+
if (hasAgent) awardBadge('token_maxxer_badge');
2347+
}
2348+
2349+
// ---- Passive token generation loop -----------------------
2350+
2351+
function startPassiveLoop() {
2352+
// Tick every 200 ms — add passiveRate × 0.2 tokens per tick.
2353+
// Only update the minimal counter elements here; full UI re-renders
2354+
// (shop affordability, challenge progress bars) happen via handleTap() and
2355+
// purchase actions so we avoid heavy DOM churn every 200 ms.
2356+
setInterval(() => {
2357+
if (acc.passiveRate <= 0) return;
2358+
const tokensAdded = acc.passiveRate * 0.2;
2359+
acc.personalTokens += tokensAdded;
2360+
acc.doomPoints += tokensAdded * ACC_DP_PER_TOKEN;
2361+
// Update only the lightweight numeric displays
2362+
setAccelText('accelTokens', formatTokenCount(acc.personalTokens));
2363+
setAccelText('accelDp', formatDoomPoints(acc.doomPoints));
2364+
updateMilestoneRace();
2365+
updateBestScore();
2366+
updateChallengeProgress();
2367+
}, 200);
2368+
}
2369+
21962370
function renderChallenges() {
21972371
const row = document.getElementById('challengeRow');
21982372
if (!row) return;
@@ -2277,6 +2451,9 @@
22772451
initChallengeProgress();
22782452
renderChallenges();
22792453
renderUpgradeShop();
2454+
renderWorkforcePanel();
2455+
renderAgentShop();
2456+
updateCompanyStage();
22802457
updateAcceleratorUI();
22812458
// Show best score from storage
22822459
const valueEl = document.getElementById('bestScoreValue');
@@ -2307,6 +2484,8 @@
23072484

23082485
// Combo reset timer
23092486
startComboResetLoop();
2487+
// Passive token generation loop
2488+
startPassiveLoop();
23102489
}
23112490

23122491
// ---- Bootstrap ------------------------------------------

0 commit comments

Comments
 (0)