Add the Corporate Ladder pack, and two engine features it needed
Seventeen events across the mailroom: ordinary days, Marlene Hobb and Trevor Pike, a misrouted envelope that can be delivered or kept, and a hard-times branch that fires on negative money and always leaves an ungated way out. Writing real content turned up two things the engine could not express. Per-turn upkeep (`pack.upkeep`): the MVP spec wants recurring income and costs, but effects only run when a player chooses something, and baking wages into every option would be tedious and easy to get wrong. Upkeep entries carry their own conditions, which turned out to be enough for the loan service charge to be a three-step function that escalates with the size of the debt — no multiply operation, no engine change. Upkeep changes are recorded separately from choice changes so the UI can distinguish them. Per-character starting relationships: Trevor starts below the pack default on `likes_you` because he arrived two weeks earlier and has priced that in. Characterisation as data rather than as an opening cutscene. Nobody starts feared, either — `fears_you` defaults to 10, since a midpoint default would mean every new hire arrives already intimidating. The content suite plays 60 seeded runs and asserts every event fires and every option is taken at least once. That caught real dead content: Trevor's favour chain sat behind a gate no plausible player could reach, taking two events and a hard-times escape route with it, and debt could pass 5,500 while the service charge stayed flat, so the failure spiral flattened out instead of tightening. Both fixed in the data — the gate softened and given another route up, the loan capped and its charge tiered. The validator also gained a real fix: it counted an event with a cooldown as a stage's fallback, but a cooldown can exclude it, so a stage could still run dry. 86 tests. Reasoning in docs/DECISIONS.md §11-13, including the seeding trap that makes a correlated chooser look exactly like a content bug. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VMSFHyVPitUoosW5wyEADj
This commit is contained in:
co-authored by
Claude Opus 5
parent
cc59b81155
commit
1d4ac0fdbd
@@ -0,0 +1,313 @@
|
||||
// Events that turn on where you stand with someone.
|
||||
//
|
||||
// These are gated on relationship state rather than skills, which is the point
|
||||
// of tracking three separate numbers per character: a door can open because
|
||||
// somebody likes you, or because they would rather you did not make a thing of
|
||||
// it. The envelope chain is the pack's first taste of leverage — held, then
|
||||
// spent or put back.
|
||||
|
||||
export const people = [
|
||||
{
|
||||
id: 'marlene_late',
|
||||
stage: 'mailroom',
|
||||
weight: 80,
|
||||
cooldown: 3,
|
||||
description:
|
||||
'Marlene needs someone to stay until the six o\'clock courier. She is not ' +
|
||||
'asking you specifically. She is looking at you specifically.',
|
||||
options: [
|
||||
{
|
||||
id: 'stay',
|
||||
label: 'Stay.',
|
||||
result: 'The courier is twenty minutes late. Marlene waits with you.',
|
||||
effects: [
|
||||
{ path: 'money', op: 'add', value: 45 },
|
||||
{ path: 'relationships.marlene.likes_you', op: 'add', value: 5 },
|
||||
{ path: 'relationships.marlene.wants_to_help_you', op: 'add', value: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'decline',
|
||||
label: 'Say you have somewhere to be.',
|
||||
requires: [{ path: 'skills.negotiation', op: '>=', value: 15 }],
|
||||
result: 'She says "of course" in a way that files it somewhere.',
|
||||
effects: [
|
||||
{ path: 'skills.negotiation', op: 'add', value: 2 },
|
||||
{ path: 'relationships.marlene.likes_you', op: 'add', value: -3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'volunteer_trevor',
|
||||
label: 'Suggest Trevor is free.',
|
||||
result: 'Trevor is, as it turns out, free. He finds out the way you did.',
|
||||
effects: [
|
||||
{ path: 'relationships.trevor.likes_you', op: 'add', value: -6 },
|
||||
{ path: 'relationships.trevor.fears_you', op: 'add', value: 4 },
|
||||
{ path: 'relationships.marlene.likes_you', op: 'add', value: 1 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
id: 'trevor_credit',
|
||||
stage: 'mailroom',
|
||||
weight: 60,
|
||||
cooldown: 6,
|
||||
description:
|
||||
'The floor manager thanks Trevor for the reorganised outbound shelf. ' +
|
||||
'Trevor accepts this. You built the shelf on Tuesday.',
|
||||
options: [
|
||||
{
|
||||
id: 'let_it_go',
|
||||
label: 'Let it go.',
|
||||
result: 'Trevor is warmer to you all week, which is somehow worse.',
|
||||
effects: [
|
||||
{ path: 'reputation', op: 'add', value: -1 },
|
||||
{ path: 'relationships.trevor.likes_you', op: 'add', value: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'correct',
|
||||
label: 'Correct the record, politely.',
|
||||
requires: [{ path: 'skills.negotiation', op: '>=', value: 15 }],
|
||||
result:
|
||||
'You do it well enough that nobody is embarrassed except Trevor, who ' +
|
||||
'is embarrassed thoroughly.',
|
||||
effects: [
|
||||
{ path: 'skills.negotiation', op: 'add', value: 2 },
|
||||
{ path: 'reputation', op: 'add', value: 4 },
|
||||
{ path: 'relationships.trevor.likes_you', op: 'add', value: -8 },
|
||||
{ path: 'relationships.trevor.fears_you', op: 'add', value: 5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'private',
|
||||
label: 'Mention it to Trevor later, alone.',
|
||||
result: 'He apologises twice, which is once more than he needed to.',
|
||||
effects: [
|
||||
{ path: 'skills.negotiation', op: 'add', value: 3 },
|
||||
{ path: 'relationships.trevor.likes_you', op: 'add', value: -2 },
|
||||
{ path: 'relationships.trevor.fears_you', op: 'add', value: 6 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
id: 'misrouted_envelope',
|
||||
stage: 'mailroom',
|
||||
weight: 35,
|
||||
cooldown: 8,
|
||||
requires: [{ path: 'flags.holds_the_envelope', op: '==', value: false }],
|
||||
description:
|
||||
'A sealed envelope, misrouted. Wrong floor, wrong name — and the name is ' +
|
||||
'one you have seen on the quarterly. It is not sealed especially well.',
|
||||
options: [
|
||||
{
|
||||
id: 'deliver',
|
||||
label: 'Deliver it. Say nothing.',
|
||||
result: 'It goes where it was going. You think about it on the way back.',
|
||||
effects: [
|
||||
{ path: 'reputation', op: 'add', value: 2 },
|
||||
{ path: 'notable', op: 'push', value: 'Delivered the misrouted envelope unopened.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'hold',
|
||||
label: 'Put it in your locker for now.',
|
||||
result: 'For now.',
|
||||
effects: [
|
||||
{ path: 'flags.holds_the_envelope', op: 'set', value: true },
|
||||
{ path: 'notable', op: 'push', value: 'Kept an envelope that was not addressed to you.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'to_marlene',
|
||||
label: 'Take it to Marlene.',
|
||||
result: 'She reroutes it without looking at it, which took her years.',
|
||||
effects: [
|
||||
{ path: 'relationships.marlene.likes_you', op: 'add', value: 4 },
|
||||
{ path: 'relationships.marlene.wants_to_help_you', op: 'add', value: 3 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
id: 'envelope_pressure',
|
||||
stage: 'mailroom',
|
||||
weight: 45,
|
||||
cooldown: 6,
|
||||
requires: [{ path: 'flags.holds_the_envelope', op: '==', value: true }],
|
||||
description:
|
||||
'The envelope is still in your locker. It has begun to feel heavier than paper.',
|
||||
options: [
|
||||
{
|
||||
id: 'return_it',
|
||||
label: 'Put it back into the system, late.',
|
||||
result: 'Late mail is nobody\'s scandal. It clears.',
|
||||
effects: [
|
||||
{ path: 'flags.holds_the_envelope', op: 'set', value: false },
|
||||
{ path: 'reputation', op: 'add', value: -1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'let_it_be_known',
|
||||
label: 'Let it be known, quietly, that you have it.',
|
||||
requires: [{ path: 'skills.negotiation', op: '>=', value: 30 }],
|
||||
result:
|
||||
'You never say what is in it. You never have to. The mailroom gets ' +
|
||||
'noticeably more punctual.',
|
||||
effects: [
|
||||
{ path: 'relationships.marlene.fears_you', op: 'add', value: 10 },
|
||||
{ path: 'relationships.trevor.fears_you', op: 'add', value: 10 },
|
||||
{ path: 'reputation', op: 'add', value: -4 },
|
||||
{ path: 'notable', op: 'push', value: 'Let it be known you had the envelope.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'leave_it',
|
||||
label: 'Leave it where it is.',
|
||||
result: 'You check the locker twice on your way out.',
|
||||
effects: [{ path: 'reputation', op: 'add', value: -1 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
id: 'trevor_favor',
|
||||
stage: 'mailroom',
|
||||
weight: 55,
|
||||
cooldown: 5,
|
||||
requires: [
|
||||
{ path: 'relationships.trevor.likes_you', op: '>=', value: 48 },
|
||||
{ path: 'flags.covered_for_trevor', op: '==', value: false },
|
||||
],
|
||||
description:
|
||||
'Trevor asks if you would cover the last hour on Thursday. He does not ' +
|
||||
'say why. He does say it twice.',
|
||||
options: [
|
||||
{
|
||||
id: 'cover',
|
||||
label: 'Cover it.',
|
||||
result: 'Thursday is quiet. Trevor is not there to see how quiet.',
|
||||
effects: [
|
||||
{ path: 'flags.covered_for_trevor', op: 'set', value: true },
|
||||
{ path: 'relationships.trevor.likes_you', op: 'add', value: 6 },
|
||||
{ path: 'relationships.trevor.wants_to_help_you', op: 'add', value: 8 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'trade',
|
||||
label: 'Cover it, and say what you want in return.',
|
||||
requires: [{ path: 'skills.negotiation', op: '>=', value: 15 }],
|
||||
result: 'He agrees quickly, and then thinks about how quickly he agreed.',
|
||||
effects: [
|
||||
{ path: 'flags.covered_for_trevor', op: 'set', value: true },
|
||||
{ path: 'skills.negotiation', op: 'add', value: 3 },
|
||||
{ path: 'relationships.trevor.wants_to_help_you', op: 'add', value: 5 },
|
||||
{ path: 'relationships.trevor.fears_you', op: 'add', value: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'refuse',
|
||||
label: 'Say no.',
|
||||
result: 'He says it is fine. He asks Marlene, who says no as well.',
|
||||
effects: [{ path: 'relationships.trevor.likes_you', op: 'add', value: -5 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
id: 'call_in_favor',
|
||||
stage: 'mailroom',
|
||||
weight: 50,
|
||||
cooldown: 4,
|
||||
requires: [{ path: 'flags.covered_for_trevor', op: '==', value: true }],
|
||||
description:
|
||||
'Thursday was a while ago now. Trevor has not brought it up, which is its ' +
|
||||
'own kind of answer.',
|
||||
options: [
|
||||
{
|
||||
id: 'call_it_in',
|
||||
label: 'Ask him to take the afternoon bins.',
|
||||
result: 'He takes them. The ledger is closed and both of you know it.',
|
||||
effects: [
|
||||
{ path: 'flags.covered_for_trevor', op: 'set', value: false },
|
||||
{ path: 'skills.logistics', op: 'add', value: 1 },
|
||||
{ path: 'reputation', op: 'add', value: 1 },
|
||||
{ path: 'relationships.trevor.wants_to_help_you', op: 'add', value: -4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'bank_it',
|
||||
label: 'Leave it. It keeps.',
|
||||
result: 'It keeps. He knows it keeps.',
|
||||
effects: [{ path: 'relationships.trevor.fears_you', op: 'add', value: 2 }],
|
||||
},
|
||||
{
|
||||
id: 'forgive',
|
||||
label: 'Tell him to forget it.',
|
||||
result: 'He does not forget it, but he does stop bringing you coffee wrong.',
|
||||
effects: [
|
||||
{ path: 'flags.covered_for_trevor', op: 'set', value: false },
|
||||
{ path: 'relationships.trevor.likes_you', op: 'add', value: 7 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
id: 'marlene_confides',
|
||||
stage: 'mailroom',
|
||||
weight: 50,
|
||||
cooldown: 8,
|
||||
requires: [{ path: 'relationships.marlene.likes_you', op: '>=', value: 65 }],
|
||||
description:
|
||||
'Marlene tells you, without preamble, that she was told nineteen years ago ' +
|
||||
'that the mailroom was a two-year posting. She then asks whether the ' +
|
||||
'third-floor bins have gone up.',
|
||||
options: [
|
||||
{
|
||||
id: 'ask_about_it',
|
||||
label: 'Ask her about it.',
|
||||
result: 'She talks for a while. She has not told this to Trevor.',
|
||||
effects: [
|
||||
{ path: 'skills.negotiation', op: 'add', value: 2 },
|
||||
{ path: 'relationships.marlene.likes_you', op: 'add', value: 5 },
|
||||
{ path: 'relationships.marlene.wants_to_help_you', op: 'add', value: 6 },
|
||||
{
|
||||
path: 'notable',
|
||||
op: 'push',
|
||||
value: 'Marlene told you about the two-year posting.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'answer_the_bins',
|
||||
label: 'Tell her the bins have gone up.',
|
||||
result: 'She writes it down. The subject is closed with visible relief.',
|
||||
effects: [
|
||||
{ path: 'skills.logistics', op: 'add', value: 1 },
|
||||
{ path: 'relationships.marlene.likes_you', op: 'add', value: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ask_how_to_leave',
|
||||
label: 'Ask her how to get out of the mailroom.',
|
||||
requires: [{ path: 'skills.negotiation', op: '>=', value: 15 }],
|
||||
result:
|
||||
'She gives you three names and a piece of advice she says she wishes ' +
|
||||
'someone had given her, which is how you know it is good.',
|
||||
effects: [
|
||||
{ path: 'skills.negotiation', op: 'add', value: 4 },
|
||||
{ path: 'reputation', op: 'add', value: 2 },
|
||||
{ path: 'relationships.marlene.wants_to_help_you', op: 'add', value: 5 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default people;
|
||||
Reference in New Issue
Block a user