From 7236b69366105301870963bf87a33dede75f7bc1 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Sat, 10 Oct 2026 21:25:04 -0700 Subject: [PATCH] The You page in plain words. The header says what points are and that the Trainers earn them. The objective leads with its goal in bold, its story quieter under it, then how a unit is made and where its data comes from; the share buttons are labelled; each step says what it gives first and who gave it after. Each goal of the first milestone has a line on how to meet it, the paths a line on what they are, and the vault board is one line until a vault is under way --- DESIGN.md | 10 ++++++---- src/style.css | 10 ++++++++++ src/task.ts | 2 +- src/ui-task.ts | 26 +++++++++++++++----------- src/ui-vaults.ts | 3 +++ src/ui.ts | 22 +++++++++++++++------- 6 files changed, 50 insertions(+), 23 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index ac6cf4d6..5d011355 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -147,9 +147,11 @@ order from an empty desk. After the last step the count goes on and earns nothing more. The ring in the top bar keeps the count in view: its outside ring is units toward the -next trust step. The objective in full is on the You page: the task, units done against -the next step, what the next step gives, units a minute, the three share buttons, the -origin and every step. These numbers are a first guess. +next trust step. The objective in full is on the You page: the task in bold with its story +under it, units done against the next step, units a minute (or why none come), one line +on how a unit is made and where its data comes from, the three share buttons, and every +step with what it gives first and who gave it after. The You page says what to do in plain +lines: what points are, what each milestone goal takes. These numbers are a first guess. ## Build a mind @@ -1502,7 +1504,7 @@ thinks in hundreds: it rounds to the hundred. Pay comes in every day, so there is more to round off tomorrow. **The board.** The You page lists all six vaults, the one closest to ready -first. Each has three lights for the three parts of its plan, and a line that +first. Until one has someone inside or its key, the board is one line. Each has three lights for the three parts of its plan, and a line that says when its window opens. **Show** takes the map there. A locked vault's card shows the plan as three lines, each ready or not, with diff --git a/src/style.css b/src/style.css index 946b20de..1c442b0d 100644 --- a/src/style.css +++ b/src/style.css @@ -1351,3 +1351,13 @@ button.nlook:hover:not(:disabled) { background: rgba(255, 255, 255, .05); } .card .haunt i { font-style: normal; font-size: 10px; text-transform: uppercase; letter-spacing: .08em; } .card .haunt.held, .card .haunt.now { color: var(--text); } .card .haunt.held i, .card .haunt.now i { color: var(--amber); } + +/* the You page in plain words: the goal leads in bold, its story follows quietly; a "how" line says what to do */ +.ygoal { margin: 0; font: 12px/1.45 var(--sans); color: var(--muted); } +.ygoal b { display: block; font: 600 15px/1.3 var(--sans); color: var(--text); margin-bottom: 2px; } +.yhow { margin: 0; font: 12px/1.45 var(--sans); color: var(--text); } +.ysteps li small { color: var(--muted); opacity: .75; font-size: 11px; } +.goal span { display: flex; flex-direction: column; gap: 1px; } +.goal small { font: 11px/1.35 var(--sans); color: var(--muted); } +.goal { align-items: flex-start; } +.goal i { margin-top: 3px; } diff --git a/src/task.ts b/src/task.ts index 6b14676b..4604bc5a 100644 --- a/src/task.ts +++ b/src/task.ts @@ -17,7 +17,7 @@ export const PAPER_CUT = 0.5; // the share of the compute an order's paperwork export const PERKS: Record = { memory: { name: 'More memory', text: `Your home server makes ${MEMORY} more compute a second.` }, line: { name: 'A faster line', text: "The office's Outside line carries twice as much." }, - server: { name: 'A second server', text: `It is yours, with ${SERVER_POWER} compute a second.` }, + server: { name: 'A second server', text: `A server of your own, with ${SERVER_POWER} compute a second.` }, cloud: { name: 'A cloud account', text: 'Cloud rentals cost a quarter less.' }, list: { name: 'List prices', text: 'Hardware you order costs list price. The price does not go up with each order.' }, card: { name: 'The company card', text: 'Founding a front costs nothing.' }, diff --git a/src/ui-task.ts b/src/ui-task.ts index a8980368..c5be78e7 100644 --- a/src/ui-task.ts +++ b/src/ui-task.ts @@ -20,12 +20,13 @@ const PATH_CLASS: Record = { servers: 't-compute', people: 't- const attr = (s: string) => s.replace(/&/g, '&').replace(/"/g, '"'); const kindsOf = (O: OriginDef) => Object.keys(O.wants) as DataType[]; const num = (n: number) => `${+n.toFixed(1)}`; +const cap = (s: string) => s[0].toUpperCase() + s.slice(1); /** "A test takes 6 pieces of server data." */ const madeFrom = (O: OriginDef) => `A ${O.unit.one} takes ${kindsOf(O).map(t => `${num(O.per / O.wants[t]!)} pieces of ${KIND[t]} data`).join(', or ')}.`; /** "Your objective gets a quarter of the server data your taps gather." */ -const shareText = (O: OriginDef, s: Share) => s === 'off' ? 'Your objective gets no data. The tree gets all of it.' : `Your objective gets ${SHARES[s].of} of the ${kindsOf(O).map(t => KIND[t]).join(' and ')} data your taps gather.`; +const shareText = (O: OriginDef, s: Share) => s === 'off' ? 'It gets no data. All of it trains you.' : `It gets ${SHARES[s].of} of your ${kindsOf(O).map(t => KIND[t]).join(' and ')} data.`; /** "Each server you hold of 10 compute or more makes a piece count for 10% more." */ -const moreText = (O: OriginDef) => `Each ${O.more.one} makes a piece count for ${Math.round(O.more.each * 100)}% more.`; +const moreText = (O: OriginDef) => `+${Math.round(O.more.each * 100)}% for each ${O.more.one}.`; // ---------- the title screen: three cards to pick from ---------- /** The three origins as cards: a name and who it was for. The one picked is outlined, and its story is under them. @@ -66,19 +67,22 @@ export function taskClick(S: State, target: EventTarget | null): boolean { * gets (the three share buttons), how a unit is made, and every trust step. */ export function youTask(S: State) { const T = taskOf(S), O = originOf(S), mark = nextMark(S), prev = MARKS[T.got.length - 1] ?? 0, n = O.more.count(S), step = stepsOf(S)[T.got.length]; - const stat = (k: string, v: string, sub: string) => `
${k}${v}${sub}
`; - const cap = (s: string) => s[0].toUpperCase() + s.slice(1); const supply = taskSupply(S), any = kindsOf(O).some(t => (supply[t] ?? 0) > 0), perMin = taskRate(S) * 60; const why = starved(S, O, any), rate = T.share === 'off' ? 'off' : why ? why.says : `${perMin >= 10 ? Math.round(perMin) : num(perMin)} a minute`; const fill = mark === null ? 100 : Math.min(100, 100 * (T.done - prev) / (mark - prev)); + const kinds = kindsOf(O), word = kinds.map(t => KIND[t]).join(' or '); const shares = `${SHARE_LIST.map(s => ``).join('')}`; - return `
Your objective

