Fix repayments that overcharged, and act on the first Frontier playtest

A debt repayment took its whole instalment even when less was owed: four
dollars owed at the store cost twenty. Content could not express "pay what is
owed", so an add effect's amount may now be read from state, with a cap. Five
repayments across both packs use it, and a conformance test holds every pack to
it. The turn record now says which options were hidden, so the analyser stops
counting doors nobody saw, and it reads only feedback logs.

From the same session: wages are labelled as wages, a clamped change says where
it stopped, two unreadable option labels are rewritten, the sale barn no longer
offers what the player already owns, Ward hints at what the promotion waits for,
and the office has five more events. DECISIONS #38-40.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6UDQ9o6L6Ey173U7XVou6
This commit is contained in:
JesseMarkowitz
2026-09-15 10:23:15 -04:00
co-authored by Claude Opus 5
parent d84ae495f4
commit 112e558dbb
22 changed files with 723 additions and 90 deletions
+309 -24
View File
@@ -160,7 +160,7 @@ export const deputy = [
options: [
{
id: 'wade_in',
label: 'Go in and take hold of the nearest one.',
label: 'Go in and take hold of the nearest man who is fighting.',
result:
'It stops. Your sleeve does not survive it and neither does a ' +
'table, and the whole street knows by morning which it was.',
@@ -580,15 +580,281 @@ export const deputy = [
],
},
// Five more days in the office, added after the first real play of this
// stage: 69 turns saw 13 distinct situations, and `office_day` and
// `the_cells` were a sixth of the stage each. See DECISIONS #40.
{
id: 'stage_came_in_short',
stage: 'deputy',
weight: 50,
cooldown: 7,
description:
'The stage came in with the strongbox eleven pounds lighter than the ' +
'manifest, and the driver swears on his mother that he never once ' +
'stopped between the Wells and here.',
options: [
{
id: 'wire_the_county',
label: 'Wire the county and let them send a man.',
result:
'The county sends a man in nine days, by which time the driver has ' +
'told the story often enough to believe every word of it.',
effects: [
{ path: 'skills.trade', op: 'add', value: 2 },
{ path: 'relationships.ward.trusts_you', op: 'add', value: 2 },
{ path: 'standing', op: 'add', value: -1 },
],
},
{
id: 'ride_the_route',
label: 'Ride the route back yourself, looking at the ground.',
result:
'Wheel ruts off the road at the second creek, where the driver says ' +
'he never stopped. You do not find the eleven pounds. You find where ' +
'they got off.',
effects: [
{ path: 'money', op: 'add', value: -3 },
{ path: 'skills.nerve', op: 'add', value: 3 },
{ path: 'standing', op: 'add', value: 3 },
],
},
{
id: 'lean_on_the_driver',
label: 'Lean on the driver until his story changes.',
requires: [{ path: 'skills.nerve', op: '>=', value: 30 }],
result:
'It changes on the fourth telling, at the second creek, and then ' +
'again at the part where somebody was expecting him.',
effects: [
{ path: 'skills.nerve', op: 'add', value: 3 },
{ path: 'standing', op: 'add', value: 4 },
{ path: 'relationships.ward.wary_of_you', op: 'add', value: 5 },
{ path: 'notable', op: 'push', value: 'Leaned on a stage driver until the story changed.' },
],
},
],
},
{
id: 'the_county_fees',
stage: 'deputy',
weight: 45,
cooldown: 8,
description:
'The county wants its licence fees collected from every business on the ' +
'street, and the list it sent includes the woman who sells pies out of ' +
'her kitchen window on Sundays.',
options: [
{
id: 'collect_all',
label: 'Collect from everybody on the list.',
result:
'All of it, pies included. The county is pleased. The street is ' +
'polite to you in a way it was not last week.',
effects: [
{ path: 'standing', op: 'add', value: -4 },
{ path: 'skills.trade', op: 'add', value: 1 },
{ path: 'relationships.ward.trusts_you', op: 'add', value: 4 },
],
},
{
id: 'pay_hers',
label: 'Collect from the rest, and pay hers out of your own pocket.',
result:
'A dollar fifty. Nobody is told, and by Sunday everybody knows, which ' +
'is how the street keeps its accounts.',
effects: [
{ path: 'money', op: 'add', value: -5 },
{ path: 'standing', op: 'add', value: 5 },
{ path: 'notable', op: 'push', value: 'Paid the pie licence out of your own pocket.' },
],
},
{
id: 'strike_her_off',
label: 'Strike her off the list and let the county notice if it can.',
requires: [{ path: 'skills.trade', op: '>=', value: 28 }],
result:
'You find the statute that exempts a kitchen window, and cite it in ' +
'the return. The county never writes back, which is the county ' +
'agreeing.',
effects: [
{ path: 'skills.trade', op: 'add', value: 3 },
{ path: 'standing', op: 'add', value: 3 },
{ path: 'relationships.ward.wary_of_you', op: 'add', value: 4 },
],
},
],
},
{
id: 'the_bounty_man',
stage: 'deputy',
weight: 40,
cooldown: 9,
description:
'A man with a paper from Bitter Wells and a rifle he keeps too clean ' +
'wants to know whether a teamster called Pike is in Cold Fork. There is ' +
'a teamster called Pike in Cold Fork, and he has been decent to you.',
options: [
{
id: 'tell_him',
label: 'Tell him where Pike is.',
result:
'He thanks you and leaves money on the desk, which you did not ask ' +
'for and do not give back. The teamsters hear about the money first.',
effects: [
{ path: 'money', op: 'add', value: 6 },
{ path: 'standing', op: 'add', value: -3 },
{ path: 'relationships.ward.trusts_you', op: 'add', value: 3 },
{ path: 'relationships.dutch.wary_of_you', op: 'add', value: 6 },
],
},
{
id: 'send_him_on',
label: 'Tell him Pike went west a month ago.',
result:
'He looks at you for long enough that you understand he knows. Then ' +
'he rides west, because the paper does not pay him to argue.',
effects: [
{ path: 'skills.nerve', op: 'add', value: 2 },
{ path: 'relationships.dutch.trusts_you', op: 'add', value: 5 },
{ path: 'relationships.ward.wary_of_you', op: 'add', value: 5 },
],
},
{
id: 'check_the_paper',
label: 'Read his paper properly before you say anything at all.',
requires: [{ path: 'skills.trade', op: '>=', value: 30 }],
result:
'It names a Pike, a year and a county, and two of the three are ' +
'wrong. You tell him so, with the statute, and he goes home.',
effects: [
{ path: 'skills.trade', op: 'add', value: 3 },
{ path: 'standing', op: 'add', value: 4 },
{ path: 'relationships.ward.trusts_you', op: 'add', value: 4 },
{ path: 'notable', op: 'push', value: 'Read a bounty man’s paper closely enough to send him home.' },
],
},
],
},
{
id: 'fire_at_the_store',
stage: 'deputy',
weight: 35,
cooldown: 12,
description:
'A lamp goes over in the back room of the store an hour after midnight, ' +
'and by the time you are in your boots the whole street is standing in ' +
'it in its nightclothes.',
options: [
{
id: 'bucket_line',
label: 'Get a bucket line going from the trough.',
result:
'It is out by two. The back room is gone and the front is black to ' +
'the ceiling, and the storekeeper shakes every hand on the line.',
effects: [
{ path: 'money', op: 'add', value: -2 },
{ path: 'skills.nerve', op: 'add', value: 2 },
{ path: 'standing', op: 'add', value: 5 },
],
},
{
id: 'go_in_for_the_book',
label: 'Go in for the book on the counter.',
requires: [{ path: 'skills.nerve', op: '>=', value: 34 }],
result:
'You come out with it under your coat and no eyebrows. Half the town ' +
'is in that book, and every one of them knows who carried it out.',
effects: [
{ path: 'skills.nerve', op: 'add', value: 4 },
{ path: 'standing', op: 'add', value: 6 },
{ path: 'notable', op: 'push', value: 'Went into the burning store for the book.' },
],
},
{
id: 'hold_the_crowd',
label: 'Keep the crowd back and let it burn itself out.',
result:
'Nobody else is hurt, which Ward says is the only part of a fire that ' +
'is the office’s business. The storekeeper does not agree, loudly.',
effects: [
{ path: 'skills.trade', op: 'add', value: 2 },
{ path: 'standing', op: 'add', value: -2 },
{ path: 'relationships.ward.trusts_you', op: 'add', value: 3 },
],
},
],
},
{
id: 'dutch_wants_the_ridge',
stage: 'deputy',
weight: 40,
cooldown: 10,
description:
'Dutch comes to the office sober and early, which has not happened ' +
'before, and asks whether a deputy has any say in who drives the Ridge ' +
'road now that Vane is short a man.',
options: [
{
id: 'put_him_forward',
label: 'Say you will put his name in with Vane.',
result:
'He nods once and goes, and Vane hears about it from Dutch before he ' +
'hears about it from you, which is not how you meant it to go.',
effects: [
{ path: 'relationships.dutch.trusts_you', op: 'add', value: 8 },
{ path: 'relationships.dutch.owes_you', op: 'add', value: 10 },
{ path: 'relationships.hollis.wary_of_you', op: 'add', value: 5 },
],
},
{
id: 'office_stays_out',
label: 'Say the office does not hire teamsters.',
result:
'He says that is true, and it is, and he takes his hat off the desk ' +
'and goes back down the street to wait for somebody else to decide.',
effects: [
{ path: 'standing', op: 'add', value: 2 },
{ path: 'relationships.dutch.trusts_you', op: 'add', value: -5 },
{ path: 'relationships.ward.trusts_you', op: 'add', value: 2 },
],
},
{
id: 'ask_vane_for_him',
label: 'Walk across the street and ask Vane yourself.',
requires: [{ path: 'relationships.hollis.trusts_you', op: '>=', value: 40 }],
result:
'Vane hears you out in the doorway and says he will think about it, ' +
'and Dutch is on the Ridge road on Monday.',
effects: [
{ path: 'standing', op: 'add', value: 3 },
{ path: 'relationships.dutch.trusts_you', op: 'add', value: 10 },
{ path: 'relationships.hollis.trusts_you', op: 'add', value: -2 },
{ path: 'notable', op: 'push', value: 'Asked Vane to put Dutch on the Ridge road.' },
],
},
],
},
{
// Corporate Ladder's car lot, in Cold Fork's terms and at its scale. Same
// structure deliberately: an offer, never an obligation, with the weekly
// charge for falling short of it sitting in upkeep instead.
//
// The horse and the room used to be one event, whose description was
// always about the horse. A real session was offered the room under that
// text, and shown "Buy the bay" locked after buying it — a door that could
// never open. Now each is its own event, gated on the tier it offers, so
// neither can appear once it is bought. See DECISIONS #40.
id: 'the_sale_barn',
stage: 'deputy',
weight: 45,
cooldown: 6,
requires: [{ path: 'lifestyle', op: '<', value: 2 }],
requires: [{ path: 'lifestyle', op: '==', value: 0 }],
description:
'The sale barn has a bay with four good years in him and a price on ' +
'him. Ward says nothing about it, except that the county pays mileage ' +
@@ -597,10 +863,7 @@ export const deputy = [
{
id: 'buy_the_bay',
label: 'Buy the bay.',
requires: [
{ path: 'lifestyle', op: '==', value: 0 },
{ path: 'money', op: '>=', value: 70 },
],
requires: [{ path: 'money', op: '>=', value: 70 }],
result:
'He is a better animal than the livery has ever put under you, and ' +
'the whole street watches you ride him down it once.',
@@ -611,24 +874,6 @@ export const deputy = [
{ path: 'notable', op: 'push', value: 'Bought the bay out of the sale barn.' },
],
},
{
id: 'take_the_room',
label: 'Take the room over the Ellis place, with the stove.',
requires: [
{ path: 'lifestyle', op: '==', value: 1 },
{ path: 'money', op: '>=', value: 150 },
],
result:
'A door that locks, a stove, and a window over the street. The ' +
'Cordwood does not hold it against you; the Cordwood holds nothing ' +
'against anybody who pays up.',
effects: [
{ path: 'money', op: 'add', value: -150 },
{ path: 'lifestyle', op: 'set', value: 2 },
{ path: 'standing', op: 'add', value: 5 },
{ path: 'notable', op: 'push', value: 'Took the room with the stove over the Ellis place.' },
],
},
{
id: 'ride_the_livery',
label: 'Ride what the livery gives you.',
@@ -639,6 +884,46 @@ export const deputy = [
},
],
},
{
id: 'the_ellis_room',
stage: 'deputy',
weight: 45,
cooldown: 6,
requires: [{ path: 'lifestyle', op: '==', value: 1 }],
description:
'The room over the Ellis place is empty again: a stove, a door that ' +
'locks, and a window over the street. The Cordwood charges a deputy the ' +
'same as a teamster, and says so to both of them.',
options: [
{
id: 'take_the_room',
label: 'Take the room over the Ellis place, with the stove.',
requires: [{ path: 'money', op: '>=', value: 150 }],
result:
'A door that locks, a stove, and a window over the street. The ' +
'Cordwood does not hold it against you; the Cordwood holds nothing ' +
'against anybody who pays up.',
effects: [
{ path: 'money', op: 'add', value: -150 },
{ path: 'lifestyle', op: 'set', value: 2 },
{ path: 'standing', op: 'add', value: 5 },
{ path: 'notable', op: 'push', value: 'Took the room with the stove over the Ellis place.' },
],
},
{
id: 'stay_at_the_cordwood',
label: 'Stay at the Cordwood.',
result:
'The walls are thin and the talk comes through them, and some of what ' +
'comes through is worth a deputy hearing.',
effects: [
{ path: 'skills.trade', op: 'add', value: 1 },
{ path: 'relationships.dutch.trusts_you', op: 'add', value: 2 },
],
},
],
},
];
export default deputy;
+9 -6
View File
@@ -99,14 +99,16 @@ export const hardTimes = [
'book and yours is one of them. He mentions this to you in the street, ' +
'in daylight, in front of two other people.',
options: [
// Both payments take what is owed up to their size, never more — money
// first, because both effects read `owed` before it moves. See #38.
{
id: 'pay_chunk',
label: 'Pay a real piece of it down.',
label: 'Pay a real piece of it down, up to forty-five dollars.',
requires: [{ path: 'money', op: '>=', value: 45 }],
result: 'He rules a line through most of it and is disappointed about it.',
effects: [
{ path: 'money', op: 'add', value: -45 },
{ path: 'owed', op: 'add', value: -45 },
{ path: 'money', op: 'add', value: { from: 'owed', times: -1, cap: 45 } },
{ path: 'owed', op: 'add', value: { from: 'owed', times: -1, cap: 45 } },
{ path: 'standing', op: 'add', value: 2 },
],
},
@@ -114,10 +116,11 @@ export const hardTimes = [
id: 'pay_minimum',
label: 'Pay what you have on you.',
requires: [{ path: 'money', op: '>=', value: 12 }],
result: 'Twelve dollars, ten of which is the line and two of which is coffee.',
result: 'Up to ten dollars against the line, and two dollars of coffee while you are in there.',
effects: [
{ path: 'money', op: 'add', value: -12 },
{ path: 'owed', op: 'add', value: -10 },
{ path: 'money', op: 'add', value: -2 },
{ path: 'money', op: 'add', value: { from: 'owed', times: -1, cap: 10 } },
{ path: 'owed', op: 'add', value: { from: 'owed', times: -1, cap: 10 } },
],
},
{
+52
View File
@@ -10,6 +10,58 @@
// than a mistake.
export const promotion = [
{
// The first real player asked, thirty turns in, how they would know they
// were getting close. Nothing said. This does, in the town's own terms: it
// names what the offer is waiting for without printing a threshold. It
// comes once, somewhat before the offer can, and only to a hand already
// heading that way. See DECISIONS #40.
id: 'ward_takes_notice',
stage: 'hand',
weight: 300,
once: true,
requires: [
{ path: 'turn', op: '>=', value: 24 },
{ path: 'standing', op: '>=', value: 55 },
],
description:
'Ward stops at the yard gate on his way past and says, to nobody in ' +
'particular, that the county will want a second man by winter. The man ' +
'will need a name the street says for the right reasons, and he will ' +
'need to be one Ward can take at his word — or one who has already ' +
'walked into trouble on the Ridge road and walked back out of it.',
options: [
{
id: 'ask_what_it_takes',
label: 'Ask him how long a man would have to wait.',
result:
'He says not before the fortieth day of anybody’s employment, and ' +
'that he is not talking about anybody in particular, and goes on up ' +
'the street.',
effects: [
{ path: 'relationships.ward.trusts_you', op: 'add', value: 3 },
{ path: 'notable', op: 'push', value: 'Heard from Ward that the county would want a second man.' },
],
},
{
id: 'happy_in_the_yard',
label: 'Say you are happy in the yard.',
result: 'Vane, in the office doorway, hears you say it.',
effects: [
{ path: 'relationships.hollis.trusts_you', op: 'add', value: 3 },
],
},
{
id: 'remember_it',
label: 'Say nothing, and remember it.',
result: 'He did not expect an answer, and seems to like not getting one.',
effects: [
{ path: 'skills.nerve', op: 'add', value: 1 },
],
},
],
},
{
id: 'deputy_offer',
stage: 'hand',
+8 -4
View File
@@ -243,7 +243,7 @@ export const routine = [
},
{
id: 'stay_out',
label: 'Have no view on the axle.',
label: 'Stay out of it, and take neither side about the axle.',
result: 'Both of them decide, separately, that you are no use to them.',
effects: [
{ path: 'relationships.dutch.trusts_you', op: 'add', value: -4 },
@@ -298,7 +298,7 @@ export const routine = [
},
{
id: 'pay_down',
label: 'Put money against the book.',
label: 'Pay the book down, up to twenty dollars.',
// Hidden rather than greyed out when there is nothing owed. A locked
// door is worth showing when it is a door the player could open later
// by playing differently; "pay down what you owe" shown to a man who
@@ -310,9 +310,13 @@ export const routine = [
{ path: 'money', op: '>=', value: 20 },
],
result: 'He crosses a line through it and turns the page back over.',
// What is owed, up to twenty, and never more. A flat -20 against a
// four-dollar balance took sixteen dollars for nothing in a real
// session. Money comes first: both effects read `owed` before it moves.
// See DECISIONS #38.
effects: [
{ path: 'money', op: 'add', value: -20 },
{ path: 'owed', op: 'add', value: -20 },
{ path: 'money', op: 'add', value: { from: 'owed', times: -1, cap: 20 } },
{ path: 'owed', op: 'add', value: { from: 'owed', times: -1, cap: 20 } },
{ path: 'standing', op: 'add', value: 2 },
],
},
+6 -3
View File
@@ -20,7 +20,7 @@ export const 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',
version: '0.1.1',
turnUnit: 'day',
startingStage: 'hand',
peopleHeading: 'Cold Fork',
@@ -100,15 +100,18 @@ export const frontier = {
offset: 3,
effects: [{ path: 'money', op: 'add', value: -8 }],
},
// Labelled as wages, because they are. "Settlement" read as jargon to the
// first real player, who could not tell it was the twenty-four a run the
// first morning quotes; the ledger is written for a player (DECISIONS #27).
{
label: 'Settlement',
label: 'Wages from Vane',
every: 4,
offset: 4,
requires: [{ path: 'stage', op: '==', value: 'hand' }],
effects: [{ path: 'money', op: 'add', value: 24 }],
},
{
label: 'Deputy pay',
label: 'Wages from the county',
every: 4,
offset: 4,
// The town pays a real wage, and it is still not much of a living. What