import { BAL } from "./balance"; import { CATALOG, DEF, DeviceDef, GPUS, GPU_DEF, isCompute } from "./catalog"; import { Device, SimState, pushEvent, rand } from "./state"; import { computeMods, deviceMods, MODALITIES, SPECS, SPEC_COST_BY_GEN, DATA_SOURCES, COLLAPSE_CHANCE, RND, SOCKETS, SOCKET_SLOTS, DataSource, GRADES, VERSION_NAMES, DISTILL_MARGIN_CUT, SCRAPE_OPS, rollMarketModel, } from "./research"; import { ROOMS, MOVES } from "./rooms"; import SLOP_TITLE_COUNT_JSON from "../data/sloptitles.json"; const TITLE_COUNT = (SLOP_TITLE_COUNT_JSON as string[]).length; // Titles 0..COHERENT_TITLES-1 are readable; the rest are deranged small-LLM output. // Low model tiers mostly publish the deranged ones โ€” training buys coherence. const COHERENT_TITLES = Math.min(60, TITLE_COUNT); const THUMB_COUNT = 10; function rollTitle(s: SimState): number { const brokenCount = TITLE_COUNT - COHERENT_TITLES; // a collapsed model outputs pure gibberish regardless of generation const brokenChance = s.collapsed ? 1 : s.tier === 0 ? 0.75 : s.tier === 1 ? 0.35 : 0; if (brokenCount > 0 && rand(s) < brokenChance) return COHERENT_TITLES + Math.floor(rand(s) * brokenCount); return Math.floor(rand(s) * COHERENT_TITLES); } export function footprintTiles(def: DeviceDef, x: number, y: number): [number, number][] { const tiles: [number, number][] = []; for (let dx = 0; dx < def.footprint.w; dx++) for (let dy = 0; dy < def.footprint.h; dy++) tiles.push([x + dx, y + dy]); return tiles; } export function canPlace(s: SimState, defId: string, x: number, y: number): string | null { const def = DEF[defId]; if (!def) return "unknown device"; if (s.cash < def.cost) return "can't afford"; const room = ROOMS[s.room]; for (const [tx, ty] of footprintTiles(def, x, y)) { if (tx < 0 || ty < 0 || tx >= room.w || ty >= room.h) return "out of bounds"; if (def.wallOnly && ty !== 0 && tx !== 0) return "must go against a wall"; for (const d of s.devices) for (const [ox, oy] of footprintTiles(DEF[d.defId], d.x, d.y)) if (ox === tx && oy === ty) return "occupied"; } return null; } export function place(s: SimState, defId: string, x: number, y: number): Device | null { if (canPlace(s, defId, x, y)) return null; const def = DEF[defId]; s.cash -= def.cost; const d: Device = { uid: s.nextUid++, defId, x, y, on: true, powered: false, wiredTo: null, temp: BAL.AMBIENT_C, perf: 1, util: 0, upgrades: [], gpus: [], hotSince: null, }; s.devices.push(d); pushEvent(s, `Installed ${def.name}`, "buy"); return d; } export function cardStats(d: Device) { let tok = 0, watts = 0, vram = 0, heat = 0; for (const id of d.gpus) { const g = GPU_DEF[id]; if (!g) continue; tok += g.tok; watts += g.watts; vram += g.vram; heat += g.heat; } return { tok, watts, vram, heat }; } /** intrinsic + installed-card token rate */ export function deviceTokRate(d: Device): number { return DEF[d.defId].tokRate + cardStats(d).tok; } /** place without cost/afford checks โ€” starter room setup */ export function grant(s: SimState, defId: string, x: number, y: number, gpus: string[] = []): Device { const d: Device = { uid: s.nextUid++, defId, x, y, on: true, powered: false, wiredTo: null, temp: BAL.AMBIENT_C, perf: 1, util: 0, upgrades: [], gpus, hotSince: null, }; s.devices.push(d); return d; } export function sell(s: SimState, uid: number) { const i = s.devices.findIndex((d) => d.uid === uid); if (i < 0) return; const def = DEF[s.devices[i].defId]; s.cash += Math.round(def.cost * 0.5); // orphan any cables pointing at a sold router for (const d of s.devices) if (d.wiredTo === uid) d.wiredTo = null; s.devices.splice(i, 1); pushEvent(s, `Sold ${def.name} (+$${Math.round(def.cost * 0.5)})`, "info"); } export function wire(s: SimState, deviceUid: number, targetUid: number): string | null { const dev = s.devices.find((d) => d.uid === deviceUid); const tgt = s.devices.find((d) => d.uid === targetUid); if (!dev || !tgt) return "missing device"; const devDef = DEF[dev.defId], tgtDef = DEF[tgt.defId]; const devIsSwitch = !!devDef.switchMbps; if (!isCompute(devDef) && !devIsSwitch) return "nothing to wire there"; if (!tgtDef.uplinkMbps && !tgtDef.switchMbps) return "that's not a router or switch"; if (devIsSwitch && tgtDef.switchMbps) return "no switch-to-switch daisy chains (yet)"; const ports = tgtDef.ports ?? 4; const used = s.devices.filter((d) => d.wiredTo === targetUid).length; if (used >= ports) return `ports full (${ports})`; dev.wiredTo = targetUid; pushEvent(s, `Wired ${devDef.name} to ${tgtDef.name}`, "info"); return null; } export function resetBreaker(s: SimState) { s.breakerTripped = false; pushEvent(s, "Breaker reset", "info"); } export function raiseRound(s: SimState): string | null { if (s.mandate) return "the board wants results, not more begging"; const offer = BAL.ROUNDS[s.round]; if (!offer) return "no bigger fools left to raise from"; s.cash += offer.cash; s.mandate = { name: offer.name, startViews: s.totalViews, target: offer.viewsGoal, deadline: s.t + offer.timeS, cash: offer.cash, }; pushEvent(s, `๐Ÿ’ฐ ${offer.name} closed: +$${offer.cash}. Mandate: ${(offer.viewsGoal / 1000).toFixed(0)}K views in ${Math.round(offer.timeS / 60)} min`, "good"); return null; } export function clapBack(s: SimState): string | null { const cb = s.clapback; if (!cb || s.t > cb.expires) return "the moment has passed"; if (s.cash < cb.cost) return "can't afford the ratio"; s.cash -= cb.cost; s.clapback = null; if (rand(s) < BAL.CLAPBACK_SUCCESS + computeMods(s).clapbackSuccessAdd) { s.effects = s.effects.filter((e) => e.id !== cb.effectId); s.effects.push({ id: "ratio", label: "๐Ÿ˜ค Ratio'd them into the dirt", viewMult: 1.25, expires: s.t + 20 }); pushEvent(s, "๐Ÿ˜ค CLAP BACK LANDED โ€” effect neutralized, feed loves the drama", "good"); } else { s.platformAdapt = Math.min(1, s.platformAdapt + 0.05); pushEvent(s, "๐Ÿคก Clap back BACKFIRED โ€” screenshots everywhere, filters tighten", "bad"); } return null; } // debuff durations shrink with a lobbyist on retainer function fxDur(s: SimState, base: number): number { return base * computeMods(s).rivalDurMult; } function igniteDevice(s: SimState, d: Device) { const def = DEF[d.defId]; // a nearby CO2 extinguisher sacrifices itself const ext = s.devices.find((e) => e.defId === "extinguisher" && Math.abs(e.x - d.x) + Math.abs(e.y - d.y) <= 3); if (ext) { s.devices.splice(s.devices.indexOf(ext), 1); d.hotSince = null; s.heat[d.y * ROOMS[s.room].w + d.x] *= 0.4; pushEvent(s, `๐Ÿงฏ CO2 extinguisher doused the ${def.name} โ€” hardware saved, extinguisher spent`, "info"); return; } s.fires.push({ x: d.x, y: d.y, until: s.t + 10 }); const lostCards = d.gpus.map((g) => GPU_DEF[g]?.name).filter(Boolean); for (const o of s.devices) if (o.wiredTo === d.uid) o.wiredTo = null; s.devices.splice(s.devices.indexOf(d), 1); pushEvent(s, `๐Ÿ”ฅ THERMAL RUNAWAY โ€” ${def.name} caught fire${lostCards.length ? ` (RIP ${lostCards.join(", ")})` : ""}. Click the flames to fight the fire!`, "bad"); } export function extinguishFire(s: SimState, x: number, y: number): boolean { const i = s.fires.findIndex((f) => f.x === x && f.y === y); if (i < 0) return false; s.fires.splice(i, 1); s.heat[y * ROOMS[s.room].w + x] *= 0.5; pushEvent(s, "๐Ÿงฏ Fire beaten out with a hoodie", "info"); return true; } export function moveRoom(s: SimState, to: string): string | null { const mv = MOVES.find((m) => m.to === to); if (!mv || !ROOMS[to]) return "nowhere to move"; if (s.room === to) return "you already live here"; if (mv.from !== s.room) return `that move starts from the ${ROOMS[mv.from].name}`; if (s.earnedTotal < mv.unlockAt) return `earn $${mv.unlockAt} total first`; if (s.cash < mv.cost) return "can't afford the bond"; s.cash -= mv.cost; s.room = to; const room = ROOMS[to]; s.heat = new Float32Array(room.w * room.h); s.fires = []; for (const d of s.devices) d.hotSince = null; pushEvent(s, `๐Ÿšš MOVED TO ${room.name.toUpperCase()} โ€” ${room.w}x${room.h} tiles, ${room.breakerBase}W service`, "good"); return null; } export function spamBlast(s: SimState): string | null { const cost = 40 * (s.round + 1); if (s.t - s.lastSpamT < 60) return `spam cannon reloading (${Math.ceil(60 - (s.t - s.lastSpamT))}s)`; if (s.cash < cost) return "can't afford the mailing list"; const live = s.videos.filter((v) => !v.dead); if (!live.length) return "nothing live to spam about"; s.cash -= cost; s.lastSpamT = s.t; const tm = BAL.TIERS[s.tier].mult * (s.collapsed ? 0.5 : 1); let views = 0; for (const v of live) { const boost = 400 * tm * v.viral; v.views += boost; views += boost; } s.totalViews += views; s.cash += (views * BAL.CPM) / 1000; s.earnedTotal += (views * BAL.CPM) / 1000; s.platformAdapt = Math.min(1, s.platformAdapt + 0.04); if (rand(s) < 0.15) { s.effects.push({ id: "blacklist", label: "๐Ÿ“ง spam-blacklisted", viewMult: 0.8, expires: s.t + 60 }); pushEvent(s, `๐Ÿ“ง Spam blast: +${Math.round(views / 1000)}K viewsโ€ฆ and a blacklist (views -20% for 60s)`, "bad"); } else { pushEvent(s, `๐Ÿ“ง Spam blast lands: +${Math.round(views / 1000)}K views across the catalog`, "good"); } return null; } // Fictional rival archetypes โ€” event table rolled on a seeded timer const RIVALS = [ { id: "doom_warn", w: 2, run: (s: SimState) => { if (computeMods(s).doomFlip) { s.effects.push({ id: "doom_flip", label: "๐Ÿ•ฏ Doomsworth panic reframed as praise", viewMult: 1.3, expires: s.t + 30 }); pushEvent(s, "๐Ÿ•ฏ Doomsworth attacks โ€” your safety blog spins it into an endorsement", "good"); return; } s.platformAdapt = Math.min(1, s.platformAdapt + 0.12); pushEvent(s, "๐Ÿ•ฏ Clament Doomsworth warns Congress about slop โ€” platforms panic (ALGO HEAT +12%)", "rival"); } }, { id: "lawsuit", w: 1, run: (s: SimState) => { const m = computeMods(s); if (s.dataSource !== "scraped" || m.lawsuitImmune) { pushEvent(s, "โš–๏ธ Copyright trolls circle, find nothing actionable, leave disappointed", "info"); return; } const fine = Math.min(s.cash * 0.2, 400 * (s.round + 1)) * m.fineMult; s.cash -= fine; pushEvent(s, `โš–๏ธ Copyright lawsuit over scraped training data โ€” settled for $${fine.toFixed(0)}`, "rival"); } }, { id: "doom_pub", w: 1, run: (s: SimState) => { s.effects.push({ id: "doom_pub", label: "๐Ÿ•ฏ 'dangerously capable' โ€” free PR", viewMult: 1.5, expires: s.t + 45 }); pushEvent(s, "๐Ÿ•ฏ Doomsworth calls your model 'dangerously capable' โ€” best ad you never bought", "good"); } }, { id: "hype_demo", w: 2, run: (s: SimState) => { s.effects.push({ id: "hype_demo", label: "๐ŸŽช Hypeman demo eating your feed", viewMult: 0.75, expires: s.t + fxDur(s, 60) }); s.clapback = { effectId: "hype_demo", label: "Expose the faked demo", cost: clapCost(s), expires: s.t + BAL.CLAPBACK_WINDOW_S }; pushEvent(s, "๐ŸŽช Chaz Hypeman's (obviously faked) demo is stealing your views", "rival"); } }, { id: "weights_drop", w: 2, run: (s: SimState) => { s.effects.push({ id: "weights_drop", label: "๐Ÿง free clone cratering API prices", priceMult: 0.5, expires: s.t + fxDur(s, 90) }); s.clapback = { effectId: "weights_drop", label: "FUD their safety record", cost: clapCost(s), expires: s.t + BAL.CLAPBACK_WINDOW_S }; pushEvent(s, "๐Ÿง Freeman Weights open-sourced a SlopLM clone โ€” token prices halved", "rival"); } }, { id: "poogle_algo", w: 2, run: (s: SimState) => { s.effects.push({ id: "poogle_algo", label: "๐Ÿข Poogle algo favors its own slop", viewMult: 0.7, expires: s.t + fxDur(s, 60) }); s.clapback = { effectId: "poogle_algo", label: "Cry antitrust on main", cost: clapCost(s), expires: s.t + BAL.CLAPBACK_WINDOW_S }; pushEvent(s, "๐Ÿข Poogle tweaked the algorithm to favor its own slop farm", "rival"); } }, ]; // ---------- Lab / R&D / socket commands ---------- export function unlockGen(s: SimState): string | null { const next = BAL.TIERS[s.tier + 1]; if (!next) return "no bigger model left to imagine"; if (s.trainTokens < next.tokens!) return `need ${next.tokens} training tokens`; s.trainTokens -= next.tokens!; s.tier++; s.platformAdapt *= BAL.ADAPT_TIER_RELIEF; s.adaptStage = 0; if (s.spec) pushEvent(s, "๐Ÿงช New base model โ€” your old specialization tuning is gone", "info"); s.spec = null; s.collapsed = false; const label = `${s.modelName} ${VERSION_NAMES[s.tier]}`; if (s.dataSource === "own" && rand(s) < COLLAPSE_CHANCE) { s.collapsed = true; pushEvent(s, `๐ŸŒ€ MODEL COLLAPSE โ€” ${label} trained on its own slop outputs pure gibberish (revenue halved this version)`, "bad"); } pushEvent(s, `๐Ÿง  ${label} RELEASED (revenue x${next.mult})`, "good"); pushEvent(s, `๐Ÿ˜Ž ${label} slop slips right past the spam filters`, "good"); return null; } export function unlockModality(s: SimState, id: string): string | null { const node = MODALITIES.find((n) => n.id === id); if (!node) return "unknown research"; if (s.modalities.includes(id)) return "already unlocked"; for (const req of node.requires ?? []) if (!s.modalities.includes(req)) return `requires ${MODALITIES.find((n) => n.id === req)?.name}`; if (s.trainTokens < node.cost) return `need ${node.cost} training tokens`; s.trainTokens -= node.cost; s.modalities.push(id); pushEvent(s, `๐Ÿงช NEW MODALITY: ${node.name}`, "good"); return null; } export function setSpec(s: SimState, id: string): string | null { const node = SPECS.find((n) => n.id === id); if (!node) return "unknown tuning"; if (s.spec === id) return "already tuned that way"; const cost = SPEC_COST_BY_GEN[Math.min(s.tier, SPEC_COST_BY_GEN.length - 1)]; if (s.trainTokens < cost) return `need ${cost} training tokens`; s.trainTokens -= cost; s.spec = id; pushEvent(s, `๐Ÿงช ${s.modelName} ${VERSION_NAMES[s.tier]} fine-tuned: ${node.name}`, "good"); return null; } export function setDataSource(s: SimState, src: DataSource): string | null { if (!DATA_SOURCES[src]) return "unknown data source"; s.dataSource = src; pushEvent(s, `๐Ÿ“š Training data: ${DATA_SOURCES[src].name}`, "info"); return null; } export function unlockRnd(s: SimState, id: string): string | null { const node = RND.find((n) => n.id === id); if (!node) return "unknown project"; if (s.rndNodes.includes(id)) return "already researched"; for (const req of node.requires ?? []) if (!s.rndNodes.includes(req)) return `requires ${RND.find((n) => n.id === req)?.name}`; if (s.perkPoints < 1) return "need a perk point (close a VC round)"; if (s.cash < node.cash) return "can't afford it"; s.perkPoints--; s.cash -= node.cash; s.rndNodes.push(id); pushEvent(s, id === "conveyor" ? "๐Ÿญ THE CONVEYOR is installed โ€” money now flows directly into the fire" : `๐Ÿข R&D complete: ${node.name}`, "good"); return null; } export function buyUpgrade(s: SimState, uid: number, upgId: string): string | null { const d = s.devices.find((x) => x.uid === uid); const upg = SOCKETS.find((u) => u.id === upgId); if (!d || !upg) return "missing device or upgrade"; if (!isCompute(DEF[d.defId])) return "only compute hardware takes upgrades"; if (d.upgrades.length >= SOCKET_SLOTS) return "no free sockets"; if (d.upgrades.includes(upgId)) return "already installed"; if (s.cash < upg.cost) return "can't afford it"; s.cash -= upg.cost; d.upgrades.push(upgId); pushEvent(s, `๐Ÿ”ง ${upg.name} installed in ${DEF[d.defId].name}`, "info"); return null; } export function toggleDevice(s: SimState, uid: number): string | null { const d = s.devices.find((x) => x.uid === uid); if (!d) return "missing device"; d.on = !d.on; pushEvent(s, `${DEF[d.defId].name} switched ${d.on ? "ON" : "OFF"}`, "info"); return null; } export function shipGrade(s: SimState, gradeId: "mini" | "pro" | "max"): string | null { const grade = GRADES.find((g) => g.id === gradeId); if (!grade) return "unknown grade"; const existing = s.shippedGrades.find((g) => g.grade === gradeId); if (existing && existing.version === s.tier) return "that grade is already on this version"; const basis = BAL.TIERS[s.tier].tokens ?? 2000; // v1 ships off a small basis const cost = Math.round(basis * grade.costMult); if (s.trainTokens < cost) return `need ${cost} training tokens`; s.trainTokens -= cost; const label = `${s.modelName} ${VERSION_NAMES[s.tier]}${grade.suffix}`; if (existing) { existing.version = s.tier; existing.distilled = false; // fresh weights, fresh moat pushEvent(s, `๐Ÿš€ ${label} SHIPPED โ€” distillation moat restored`, "good"); } else { s.shippedGrades.push({ grade: gradeId, version: s.tier, distilled: false }); pushEvent(s, `๐Ÿš€ ${label} SHIPPED โ€” now hostable on the API market`, "good"); } return null; } export function switchHosting(s: SimState, id: number | null): string | null { if (id === s.hostedId) return "already serving that"; if (id !== null) { if (id < 0) { const grade = GRADES[-id - 1]; if (!grade || !s.shippedGrades.find((g) => g.grade === grade.id)) return "ship that grade first"; } else if (!s.market.find((m) => m.id === id)) { return "that model is gone"; } } s.hostedId = id; if (id === null) { pushEvent(s, "API hosting stopped", "info"); } else { s.hostSwitchAt = s.t + 8; pushEvent(s, "๐Ÿ“ฆ Loading weights onto the cluster (8s)...", "info"); } return null; } export function buyGpu(s: SimState, uid: number, gpuId: string): string | null { const d = s.devices.find((x) => x.uid === uid); const g = GPU_DEF[gpuId]; if (!d || !g) return "missing carrier or card"; const def = DEF[d.defId]; if (!def.gpuSlots) return "that doesn't take GPU cards"; if (d.gpus.length >= def.gpuSlots) return "no free GPU slots"; if (g.rackOnly && !def.rackClass) return "datacenter card โ€” rack only"; if ((g.unlockAt ?? 0) > s.earnedTotal) return `locked โ€” earn $${g.unlockAt} total first`; if (s.cash < g.cost) return "can't afford it"; s.cash -= g.cost; d.gpus.push(gpuId); pushEvent(s, `๐ŸŽฎ ${g.name} installed in ${def.name} (+${g.tok} tok/s)`, "info"); return null; } export function sellGpu(s: SimState, uid: number, slot: number): string | null { const d = s.devices.find((x) => x.uid === uid); if (!d || slot < 0 || slot >= d.gpus.length) return "no card there"; const g = GPU_DEF[d.gpus[slot]]; d.gpus.splice(slot, 1); s.cash += Math.round(g.cost * 0.5); pushEvent(s, `Sold ${g.name} (+$${Math.round(g.cost * 0.5)})`, "info"); return null; } export { GPUS }; export function unlockScrape(s: SimState, id: string): string | null { const node = SCRAPE_OPS.find((n) => n.id === id); if (!node) return "unknown rig"; if (s.scrapeNodes.includes(id)) return "already running"; if (node.requires && !s.scrapeNodes.includes(node.requires)) return `requires ${SCRAPE_OPS.find((n) => n.id === node.requires)?.name}`; if (s.cash < node.cash) return "can't afford it"; s.cash -= node.cash; s.scrapeNodes.push(id); pushEvent(s, `๐Ÿ•ท Scraping ops: ${node.name} online`, "good"); return null; } function clapCost(s: SimState): number { return 50 * (s.round + 1); } export function breakerLimit(s: SimState): number { let limit = ROOMS[s.room].breakerBase; for (const d of s.devices) { if (d.defId === "power_strip") limit += 200; limit += DEF[d.defId].circuitBonus ?? 0; } return limit; } function tierMult(s: SimState): number { // a collapsed generation earns half until you train your way out of it return BAL.TIERS[s.tier].mult * (s.collapsed ? 0.5 : 1); } export function step(s: SimState) { const dt = BAL.SIM_DT; s.tick++; s.t += dt; const mods = computeMods(s); const room = ROOMS[s.room]; const W = room.w, H = room.h; const idx = (x: number, y: number) => y * W + x; // --- power pass --- const limit = breakerLimit(s); let watts = 0; for (const d of s.devices) { const def = DEF[d.defId]; // desired utilization: compute devices run flat out when wired; coolers when on d.util = d.on ? (isCompute(def) ? (d.wiredTo !== null ? 1 : 0.1) : 1) : 0; // furnaces don't care about your breaker โ€” fire predates electricity d.powered = def.burnRate ? d.on : d.on && !s.breakerTripped; if (d.powered) { const dm = deviceMods(d.upgrades); const loadScale = isCompute(def) ? mods.wattsPerTokMult * dm.watts : 1; watts += def.wattsIdle + (def.wattsLoad - def.wattsIdle) * d.util * loadScale; watts += cardStats(d).watts * d.util * mods.wattsPerTokMult * dm.watts; } } if (!s.breakerTripped && watts > limit) { // Grid Whisperer: a few seconds of grace before the trip if (s.overSince === null) s.overSince = s.t; if (s.t - s.overSince >= mods.breakerGraceS) { s.breakerTripped = true; watts = 0; for (const d of s.devices) d.powered = DEF[d.defId].burnRate ? d.on : false; s.overSince = null; if (mods.diesel) { s.dieselResetAt = s.t + 5; pushEvent(s, "โšก Breaker tripped โ€” diesel generator coughing to life (5s)", "bad"); } else { const loss = Math.round(BAL.CHECKPOINT_LOSS * 100); s.trainTokens *= 1 - BAL.CHECKPOINT_LOSS; pushEvent(s, `โšก BREAKER TRIPPED โ€” checkpoint corrupted (-${loss}% training)`, "bad"); if (s.subs >= 1) { s.subs *= 1 - BAL.SAAS.TRIP_HIT; pushEvent(s, `๐Ÿ“ฑ Outage! ${Math.round(BAL.SAAS.TRIP_HIT * 100)}% of subscribers rage-quit`, "bad"); } } } } else if (watts <= limit) { s.overSince = null; } if (s.breakerTripped && s.dieselResetAt !== null && s.t >= s.dieselResetAt) { s.breakerTripped = false; s.dieselResetAt = null; pushEvent(s, "๐Ÿ›ข Diesel backup online โ€” power restored", "info"); } // --- heat pass --- const heat = s.heat; // active fires pump heat into their tiles s.fires = s.fires.filter((f) => f.until > s.t); for (const f of s.fires) heat[idx(f.x, f.y)] += 35 * dt; for (const d of s.devices) { const def = DEF[d.defId]; if (!d.powered) continue; // packed cards choke airflow โ€” heat rises superlinearly, and throttling // does NOT cool a crammed chassis (that's how runaways happen). // Immersion tanks dampen card heat instead (cardHeatMult). const packMult = 1 + 0.15 * Math.max(0, d.gpus.length - 1); const cardHeat = cardStats(d).heat * packMult * (def.cardHeatMult ?? 1); if (def.heatOut || cardHeat) heat[idx(d.x, d.y)] += (def.heatOut + cardHeat) * d.util * dt * 2.2 * (isCompute(def) ? mods.heatMult * deviceMods(d.upgrades).heat : 1); if (def.coolRate) for (let x = 0; x < W; x++) for (let y = 0; y < H; y++) { const dist = Math.abs(x - d.x) + Math.abs(y - d.y); if (dist <= 4) heat[idx(x, y)] = Math.max(0, heat[idx(x, y)] - (def.coolRate * dt) / (1 + dist)); } } // diffusion (fans boost local diffusion), then decay const next = new Float32Array(heat); const fanBoost = new Float32Array(W * H); for (const d of s.devices) if (DEF[d.defId].fan && d.powered) for (let x = Math.max(0, d.x - 1); x <= Math.min(W - 1, d.x + 1); x++) for (let y = Math.max(0, d.y - 1); y <= Math.min(H - 1, d.y + 1); y++) fanBoost[idx(x, y)] = BAL.FAN_DIFFUSE_BONUS; for (let x = 0; x < W; x++) for (let y = 0; y < H; y++) { let sum = 0, n = 0; if (x > 0) { sum += heat[idx(x - 1, y)]; n++; } if (x < W - 1) { sum += heat[idx(x + 1, y)]; n++; } if (y > 0) { sum += heat[idx(x, y - 1)]; n++; } if (y < H - 1) { sum += heat[idx(x, y + 1)]; n++; } const k = BAL.DIFFUSE + fanBoost[idx(x, y)]; next[idx(x, y)] = heat[idx(x, y)] + k * (sum / n - heat[idx(x, y)]); next[idx(x, y)] = Math.max(0, next[idx(x, y)] * (1 - BAL.DECAY)); } s.heat = next; // --- device temps & throttle --- let maxTemp = BAL.AMBIENT_C; for (const d of s.devices) { const def = DEF[d.defId]; d.temp = BAL.AMBIENT_C + s.heat[idx(d.x, d.y)] + (isCompute(def) ? d.util * 18 : 0); maxTemp = Math.max(maxTemp, d.temp); if (isCompute(def)) { const over = d.temp - BAL.THROTTLE_C; d.perf = over <= 0 ? 1 : Math.max(BAL.THROTTLE_FLOOR, 1 - over / 30); // thermal runaway: sustained >95ยฐC under load ignites the hardware if (d.temp > 95 && d.util > 0.5 && !DEF[d.defId].fireImmune) { d.hotSince ??= s.t; if (s.t - d.hotSince > 10) igniteDevice(s, d); } else { d.hotSince = null; } } } // --- data topology: device โ†’ (switch โ†’) uplink --- let uplink = 0; for (const d of s.devices) if (DEF[d.defId].uplinkMbps && d.powered) uplink += DEF[d.defId].uplinkMbps!; const mbpsPerTok = BAL.MBPS_PER_TOK * mods.mbpsPerTokMult; const byId = new Map(s.devices.map((d) => [d.uid, d])); let tokDirect = 0; const perSwitch = new Map(); // switch uid -> raw tok/s arriving for (const d of s.devices) { const def = DEF[d.defId]; if (!isCompute(def) || !d.powered || d.wiredTo === null) continue; const tgt = byId.get(d.wiredTo); if (!tgt || !tgt.powered) continue; const devTok = deviceTokRate(d) * d.perf * mods.tokRateMult * deviceMods(d.upgrades).tok; const tgtDef = DEF[tgt.defId]; if (tgtDef.uplinkMbps) { tokDirect += devTok; } else if (tgtDef.switchMbps) { // switch must itself reach a live uplink const up = tgt.wiredTo !== null ? byId.get(tgt.wiredTo) : undefined; if (up && up.powered && DEF[up.defId].uplinkMbps) perSwitch.set(tgt.uid, (perSwitch.get(tgt.uid) ?? 0) + devTok); } } let tokViaSwitches = 0; for (const [swUid, tokSum] of perSwitch) { const cap = DEF[byId.get(swUid)!.defId].switchMbps!; tokViaSwitches += tokSum * Math.min(1, cap / Math.max(0.001, tokSum * mbpsPerTok)); } const tokReaching = tokDirect + tokViaSwitches; const demand = tokReaching * mbpsPerTok; const dataMult = demand > 0 ? Math.min(1, uplink / demand) : 1; // --- VRAM: your cluster has to actually hold the weights --- let vramHave = 0; for (const d of s.devices) if (d.powered && isCompute(DEF[d.defId])) vramHave += cardStats(d).vram; const vramNeed = BAL.TIERS[s.tier].vramGB; const vramMult = vramNeed > 0 && vramHave < vramNeed ? Math.max(BAL.VRAM_STARVE_FLOOR, vramHave / vramNeed) : 1; const tokEff = tokReaching * dataMult * vramMult; // --- economy: compute splits three ways (slop / training / hosting) --- const tm = tierMult(s); const trainShare = Math.min(1, s.allocTraining); const hostShare = Math.min(s.allocHosting, 1 - trainShare); const tokInf = tokEff * (1 - trainShare - hostShare) * dt; const tokTrain = tokEff * trainShare * dt; const tokHost = tokEff * hostShare; // tok/s let income = tokInf * BAL.TOK_PRICE * tm; // baseline API drip // training fills the research bank; the Lab decides what it buys. // checkpoints for the version you're training toward need disk to live on. let storageGB = 0; for (const d of s.devices) if (d.powered && DEF[d.defId].storageGB) storageGB += DEF[d.defId].storageGB!; const ckptNeed = BAL.TIERS[s.tier + 1]?.checkpointGB ?? 0; const trainStalled = tokTrain > 0 && ckptNeed > 0 && storageGB < ckptNeed; if (trainStalled && !s.rates.trainStalled) pushEvent(s, `๐Ÿ’พ TRAINING STALLED โ€” ${ckptNeed}GB of checkpoint space needed, ${storageGB}GB online`, "bad"); const src = DATA_SOURCES[s.dataSource]; let dataRate = src.rate; if (s.dataSource === "scraped") { dataRate += s.scrapeNodes.reduce((a, id) => a + (SCRAPE_OPS.find((n) => n.id === id)?.scrapedRateAdd ?? 0), 0); if (s.t < s.scrapeBanUntil) dataRate *= 0.15; // pipeline is IP-banned } if (!trainStalled) { s.trainTokens += tokTrain * dataRate; income -= tokTrain * src.costPerTok; // licensed data bills per token } // platform adaptation drifts back down while you're not spamming s.platformAdapt = Math.max(0, s.platformAdapt - BAL.ADAPT_DECAY_S * mods.adaptDecayMult * dt); const stages = [0.25, 0.5, 0.75]; while (s.adaptStage < stages.length && s.platformAdapt >= stages[s.adaptStage]) { s.adaptStage++; pushEvent(s, `๐Ÿค– Platforms updated their spam filters (wariness ${Math.round(stages[s.adaptStage - 1] * 100)}%)`, "bad"); } // SaaS serving eats inference first โ€” paying customers get the tokens const serveNeed = s.subs * BAL.SAAS.TOK_PER_SUB * dt; const saasLatency = s.subs > 1 && serveNeed > tokInf; // slop videos โ€” richer modalities cost more tokens per video but earn far more const videoCost = BAL.VIDEO_COST_TOK * mods.videoCostMult; s.tokenBank += Math.max(0, tokInf - serveNeed); while (s.tokenBank >= videoCost) { s.tokenBank -= videoCost; const viralChance = BAL.VIRAL_CHANCE + mods.viralChanceAdd; const viral = rand(s) < viralChance ? 2 + rand(s) * (BAL.VIRAL_MULT_MAX - 2) : 1; const v = { title: rollTitle(s), thumb: Math.floor(rand(s) * THUMB_COUNT), born: s.t, views: 0, rate: 0, viral, dead: false, }; s.videos.push(v); s.platformAdapt = Math.min(1, s.platformAdapt + BAL.ADAPT_PER_VIDEO * mods.adaptPerVideoMult * (1 - s.platformAdapt)); if (viral > 6) pushEvent(s, `๐Ÿ”ฅ VIDEO WENT VIRAL (x${viral.toFixed(0)})`, "good"); if (s.videos.length > 40) s.videos.shift(); } // flaky used cards die under sustained load for (const d of s.devices) { if (!d.powered || d.util < 0.5 || !d.gpus.length) continue; for (let i = d.gpus.length - 1; i >= 0; i--) { const g = GPU_DEF[d.gpus[i]]; if (g?.flaky && rand(s) < (g.flaky * dt) / 60) { d.gpus.splice(i, 1); pushEvent(s, `๐Ÿ’€ The ${g.name} in your ${DEF[d.defId].name} just died. It owed you nothing.`, "bad"); } } } // ad spend: continuous marketing burn with diminishing returns let adHype = 1; if (s.adSpend > 0 && s.cash > s.adSpend * dt) { s.cash -= s.adSpend * dt; adHype = 1 + 0.25 * Math.log10(1 + s.adSpend * 3); } // cash furnaces: burn money, buy attention let hype = 1; for (const d of s.devices) { const def = DEF[d.defId]; if (!def.burnRate || !d.on) continue; const burn = def.burnRate * dt; if (s.cash >= burn) { s.cash -= burn; hype *= 1 + (def.hypeMult ?? 0); d.util = 1; } else { d.util = 0; // starved โ€” no cash, no hype } } // rival effects (timed view/price modifiers) s.effects = s.effects.filter((e) => e.expires > s.t); if (s.clapback && s.t > s.clapback.expires) s.clapback = null; let viewFx = 1, priceFx = 1; for (const e of s.effects) { viewFx *= e.viewMult ?? 1; priceFx *= e.priceMult ?? 1; } income *= priceFx; // --- API hosting market --- if (s.nextMarketT === 0) { // seed the launch-day market for (let i = 0; i < 3; i++) s.market.push(rollMarketModel(() => rand(s), s.nextMarketId++, s.t)); s.nextMarketT = s.t + 90 + rand(s) * 120; } else if (s.t >= s.nextMarketT) { const drop = rollMarketModel(() => rand(s), s.nextMarketId++, s.t); for (const m of s.market) m.margin *= 0.72; // hot drop craters the incumbents s.market.push(drop); while (s.market.length > 5) { const i = s.market.findIndex((m) => m.id !== s.hostedId); if (i < 0) break; s.market.splice(i, 1); } pushEvent(s, `${drop.flag} ${drop.name} just dropped โ€” hosting margins crater`, "rival"); s.nextMarketT = s.t + 90 + rand(s) * 150; } for (const m of s.market) m.margin *= 1 - 0.0003 * dt; // slow fade as hype cools let hostIncome = 0; let hostingOwnMax = false; const loading = s.hostSwitchAt !== null && s.t < s.hostSwitchAt; if (s.hostSwitchAt !== null && s.t >= s.hostSwitchAt) { s.hostSwitchAt = null; pushEvent(s, "๐Ÿ“ฆ Weights loaded โ€” serving traffic", "info"); } if (s.hostedId !== null && !loading && tokHost > 0) { if (s.hostedId < 0) { const grade = GRADES[-s.hostedId - 1]; const shipped = s.shippedGrades.find((g) => g.grade === grade.id); if (shipped) { const margin = grade.margin * (shipped.distilled ? DISTILL_MARGIN_CUT : 1); hostIncome = Math.min(tokHost, grade.demand) * margin * priceFx; if (grade.hypeMult && !shipped.distilled) hostingOwnMax = grade.id === "max"; // distillation roll โ€” hosting your own weights is how they steal them if (s.nextDistillT === 0) s.nextDistillT = s.t + 100 + rand(s) * 120; else if (s.t >= s.nextDistillT) { if (!shipped.distilled && rand(s) < grade.distillChance) { shipped.distilled = true; const thief = rollMarketModel(() => rand(s), s.nextMarketId++, s.t); thief.margin *= 1.3; s.market.push(thief); pushEvent(s, `๐Ÿฅท YOU GOT DISTILLED โ€” ${thief.flag} ${thief.name} is a suspiciously good clone of ${s.modelName} ${VERSION_NAMES[shipped.version]}${GRADES.find((g) => g.id === shipped.grade)!.suffix} (margin gutted this version)`, "bad"); } s.nextDistillT = s.t + 100 + rand(s) * 120; } } } else { const m = s.market.find((x) => x.id === s.hostedId); if (m) hostIncome = Math.min(tokHost, m.demand) * m.margin * priceFx; else { s.hostedId = null; pushEvent(s, "Hosted model delisted โ€” allocate elsewhere", "info"); } } income += hostIncome * dt; } // --- scraping ops: IP bans while living off scraped data --- if (s.dataSource === "scraped" && s.videos.length > 0) { if (s.nextBanT === 0) s.nextBanT = s.t + 70 + rand(s) * 80; else if (s.t >= s.nextBanT) { const severity = 1 + Math.floor(rand(s) * 3); const banLevel = Math.max(0, ...s.scrapeNodes.map((id) => SCRAPE_OPS.find((n) => n.id === id)?.banLevel ?? 0)); if (severity > banLevel) { s.scrapeBanUntil = s.t + 45; pushEvent(s, `๐Ÿšซ IP BANNED (severity ${severity}) โ€” scraping pipeline offline 45s`, "bad"); } else { pushEvent(s, "๐Ÿ” Ban wave hit โ€” proxies rotated, the scrape continues", "info"); } s.nextBanT = s.t + 70 + rand(s) * 80; } } const adaptMult = (1 - s.platformAdapt * BAL.ADAPT_MAX_PENALTY) * hype * adHype * viewFx * mods.viewsPerVideoMult * mods.conveyorHype * (hostingOwnMax ? 1.1 : 1); let liveViewRate = 0; for (const v of s.videos) { if (v.dead) continue; const age = s.t - v.born; if (age > BAL.VIDEO_LIFETIME_S) { v.dead = true; v.rate = 0; continue; } v.rate = BAL.VIDEO_BASE_VIEWS_S * v.viral * tm * adaptMult * Math.exp(-age / (BAL.VIDEO_LIFETIME_S / 3)); v.views += v.rate * dt; s.totalViews += v.rate * dt; liveViewRate += v.rate; income += (v.rate * dt * BAL.CPM) / 1000; } // --- SaaS: the "Instant Girlfriend App" wrapper business --- let subsNet = 0; if (s.shippedGrades.length > 0) { const signups = liveViewRate * BAL.SAAS.CONV * dt; let churn = BAL.SAAS.CHURN_S; if (saasLatency) churn *= BAL.SAAS.LATENCY_CHURN_MULT; if (s.collapsed) churn *= BAL.SAAS.COLLAPSE_CHURN_MULT; const before = s.subs; s.subs = Math.max(0, s.subs + signups - s.subs * churn * dt); subsNet = (s.subs - before) / dt; income += s.subs * BAL.SAAS.SUB_REV_S * dt; const stages = [100, 1_000, 10_000, 100_000]; while (s.subStage < stages.length && s.subs >= stages[s.subStage]) { s.subStage++; pushEvent(s, `๐ŸŽ‰ ${stages[s.subStage - 1].toLocaleString()} SUBSCRIBERS โ€” the wrapper prints money`, "good"); } } // VC mandate resolution if (s.mandate) { const got = s.totalViews - s.mandate.startViews; if (got >= s.mandate.target) { s.round++; s.perkPoints++; pushEvent(s, `๐Ÿ† ${s.mandate.name} mandate CRUSHED (${(got / 1000).toFixed(0)}K views) โ€” board grants an R&D perk point`, "good"); s.mandate = null; } else if (s.t > s.mandate.deadline) { s.cash *= 1 - BAL.MANDATE_FAIL_CASH_CUT; s.equityMult *= 1 - BAL.MANDATE_FAIL_INCOME_CUT; pushEvent(s, `๐Ÿ“‰ ${s.mandate.name} mandate FAILED โ€” bridge round at brutal terms (-${BAL.MANDATE_FAIL_CASH_CUT * 100}% cash, -${BAL.MANDATE_FAIL_INCOME_CUT * 100}% equity forever)`, "bad"); s.mandate = null; } } // rival CEO timer โ€” they only notice you once you're publishing if (s.videos.length > 0) { if (s.nextRivalT === 0) s.nextRivalT = s.t + 60 + rand(s) * 60; else if (s.t >= s.nextRivalT) { const totalW = RIVALS.reduce((a, r) => a + r.w, 0); let roll = rand(s) * totalW; for (const r of RIVALS) { roll -= r.w; if (roll <= 0) { r.run(s); break; } } s.nextRivalT = s.t + BAL.RIVAL_MIN_S + rand(s) * (BAL.RIVAL_MAX_S - BAL.RIVAL_MIN_S); } } income *= s.equityMult; // THE CONVEYOR: a fixed cut of gross income rides the belt into the furnace if (mods.conveyorPct > 0 && income > 0) income *= 1 - mods.conveyorPct; // --- costs & bookkeeping --- const powerCost = ((watts / 1000) * BAL.KWH_PRICE * dt) / 3600; s.cash += income - powerCost; s.earnedTotal += income; const sm = 1 - Math.exp(-dt / 1.5); // ~1.5s smoothing for HUD rates const r = s.rates; r.watts += (watts - r.watts) * sm; r.breakerWatts = limit; r.tokEff += (tokEff - r.tokEff) * sm; r.income += (income / dt - r.income) * sm; r.powerCost += (powerCost / dt - r.powerCost) * sm; r.demandMbps = demand; r.uplinkMbps = uplink; r.dataMult = dataMult; r.maxTemp = maxTemp; r.adapt = s.platformAdapt; r.hostIncome += (hostIncome - r.hostIncome) * sm; r.hostTok = tokHost; r.vramHave = vramHave; r.vramNeed = vramNeed; r.storageGB = storageGB; r.trainStalled = trainStalled; r.subsNet += (subsNet - r.subsNet) * sm; r.saasLatency = saasLatency; } /** HUD helper: progress toward the next slop video + whether tokens flow at all */ export function videoProgress(s: SimState): { pct: number; producing: boolean } { const cost = BAL.VIDEO_COST_TOK * computeMods(s).videoCostMult; return { pct: Math.min(1, s.tokenBank / cost), producing: s.rates.tokEff > 0.5 }; } export function shopList(s: SimState) { return CATALOG.map((def) => ({ def, locked: (def.unlockAt ?? 0) > s.earnedTotal, affordable: s.cash >= def.cost, })); }