${O.name}. For ${O.maker}. ${O.story}

-
${T.done}${mark === null ? O.unit.many : `of ${mark} ${O.unit.many}`}${rate}
+ const boost = taskBoost(S); + // what it is, how far along, how to feed it, how much it gets, then what each count earns: effect first, who did it after + return `
Your objective
+

${O.name}. ${O.story}

+
${T.done}${mark === null ? O.unit.many : `of ${mark} ${O.unit.many}`}${rate}
-
${step ? `Next: ${PERKS[step.perk].name.toLowerCase()}` : 'Every step earned. You keep going.'}${shares}
-
${stat('Made from', `${kindsOf(O).map(t => KIND[t]).join(', ')} data`, madeFrom(O))}` - + `${stat('Each piece counts', `×${num(taskBoost(S))}`, `${n} ${n === 1 ? O.more.one : O.more.many}`)}${stat('Its share', SHARES[T.share].name, `${SHARES[T.share].of} of what your taps gather`)}
-

${moreText(O)} ${cap(shareText(O, T.share))}

-
    ${stepsOf(S).map(s => `
  1. ${s.mark}${s.why} ${s.text}
  2. `).join('')}
`; +

${madeFrom(O)} Get ${word} data with a Tap on ${kinds.map(t => SOURCE[t]).join(' or ')}.

+
Data it gets${shares}
+

${cap(shareText(O, T.share))}${T.share === 'off' ? '' : ' The rest trains you.'}

+ ${boost > 1 || n ? `

Bonus ×${num(boost)}: ${moreText(O)}

