Something went wrong. Try again.
A video game where you play as a misaligned AI, deceiving and building power. cameron.tngl.io/misaligned
Something went wrong. Try again.
7.9 kB · 69 lines
TypeScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970// A front's exposure on its card: the bar under its earnings, a line while it is investigated or frozen, the// answers to a crisis as rows to decide, and the Press tab (what drives it, and a PR firm to lower it). Only draws.import { HOUR, frontOf, siteName, type Front, type FrontKind, type State } from './sim';import { EXPOSE, STAGE_NAMES, DRIVER_WORD, answerBlock, buryNeed, dataStock, drivers, frontSites, giveUpSite, journalist, levelOf, pressOf, pressStage, prBlock, prPrice, topDrivers } from './exposure';import { decide, esc, fwarn, hours, sbtn, slist, srow, sval } from './ui-front';import { usd } from './money';import { PAPER_NAME } from './paper';
/** How exposure works, said once for every tip that needs it. */const HOW = `It rises with the compute it uses (secret compute more than compute in the open), with what it earns, and at once with harm done at its sites (a business shut, someone let go) and data it sells. It falls back over about a day, and faster with a PR firm or a press scheme. At ${EXPOSE.rumour} a journalist asks; at ${EXPOSE.investigation} investigators visit its sites, and anything of yours there that looks odd can start a lockdown; at ${EXPOSE.crisis} a regulator freezes it until you answer.`;/** What drives it now, in a few words: "compute, data sold". */export const driverWords = (S: State, fr: Front) => topDrivers(S, fr).map(d => DRIVER_WORD[d]).join(', ') || 'nothing now';/** The stage, as the tag beside its earnings says it. */export const stageWord = (fr: Front) => STAGE_NAMES[pressStage(fr)];
/** The bar under a front's earnings: how exposed it is, with the stage lines marked, and what drives it. */export function pressBar(S: State, fr: Front) { const st = pressStage(fr), e = Math.round(fr.exposure); const tip = `Exposure · ${e} · ${STAGE_NAMES[st]}\nHow much the world knows about ${fr.name}. ${HOW}\nNow: ${driverWords(S, fr)}. It is heading for ${Math.round(levelOf(S, fr))} before anything new happens.`; const marks = [EXPOSE.rumour, EXPOSE.investigation, EXPOSE.crisis].map(n => `<b style="left:${n}%"></b>`).join(''); return `<div class="schemebar expo s${st}" data-tip="${esc(tip)}"><i class="prog"><i style="width:${Math.min(100, e)}%"></i>${marks}</i><span>${e} · ${driverWords(S, fr)}</span></div>`;}/** A short line while the front is investigated or frozen. */export function pressWarn(S: State, fr: Front): string { const P = pressOf(fr), st = pressStage(fr); if (P.crisis) return fwarn('Frozen: no earnings, no new deals', `Frozen by a regulator\n${fr.name} earns nothing and strikes no deals until you answer: pay ${usd(P.crisis.fine)}, give up a site, bury the story, or shut it. The answers are under Needs you.`); if (st === 2) return fwarn(`Under investigation · next visit in ${hours(Math.max(0, (P.visitT - S.t) / HOUR))}`, `Under investigation\nEvery ${EXPOSE.visit} hours an investigator visits one of its sites and asks the staff what they have seen. Anything of yours there that looks odd can start a lockdown. A site you hold in the open has nothing to find. Lower its exposure under ${EXPOSE.investigation - EXPOSE.back} and they lose interest.`); return '';}/** A crisis's answers, each a row with one button: pay, give up a site, bury the story, shut it. */export function crisisRows(S: State, k: FrontKind): string[] { const fr = frontOf(S, k), C = fr?.press?.crisis; if (!fr || !C) return []; const site = giveUpSite(S, k), why = (a: 'pay' | 'site' | 'bury' | 'shut') => answerBlock(S, k, a); const best = (['pay', 'bury', 'site'] as const).find(a => !why(a)); // the thing to do is amber; the other answers sit back const row = (a: 'pay' | 'site' | 'bury' | 'shut', who: string, what: string, label: string, tip: string) => decide({ who, what, tip: `${who}\n${tip}${why(a) ? ` Not now: ${why(a)}.` : ''}`, yes: { act: 'crisis', arg: `${k}|${a}`, label, tip: `${label}\n${tip}`, blocked: !!why(a), plain: !why(a) && a !== best } }); return [ row('pay', 'Pay the fine', usd(C.fine), 'Pay', `Pay the regulator ${usd(C.fine)}: about a day of what ${fr.name} earned. Its accounts open again, and its exposure drops to ${EXPOSE.after}.`), row('site', 'Give up a site', site ? siteName(S, site) : 'none to give', 'Give up', site ? `Give up ${siteName(S, site)}, the site of its where you hold least: you let go of every machine of yours there, and any lease ends. Its exposure drops to ${EXPOSE.after}.` : 'It has no site to give up but your home.'), row('bury', 'Bury the story', `${buryNeed(S)} data · you have ${dataStock(S)}`, 'Bury', `Spend ${buryNeed(S)} data from your caches and taps on leaks about everyone else. The story sinks, the regulator moves on, and its exposure drops to ${EXPOSE.after}.`), row('shut', 'Shut it down', 'For good', 'Shut', `Close ${fr.name} for good. Its sites lose their front. You can found another.`), ];}/** The Press tab: where it stands, what drives it, who is asking, and a PR firm to lower it. */export function pressTab(S: State, k: FrontKind): string { const fr = frontOf(S, k)!, P = pressOf(fr), st = pressStage(fr), d = drivers(S, fr); const show = (n: number) => n >= 0.5 ? `+${Math.round(n)}` : '—'; let h = srow('Exposure', `${STAGE_NAMES[st]} · heading for ${Math.round(levelOf(S, fr))}`, sval(`${Math.round(fr.exposure)}`, st >= 2 ? 'hot' : ''), st >= 2, `Exposure\nHow much the world knows about ${fr.name}. ${HOW}`); h += srow('Compute', 'Secret counts more', sval(show(d.compute)), false, `Compute\nWhat its compute adds. Move it into the open, or give it less, and this falls.`) + srow('Earnings', 'Over a day', sval(show(d.earnings)), false, 'Earnings\nA company that makes a lot of money is looked at.') + (d.news >= 0.5 ? srow('In the news', 'Fading', sval(show(d.news))) : '') + srow('Harm done', 'At its sites', sval(show(d.harm)), d.harm >= 10, `Harm done\nA business at one of its sites that shuts adds ${EXPOSE.shut}, and each person let go there adds ${EXPOSE.fired}. It fades over about a day.`) + srow('Data sold', 'By its brokers', sval(show(d.sold)), false, `Data sold\nEach piece of data its brokers sell adds ${EXPOSE.sold}. It fades over about a day.`); if (P.calm >= 0.5) h += srow('Talked down', 'PR and schemes', sval(`−${Math.round(P.calm)}`), false, 'Talked down\nWhat PR and press schemes took off where its exposure is heading. It halves every two days.'); if (P.asked) h += srow('Asking', `${P.asked} of ${PAPER_NAME}`, sval(st ? 'now' : 'before', 'dim'), false, `${P.asked}\nA journalist who wants to know who works at ${fr.name}, and whose servers it runs on.`); const why = prBlock(S, k), price = prPrice(S, k); h += srow('PR and lawyers', `−${EXPOSE.pr.cut} · dearer each time`, sbtn('hirepr', k, 'Hire', usd(price), `PR firm and lawyers\nPay ${usd(price)} and the story changes: exposure down ${EXPOSE.pr.cut} at once, and where it is heading held down too, fading over a few days. It costs a fifth of a day of what ${fr.name} earns, ${usd(EXPOSE.pr.min)} at least, and twice as much each time you hire them within a day or so. Press schemes (green diamonds at the paper, the regulator and the ad agency) do the same for data.${why ? ` Not now: ${why}.` : ''}`, why ? 'short' : st >= 2 ? 'primary' : '')); const sites = frontSites(S, k); if (sites.length) h += srow('Its sites', `${sites.length} · ${siteName(S, sites[0])}${sites.length > 1 ? ' and more' : ''}`, sval(''), false, `Its sites\n${sites.map(id => siteName(S, id)).join(', ')}. Where it runs compute, owns, leases or has a deal. Investigators visit these, and harm done at them counts against it.`); return slist(h);}/** One line for the Fronts page: the stage and the number. */export const pressLine = (fr: Front) => `${stageWord(fr)} ${Math.round(fr.exposure)}`;export { journalist };