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,