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:
co-authored by
Claude Opus 5
parent
7f20be83e8
commit
5d645e6452
@@ -48,16 +48,22 @@ function randomRun(pack, runSeed, turns) {
|
||||
* on where the last rent lands — Corporate Ladder measures -19 a fortnight over
|
||||
* 280 turns and -42 over 560, and only one of those is the steady state.
|
||||
*
|
||||
* Entries gated on a debt path are excluded: the baseline is what a player
|
||||
* faces owing nothing, which is the floor the pack is designed around.
|
||||
* Entries gated on a path the pack calls elective are excluded: a debt it took
|
||||
* on, a lifestyle it bought. The baseline is what a player faces having opted
|
||||
* into none of them, which is the floor the pack is designed around.
|
||||
*
|
||||
* Which paths those are is the pack's to name, in `tuning.baselineExcludes`.
|
||||
* This used to be a regex over path names — /debt|owed|loan/i — which is the
|
||||
* generic suite guessing at content's vocabulary, exactly what DECISIONS #31
|
||||
* says it must not do. It also silently missed anything named otherwise.
|
||||
*/
|
||||
function baselinePerCycle(pack, stage) {
|
||||
const cycle = pack.calendar?.cycleLength ?? 1;
|
||||
const debtish = (path) => /debt|owed|loan/i.test(path);
|
||||
const elective = new Set(pack.tuning?.baselineExcludes ?? []);
|
||||
return (pack.upkeep ?? [])
|
||||
.filter((entry) => {
|
||||
const gate = entry.requires ?? [];
|
||||
if (gate.some((c) => debtish(c.path))) return false;
|
||||
if (gate.some((c) => elective.has(c.path))) return false;
|
||||
return !gate.some((c) => c.path === 'stage' && c.value !== stage);
|
||||
})
|
||||
.reduce((total, entry) => {
|
||||
@@ -90,6 +96,12 @@ for (const pack of packs) {
|
||||
assert.ok(pack.stages[stage].title, `a title for stage "${stage}"`);
|
||||
assert.ok(pack.tuning.baselinePerCycle[stage], `a baseline for stage "${stage}"`);
|
||||
}
|
||||
// A typo here would not fail: the entry simply would not be excluded, and
|
||||
// the baseline would quietly become a number nobody meant.
|
||||
for (const path of pack.tuning.baselineExcludes ?? []) {
|
||||
assert.ok(path in pack.state,
|
||||
`baselineExcludes names "${path}", which the pack does not declare`);
|
||||
}
|
||||
});
|
||||
|
||||
test(`${where} no content is dead: every event fires and every option becomes available`, () => {
|
||||
|
||||
Reference in New Issue
Block a user