Make standing cost money to keep, in both settings

Each pack declares a lifestyle tier, bought on the second rung and charged for
every cycle after: Corporate Ladder's car and address, the Frontier's bay and
the room over the Ellis place. Falling behind what the job expects costs
standing, in steps that steepen as standing rises. A Dispatch coordinator
keeping up fully nets -21 a week and earns the difference out of the days. No
engine change; the only interface change is a tier display format.

Sized by measurement, not feel (DECISIONS #36): a -2/week penalty is invisible
against Dispatch's standing gains, and a player who chases standing every turn
beats any flat penalty — 99 standing while buying nothing, and $2,245 richer
than one who kept up. Hence the top step.

Two defects fixed on the way. The conformance suite excluded debt from the
economic baseline by matching /debt|owed|loan/i on path names, which would have
counted every lifestyle tier into it; packs now declare tuning.baselineExcludes.
And 'the promotion leaves a player better off' pooled end balances across
archetypes that do not spend alike, so it measured the mix — it is now derived
from the upkeep arithmetic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxdJFbLq1rBKsUeticGV1g
This commit is contained in:
JesseMarkowitz
2026-09-12 21:53:03 -04:00
co-authored by Claude Opus 5
parent 7f20be83e8
commit 5d645e6452
12 changed files with 507 additions and 15 deletions
+5 -1
View File
@@ -34,7 +34,11 @@ export function labelFor(pack, path) {
/** Money is written as money; everything else is a plain number. */
export function formatValue(pack, path, value) {
if (typeof value === 'boolean') return value ? 'yes' : 'no';
if (displayFor(pack, path)?.format === 'money') return formatMoney(value);
const rule = displayFor(pack, path);
if (rule?.format === 'money') return formatMoney(value);
// A tier is a number the player should never see as one: a pack declares
// what each step is called and the interface reads the name off the list.
if (rule?.format === 'tier') return rule.labels?.[value] ?? String(value);
return String(value);
}