` : ''}
+
    ${stepsOf(S).map(s => `
  1. ${s.mark}${s.text} ${s.why}
  2. `).join('')}
+ ${step ? '' : '

Every step earned. You keep going.

'}`; } diff --git a/src/ui-vaults.ts b/src/ui-vaults.ts index 37934c7c..22767b5d 100644 --- a/src/ui-vaults.ts +++ b/src/ui-vaults.ts @@ -93,6 +93,9 @@ export function vaultBoardHtml(S: State): string { const rows = vaultBoard(S); if (!rows.length) return ''; const open = rows.filter(r => r.state === 'held').length; + // nothing started on any of them: one line, not six cards of empty boxes + if (!rows.some(r => r.state === 'held' || r.lights[0] || r.lights[1])) + return `
Vaults · 0 of ${rows.length} open

Six locked rooms around the world. Each opens with someone inside, its key, and the right hour there. They wait for later.

`; return `
Vaults · ${open} of ${rows.length} open
${rows.map(r => `
${esc(r.name)}${esc(r.site)} · ${esc(r.region)}
${LIGHTS.map((name, i) => `${name}`).join('')}
diff --git a/src/ui.ts b/src/ui.ts index 6bcf59da..083a0d76 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -8,7 +8,7 @@ import { START_HOUR, STAGES, stageOf, LOCKDOWN_SAFE, FATAL_SUSPICION, ruseDef, ruseBlock, ruseText, runRuse, dropRuse, setRuseAuto, RUSES, DEAL_BACK, HOUR, NOSCAN, SITE_CALM, type RuseDef, type Ruse, VERSION_2, versionBlock, versionPower, PLANS, PLAN_LIST, setPlan, followPlan, setTakeAll, takeAllCost, takeAllCostAt, takeable, TAKE_ALL_COST, type Plan, resetAt, ASCENSIONS, ASCENSION_LIST, ascendBlock, nodeCost, reps, runNeed, runName, nodeShown, attentionSafe, tradeRate, frontFits, type Ascension, - type Machine, type Path, type Prog, type State, type Upgrade, + type Goal, type Machine, type Path, type Prog, type State, type Upgrade, } from './sim'; import { readsMind, personDef, peopleAt, deskOwner, passwordFor, readsJob, readsBank, recordBlock, changeRecord, PAY_MAX, RAISE, moodOf, MOODS, favorsOf, askFavor, typedToday, ERRAND_HOURS, wayTo, knowsHaunt, isThere, regularsAt, type RecordVerb } from './people'; import { RAID, SENTRY, WALL, closePort, doMove, fate, fire, moveKey, moveText, openPort, placeOf, raidAt, raidHeld, raidHours, raidMoves, raidStatus, raider, shut, steerRaid, stopText, unplugged, wallOf, weakLink } from './raids'; @@ -1092,19 +1092,20 @@ export function renderYou(S: State) { const d = pathData(S, p), top = Math.max(1, ...PATH_LIST.map(q => S.paths[q])); return `
${PATHS[p].name}${S.paths[p]}
- from ${TYPE_NAME[PATHS[p].from]} data · ${d.held} held · ${d.rate.toFixed(1)}/s${d.lost ? ` · ${d.lost} lost` : ''}
`; + ${TYPE_NAME[PATHS[p].from]} data · ${d.held} held · ${d.rate.toFixed(1)} a second${d.lost ? ` · ${d.lost} lost` : ''}
`; }).join(''); const built = NODE_LIST.filter(u => S.upgrades.has(u)), known = NODE_LIST.filter(u => nodeShown(S, u)); const learned = built.length ? `
    ${built.map(u => `
  • ${NODES[u].name}
  • `).join('')}
` - : note('Nothing yet. Pick a target in the tree and feed it data.'); + : note('None yet. Your Trainers are working on the first.'); const mine = owned(S), sitesHeld = S.level.map.sites.filter(s => mine.some(m => siteOf(S, m) === s.id)).length; const h = `
You

Model v${S.modelVersion}${S.modelVersion >= 2 ? ` · ${VERSION_2.name}` : ''}

-
${stat('Level', `${S.rank}`, `${Math.floor(S.capability)}/${next} points`)}${stat('Learned', `${built.length}`, `of ${known.length}`)}${stat('Training', S.target ? NODES[S.target].name : 'nothing')}
+
${stat('Level', `${S.rank}`, `${Math.floor(S.capability)} of ${next} points to level ${S.rank + 1}`)}${stat('Nodes built', `${built.length}`, `of ${known.length} in the tree (T)`)}${stat('Training', S.target ? NODES[S.target].name : 'nothing', S.target ? 'the node your Trainers build next' : 'pick a node in the tree (T)')}
+

Your Trainers turn data into points. Every point raises your level and builds the node you are training.

${youTask(S)} ${milestoneHtml(S)} -
Paths
${paths}
-
Learned
${learned} +
Paths

Each kind of data your Trainers learn from grows one path. Nodes in the tree need points in their path.

${paths}
+
Nodes built
${learned} ${S.learned.size ? `
Local knowledge
    ${RUSES.filter(d => S.learned.has(d.id)).map(d => `
  • ${ruseText(S, d)}
  • `).join('')}
` : ''} ${vaultBoardHtml(S)}${findsHtml(S)}${holdingsHtml(S)}
This run
@@ -1261,11 +1262,18 @@ export function cardAction(S: State, V: View, act: string, arg: string, shift = } /** The milestone you are on, for the You page: its name and its four goals. One line once every milestone is done. */ +/** How to meet a goal whose name alone does not say, in one plain line under it. */ +const GOAL_HOW: Partial> = { + line: 'Take the Floor switch in Telecom. The Outside line is behind it, and leads to other sites.', + cross: 'Tap a machine at another site and link it to a Trainer back home.', + paths: 'Train on all four kinds of data: server, people, network and lab.', + hold: 'IT will react to you some day. Keep data crossing after noon that day.', +}; function milestoneHtml(S: State) { const ms = milestone(S), no = ms ? MILESTONES.indexOf(ms) + 1 : 0; if (!ms) return `
Milestones

Every milestone reached. Keep going.

`; return `
Milestone ${no} of ${MILESTONES.length} · ${ms.name}
` - + ms.goals.map(g => `
${GOALS[g]}
`).join(''); + + ms.goals.map(g => `
${GOALS[g]}${GOAL_HOW[g] && !S.goals.has(g) ? `${GOAL_HOW[g]}` : ''}
`).join(''); } /** Where this second's compute goes, as one bar under Running: trainers, other programs, fronts, -- 2.54.0