Files
TheLadder/content/corporateladder/events/hard-times.js
T
JesseMarkowitzandClaude Opus 5 5c1784d990 Fix an inescapable debt trap the playtest logs exposed
A 182-turn session spent 30% of its turns below zero, went negative on turn 18
and never recovered, and chose the same hard-times option — "take every shift
going" — thirty-seven times.

The cause was a tuning change made without re-deriving what it meant. Rent went
from 385 to 455 while chasing a different problem, which took the mailroom
baseline from the designed -$3/day to -$13/day. Worse, at the debt that run
accumulated the weekly service charge came to $180 against an escape option
worth $120. The hole was inescapable by arithmetic, whatever the player did.

Nothing caught it. Every archetype either optimised money or got promoted out of
the problem before it bit, and the dominance test passed because rent_bounced
was only 17% of turns.

Fixes: mailroom wages 980 -> 1120, restoring the -$3/day baseline; the escape
option 120 -> 220, so it is worth more than a week's rent; rent_bounced from
weight 5000 with no cooldown to 900 on a cooldown of 3, because being broke
should colour a run rather than replace it; debt_collector from weight 25 to 45,
since at 25 it appeared six times in 262 turns and debt became a ratchet.

Three guards so this class of bug cannot recur quietly.

The baseline is now asserted directly: a test sums the pack's own upkeep over
twenty fortnights and requires the mailroom to net between -90 and +10, and
Dispatch to be better but not so much better that money stops mattering. That
would have failed the moment rent changed. Outcome tests over simulated play are
a slow and noisy way to detect a number that is simply wrong.

A `lifer` archetype refuses any option that would change stage — generically, by
looking for an effect on `stage` — and otherwise plays for people. It reproduces
the session that found this; no other archetype can.

And no archetype may spend more than a quarter of a run below zero. Hard times
is a state a player passes through; living in it is the failure mode.

Also fixed: the analyser reported "every ~-5 turns". One export can hold several
playthroughs and turn numbers restart with each, so spans are now accumulated
per run and pooled rather than measured across the seam between two.

121 tests. Reasoning in docs/DECISIONS.md §29-30.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VMSFHyVPitUoosW5wyEADj
2026-09-10 06:01:33 -04:00

137 lines
5.2 KiB
JavaScript

// Running out of money.
//
// There is no game over here and no separate failure mode in the engine — this
// is an ordinary event whose conditions happen to be "your money is negative",
// carrying enough weight to crowd out an ordinary day. Every route out costs
// something other than money, and every one of them leaves you still playing.
//
// Both events always offer at least one ungated option, so a player with no
// skills and no friends is never stuck.
export const hardTimes = [
{
id: 'rent_bounced',
// No stage: rent is due wherever you work, and the branch has to follow
// the player up rather than vanishing on promotion.
//
// Weighted heavily but not overwhelmingly, and on a cooldown: being broke
// should colour a run, not replace it. At weight 5000 with no cooldown a
// player who could not climb out saw this and almost nothing else, 45
// times in one session.
weight: 900,
cooldown: 3,
requires: [{ path: 'money', op: '<', value: 0 }],
description:
'Rent came out and did not clear. The letter uses the word ' +
'"unfortunately" four times and encloses a form.',
options: [
{
id: 'take_loan',
label: 'Take the loan the letter suggests.',
// They stop lending eventually, which is when the spiral gets real:
// past this, the only ways out cost standing rather than interest.
requires: [{ path: 'debt', op: '<', value: 2000 }],
result:
'The money arrives the same afternoon, which tells you something about ' +
'the rate.',
effects: [
{ path: 'money', op: 'add', value: 500 },
{ path: 'debt', op: 'add', value: 500 },
{ path: 'flags.took_loan', op: 'set', value: true },
{ path: 'reputation', op: 'add', value: -2 },
{ path: 'notable', op: 'push', value: 'Took the loan.' },
],
},
{
id: 'ask_trevor',
label: 'Ask Trevor for a float.',
requires: [{ path: 'relationships.trevor.likes_you', op: '>=', value: 40 }],
result:
'He lends it without making a thing of it, and from then on he is a ' +
'man who lent you money.',
effects: [
{ path: 'money', op: 'add', value: 150 },
{ path: 'reputation', op: 'add', value: -3 },
{ path: 'relationships.trevor.fears_you', op: 'add', value: -8 },
{ path: 'relationships.trevor.wants_to_help_you', op: 'add', value: -5 },
{ path: 'notable', op: 'push', value: 'Borrowed money from Trevor.' },
],
},
{
id: 'ask_marlene',
label: 'Ask Marlene for an advance.',
requires: [{ path: 'relationships.marlene.wants_to_help_you', op: '>=', value: 55 }],
result:
'She processes it herself and does not put it through the system. ' +
'Neither of you mentions it again.',
effects: [
{ path: 'money', op: 'add', value: 200 },
{ path: 'relationships.marlene.wants_to_help_you', op: 'add', value: -10 },
{ path: 'relationships.marlene.likes_you', op: 'add', value: -3 },
],
},
{
id: 'extra_shifts',
label: 'Take every shift going.',
result:
'You do eleven days without one off. The bins stop being individual ' +
'bins after a while.',
// Has to be worth more than a week of rent, or it is not a way out of
// the hole — it is the hole with extra steps.
effects: [
{ path: 'money', op: 'add', value: 220 },
{ path: 'skills.logistics', op: 'add', value: 2 },
{ path: 'reputation', op: 'add', value: -1 },
],
},
],
},
{
id: 'debt_collector',
// The chance to pay down has to come round often enough to matter; at
// weight 25 it appeared six times in 262 turns and debt became a ratchet.
weight: 45,
cooldown: 4,
requires: [{ path: 'debt', op: '>', value: 0 }],
description:
'A man from the lender calls the mailroom phone. Marlene answers it. She ' +
'hands it to you without expression and then, notably, without leaving.',
options: [
{
id: 'pay_chunk',
label: 'Pay a real chunk of it down.',
requires: [{ path: 'money', op: '>=', value: 350 }],
result: 'He is audibly disappointed.',
effects: [
{ path: 'money', op: 'add', value: -350 },
{ path: 'debt', op: 'add', value: -350 },
{ path: 'reputation', op: 'add', value: 1 },
],
},
{
id: 'pay_minimum',
label: 'Pay what you can.',
requires: [{ path: 'money', op: '>=', value: 60 }],
result: 'Sixty dollars, fifty of which is the loan.',
effects: [
{ path: 'money', op: 'add', value: -60 },
{ path: 'debt', op: 'add', value: -50 },
],
},
{
id: 'stall',
label: 'Tell him next month.',
result: 'He says next month. Marlene goes back to her desk.',
effects: [
{ path: 'debt', op: 'add', value: 25 },
{ path: 'reputation', op: 'add', value: -3 },
{ path: 'relationships.marlene.likes_you', op: 'add', value: -4 },
],
},
],
},
];
export default hardTimes;