// Corporate Ladder — the default content pack for The Ladder. // // A modern office, the mailroom, and two people who have been there longer // than you. Everything the engine needs to run this setting is in this file // and the ones it imports; the engine itself knows nothing about mailrooms. import { characters } from './characters.js'; import { events } from './events/index.js'; export const corporateLadder = { id: 'corporateladder', name: 'Corporate Ladder', // One sentence for the setting picker. The interface reads it and knows // nothing else about what this pack is. blurb: 'A modern office and a mailroom, two people who have been here longer ' + 'than you, and a laminated org chart with your name not on it.', version: '0.1.1', turnUnit: 'day', startingStage: 'mailroom', // What the interface calls the panel of people. The UI used to hardcode // "The mailroom" here, which was the one place it named a setting. peopleHeading: 'The mailroom', stages: { mailroom: { label: 'Mailroom', title: 'Mail Associate I' }, dispatch: { label: 'Dispatch', title: 'Dispatch Coordinator' }, }, // Turn 1 is a Monday. Content can gate on the day — and, more importantly, // money arrives and leaves on a rhythm the player can plan around instead of // draining by an unexplained amount every day. calendar: { cycleLength: 7, cycleName: 'Week', unitNames: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], }, // Money is tight but not hopeless. The baseline bleeds about three dollars a // day, so a careful player creeps forward and a careless one does not — the // pressure comes from what choices cost, not from an unexplained drain. state: { 'money': { initial: 520, min: -5000, max: 1000000 }, 'debt': { initial: 0, min: 0, max: 1000000 }, 'reputation': { initial: 50, min: 0, max: 100 }, 'skills.negotiation': { initial: 5, min: 0, max: 100 }, 'skills.logistics': { initial: 5, min: 0, max: 100 }, // What standing costs to keep. Nothing forces a player to climb this, but // Dispatch charges standing for staying at the bottom of it — see the // "Looking the part" entries in upkeep, and DECISIONS #36. 'lifestyle': { initial: 0, min: 0, max: 2 }, 'flags.took_loan': { initial: false }, 'flags.holds_the_envelope': { initial: false }, 'flags.covered_for_trevor': { initial: false }, 'flags.promoted': { initial: false }, 'flags.recommended_trevor': { initial: false }, // Moments the retrospective reads back. Content decides what is memorable; // the full turn-by-turn record lives in history regardless. 'notable': { initial: [] }, }, // Nobody fears a new hire. Trevor's defaults are lower still — see // characters.js, where he starts two weeks ahead of you and knows it. relationshipStats: { likes_you: { label: 'Likes you', initial: 50, min: 0, max: 100 }, fears_you: { label: 'Wary of you', initial: 10, min: 0, max: 100 }, wants_to_help_you: { label: 'Will help you', initial: 40, min: 0, max: 100 }, }, // Money moves on a schedule the player can feel: paid on alternate Fridays, // rent out every Monday, and only small change in between. // // Keep this arithmetic honest when tuning: wages, minus two rents, minus // fourteen days of small change. Per week that is 560 - 455 - 126 = -21, so // about forty dollars down over a fortnight — a drift, not a bleed. Rent was // once raised without re-deriving it, which quietly made the baseline four // times steeper and trapped a real playtester below zero for 30% of a // 182-turn run. The range is asserted from `tuning` below. upkeep: [ { label: 'Coffee, transit, lunch', effects: [{ path: 'money', op: 'add', value: -18 }] }, // Turn 1 is a Monday, so offset 4 is the first Friday and every 14 makes it // alternate Fridays. Rent lands on Mondays, starting the second week. // Wages depend on the job. Two entries rather than one, gated on stage: // the engine has no idea what a promotion is, and does not need to. { label: 'Wages', every: 14, offset: 4, requires: [{ path: 'stage', op: '==', value: 'mailroom' }], effects: [{ path: 'money', op: 'add', value: 1120 }], }, { label: 'Wages', every: 14, offset: 4, // A real raise, but not a different life — the coordinator job is, as // Marlene puts it, mostly the same work with a different phone. Net of // rent it comes to a few dollars a day ahead instead of behind. requires: [{ path: 'stage', op: '==', value: 'dispatch' }], effects: [{ path: 'money', op: 'add', value: 1260 }], }, { label: 'Rent', every: 7, offset: 7, effects: [{ path: 'money', op: 'add', value: -455 }] }, // What the job expects of you, and what meeting that expectation costs. // Dispatch nets +49 a week; a coordinator with the car is +19, and one // with the car and the address is -21 and has to earn the difference out // of the days themselves. That is the answer to "money stops mattering // late": it stops mattering only if standing is free to keep. { label: 'Car payment and parking', every: 7, offset: 7, requires: [{ path: 'lifestyle', op: '>=', value: 1 }], effects: [{ path: 'money', op: 'add', value: -30 }], }, { label: 'The better address', every: 7, offset: 7, requires: [{ path: 'lifestyle', op: '>=', value: 2 }], effects: [{ path: 'money', op: 'add', value: -40 }], }, // The pressure that makes the spending a choice rather than a vanity. Both // are gated on Dispatch: the mailroom pays too little for this to be // anything but a trap, and "later in a career" is the point. { label: 'Looking the part', every: 7, offset: 7, requires: [ { path: 'stage', op: '==', value: 'dispatch' }, { path: 'lifestyle', op: '<', value: 1 }, ], effects: [{ path: 'reputation', op: 'add', value: -4 }], }, { // Standing above sixty-five is a thing you are seen to be, and being seen // to be it runs on a better address than a coordinator's first one. // // Sized by measurement, not by feel. At -2 a week this was invisible: // Dispatch hands out standing several points at a time, so a coordinator // who bought nothing still finished pinned at 100 and the charge may as // well not have existed. At -6, with the entry above, a non-buyer settles // in the low seventies while somebody keeping up finishes at 100 — the // ceiling is real and money is what lifts it. label: 'Looking the part', every: 7, offset: 7, requires: [ { path: 'stage', op: '==', value: 'dispatch' }, { path: 'reputation', op: '>=', value: 65 }, { path: 'lifestyle', op: '<', value: 2 }, ], effects: [{ path: 'reputation', op: 'add', value: -6 }], }, { // The top step, and the one that makes the whole thing a choice rather // than a tax on carelessness. Without it, a player who spends every turn // chasing standing simply out-earns the charge: measured, they held 99 // while never buying anything, and finished $2,245 richer than a player // who kept up — so the lifestyle was a pure loss to anybody paying // attention. With it they plateau in the low eighties. What money buys // up here is the last fifteen points, which cannot be worked for. label: 'Looking the part', every: 7, offset: 7, requires: [ { path: 'stage', op: '==', value: 'dispatch' }, { path: 'reputation', op: '>=', value: 80 }, { path: 'lifestyle', op: '<', value: 2 }, ], effects: [{ path: 'reputation', op: 'add', value: -14 }], }, // The charge that never stops, and the only one with no purchase that ends // it. Everything above is a ladder with a top: buy both tiers, reach 100 // standing, and every charge switches off. A real 96-turn session arrived // there at turn 84 with nothing left to spend on and a balance that only // climbed. // // It steps on money rather than being flat, and that is the whole design. // A flat charge cannot flatten a slope: late income comes from choices and // scales with how well the player is playing, so a charge big enough to // stop an optimiser accumulating (-360 a week, measured) put ordinary // archetypes below zero for 44% of a run and ended them $1,385 in debt. // Stepping on money lands it on whoever has it. Measured: the balance // settles around $4,250 and stops climbing (-3 a week), while ordinary play // gets *better* than before it existed — 13% of a run below zero against // 17%, and a tier-2 run ending +$175 rather than -$25. { label: 'What the standing costs', every: 7, offset: 7, requires: [ { path: 'stage', op: '==', value: 'dispatch' }, { path: 'reputation', op: '>=', value: 85 }, { path: 'money', op: '>=', value: 1200 }, ], effects: [{ path: 'money', op: 'add', value: -60 }], }, { label: 'What the standing costs', every: 7, offset: 7, requires: [ { path: 'stage', op: '==', value: 'dispatch' }, { path: 'reputation', op: '>=', value: 85 }, { path: 'money', op: '>=', value: 2400 }, ], effects: [{ path: 'money', op: 'add', value: -120 }], }, { label: 'What the standing costs', every: 7, offset: 7, requires: [ { path: 'stage', op: '==', value: 'dispatch' }, { path: 'reputation', op: '>=', value: 85 }, { path: 'money', op: '>=', value: 3600 }, ], effects: [{ path: 'money', op: 'add', value: -240 }], }, // The service charge steps up with the size of the debt, so the spiral // tightens instead of flattening out. A step function in data costs the // engine nothing; a percentage would have needed a multiply operation. { label: 'Loan service charge', every: 7, offset: 7, requires: [{ path: 'debt', op: '>', value: 0 }], effects: [{ path: 'money', op: 'add', value: -70 }], }, { label: 'Loan service charge (arrears)', every: 7, offset: 7, requires: [{ path: 'debt', op: '>=', value: 1000 }], effects: [{ path: 'money', op: 'add', value: -40 }], }, { label: 'Loan service charge (collections)', every: 7, offset: 7, requires: [{ path: 'debt', op: '>=', value: 2000 }], effects: [{ path: 'money', op: 'add', value: -70 }], }, ], // What the conformance suite holds this pack to. The numbers a setting must // hit are the setting's business, so they are declared here rather than // written into a test that names this pack. See DECISIONS.md #31. tuning: { // Net movement of `money` from upkeep alone, per calendar cycle — here a // week — at each stage, with no debt outstanding. baselinePerCycle: { mailroom: [-45, 5], dispatch: [5, 125], }, // Upkeep gated on these is excluded from that baseline: a debt taken on, a // lifestyle bought, a standing earned. The baseline is the floor underneath // everybody — what a player faces having none of the three. baselineExcludes: ['debt', 'lifestyle', 'reputation'], }, // How the interface names and formats this pack's state. The UI reads only // this map, so renaming a stat or reordering the header is content's job. display: { 'money': { label: 'Money', format: 'money', order: 1 }, 'debt': { label: 'Debt', format: 'money', order: 2, hideWhenZero: true }, 'reputation': { label: 'Standing', format: 'meter', order: 3 }, 'skills.negotiation': { label: 'Negotiation', format: 'meter', order: 4 }, 'skills.logistics': { label: 'Logistics', format: 'meter', order: 5 }, // Hidden until it is bought: a mailroom associate has no lifestyle to // speak of, and a row reading "The bus" from day one is noise. 'lifestyle': { label: 'Lifestyle', format: 'tier', order: 6, hideWhenZero: true, labels: ['The bus', 'A car', 'The address'], }, // Kept out of the header, and out of the ledger too: `notable` is the // retrospective's own list, and "Notable — changed" tells nobody anything. 'notable': { hidden: true, inLedger: false }, // Flags do belong in the ledger — a flag flipping is a fact about the // world — but as a sentence rather than as "now true". 'flags.took_loan': { label: 'The loan', hidden: true, whenTrue: 'You are carrying a loan now.', whenFalse: 'The loan is settled.', }, 'flags.holds_the_envelope': { label: 'The envelope', hidden: true, whenTrue: 'The envelope is in your locker.', whenFalse: 'The envelope is back in the system.', }, 'flags.covered_for_trevor': { label: "Trevor's Thursday", hidden: true, whenTrue: 'Trevor owes you a Thursday.', whenFalse: 'You and Trevor are square.', }, 'flags.promoted': { label: 'Promotion', hidden: true, whenTrue: 'You are out of the mailroom.', whenFalse: 'Back in the mailroom.', }, 'flags.recommended_trevor': { label: "Trevor's recommendation", hidden: true, whenTrue: "Trevor's name is in for the mailroom.", whenFalse: "Trevor's name is not in for the mailroom.", }, }, characters, events, }; export default corporateLadder;