From 53ac10640dcfa6c32f2fe0e77cb65a5722ed8ba0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Sep 2026 23:01:42 -0700 Subject: [PATCH] Front attention over 40% raises suspicion while the front uses secret compute; open compute stays safe --- DESIGN.md | 9 +++++---- src/sim.ts | 9 +++++++++ src/ui.ts | 6 +++--- tests/sim.test.ts | 16 +++++++++++++++- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index b4dbc197..b0d28bd6 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -158,7 +158,9 @@ $0.20 for compute held in the open, which sells at market rates. A **Startup** makes lab data train Research half again as fast; it needs a place with people. A **Neo Cloud** gets 30% off cloud rentals and can bid at auctions. Each front shows **attention**: how much the outside world looks at it. Secret -compute draws more than open compute. Nothing acts on it yet. These numbers +compute draws more than open compute. Over 40%, people ask where the front's +compute comes from: while it uses secret compute, suspicion rises, faster the +higher attention goes. Move it into the open, or give it less. These numbers are a first guess. Each path can make fronts better, so the tree is also how a front grows. @@ -373,10 +375,9 @@ When you are drowning in data late in a run, it has places to go: time. Compute: trainers faster. Research: more points per piece of data. Social: fronts earn more. Stealth: trails fade faster. -Everyone can still buy sites. High attention raises suspicion at that front's -secret sites. +Everyone can still buy sites. -Build order: card stories; attention raising suspicion; the choice with +Build order: card stories; attention raising suspicion (done); the choice with Company and Oracle; repeatables and reprogramming; Worm; Maker. ## First playable slice diff --git a/src/sim.ts b/src/sim.ts index b9bb8c25..45c23c5c 100644 --- a/src/sim.ts +++ b/src/sim.ts @@ -878,6 +878,9 @@ export function letGo(S: State, m: Machine) { * calls (one site bought before model v2), and everyone looks at it (attention that fades slowly). */ export const UNICORN_AT = 2000; export const UNICORN_FAME = 50; +/** Attention a front can have before its secret compute raises suspicion, and how fast it does at 100%. */ +export const ATTENTION_SAFE = 40; +export const attentionHeat = (att: number) => Math.max(0, att - ATTENTION_SAFE) / (100 - ATTENTION_SAFE) * 0.15; /** Double what an egress carries. */ export function upgradeLine(S: State, m: Machine) { const cost = lineCost(m); @@ -1053,6 +1056,12 @@ function stepMoney(S: State, dt: number) { fr.fame = (fr.fame ?? 0) * Math.exp(-dt / 600); const aim = Math.min(100, open * 2 + (all - open) * 5 + fr.fame); fr.attention += (aim - fr.attention) * (1 - Math.exp(-dt / 30)); + // past a point, people start asking where a front's compute comes from: its secret sites get noticed + const secret = all - open; + if (fr.attention > ATTENTION_SAFE && secret > 0.01) { + const at = S.machines.find(m => m.owned && (S.frontUse[m.id] ?? 0) > 0 && S.sites[siteOf(S, m)]?.front === fr.kind && !S.publicSites.has(siteOf(S, m))); + raise(S, attentionHeat(fr.attention) * (secret / all) * dt, `People are asking where ${fr.name} gets its compute`, at?.id ?? null); + } } S.pay = earn - bill; S.money += S.pay * dt; diff --git a/src/ui.ts b/src/ui.ts index f2166440..eb68d9fb 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -2,7 +2,7 @@ import { has, NODES, NODE_LIST, PATHS, PATH_LIST, PROGRAMS, pathData, THREATS, monitorAnswer, ticketDesk, ticketWork, needs, nodeOpen, setAllow, tapBuffer, tapStuck, cacheSize, dataReaches, dataType, trainerBuffer, canInstall, tapActive, tapRate, cleanupTarget, freshTrails, clock, dayOf, hourOf, income, installProg, jobFor, levelNeed, BUILDS, BUILD_LIST, orderPrice, orderWork, type Build, pathOf, removeProg, letGo, canLetGo, roomMachines, setMode, share, sourceFor, spareOf, progNeed, takeOver, takeWork, timeLabel, traceCold, unclaim, owned, gateLoad, lostPerMin, siteName, siteOf, - SIZES, LEASE_TIME, BROKER_LOT, buyBlock, buySite, sitePrice, clusters, clusterBoost, brokerFront, AUCTION, GOALS, auctionBlock, startAuction, type Goal, type LogLine, + SIZES, LEASE_TIME, BROKER_LOT, buyBlock, buySite, sitePrice, ATTENTION_SAFE, clusters, clusterBoost, brokerFront, AUCTION, GOALS, auctionBlock, startAuction, type Goal, type LogLine, FRONT_KINDS, FRONT_LIST, payRate, siteData, frontLimit, frontOf, found, foundBlock, setSiteFront, setSiteShare, type FrontKind, canRent, endLease, leaseRate, leaseUpfront, lineCost, openAccount, rent, setPrio, upgradeLine, type Size, STAGES, stageOf, LOCKDOWN_SAFE, lyingLow, ruseDef, ruseBlock, HOUR, NOSCAN, SITE_CALM, type RuseDef, VERSION_2, versionBlock, versionPower, PLANS, PLAN_LIST, setPlan, followPlan, takeSite, type Plan, @@ -364,9 +364,9 @@ function frontCard(S: State, k: FrontKind) { const fr = frontOf(S, k)!, K = FRONT_KINDS[k]; const sites = Object.entries(S.sites).filter(([, v]) => v.front === k).map(([id]) => id); let h = `
Front · ${K.name}

