M3: VC mandates and rival CEO events
Raise Seed through Series C for cash against views-delta mandates with deadlines; success advances the round ladder, failure burns 25% of cash and permanently dilutes income 15%. Four fictional rival archetypes fire on a seeded timer: Doomsworth congressional panics (ALGO HEAT spikes), Hypeman fake demos and Poogle algo tweaks (view debuffs), Freeman Weights clone drops (API price crash) — with a paid 60/40 CLAP BACK gamble. HUD: mandate progress banner, rival effect badges, total views stat. Fix: scene texture list now derives from the catalog (new sprites were rendering as purple placeholders). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b9e45d5cf7
commit
03bb13aff9
10
CLAUDE.md
10
CLAUDE.md
@ -35,8 +35,14 @@ Date.now/Math.random inside sim).
|
||||
squeeze → train → relief. Infra: 20A sub-panel (+1500W), fiber uplink
|
||||
(1Gbps/8 ports), Cash Furnace (burns $1.5/s for +40% views; John's idea,
|
||||
keep it — endgame R&D tree should have a literal cash-conveyor upgrade).
|
||||
→ M3: VC mandates + rival CEO event system (fictional archetypes: Doom
|
||||
Prophet, Hype Bro, Open-Weights Zealot, Legacy Giant) + hazards.
|
||||
✅ M3: VC rounds (Seed→C) with views-delta mandates — fail = -25% cash and
|
||||
permanent equity dilution; rival CEO events (Doomsworth/Hypeman/Freeman
|
||||
Weights/Poogle) on a seeded timer with timed view/price effects and a
|
||||
60/40 CLAP BACK gamble. Seed goal calibrated empirically: decisive play
|
||||
≈48K views/8min, goal 40K, idle ≈12K.
|
||||
→ M4 candidates: hazards (thermal runaway fire), Tier 2 garage move
|
||||
(bigger room = new backdrop + grid refit), R&D skill tree (endgame:
|
||||
automated cash-conveyor→furnace), subscribers/SaaS as second revenue.
|
||||
Design pillars (John-approved framing): Throughput, Allocation, Attention,
|
||||
Capital, Expansion. Macro layer (Tier 4-5) = strategic map, NOT city-builder.
|
||||
Cut until earned: Tiers 3–5, immersion cooling, offshore ships, lobbying.
|
||||
|
||||
12
src/main.ts
12
src/main.ts
@ -1,6 +1,6 @@
|
||||
import { BAL } from "./sim/balance";
|
||||
import { createState, pushEvent } from "./sim/state";
|
||||
import { step, place, canPlace, wire, sell, grant } from "./sim/sim";
|
||||
import { step, place, canPlace, wire, sell, grant, raiseRound, clapBack } from "./sim/sim";
|
||||
import { saveState, loadState, clearSave } from "./sim/save";
|
||||
import { DEF } from "./sim/catalog";
|
||||
import { Scene } from "./render/scene";
|
||||
@ -33,6 +33,14 @@ async function boot() {
|
||||
},
|
||||
onAlloc: (v) => { state.allocTraining = v; },
|
||||
onNewGame: () => { wiped = true; clearSave(); location.reload(); },
|
||||
onRaise: () => {
|
||||
const err = raiseRound(state);
|
||||
if (err) { pushEvent(state, err, "info"); sfx.error(); } else sfx.money();
|
||||
},
|
||||
onClapback: () => {
|
||||
const err = clapBack(state);
|
||||
if (err) { pushEvent(state, err, "info"); sfx.error(); }
|
||||
},
|
||||
});
|
||||
hud.bindBreaker(state);
|
||||
|
||||
@ -150,6 +158,8 @@ async function boot() {
|
||||
wire: (a: number, b: number) => wire(state, a, b),
|
||||
stepN: (n: number) => { for (let i = 0; i < n; i++) step(state); },
|
||||
newGame: () => { wiped = true; clearSave(); location.reload(); },
|
||||
raise: () => raiseRound(state),
|
||||
clap: () => clapBack(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Application, Assets, Container, Graphics, Sprite, Texture } from "pixi.js";
|
||||
import { BAL } from "../sim/balance";
|
||||
import { DEF } from "../sim/catalog";
|
||||
import { CATALOG, DEF } from "../sim/catalog";
|
||||
import { SimState, Device } from "../sim/state";
|
||||
import { TILE_W, TILE_H, ROOM_IMG, tileToScreen, screenToTile, diamond, lPath, inBounds } from "./iso";
|
||||
|
||||
@ -25,8 +25,8 @@ export class Scene {
|
||||
await this.app.init({ background: "#07070c", resizeTo: window, antialias: true });
|
||||
el.appendChild(this.app.canvas);
|
||||
|
||||
// load textures with graceful fallback
|
||||
const names = ["desk", "desk_pc", "router", "desk_fan", "gpu_rig", "window_ac", "power_strip", "server_rack_mini"];
|
||||
// load textures with graceful fallback — sprite list derives from the catalog
|
||||
const names = [...new Set(CATALOG.map((d) => d.sprite))];
|
||||
const room = await this.tryLoad("/assets/gen/room.png");
|
||||
for (const n of names) {
|
||||
const t = await this.tryLoad(`/assets/gen/${n}_cut.png`);
|
||||
|
||||
@ -38,6 +38,22 @@ export const BAL = {
|
||||
ADAPT_MAX_PENALTY: 0.85, // at 100% wariness, views are cut by this fraction
|
||||
ADAPT_TIER_RELIEF: 0.35, // wariness is multiplied by this on tier-up
|
||||
|
||||
// VC rounds: cash now, views-delta mandate by deadline. Fail = cash cut + permanent dilution.
|
||||
ROUNDS: [
|
||||
{ name: "Seed", cash: 1500, viewsGoal: 40_000, timeS: 480 },
|
||||
{ name: "Series A", cash: 8_000, viewsGoal: 300_000, timeS: 600 },
|
||||
{ name: "Series B", cash: 40_000, viewsGoal: 2_000_000, timeS: 720 },
|
||||
{ name: "Series C", cash: 200_000, viewsGoal: 12_000_000, timeS: 900 },
|
||||
],
|
||||
MANDATE_FAIL_CASH_CUT: 0.25, // bridge round at brutal terms
|
||||
MANDATE_FAIL_INCOME_CUT: 0.15, // permanent dilution per failure
|
||||
|
||||
// Rival CEOs: random events every RIVAL_MIN..MAX seconds once you're publishing
|
||||
RIVAL_MIN_S: 90,
|
||||
RIVAL_MAX_S: 200,
|
||||
CLAPBACK_WINDOW_S: 20,
|
||||
CLAPBACK_SUCCESS: 0.6,
|
||||
|
||||
// Training / model tiers: tokens required to reach tier i+1, revenue multiplier per tier
|
||||
TIERS: [
|
||||
{ name: "SlopLM-1B", mult: 1 },
|
||||
|
||||
108
src/sim/sim.ts
108
src/sim/sim.ts
@ -94,6 +94,70 @@ export function resetBreaker(s: SimState) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Fictional rival archetypes — event table rolled on a seeded timer
|
||||
const RIVALS = [
|
||||
{ id: "doom_warn", w: 2, run: (s: SimState) => {
|
||||
s.platformAdapt = Math.min(1, s.platformAdapt + 0.12);
|
||||
pushEvent(s, "🕯 Clament Doomsworth warns Congress about slop — platforms panic (ALGO HEAT +12%)", "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 + 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 + 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 + 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");
|
||||
} },
|
||||
];
|
||||
|
||||
function clapCost(s: SimState): number {
|
||||
return 50 * (s.round + 1);
|
||||
}
|
||||
|
||||
export function breakerLimit(s: SimState): number {
|
||||
let limit = BAL.BREAKER_WATTS;
|
||||
for (const d of s.devices) {
|
||||
@ -249,7 +313,17 @@ export function step(s: SimState) {
|
||||
}
|
||||
}
|
||||
|
||||
const adaptMult = (1 - s.platformAdapt * BAL.ADAPT_MAX_PENALTY) * 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;
|
||||
|
||||
const adaptMult = (1 - s.platformAdapt * BAL.ADAPT_MAX_PENALTY) * hype * viewFx;
|
||||
for (const v of s.videos) {
|
||||
if (v.dead) continue;
|
||||
const age = s.t - v.born;
|
||||
@ -257,9 +331,41 @@ export function step(s: SimState) {
|
||||
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;
|
||||
income += (v.rate * dt * BAL.CPM) / 1000;
|
||||
}
|
||||
|
||||
// VC mandate resolution
|
||||
if (s.mandate) {
|
||||
const got = s.totalViews - s.mandate.startViews;
|
||||
if (got >= s.mandate.target) {
|
||||
s.round++;
|
||||
pushEvent(s, `🏆 ${s.mandate.name} mandate CRUSHED (${(got / 1000).toFixed(0)}K views) — the board loves you`, "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;
|
||||
|
||||
// --- costs & bookkeeping ---
|
||||
const powerCost = ((watts / 1000) * BAL.KWH_PRICE * dt) / 3600;
|
||||
s.cash += income - powerCost;
|
||||
|
||||
@ -38,6 +38,13 @@ export interface SimState {
|
||||
trainTokens: number;
|
||||
platformAdapt: number; // 0..1 spam-filter wariness
|
||||
adaptStage: number; // last threshold toasted (0-3)
|
||||
totalViews: number; // lifetime views across all slop
|
||||
round: number; // VC rounds successfully closed
|
||||
equityMult: number; // permanent income multiplier (dilution damage)
|
||||
mandate: { name: string; startViews: number; target: number; deadline: number; cash: number } | null;
|
||||
effects: { id: string; label: string; viewMult?: number; priceMult?: number; expires: number }[];
|
||||
nextRivalT: number;
|
||||
clapback: { effectId: string; label: string; cost: number; expires: number } | null;
|
||||
tokenBank: number;
|
||||
videos: Video[];
|
||||
rates: {
|
||||
@ -72,6 +79,13 @@ export function createState(): SimState {
|
||||
trainTokens: 0,
|
||||
platformAdapt: 0,
|
||||
adaptStage: 0,
|
||||
totalViews: 0,
|
||||
round: 0,
|
||||
equityMult: 1,
|
||||
mandate: null,
|
||||
effects: [],
|
||||
nextRivalT: 0,
|
||||
clapback: null,
|
||||
tokenBank: 0,
|
||||
videos: [],
|
||||
rates: {
|
||||
|
||||
@ -76,6 +76,20 @@
|
||||
.toast.bad { background: #3a1214; border: 1px solid var(--red); color: #ffb4b4; }
|
||||
.toast.good { background: #12312a; border: 1px solid var(--green); color: #a7f3d0; }
|
||||
.toast.info { background: #16162a; border: 1px solid var(--edge); color: var(--dim); }
|
||||
.toast.rival { background: #251330; border: 1px solid var(--purple); color: #d8b4fe; }
|
||||
|
||||
#fxrow { position: absolute; top: 64px; left: 50%; transform: translateX(-50%);
|
||||
display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; max-width: 60vw; }
|
||||
.fxbadge { background: var(--panel); border: 1px solid var(--edge); border-radius: 999px;
|
||||
padding: 3px 10px; font-size: 10.5px; pointer-events: none; }
|
||||
.fxbadge.down { border-color: var(--red); color: #fda4af; }
|
||||
.fxbadge.up { border-color: var(--green); color: #a7f3d0; }
|
||||
|
||||
#mandate { padding: 4px 2px; }
|
||||
#mandate #mtext { font-size: 10.5px; margin-bottom: 3px; }
|
||||
#clap { background: #7c2d12; border: 1px solid var(--amber); color: #fed7aa; border-radius: 6px;
|
||||
padding: 6px 9px; font: inherit; font-size: 11px; cursor: pointer; text-align: left;
|
||||
animation: flash 0.5s infinite alternate; }
|
||||
@keyframes rise { 0% { opacity: 0; transform: translateY(8px); } 8% { opacity: 1; transform: none; } 80% { opacity: 1; } 100% { opacity: 0; } }
|
||||
|
||||
#hint { position: absolute; bottom: 12px; right: 215px; color: var(--dim); font-size: 11px; text-align: right; }
|
||||
|
||||
@ -11,6 +11,8 @@ export interface HudCallbacks {
|
||||
onWireTool: () => void;
|
||||
onAlloc: (trainShare: number) => void;
|
||||
onNewGame: () => void;
|
||||
onRaise: () => void;
|
||||
onClapback: () => void;
|
||||
}
|
||||
|
||||
const NAME_A = ["Slop", "Grift", "Chungus", "Synergy", "Vibe", "Goon", "Brainrot", "Yeet", "Chud", "Slud", "Content", "Engagement"];
|
||||
@ -43,11 +45,13 @@ export class Hud {
|
||||
<div class="stat"><span class="label">POWER</span><span class="val" id="watts">0W</span><div class="bar"><div id="wattbar"></div></div></div>
|
||||
<div class="stat"><span class="label">HOTTEST</span><span class="val" id="temp">24°C</span></div>
|
||||
<div class="stat"><span class="label">SLOP RATE</span><span class="val" id="tok">0 tok/s</span></div>
|
||||
<div class="stat"><span class="label">VIEWS</span><span class="val" id="views">0</span></div>
|
||||
<div class="stat"><span class="label">UPLINK</span><span class="val" id="net">0/0 Mbps</span></div>
|
||||
<div class="stat"><span class="label">MODEL</span><span class="val" id="tier">SlopLM-1B</span><div class="bar train"><div id="trainbar"></div></div></div>
|
||||
<div class="stat" title="Platform spam-filter wariness. Publishing raises it, views drop as it climbs. Train a new model tier for relief."><span class="label">ALGO HEAT</span><span class="val" id="adapt">0%</span><div class="bar"><div id="adaptbar"></div></div></div>
|
||||
<button id="breaker-reset">RESET BREAKER</button>
|
||||
</div>
|
||||
<div id="fxrow"></div>
|
||||
<div id="controls" class="panel">
|
||||
<button class="chip" data-ov="heat">🌡 Heat</button>
|
||||
<button class="chip" data-ov="power">⚡ Power</button>
|
||||
@ -56,6 +60,12 @@ export class Hud {
|
||||
<button class="chip" id="wire-tool">🔌 Wire (W)</button>
|
||||
<button class="chip" id="mute">${isMuted() ? "🔇 Muted" : "🔊 Sound"}</button>
|
||||
<button class="chip" id="new-game">🗑 New Game</button>
|
||||
<button class="chip" id="raise" style="display:none">💰 Raise</button>
|
||||
<div id="mandate" style="display:none">
|
||||
<div id="mtext"></div>
|
||||
<div class="bar" style="width:100%"><div id="mbar"></div></div>
|
||||
</div>
|
||||
<button id="clap" style="display:none"></button>
|
||||
<div id="alloc-row">
|
||||
<span class="label" style="color:var(--dim);font-size:9px">TRAIN <b id="allocpct">25</b>% / INFER</span>
|
||||
<input id="alloc" type="range" min="0" max="90" value="25" />
|
||||
@ -81,6 +91,8 @@ export class Hud {
|
||||
(this.root.querySelector("#new-game") as HTMLButtonElement).onclick = () => {
|
||||
if (confirm("Torch the whole company and start over?")) cb.onNewGame();
|
||||
};
|
||||
(this.root.querySelector("#raise") as HTMLButtonElement).onclick = () => cb.onRaise();
|
||||
(this.root.querySelector("#clap") as HTMLButtonElement).onclick = () => cb.onClapback();
|
||||
const alloc = this.root.querySelector("#alloc") as HTMLInputElement;
|
||||
alloc.oninput = () => {
|
||||
(this.root.querySelector("#allocpct") as HTMLElement).textContent = alloc.value;
|
||||
@ -174,6 +186,43 @@ export class Hud {
|
||||
q("#tok").textContent = fmt(s.rates.tokEff, 1) + " tok/s";
|
||||
q("#net").textContent = `${fmt(s.rates.demandMbps, 0)}/${fmt(s.rates.uplinkMbps, 0)} Mbps`;
|
||||
(q("#net") as HTMLElement).style.color = s.rates.dataMult < 1 ? "var(--red)" : "";
|
||||
q("#views").textContent = fmt(s.totalViews, 0);
|
||||
|
||||
// VC raise chip / mandate progress
|
||||
const raise = q("#raise"), mandate = q("#mandate");
|
||||
const offer = BAL.ROUNDS[s.round];
|
||||
if (s.mandate) {
|
||||
raise.style.display = "none";
|
||||
mandate.style.display = "block";
|
||||
const got = s.totalViews - s.mandate.startViews;
|
||||
const left = Math.max(0, s.mandate.deadline - s.t);
|
||||
q("#mtext").textContent = `${s.mandate.name}: ${fmt(got, 0)}/${fmt(s.mandate.target, 0)} 👁 · ${Math.floor(left / 60)}:${String(Math.floor(left % 60)).padStart(2, "0")}`;
|
||||
(q("#mbar") as HTMLElement).style.width = Math.min(100, (got / s.mandate.target) * 100) + "%";
|
||||
(q("#mbar") as HTMLElement).style.background = left < 60 ? "var(--red)" : "var(--green)";
|
||||
(q("#mtext") as HTMLElement).style.color = left < 60 ? "var(--red)" : "var(--text)";
|
||||
} else {
|
||||
mandate.style.display = "none";
|
||||
raise.style.display = offer ? "block" : "none";
|
||||
if (offer) raise.textContent = `💰 Raise ${offer.name}: +$${fmt(offer.cash, 0)}`;
|
||||
}
|
||||
|
||||
// clap-back window
|
||||
const clap = q("#clap");
|
||||
if (s.clapback) {
|
||||
clap.style.display = "block";
|
||||
const secs = Math.max(0, s.clapback.expires - s.t);
|
||||
clap.textContent = `😤 ${s.clapback.label} ($${s.clapback.cost}) — ${secs.toFixed(0)}s`;
|
||||
} else {
|
||||
clap.style.display = "none";
|
||||
}
|
||||
|
||||
// rival effect badges
|
||||
const fxrow = q("#fxrow");
|
||||
fxrow.innerHTML = s.effects.map((e) => {
|
||||
const good = (e.viewMult ?? 1) > 1;
|
||||
return `<span class="fxbadge ${good ? "up" : "down"}">${e.label} · ${Math.max(0, e.expires - s.t).toFixed(0)}s</span>`;
|
||||
}).join("");
|
||||
|
||||
const ad = s.rates.adapt;
|
||||
q("#adapt").textContent = Math.round(ad * 100) + "%";
|
||||
q("#adapt").classList.toggle("hot", ad > 0.5);
|
||||
@ -204,7 +253,9 @@ export class Hud {
|
||||
const toasts = q("#toasts");
|
||||
for (const ev of s.events.slice(this.lastEventCount)) {
|
||||
if (ev.kind === "bad") sfx.breaker();
|
||||
else if (ev.kind === "good") ev.msg.includes("TIER UP") ? sfx.tierUp() : sfx.viral();
|
||||
else if (ev.kind === "rival") sfx.rival();
|
||||
else if (ev.kind === "good")
|
||||
ev.msg.includes("TIER UP") ? sfx.tierUp() : ev.msg.includes("$") ? sfx.money() : sfx.viral();
|
||||
if (ev.msg.includes("VIRAL")) {
|
||||
const f = document.createElement("div");
|
||||
f.id = "flash";
|
||||
|
||||
@ -60,4 +60,6 @@ export const sfx = {
|
||||
tierUp: () => [392, 523, 659, 784].forEach((f, i) => tone(f, 0.3, "sawtooth", 0.05, i * 0.12)),
|
||||
breaker: () => { noise(0.4, 0.5, 250); tone(80, 0.5, "sawtooth", 0.2, 0, 35); },
|
||||
error: () => tone(180, 0.15, "square", 0.06),
|
||||
money: () => { tone(1319, 0.09, "sine", 0.08); tone(1760, 0.16, "sine", 0.08, 0.09); },
|
||||
rival: () => { tone(330, 0.14, "sawtooth", 0.05); tone(311, 0.2, "sawtooth", 0.05, 0.13); },
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user