Files
TheLadder/content/frontier/index.js
T
JesseMarkowitzandClaude Opus 5 7f20be83e8 Add The Frontier, a second setting, and a picker to choose between them
Cold Fork is a freight yard: 29 events across two stages, a four-day cycle, and
its own relationship and skill vocabulary. src/engine/ was not touched. Adding it
needed three changes outside the engine: one save slot per pack, app.js holding
the current pack, and turn.js no longer naming the mailroom (DECISIONS #34).
build.js now handles imports wrapped across lines. A conformance suite runs over
every registered pack. DECISIONS #35 records three calls on open questions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxdJFbLq1rBKsUeticGV1g
2026-09-12 21:03:50 -04:00

193 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The Frontier — the second content pack for The Ladder.
//
// Cold Fork, a freight town at the end of a stage line. You are a hired hand
// in Vane & Co.'s yard; the town has a marshal who is tired and a road that is
// not safe. Two rungs, the same as Corporate Ladder, and not one line of
// engine code between them.
//
// This pack exists to prove the split holds. Everything that makes it a
// different game from Corporate Ladder is in this directory: a four-day cycle
// instead of a week, money that arrives from work rather than from a wage,
// relationship stats named for a place where being owed a favour matters more
// than being liked, and a skill pair of nerve and trade.
import { characters } from './characters.js';
import { events } from './events/index.js';
export const frontier = {
id: 'frontier',
name: 'The Frontier',
blurb:
'Cold Fork, at the end of a stage line: a freight yard, a road that is ' +
'not safe, and a marshal who has been six years retiring.',
version: '0.1.0',
turnUnit: 'day',
startingStage: 'hand',
peopleHeading: 'Cold Fork',
stages: {
hand: { label: 'The Yard', title: 'Hired Hand' },
deputy: { label: 'The Office', title: 'Deputy Marshal' },
},
// Not a week. The stage comes every fourth day, and the whole town runs on
// that: the outfit settles up when the stage comes in, the boarding house
// collects on the rest day before it. A pack that wants a seven-day week
// says seven here; the engine has no opinion either way, and Corporate
// Ladder's calendar is the same field with different numbers in it.
calendar: {
cycleLength: 4,
cycleName: 'Run',
unitNames: ['Stage day', 'Haul', 'Yard', 'Rest'],
},
// Money is smaller here than in Corporate Ladder by a factor of about
// fifteen, and it behaves differently: the settlement covers board and room
// and very little else, so the difference between a good run and a bad one
// is what you took from the work. See `tuning` below for the arithmetic that
// is actually asserted.
state: {
'money': { initial: 40, min: -400, max: 100000 },
// The store ledger. Named for what it is rather than for a loan: nobody in
// Cold Fork lends money, they let you carry a balance.
'owed': { initial: 0, min: 0, max: 100000 },
'standing': { initial: 50, min: 0, max: 100 },
'skills.nerve': { initial: 5, min: 0, max: 100 },
'skills.trade': { initial: 5, min: 0, max: 100 },
'flags.carries_iron': { initial: false },
'flags.owes_the_store': { initial: false },
'flags.covered_for_dutch': { initial: false },
'flags.sworn_in': { initial: false },
'flags.spoke_for_dutch': { initial: false },
'notable': { initial: [] },
},
// A different vocabulary from Corporate Ladder's likes/fears/helps, and the
// reason the vocabulary is content's to choose. Out here the useful question
// about a person is not whether they like you: it is whether they will take
// your word, and whether they are carrying a debt to you they mean to pay.
relationshipStats: {
trusts_you: { label: 'Takes your word', initial: 45, min: 0, max: 100 },
owes_you: { label: 'Owes you', initial: 10, min: 0, max: 100 },
wary_of_you: { label: 'Wary of you', initial: 15, min: 0, max: 100 },
},
// The economic shape, and the thing that makes this pack a real test of the
// engine rather than Corporate Ladder in a hat: there is no wage worth
// living on. Vane settles up when the stage comes in and it covers the room
// and the feed and four dollars besides. Everything else you earn, you earn
// on a turn, by choosing to — and what the choices cost is where the
// pressure comes from, not an unexplained drain.
//
// Keep this arithmetic honest when tuning. Per four-day run:
// hand: -12 board -8 room +24 settlement = +4
// deputy: -12 board -8 room +38 pay = +18
// Both are asserted directly by the conformance suite against `tuning`
// below, so a number changed here without re-deriving it fails the build
// rather than trapping a player forty turns in.
upkeep: [
{ label: 'Board and feed', effects: [{ path: 'money', op: 'add', value: -3 }] },
// Turn 1 is a Stage day, so offset 3 is the first Rest day and offset 4
// the next Stage day — the room is collected the night before the outfit
// settles up, which is the boarding house's whole business model.
{
label: 'Room at the Cordwood',
every: 4,
offset: 3,
effects: [{ path: 'money', op: 'add', value: -8 }],
},
{
label: 'Settlement',
every: 4,
offset: 4,
requires: [{ path: 'stage', op: '==', value: 'hand' }],
effects: [{ path: 'money', op: 'add', value: 24 }],
},
{
label: 'Deputy pay',
every: 4,
offset: 4,
// The town pays a real wage, and it is still not much of a living. What
// changes with the star is not the money; it is what the days contain.
requires: [{ path: 'stage', op: '==', value: 'deputy' }],
effects: [{ path: 'money', op: 'add', value: 38 }],
},
// Store credit costs more the deeper in you are, the same step function
// Corporate Ladder uses for its loan — a shape, not a setting.
{
label: 'Store credit',
every: 4,
offset: 4,
requires: [{ path: 'owed', op: '>', value: 0 }],
effects: [{ path: 'money', op: 'add', value: -3 }],
},
{
label: 'Store credit (carried)',
every: 4,
offset: 4,
requires: [{ path: 'owed', op: '>=', value: 60 }],
effects: [{ path: 'money', op: 'add', value: -3 }],
},
{
label: 'Store credit (posted)',
every: 4,
offset: 4,
requires: [{ path: 'owed', op: '>=', value: 120 }],
effects: [{ path: 'money', op: 'add', value: -4 }],
},
],
// What the conformance suite holds this pack to. Declaring it here rather
// than writing it into a test is the same principle as the rest of the pack:
// the numbers a setting must hit are the setting's business. See
// DECISIONS.md #31.
tuning: {
// Net movement of `money` from upkeep alone, per calendar cycle, at each
// stage. Upkeep is the floor a player plays against: barely above water in
// the yard, and a real if unremarkable living with the star.
baselinePerCycle: {
hand: [0, 12],
deputy: [10, 30],
},
},
display: {
'money': { label: 'Money', format: 'money', order: 1 },
'owed': { label: 'Owed at the store', format: 'money', order: 2, hideWhenZero: true },
'standing': { label: 'Standing', format: 'meter', order: 3 },
'skills.nerve': { label: 'Nerve', format: 'meter', order: 4 },
'skills.trade': { label: 'Trade', format: 'meter', order: 5 },
'notable': { hidden: true, inLedger: false },
'flags.carries_iron': {
label: 'The scattergun', hidden: true,
whenTrue: 'You are carrying Vane’s scattergun on the box.',
whenFalse: 'The scattergun is back under the office counter.',
},
'flags.owes_the_store': {
label: 'The store ledger', hidden: true,
whenTrue: 'Your name is in the book at the store.',
whenFalse: 'You are square with the store.',
},
'flags.covered_for_dutch': {
label: 'Dutch’s run', hidden: true,
whenTrue: 'Dutch owes you a run.',
whenFalse: 'You and Dutch are square.',
},
'flags.sworn_in': {
label: 'The star', hidden: true,
whenTrue: 'You are sworn in.',
whenFalse: 'You are back in the yard.',
},
'flags.spoke_for_dutch': {
label: 'Dutch’s name', hidden: true,
whenTrue: 'Dutch’s name is in for the good runs.',
whenFalse: 'Dutch’s name is not in for the good runs.',
},
},
characters,
events,
};
export default frontier;