${fr.name}

`; - h += chips([['earns', `${money(S.frontPay[k] ?? 0)}/s`], ['uses', `${(S.frontPower[k] ?? 0).toFixed(1)}/s`], ['attention', `${Math.round(fr.attention)}%`]]); + h += chips([['earns', `${money(S.frontPay[k] ?? 0)}/s`], ['uses', `${(S.frontPower[k] ?? 0).toFixed(1)}/s`], ['attention', `${Math.round(fr.attention)}%`, fr.attention > ATTENTION_SAFE]]); h += note(`${K.desc} ${K.perk}`); - h += note('Attention is how much the outside world looks at it. Secret compute draws more than compute you hold in the open. Nothing acts on it yet.'); + h += note(`Attention is how much the outside world looks at it. Secret compute draws more than compute you hold in the open. Over ${ATTENTION_SAFE}%, people ask where its compute comes from, and its secret sites raise suspicion.`, fr.attention > ATTENTION_SAFE); const acts: Act[] = sites.map(id => ({ act: 'site', arg: id, title: `${siteName(S, id)} · ${Math.round(S.sites[id].share * 100)}%`, sub: S.publicSites.has(id) ? 'in the open' : 'secret' })); if (!sites.length) h += note('No sites yet. Click a site on the map and pick this front.', true); if (k === 'neocloud' && !S.schemes.has('auction')) { diff --git a/tests/sim.test.ts b/tests/sim.test.ts index 0c8a135f..bdf46cf9 100644 --- a/tests/sim.test.ts +++ b/tests/sim.test.ts @@ -2,7 +2,7 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { BUILDING } from '../src/world.ts'; -import { setPlan, takeSite, installProg, CLAIM_AT_ONCE, salePrice, siteOf, payRate, tapRate, FRONT_PAY, buyBlock, buySite, letGo, sitePrice, clusters, clusterBoost, claim, order, orderPrice, freeSpot, plugsAt, spotProblem, cancelJob, AUCTION, FRONT_PAY_OPEN, UNICORN_AT, FIREWALL, blamePrinter, canInstall, rent, NODES, NODE_LIST, found, foundBlock, setSiteShare, spareOf, cacheSize, needs, newGame, seedRandom, share, step, tapBuffer, takeOver, watchers, runRuse, LOCKDOWN, RUSES, VERSION_2, versionBlock, startVersionRun, pauseVersionRun, setTarget, setAuto, costLeft, nodeOpen, type State, type Upgrade } from '../src/sim.ts'; +import { setPlan, takeSite, installProg, CLAIM_AT_ONCE, salePrice, siteOf, payRate, tapRate, FRONT_PAY, buyBlock, buySite, letGo, sitePrice, clusters, clusterBoost, claim, order, orderPrice, freeSpot, plugsAt, spotProblem, cancelJob, AUCTION, FRONT_PAY_OPEN, UNICORN_AT, ATTENTION_SAFE, FIREWALL, blamePrinter, canInstall, rent, NODES, NODE_LIST, found, foundBlock, setSiteShare, spareOf, cacheSize, needs, newGame, seedRandom, share, step, tapBuffer, takeOver, watchers, runRuse, LOCKDOWN, RUSES, VERSION_2, versionBlock, startVersionRun, pauseVersionRun, setTarget, setAuto, costLeft, nodeOpen, type State, type Upgrade } from '../src/sim.ts'; import { playGame } from '../scripts/play.ts'; const run = (S: State, secs: number) => { for (let i = 0; i < secs * 20; i++) step(S, 0.05); }; @@ -380,6 +380,20 @@ test('every tree node has an eval tag and a training-log line, and building one assert.ok(S.log.some(l => l.text.includes(NODES.buffers.log))); }); +test('high attention raises suspicion while a front uses secret compute, not open compute', () => { + const heat = (open: boolean, att: number) => { + const S = fresh(); + S.fronts.push({ kind: 'startup', name: 'Test Co', attention: att, fame: att }); + S.sites.office = { front: 'startup', share: 0.5 }; + if (open) S.publicSites.add('office'); + run(S, 5); + return S.raises.filter(r => r.why.includes('Test Co')).reduce((a, r) => a + r.n, 0); + }; + assert.equal(heat(false, ATTENTION_SAFE - 20), 0, 'low attention is fine'); + assert.ok(heat(false, 100) > 0, 'high attention on secret compute raises suspicion'); + assert.equal(heat(true, 100), 0, 'open compute is fine'); +}); + test('Telemetry: taps gather more where the Startup is earning', () => { const S = fresh('beta'); S.fronts.push({ kind: 'startup', name: 'Test Co', attention: 0 }); -- 2.51.2