13 Commits
Author SHA1 Message Date
slaguru666andClaude Opus 5 774e95dea8 THROUGH TRAIN — the crossing starter, and two more playtest fixes
A train has been running the same four miles of track since 3 November 1881. It
has never stopped. Sixty-one people are aboard and all of them are alive.

The supernatural is science fiction underneath, which is the point. A railway
company laid track to a filed survey instead of to the ground, then described the
line as being where it was not — ten thousand times, in timetables and waybills
and telegraph orders, for six months, at a constant speed. Reality keeps a record
of what belongs where, and that is the most patient argument there is. The record
gave way with No. 9 inside it, and the RUNNING is what holds the crossing open: a
standing wave sustained by its own motion the way a bow sustains a note.

So the players must board a train that cannot stop, and then find that sixty-one
people at six passes is four hours they do not have. The ending is what they
choose to do about arithmetic they cannot beat, in front of a conductor holding
the manifest. No roll resolves it.

It is also the case that teaches Standing Order 9: NOTHING MODERN GOES THROUGH.
Using a modern object across costs a Coherence and moves the clock; carrying it
costs nothing; anchors are exempt. The catalogue already had the 1881 issue —
Webley, Martini-Henry, oil lantern, brass compass, pocket watch, buff coat,
laudanum — so the constraint needed no new content and gives the table something
to feel in the first twenty minutes.

Ships as docs/scenarios/THROUGH_TRAIN.md and a Foundry Adventure: 7 GM pages, 6
handouts including the survey that started it and a manifest deliberately held
back to Act Three, the case file at Containment 6 / Resources 5 / Clearance 4
with its four legs loaded, four of the people aboard, and 7 plates to show.

Two fixes from playing it:

  - Bhattacharya's RESEARCH was 1%. Placing the new skills last night I swapped
    Occult into Registry's core and took Research out — a records office that
    cannot look anything up. Research is back in core; Antiquities gets its
    Knowledge back the same way.

  - The boarding was unrunnable. At halved skill the set-piece put four agents on
    12–31% and all four missed, which stalls the one scene the scenario is built
    around. It is an average roll now and only a fumble is a fall, with the
    reason written into the text so nobody "fixes" it back. Re-run: 3 of 4 aboard
    on the first pass and one waiting for the next, which is the shape it wanted.

Version 1.3.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 00:25:19 +01:00
slaguru666andClaude Opus 5 37b4043746 Play a full case, then fix what it found
Ran THE HOLLOW ROAD with five agents — approach, crossing, contact, the thing on
the road, extraction, debrief — with panic and Coherence both live. It ran clean
and it found one thing that mattered.

ANOMALY LORE 0. Marta Holloway, Field Lead, Senior Field Officer, could not roll
the department's signature skill. Nor could Okonkwo. Induction was gated to
TRADES on the reasoning that a career posting is itself the agency training —
but 17 of the 25 postings do not list anomaly lore, its printed base is 0 on
purpose, and nothing else supplies it. So the people who joined the department
straight out of school were the only ones who never received its basic training,
which is exactly backwards.

Induction is a FLOOR for everybody now, posting or trade, in the live generator
and the pack builder both. It never demotes: Bhattacharya keeps Registry's 63,
Holloway goes 0 → 43, Okonkwo 0 → 33. All twenty built agents verified non-zero.

Two GM tools the case showed were missing, both found by feeling the friction
rather than by reading anything:

  - clearPanic(actors). Panic clears when the scene does — one line in the book
    and, until now, five sheets edited by hand while the table waited. It leaves
    Coherence alone, which is the whole point of having two tracks.
  - partyState(). Everybody's hit points, panic and Coherence in one card. With
    five agents the alternative is asking "what are you on?" five times a scene,
    which is friction a table absorbs silently and then blames on the game.

And the player-facing gap: the primer never mentioned panic. Players got a
one-page aide-memoire for a track they now meet in the first scene. It has a
page of its own and a row on the cheat sheet, both generated from the same
tables as the rules.

One thing I reported as a bug and withdraw: Okonkwo's "Firearm (Pistol) 5%" was
me rolling the wrong weapon. He carries a marksman rifle at 53%. check-kits was
right.

Version 1.2.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 00:10:28 +01:00
slaguru666andClaude Opus 5 7ec7efd29b Replay the scene with the fumble rule; expose panicReliefFor
Ran the east corridor again with fresh dice and every relief attempt routed
through easePanic, fumbles included.

The rule did not fire this time. All three attempts failed cleanly — Psychology
58 against 40, and two POW×5 rolls at 66 and 76 — so nobody was punished for
trying. That is worth reporting as loudly as a hit would have been: two
play-throughs in and a fumbled relief has come up once, which is about right for
something meant to be a memorable disaster rather than a tax.

Braithwaite still broke at 5 on "it said your name" and fled. Marrow went down
in a single round to a special with the entry bar — 19 through armour — against
the 4.5-round average the thirty-run tuning gave, so that was dice, not balance.

Then the question the rule actually raises. Relief rolls are made at your own
panic penalty, so the worse you are the worse your odds AND the wider your fumble
range — and now a fumble adds a point. That is a feedback loop, so I measured it
rather than trusting it, 300 runs per row:

    from 2, POW×5 60%   280 calm ·  0 broke
    from 3, POW×5 60%   227 calm ·  2 broke
    from 4, POW×5 60%   138 calm · 31 broke
    from 4, POW×5 50%    96 calm · 35 broke
    from 4, POW×5 85%   267 calm ·  5 broke

Recovery dominates everywhere. The worst case — overwhelmed, weak POW, no help —
spirals into breaking about one time in eight, which is a bad night rather than a
trap. No change needed.

Also: panicReliefFor and panicAfter were missing from the API, which I only
noticed because measuring the loop needed them and the alternative was
re-implementing the rule in the test — the exact duplication check-rules exists
to forbid. Both exposed.

Version 1.1.3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 23:31:35 +01:00
slaguru666andClaude Opus 5 5aaae186e0 A fumbled relief attempt puts a point on
Trying to steady somebody and making it worse is the most familiar thing on that
list, and until now it was the only outcome that cost nothing.

Done as a NEGATIVE relief rather than a second code path: panicReliefFor returns
-1 on a fumble, and easePanic already subtracts what it is given, so subtracting
a negative raises the track. One number, one direction, no branch that could
drift from the other. The clamp still holds — fumbling at 5 stays at 5.

The chat card had to be told, because otherwise it prints the word "Psychology"
over a number that has just gone UP and the table reads that as a bug rather than
as the rule. It now leads with a red FUMBLED tag.

Guarded three ways: a spot-check in check-rules for the -1 itself, and two
behaviour tests — one that the sign survives easePanic's subtraction and
actually raises the track, one that it cannot push past the top. A test on the
constant alone would have passed even if easePanic had healed on a fumble.

Verified live: 2 → fumble → 3 → success → 2 → special → 0, and a fumble at 5
staying at 5, with the cards reading correctly.

Version 1.1.2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 23:25:46 +01:00
slaguru666andClaude Opus 5 20306d83d9 Play a scene with panic; tighten one reference row
Ran LAST ADMISSION's east corridor with the six civilians and real dice. The
track works end to end and, more importantly, the penalty REACHES things:
Ferriby at panic 3 swung at Old Marrow with Brawl 25% printed and 15% effective,
while Sharrock at panic 1 rolled his full 58%. That is the whole point of the
track and it is the part that is usually decoration.

The arc played well. Lights out took four of them to 1. The body took Braithwaite
and Ferriby to 2. Enderby counting took three of them again. Braithwaite fumbled
his Psychology roll trying to talk Colin down, then it said his name — 5, broke,
fled, came back at 3. Renshaw steadied herself on POW×5 and dropped to 1.

One thing I got wrong and one thing worth knowing:

  - I flagged a 99 grading as a fumble as a bug. It is not. A 58% skill fumbles
    on 98-00, and both the primer and the rules page say so in those words. The
    "96-99 never succeed / 00 always fumbles" pair are floor rules, not the whole
    fumble range. Only the quick-reference row was ambiguous — it now says
    "never succeeds ... and is a fumble if it is inside your fumble range".

  - The civilians cannot use two of the three new skills. Occult, Religion and
    Psychology sit at 5% on the "(before)" sheets, so in the prologue the only
    relief that works is POW×5 at 50-85%. That is correct — a site foreman is
    not an occultist — but it means the skills are an AGENT'S tools, and the
    prologue is deliberately the game without them. The agents have them where
    they should: Bhattacharya Occult 63, Lindqvist 58, Renshaw Psychology 40,
    Braithwaite Religion 35.

Version 1.1.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:58:30 +01:00
slaguru666andClaude Opus 5 cbad87ba98 Add Occult, Religion and Psychology, and the Panic track
From the first game's list. The three skills are not decoration — they are the
levers on the new track, which is why they arrived together.

PANIC, 0 to 5, up. It is the other half of Coherence and the rules page says so
in the first line, because a table that confuses them will make the wrong call
at the worst moment:

    Coherence   what knowing has cost you       weeks     10-0, down
    Panic       what is happening to you NOW    minutes    0-5, up

A trigger gives a point with NO ROLL. Asking for a die to find out whether you
are frightened slows the scene and tells the player how to feel; the die is for
getting rid of it, which is the interesting direction.

    0 steady · 1 rattled · 2 shaken -5% · 3 losing it -10%
    4 overwhelmed -20% · 5 broken

The first point is free on purpose, so the opening shock of a scene costs the
table nothing. At 5 you break — freeze, flee or fixate, choose fast — and come
back at 3, not at 0, because breaking is not a rest.

Relief is where the skills come in. A success takes one point, a special takes
two. Psychology is the only thing that steadies somebody who is NOT you.
Religion steadies you alone and does not care whether you are right. Occult is
the odd one: naming a thing correctly, out loud, helps everybody who can hear —
the department's actual work, and the only relief that reaches a room. Plus
POW×5 to steady yourself, and your anchor object once per case, which is what
that briefing scene was for.

The three skills are placed in six registers rather than left floating: Occult
to the professor and to Registry, Psychology to the doctor and to Interview,
Religion to Antiquities and Pathology.

Guards, because a track nothing reads is this project's oldest failure: the five
new rules carry spot-checks in check-rules, and check-behaviour gains nine tests
— the free first point, monotonic penalties, only 5 breaking, clamping at both
ends, breaking not clearing the track, special taking two, every band/trigger/
relief having a label, and every relief naming a skill the catalogue actually
has.

Verified in a live game: penalty reaches the roll (58 → 58 → 53 → 48 → 38 for
panic 0-4, criticals and specials recomputed with it), the full arc runs 0 → 2 →
4 → eased to 2 → pushed to 5 → breaks → 3 with no errors, the sheet shows the
band, and the rules journal has a Panic page generated from the same tables.

Version 1.1.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 22:55:11 +01:00
slaguru666andClaude Opus 5 ab619f4b73 Remove the dice textures and the whole Dice So Nice path
Deleted: the seven PNGs in dice/, tools/make-dice-textures.mjs, the `npm run
dice` script, the RB_DICE colourset and texture tables, diceSetFor, the
diceSoNiceReady hook, and rbDressDice — which was tagging ten roll sites with
coloursets that no longer existed. 99 lines of module, plus the assets.
README and DEPLOY updated; they still advertised all of it.

Recoverable with `git revert` if it is ever wanted back.

This also explains the 01-that-was-12. The RESULT was never wrong: skillRoll
rolls one 1d100 and grades on roll.total, and the chat card derives the tens and
units it prints from that same total. Checked all 100 values of percentileFaces
and 60 live rolls — the printed faces reconstruct the graded number every time,
with no mismatches.

What disagreed was the ANIMATION. The custom addDicePreset labels registered for
d100 and d10 remapped which physical face means what, so Dice So Nice could land
showing 01 while the roll behind it was 12. Nothing downstream ever read the
animated faces, so no roll was ever adjudicated on the wrong number — but a
table watching the dice had every reason to believe otherwise, which is its own
kind of wrong. Disabling the skins in 1.0.6 already removed it; deleting them
makes it permanent.

Verified in a running Foundry after removal: skill roll, weapon damage, hit
location, major wound and Coherence all resolve and post their cards, no errors,
and game.ringbrp.diceSetFor is gone from the API.

Version 1.0.7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 22:45:52 +01:00
slaguru666andClaude Opus 5 9b6c61ffcb Turn the custom Dice So Nice skins off
Registering them with "preferred" made them the default for everybody in the
world, which overrides whatever dice a player had already chosen — the system
helping itself to a setting that belongs to the user. Off at the author's
request.

One named constant rather than a settings API: this codebase registers no game
settings at all, and adding that surface, its lang keys and its UI to switch one
feature off is more machinery than the job needs. Flip RB_DICE_ENABLED to true
and everything below it still works.

The textures in dice/ and the generator behind `npm run dice` are left alone.
Nothing reads them while this is false, and deleting work in order to turn a
feature off is a poor trade.

Verified in a running Foundry with Dice So Nice active: "custodians" is no
longer among the registered systems, and preferredSystem has gone back to
"standard" rather than ours.

Version 1.0.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 20:34:22 +01:00
slaguru666andClaude Opus 5 0852427586 Fix the pause: it would not hide, it was off centre, and it was too small
Three faults in 1.0.4, and the first was the serious one.

  1. It never went away. Core hides the overlay with `#pause { display: none }`
     and shows it with `#pause.paused { display: flex }`. 1.0.4 styled the BARE
     #pause with display:flex, which wins on specificity, so unpausing removed
     the class and changed nothing — GAME PAUSED sat over the table for the rest
     of the session. Every rule here is now scoped to `.paused` and `display` is
     not set anywhere.

  2. It was off centre because I repositioned it. Core already centres it: a
     full-width band at top calc(50vh - 100px), column flex, centred both ways,
     pointer-events none. That layout was correct and 1.0.4 fought it with
     left/bottom/transform. It is left alone now; only the height changes, and
     the top moves up by exactly half the extra so the centring holds.

  3. It was small: a 322x116 panel in the corner. It is now core's full-width
     band at 300px with a 170px dial — against core's own 100px — and the
     caption at 24px to match core's.

Guarded: check-behaviour now parses the stylesheet and fails any rule that sets
display on an unscoped #pause. Verified it bites. It strips CSS comments first,
because the first version of the guard reported the comment explaining the
guard — which is exactly what the template guard did last time, so that is twice
now and the lesson is written down properly.

Verified by driving the real toggle: running → display none, box 0x0, no class;
paused → display flex, 341x302, centre X exactly on the viewport centre;
unpaused again → display none, box 0x0.

Version 1.0.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 19:31:35 +01:00
slaguru666andClaude Opus 5 ad771ff2b5 A uniselector for the pause
Foundry's pause is a spinning d20 and a caption. This replaces both from CSS
alone, without touching the markup: figure#pause becomes a small navy panel with
a yellow rule along the top, and the <img> becomes the arm of a uniselector.

A uniselector is the stepping switch inside a telephone exchange — an arm that
clicks round one contact at a time, hunting for a free line. It is the right
object for this game. The department's whole business is a machine that says
where things belong, and OPEN DAY is set inside one. Eleven contacts, because
eleven minutes is the interval this setting keeps returning to.

It STEPS rather than sweeps: steps(11) over 7.7s, so a contact every 700ms. A
continuous spin reads as a loading throbber; a discrete click reads as a machine
working through a list, which is what the department is doing while the table is
out of the room. The arm carries a faint trailing sector, because without it a
700ms step is easy to miss and the stepping is the whole idea.

No new text. Foundry's caption is localised and this only restyles it — a string
hardcoded in a stylesheet would be one the lang file could never reach.

Two things the browser had to be asked rather than assumed:

  - Core pins #pause with BOTH top and bottom, so an auto height stretched it to
    180px for a 16px caption. It is pinned here instead and sized to content.
  - The arm is rotated, so getBoundingClientRect returns the diagonal of its own
    square (75px for a 52px element). The layout takes its size from the flex
    basis and never from the measured box.

prefers-reduced-motion stops the movement and keeps the object — a stepping dial
in the corner of the eye is exactly what that preference is asking about.

Version 1.0.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 19:25:12 +01:00
slaguru666andClaude Opus 5 53481bdfc1 Ship the LAST ADMISSION plates as showable images
A third journal in the adventure — LAST ADMISSION — Show the players — holding
the seven scene plates and the floor plan as eight image pages at player
visibility. Right-click, Show Players, done.

title.show is false and there is no caption, on purpose: the table gets the
picture and nothing else. No heading telling them which room this is, no label
naming the thing on the stairs. The page NAMES exist only for the GM's sidebar
so the right one can be found in a hurry under pressure, and the players never
see them.

Ordered by when each comes up, not by asset number, for the same reason.

The builder throws if a plate is missing from disk, because a missing image is
a blank frame on a projector in front of six people.

Verified in Foundry: eight pages import at OBSERVER, the image loads, and the
rendered page contains no headings, no figcaption and no text at all.

Version 1.0.3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 19:12:35 +01:00
slaguru666andClaude Opus 5 51bd8e618a Audit every remaining sheet and dialog; add the Culland Hall scene
Opened all of it this time, rather than driving the system through its API.

Two more defects of the same family as the NPC sheet, and in the same place —
the types added last:

  - Talent and Art had no TYPES.Item.* label, so their sheets printed the raw
    key "TYPES.Item.talent" under the name.
  - Neither had a branch in item-sheet.hbs or an isTalent/isPower flag, so the
    Detail tab rendered EMPTY over a document holding category, rule, uses per
    case, Coherence cost, push cost and origin. Eleven of thirteen types were
    handled, which is exactly the shape an eye skips.

Both now have a header statline and a full Detail section, and a guard walks
template.json: every declared type must have a label, a flag set in ringbrp.mjs,
and an {{#if isX}} section in the sheet.

One hardening fix: loseCoherence applies the cost and THEN announces it, so
anything that throws in between leaves the actor quietly poorer with no message.
localize() throws on a non-string, which an unrecognised reason key is.

Everything else came back clean, and it is worth recording what was checked so
the next audit knows what "checked" meant: 13 item types opened, all render; the
expedition sheet (4 tabs); all 7 chat cards driven through their real entry
points — roll, coherence, reaction via Combat.rollAll, hit location, weapon
damage, major wound, and contact through a case file that actually has a group;
all 3 dialogs opened by clicking the controls a player clicks. No errors, no raw
localisation keys anywhere.

Also: Culland Hall as a playable Scene. Drawn as SVG rather than generated,
because a room count, a door position and a label are all load-bearing here and
a generator cannot be trusted with any of them — three label-collision passes in
the browser before it was clean, which is the documented cost of doing it this
way. 2800x2000 at 100px to the square so the Foundry grid lands on the grid
drawn into the image; 5ft squares; no walls, because half-done walls are worse
than none and a GM who wants vision can add them in ten minutes.

Verified in Foundry: the scene imports, activates, and draws.

Version 1.0.2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 19:02:18 +01:00
slaguru666andClaude Opus 5 02ec47a351 Fix every NPC sheet, which has never opened
npc-sheet.hbs line 43 read

    {{#if (lookup ../skillsByCategory key).length}}

and Handlebars cannot read a property off a subexpression. That is a PARSE
error, so the template never compiled, so every NPC sheet in the system threw
on click — Natives & Threats, the starter's enemy roster, and the whole LAST
ADMISSION cast. #if already treats an empty array as falsy, so the .length was
never doing anything in the first place.

It was invisible to all four existing guards, and the reason is worth writing
down: check-rules reads .mjs, check-behaviour says in its own header that it
cannot reach sheets, and every play-through in this project has driven the
system through its API rather than by clicking. Nobody had ever opened an NPC
sheet, including me, in any of the sessions that "played through" scenarios
full of NPCs.

So: check-templates.mjs, a fifth guard, compiling every .hbs in about a tenth
of a second, wired into the build ahead of check-behaviour. It also names the
specific shape — a property read off a subexpression — because the raw parse
error says only where. Verified it bites by reintroducing the bug. Handlebars
is a devDependency now.

Opening the sheet then showed a second thing nobody had seen: the NPC skill
rows carry four children and were using the character sheet's seven-column
grid, so the name landed in a 13px column and the whole list overlapped
itself. The NPC row now has its own four-column template.

Verified by opening all six LAST ADMISSION cast sheets with an error hook
attached: six rendered, no errors, and Old Marrow reads HP 20/20, major wound
10, armour 2, Brawl 60%, Melee Weapon (Polearm) 60%.

Version 1.0.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 18:42:05 +01:00
183 changed files with 2438 additions and 432 deletions
+11 -11
View File
@@ -28,7 +28,8 @@ git clone https://gitea.timevans.uk/slaguru666/RingBRP.git ringbrp
```
Requires **Foundry v13+** (verified against v14.364). No modules are required. Dice So
Nice is supported but optional — the system registers four of its own dice textures.
Nice works if you have it, using your own dice — the system ships no dice skins and does
not touch anybody's dice settings.
### Deploying to a remote Foundry server
@@ -47,7 +48,7 @@ packs or regenerate art on that machine:
```bash
npm install # pulls classic-level, used to write the LevelDB packs
npm run build # rebuilds packs/ from tools/content.mjs
npm run check # the three guards; the build refuses to run if they fail
npm run check # the five guards; the build refuses to run if they fail
```
To update a deployed server: `git pull` and restart Foundry. If the pull touches
@@ -64,13 +65,13 @@ changing underneath a running server.
| **Postings** | 25, each with a key characteristic, trained skills, talents and its own kit |
| **Trades** | 10 ordinary jobs the department recruits from, plus induction |
| **Catalogue** | 236 items — 123 gear, 62 weapons, 32 armour, 19 vehicles |
| **Skills / talents** | 59 skills, 19 talents |
| **Skills / talents** | 62 skills, 19 talents |
| **Bestiary** | 21 statblocks across folklore, horror and the far side |
| **Eras** | modern, antique, future, anomalous — the agency is old and never abolishes anything |
| **Icons** | 292, all generated from `tools/icons.mjs`; no external art |
| **Starter scenario** | **BLACK PLATFORM**, a six-player action starter, importable as a single Adventure |
| **Compendia** | pregens, NPCs, skills, weapons, armour, gear, vehicles, powers, tables, rules, the starter |
| **Rules journal** | 12 pages, generated from `rules.mjs` so the book cannot drift from the code |
| **Rules journal** | 13 pages, generated from `rules.mjs` so the book cannot drift from the code |
<!-- box:end -->
---
@@ -152,13 +153,12 @@ template.json data model for every Actor and Item type
templates/ Handlebars sheets
styles/ one stylesheet
lang/en.json all user-facing text
icons/ dice/ fonts/ generated art
icons/ fonts/ art/ generated art
packs/ built LevelDB compendia (committed — see Deploying)
tools/
content.mjs the catalogue: skills, weapons, armour, gear, vehicles, NPCs
build-packs.mjs builds packs/ — runs the three guards first and refuses on failure
build-packs.mjs builds packs/ — runs the five guards first and refuses on failure
make-icons.mjs draws all 231 icons
make-dice-textures.mjs
rules-text.mjs generates the rules journal FROM rules.mjs
mission.mjs the case generator
mission-tables.mjs its content tables
@@ -176,13 +176,13 @@ stops being identity-equal to what it aliases, if a constant is re-declared as a
literal, or if an exported rule has no spot-check. The packs used to be built under
one set of numbers and played under another; this makes that impossible to ship.
The build runs all three guards before it writes anything:
The build runs all five guards before it writes anything:
<!-- guards:start -->
```
check-rules: OK — 87 rules, 20 files scanned, 3 aliases + 41 constant sets checked, 275 formulas verified
check-rules: OK — 92 rules, 21 files scanned, 3 aliases + 45 constant sets checked, 291 formulas verified
check-kits: OK — 25 roles, 10 trades, 218 catalogue items, every kit key resolves, every posting can use what it carries, every loadout distinct
check-lang: OK — en.json, 927 keys, no leaf/branch collisions
check-behaviour: OK — 43 behavioural tests
check-lang: OK — en.json, 981 keys, no leaf/branch collisions
check-behaviour: OK — 59 behavioural tests
```
<!-- guards:end -->
+159
View File
@@ -0,0 +1,159 @@
<svg xmlns="http://www.w3.org/2000/svg" width="2800" height="2000" viewBox="0 0 2800 2000">
<!-- CULLAND HALL — ground floor and grounds, for LAST ADMISSION.
Drawn rather than generated: a generator cannot be trusted with a room count,
a door position or a label, and all three are load-bearing here.
2800x2000 at 100px to the square = 28 x 20 squares of 5ft. -->
<defs>
<style>
.rm { fill:#16215f; stroke:#6674d8; stroke-width:6; }
.rm2 { fill:#101a6e; stroke:#6674d8; stroke-width:6; }
.out { fill:#0f1750; stroke:#4d5ac0; stroke-width:4; }
.wall { stroke:#8f9ae6; stroke-width:10; stroke-linecap:square; }
.door { stroke:#f2ea72; stroke-width:11; stroke-linecap:round; }
.lbl { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-weight:700;
font-size:34px; fill:#f2ea72; letter-spacing:2px; }
.sub { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;
font-size:24px; fill:#a8b2e3; letter-spacing:1px; }
.num { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-weight:700;
font-size:44px; fill:#0c125b; }
.ttl { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-weight:700;
font-size:54px; fill:#ecefee; letter-spacing:5px; }
.key { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-size:26px; fill:#ecefee; }
.grid { stroke:#233073; stroke-width:1; }
</style>
</defs>
<rect width="2800" height="2000" fill="#0c125b"/>
<!-- one-square grid, so the Foundry grid lands on the drawn one -->
<g class="grid">
<path d="M100 0V2000M200 0V2000M300 0V2000M400 0V2000M500 0V2000M600 0V2000M700 0V2000M800 0V2000M900 0V2000M1000 0V2000M1100 0V2000M1200 0V2000M1300 0V2000M1400 0V2000M1500 0V2000M1600 0V2000M1700 0V2000M1800 0V2000M1900 0V2000M2000 0V2000M2100 0V2000M2200 0V2000M2300 0V2000M2400 0V2000M2500 0V2000M2600 0V2000M2700 0V2000"/>
<path d="M0 100H2800M0 200H2800M0 300H2800M0 400H2800M0 500H2800M0 600H2800M0 700H2800M0 800H2800M0 900H2800M0 1000H2800M0 1100H2800M0 1200H2800M0 1300H2800M0 1400H2800M0 1500H2800M0 1600H2800M0 1700H2800M0 1800H2800M0 1900H2800"/>
</g>
<text x="120" y="120" class="ttl">CULLAND HALL</text>
<text x="120" y="160" class="sub">GROUND FLOOR · THE CULLAND HAUNTING · LAST ADMISSION 22:00</text>
<!-- ================= the house ================= -->
<rect x="400" y="300" width="2000" height="1200" class="rm2"/>
<!-- 5 · Long Gallery -->
<rect x="700" y="400" width="430" height="300" class="rm"/>
<circle cx="760" cy="450" r="30" fill="#f2ea72"/><text x="746" y="465" class="num">5</text>
<text x="730" y="560" class="lbl" font-size="30">LONG GALLERY</text>
<text x="730" y="598" class="sub" font-size="21">strobe · mirrors · no cast at all</text>
<!-- 6 · Ballroom -->
<rect x="700" y="720" width="430" height="360" class="rm"/>
<circle cx="760" cy="770" r="30" fill="#f2ea72"/><text x="746" y="785" class="num">6</text>
<text x="730" y="880" class="lbl" font-size="30">BALLROOM</text>
<text x="730" y="918" class="sub" font-size="21">lights up · fire exit · "thanks for coming"</text>
<!-- 1 · Entrance Hall -->
<rect x="1150" y="1100" width="500" height="400" class="rm"/>
<circle cx="1190" cy="1140" r="30" fill="#f2ea72"/><text x="1176" y="1155" class="num">1</text>
<text x="1188" y="1235" class="lbl" font-size="30">ENTRANCE HALL</text>
<text x="1188" y="1275" class="sub" font-size="21">the WALL PANEL — four names</text>
<rect x="1188" y="1300" width="200" height="26" fill="#f2ea72"/>
<text x="1188" y="1372" class="sub" font-size="21">ticket desk · head count</text>
<!-- main stairs (Edith) -->
<rect x="1660" y="1140" width="170" height="250" class="rm2"/>
<g stroke="#6674d8" stroke-width="4">
<path d="M1660 1180H1830M1660 1220H1830M1660 1260H1830M1660 1300H1830M1660 1340H1830"/>
</g>
<text x="1663" y="1168" class="sub" font-size="21">MAIN STAIRS</text>
<circle cx="1745" cy="1260" r="26" fill="#ff7a5c"/>
<path d="M1650 1265H1600" class="door"/>
<text x="1870" y="1268" class="sub" fill="#ff7a5c">EDITH CRANE stands here</text>
<!-- 2 · The Ward, east corridor -->
<rect x="1700" y="900" width="700" height="200" class="rm"/>
<circle cx="1740" cy="940" r="30" fill="#f2ea72"/><text x="1726" y="955" class="num">2</text>
<text x="1786" y="1005" class="lbl" font-size="30">THE WARD — EAST CORRIDOR</text>
<text x="1786" y="1045" class="sub" font-size="21">Sister Enderby counts</text>
<rect x="2320" y="905" width="16" height="190" fill="#ff7a5c"/>
<text x="1990" y="1160" class="sub" fill="#ff7a5c" font-size="21">JOSH PARDOE is behind this wall →</text>
<!-- 3 · Nursery -->
<rect x="1900" y="400" width="500" height="420" class="rm"/>
<circle cx="1940" cy="440" r="30" fill="#f2ea72"/><text x="1926" y="455" class="num">3</text>
<text x="1990" y="530" class="lbl">THE NURSERY</text>
<text x="1990" y="570" class="sub">Sammy in the wall · hole at ankle height</text>
<rect x="1905" y="700" width="60" height="24" fill="#f2ea72"/>
<!-- green room -->
<rect x="450" y="400" width="220" height="300" class="rm"/>
<text x="466" y="446" class="lbl" font-size="20">GREEN</text>
<text x="466" y="472" class="lbl" font-size="20">ROOM</text>
<text x="466" y="508" class="sub" font-size="19">call sheet</text>
<text x="466" y="534" class="sub" font-size="19">costumes</text>
<text x="466" y="560" class="sub" font-size="19">SET PLANS</text>
<text x="466" y="586" class="sub" font-size="19">pinned up</text>
<!-- control room -->
<rect x="450" y="720" width="220" height="180" class="rm"/>
<text x="466" y="768" class="lbl" font-size="22">CONTROL</text>
<text x="466" y="802" class="sub" font-size="19">Ferriby · comms</text>
<!-- kitchens -->
<rect x="450" y="920" width="220" height="300" class="rm"/>
<text x="466" y="966" class="lbl" font-size="22">KITCHENS</text>
<text x="466" y="1000" class="sub" font-size="19">Sharrock's way</text>
<text x="466" y="1026" class="sub" font-size="19">out — NOT on</text>
<text x="466" y="1052" class="sub" font-size="19">the fire plan</text>
<!-- yard -->
<rect x="200" y="980" width="230" height="240" class="out"/>
<text x="220" y="1030" class="lbl" font-size="28">YARD</text>
<text x="220" y="1070" class="sub">skip · taped hasp</text>
<!-- doors -->
<g class="door">
<path d="M1370 1500H1450"/> <!-- front door -->
<path d="M1130 1180H1150"/> <!-- hall to ballroom side -->
<path d="M915 1080V1100"/> <!-- ballroom to hall run -->
<path d="M1650 1000H1700"/> <!-- hall to ward -->
<path d="M2150 900V820"/> <!-- ward to nursery -->
<path d="M670 560H700"/> <!-- green room to gallery -->
<path d="M670 800H700"/> <!-- control to ballroom -->
<path d="M430 1070H450"/> <!-- kitchens to yard -->
<path d="M915 700V720"/> <!-- gallery to ballroom -->
</g>
<!-- ================= grounds ================= -->
<rect x="200" y="1560" width="2400" height="380" class="out"/>
<text x="230" y="1610" class="sub">THE LAWN — ninety feet of it</text>
<!-- marquee -->
<rect x="1150" y="1560" width="500" height="230" class="rm"/>
<text x="1200" y="1650" class="lbl" font-size="30">MARQUEE</text>
<text x="1200" y="1690" class="sub">queue · box office · muster point</text>
<!-- glasshouse -->
<rect x="2000" y="1600" width="560" height="300" class="rm"/>
<circle cx="2040" cy="1640" r="30" fill="#f2ea72"/><text x="2026" y="1655" class="num">4</text>
<text x="2090" y="1720" class="lbl">THE GLASSHOUSE</text>
<text x="2090" y="1760" class="sub">Old Marrow · the taped line</text>
<path d="M2000 1830H2560" stroke="#f2ea72" stroke-width="6" stroke-dasharray="18 14"/>
<text x="2090" y="1880" class="sub" fill="#f2ea72">he stops here — and tonight he does not</text>
<!-- car park -->
<rect x="250" y="1700" width="620" height="220" class="out"/>
<text x="280" y="1760" class="lbl" font-size="28">CAR PARK</text>
<text x="280" y="1800" class="sub">the van · the tea urn · blue lights</text>
<!-- Marrow's ground: he will not come indoors -->
<path d="M700 1545H2600" stroke="#ff7a5c" stroke-width="8" stroke-dasharray="26 18"/>
<text x="700" y="1530" class="sub" fill="#ff7a5c">OLD MARROW WILL NOT CROSS THIS LINE — inside is a complete answer</text>
<!-- key -->
<g transform="translate(2420,120)">
<rect x="-20" y="-40" width="360" height="230" fill="#101a6e" stroke="#4d5ac0" stroke-width="4"/>
<text x="0" y="0" class="key" fill="#f2ea72">KEY</text>
<rect x="0" y="30" width="34" height="14" fill="#f2ea72"/><text x="50" y="44" class="key">door / opening</text>
<circle cx="17" cy="85" r="14" fill="#ff7a5c"/><text x="50" y="94" class="key">a body, or a person</text>
<path d="M0 135H34" stroke="#ff7a5c" stroke-width="7" stroke-dasharray="12 8"/>
<text x="50" y="144" class="key">Marrow's limit</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 B

+1 -2
View File
@@ -33,10 +33,9 @@ Only if you intend to edit the catalogue there:
```bash
npm install # classic-level, for writing LevelDB
npm run check # the three guards
npm run check # the five guards
npm run build # rebuilds packs/
node tools/make-icons.mjs # redraws all icons
node tools/make-dice-textures.mjs # regenerates the Dice So Nice textures
```
`npm run build` runs the guards first and aborts if any fails, so a broken catalogue
+8
View File
@@ -426,6 +426,13 @@ If there is time — and there usually is, because this ends early — she says
## Artwork
**Showing them at the table.** The adventure ships a third journal,
**LAST ADMISSION — Show the players**, holding these as eight image pages at player
visibility. Right-click a page and *Show Players*: the table gets the picture and
nothing else — no heading, no caption, no label naming what they are looking at. The
page names are for the GM's sidebar and the players never see them.
**Style line:** flat editorial vector, deep navy and pale yellow with warm off-white,
1970s British public-information poster, `--style raw --ar 3:2 --v 6`.
@@ -439,6 +446,7 @@ If there is time — and there usually is, because this ends early — she says
| LA-ART-06 | The green room: mirrors, costumes, a clipboard on the door — used on the Act One page, where the call sheet is |
| LA-ART-07 | Edith Crane on the stairs |
| LA-ART-08 | The car park afterwards: a van, a tea urn, blue lights |
| LA-MAP-01 | **Culland Hall floor plan** — drawn, not generated (`art/maps/culland-hall.svg`), and shipped as a playable **Scene** in the adventure: 2800×2000 at 100px to the square, 5ft grid, no walls (add them if you want line-of-sight). |
---
+394
View File
@@ -0,0 +1,394 @@
# Crossing starter — The Custodians: THROUGH TRAIN
> *A train has been running the same four miles of track since the third of November,
> 1881. It has never stopped. There are sixty-one people on it and they are all alive.*
---
## GM ESSENTIALS
- **System:** d100 roll-under. Special under a fifth, critical under a twentieth.
96–99 never succeed; 00 always fumbles; a 60% skill fumbles on 98–00.
- **Panic is live** — 0–5, up. A trigger costs a point with no roll; relief is rolled.
**This is the case where it earns its keep.**
- **Standing Order 9 is live** — nothing modern goes through. Charge for *using*, never
for carrying. Anchors are exempt.
- **Runs with 4–6 agents from the duty roster.** Not the "(before)" sheets — this is the
job, and it wants people who have been trained.
- **Hard rule: at 2:45 on the clock, they are on the train.**
- **Content warnings:** entrapment and confinement; a decision that condemns named
people including children; period racism and hardship implied but not staged; heights;
one death by fire in backstory.
---
## Overview
- **Setting:** Mercy Draw, Nevada — present day, and 3–4 November 1881.
- **Tone:** Procedural, then unbearable. It is a paperwork error with sixty-one people
inside it.
- **Runtime:** 3h30 play + a 10 minute break.
- **The crossing:** opens at 11:40 every day, for about four minutes, on the grade.
### Design intent
1. **The supernatural is science fiction underneath.** Nobody is dead, nothing is
haunting anybody, and the whole event has a mechanism the players can understand and
explain back. If a table leaves thinking "ghost train", it has been run wrong.
2. **The obstacle is a physical problem, not a mystery.** They will know what is
happening by the end of Act One. Acts Two and Three are about a train that cannot
stop and an arithmetic that cannot be beaten.
3. **Standing Order 9 is the tutorial.** One shot at a time, no radio, a lantern that
runs out. The period kit *is* the difficulty setting, and it is the scene where
players learn what kind of game this is.
4. **The ending is a decision priced on both sides**, with no roll and no correct
answer, taken in front of a woman holding a manifest.
5. **What this is not:** a fight. There is no antagonist. Kell is not possessed, Vane is
not a villain, and the train is not malevolent — it is a note being held.
---
## Session Timing
| Section | Time | Cumulative | Checkpoint |
|---|---|---|---|
| Briefing — the standing order | ~20 min | 0:20 | kit swapped, somebody has complained |
| Act One — Mercy Draw, now | ~50 min | 1:10 | they know it is a survey fraud, and they have heard 11:40 |
| *Break* | *10 min* | *1:10* | lands on the whistle |
| Act Two — Cordera, 1881 | ~70 min | 2:20 | they are on the train |
| Act Three — Aboard | ~50 min | 3:10 | they have decided |
| Epilogue — the register | ~20 min | 3:30 | sixty-one names, written down |
**Hard rule:** at 2:45 they are aboard, by the department's wagon if not by their own wit.
---
## Plot Summary (GM truth)
**April 1881.** The Cordera & Western needed four miles of narrow gauge across a dry
wash called **Mercy Draw**. The company surveyor, **Elias Vane**, was told what the chart
had to say before he walked the ground. He filed a survey showing the line on the
**north bench**. The line was laid on the **south bench**, because that is where the
ground allowed it.
Nobody was hurt by that. It is the dullest possible paperwork lie. And for six months
every timetable, waybill, dispatch and telegraph order in the company's operation
described a train running along the north bench.
**Reality keeps a record of what belongs where.** It can be made to get it wrong by
anything patient enough — and a train is the most patient arguer there is: a fixed line,
a constant speed, on the hour, every day, saying the same wrong thing about where it was.
**3 November 1881, 11:40.** The record gave way. Four miles of line came loose from the
date with No. 9 and five cars on it and **sixty-one souls aboard**.
**The running is what holds it open.** This is the thing the players must arrive at: the
train is not trapped inside the crossing — the train *is* the crossing. A standing wave,
sustained by its own motion the way a bow sustains a note.
- **Nobody is dead.** They eat, sleep in shifts, and have organised a water rota. By
their clocks it has been about nine hours.
- **Nobody can get off.** Step down and you are on ground outside the error. Three
departmental attempts are on file; none returned; all three tried to stop it first.
- **Stop the train and the note ends** — and everything it holds open ends with it.
**Elias Vane is aboard.** He rode his own line to see what he had signed for. He has had
nine hours and a hundred and forty-five years to think about it. He understands the
standing wave better than the department does, he has told nobody, and he will answer any
direct question honestly because he has been waiting a very long time to be asked.
### Timeline
| When | What |
|---|---|
| 12 Apr 1881 | Vane files the survey. North bench. |
| Apr–Oct 1881 | Line laid on the south bench. Six months of paperwork says otherwise. |
| **3 Nov 1881, 11:40** | The record gives way, with No. 9 inside it. |
| 1889 | Rails lifted. The grade is still there. |
| 1974, 1991, 2009 | Three departmental attempts. No survivors. All tried to stop it. |
| Every day, 11:40 | The crossing opens on the grade for about four minutes. |
### Revelations
| # | Revelation |
|---|---|
| R1 | The filed survey puts the line on the north bench |
| R2 | There has never been a railway on the north bench |
| R3 | Six months of paperwork repeated it |
| R4 | The dispatcher's book has no 11:45 entry |
| R5 | The 11:40 sound is a train working *up* a grade — always accelerating |
| R6 | Nobody aboard is dead, and by their clocks it has been nine hours |
| R7 | The running is what holds the crossing open |
| R8 | Coal that should have lasted four hours has lasted a hundred and forty-five years |
| R9 | Kell will stop if told to, and only if told to |
| R10 | Vane signed it, and knows exactly what stopping it costs |
| R11 | Sixty-one people, six passes, four hours — and the crossing may not hold |
---
## Player Experience
You are handed a Webley, a single-shot rifle, an oil lantern and a pocket watch, and told
that the handset stays behind. Then you stand in a dry wash in Nevada at 11:38 in the
morning and listen to a train you cannot see work a grade that has had no rails on it
since 1889. By lunchtime you know it is a survey fraud from 1881. By the evening you are
lying on the top of a water tower waiting for forty seconds of slow track, and after that
you are in a warm carriage that smells of coffee, being asked by a conductor with a
manifest whether you are from the company. She has counted sixty-one every hour for a
hundred and forty-five years. She would like that noted.
---
## The Countdown — the crossing clock (0–6)
Move it on **each pass of the train**, when **somebody uses a modern object across**, and
on a **fumbled boarding attempt**.
| Clock | What |
|---|---|
| 0 | Open. Four minutes at 11:40. |
| 1 | Thinning. The whistle arrives a half-second before it should. |
| 2 | Slipping. −1 Coherence to everyone across, once. |
| 3 | The line flickers — a hundred yards of grade is briefly not there. |
| 4 | The far end stops answering. Fallon's wire goes dead in 1881. |
| 5 | One pass left, and everybody can tell. |
| 6 | It closes, with whoever is aboard. |
---
## Handouts
| ID | Description | When |
|---|---|---|
| TT-H01 | Case brief TT-004 | Briefing |
| TT-H02 | **Standing Order 9** — period crossings | Briefing |
| TT-H03 | Issue list, 1881 | Briefing |
| TT-H04 | **The filed survey** — the line on the north bench | Act One |
| TT-H05 | Working timetable and the dispatcher's book | Act One |
| TT-H06 | **Passenger manifest** — sixty-one souls, nine children | Act Three |
The manifest is deliberately held back to Act Three. It is a list of names, and the
players should not be holding it until the moment it starts to weigh something.
---
## Briefing — the standing order *(~20 min)*
> You will be crossing to November 1881. Nothing you are carrying goes with you. Stores
> has laid out what you may take instead, and I would ask you not to argue with the
> storeman, because he is right and you are in a hurry.
**Nothing modern goes through.** Give both reasons:
1. **It costs you.** An object that does not belong is a thing the far side must also be
wrong about. **Using** a modern item across costs **1 Coherence**, per object, first
time.
2. **It is a beacon.** A crossing is a place where the record is already wrong. It
notices what does not fit, and the case clock moves.
**Charge for using, never for carrying.** A player who smuggles a torch across and never
switches it on has made a good decision and should get away with it.
**The anchor is exempt.** Everybody keeps theirs.
### The 1881 issue
| Instead of | They get |
|---|---|
| service pistol | **Webley revolver** — 1d10, six shots, slow reload |
| carbine | **Martini-Henry** — 2d6+2, **one shot**, then work the lever |
| torch | **oil lantern** and lamp oil, which runs out |
| the handset | **nothing.** Out of contact from the moment they cross. |
| stab vest | **buff coat** or oilcloth coat |
| medkit | **chirurgeon's roll** and **laudanum** |
| phone and GPS | **brass compass**, **pocket watch**, **spyglass** |
---
## Act One — Mercy Draw, now *(~50 min)*
> **Goal:** find the fraud, hear the train, understand it is on a timetable.
Nevada. A dry wash four miles long with a railway grade along the south side. Rails
lifted in 1889; ballast and tie marks still there. Your watch says 11:38.
- **The south bench** — the grade, real and obvious.
- **The north bench** — nothing, and never anything. `[CUS: Spot or Engineering]`
- **The water tower footing**, half a mile up at the top of the grade. *Remember it.*
- **11:40.** A whistle a long way off, then four minutes of a train working a grade,
getting no nearer. **Automatic — nobody rolls for this.**
### Clue trail
| # | Clue | How found | Type |
|---|---|---|---|
| 1 | The filed survey puts the line on the **north** bench | **Automatic** — it is a handout with a line drawn on it | Essential |
| 2 | There has never been a railway on the north bench | **Automatic** to anyone who walks it | Essential |
| 3 | Six months of paperwork all say north bench | `[CUS: Research]`, or Registry provides | Essential |
| 4 | No 11:45 entry in the dispatcher's book | `[CUS: Research]` | Essential |
| 5 | E. Vane, surveyor, is on the manifest | `[CUS: Research]` | Optional |
| 6 | Three previous attempts, no survivors, all tried to stop it | Registry, if asked | Optional |
| 7 | The sound is a train *accelerating* up a grade | `[CUS: Listen or Engineering]` | Optional |
**Fallbacks.** 1 and 2 are the handout and the ground. 3 and 4 arrive from Registry by
telephone at the twenty-minute mark if nobody goes looking — Bewley is thorough and
slightly impatient.
**They cannot board at this end.** Too fast, grade against them. Let them try: a bruise, a
lesson, and the clock moves one.
> **Fast-track at 1:10:** the 11:40 comes round again and the door stays open long enough
> to walk through. That is what degrading looks like.
---
## Act Two — Cordera, 1881 *(~70 min)*
> **Goal:** the far side, the standing order in practice, and the boarding.
The fourth of November, 1881. A town of four hundred that smells of woodsmoke and horses.
Nobody is frightened yet — a train went into the Draw yesterday and has not come out, and
the wire says it never arrived.
- **The company office** — dispatcher, orders on spikes, the original survey in a drawer.
- **The livery** — horses. This matters.
- **Ada Reyes**, telegraph, who has sent the same query forty times and has noticed the
missing 11:45.
- **The Draw**, four miles out. The train passes about every forty minutes.
**Nobody in 1881 can help them stop it, and everybody will help them get near it.** The
company wants its train back. Let the players use that.
### The set-piece — getting aboard
The train cannot stop and cannot be flagged. The only way on is to **match its speed**,
and the only place it is slow is **the top of the grade at the water tower** — about eight
miles an hour, for perhaps forty seconds.
1. **Ride it down.** Horses alongside on the bench, step across. `[CUS: Ride, then
Athletics]` — and the horse needs its own Ride roll to go near the train at all.
2. **Drop from the tower.** Twelve feet onto a moving roof. `[CUS: Athletics — average;
**only a fumble is a fall**]`. **Do not make this difficult** — playtested at halved
skill it put four agents on 12–31% and all four missed, which stalls the one scene the
scenario is built around.
3. **The wagon on the grade.** Four horses, a plank across. Slowest to arrange, safest to
cross, and it needs the company's help.
**The wagon is easier than the tower, and the tower is easier than the horses.** A party
that spends an hour arranging the wagon has bought an easy roll and should get one.
**Everybody has to get on.** A failed attempt gets the next pass, forty minutes later, and
the clock moves — not a full stop.
**Panic:** boarding is worth a point to anybody who wants one. Offer it; do not force it.
> **At 2:45 they are aboard.** If two attempts have failed, the department's second wagon
> arrives with two more agents and a plank, and the players owe somebody a favour.
---
## Act Three — Aboard *(~50 min)*
> **Goal:** sixty-one people, one man who knows, and a decision that costs either way.
> It is warm. That is the first thing. Sixty-one people in five cars, and it smells of
> bodies and coal smoke and coffee. Somebody has organised a rota for the water. A child
> is asleep across two seats with a coat over her. They look up when you come through the
> door, and one of them says: *"Are you from the company?"*
| Where | What |
|---|---|
| The locomotive | **Marius Kell**, engineer, who has not let go of the regulator |
| Tender, car one | Coal that has not run out — the first hard evidence |
| Cars two and three | Most of the sixty-one. Organised, rationed, glad to see anyone |
| Car four | Freight, the company safe, **the signed survey** |
| Car five | **Elias Vane**, alone, watching the line come out from under |
### The three facts aboard
1. **The coal has not run out.** `[CUS: Engineering or Spot]`
2. **Kell cannot let go** — not *will* not. He worked out on day one that slowing is the
end of everything, and he has been right for a hundred and forty-five years. He is not
mad and not possessed. He is tired.
3. **Vane knows**, and will say so to a direct question.
### Getting people off
**Off the moving train is survivable. Off the crossing is not.** A person put off at the
top of the grade — the same forty seconds the agents used — lands in 1881 and lives.
**Sixty-one people. Six passes. Four hours.** They cannot do it in the time they have.
That is deliberate: the ending is what they choose to do about the arithmetic.
### The choices — no roll resolves this
| Choice | Cost |
|---|---|
| **Everyone off, then stop it** | Four hours while the crossing degrades. It may not hold. The right answer, and a gamble. |
| **Stop it now** | Clean close, clean file. Sixty-one people end. Kell will do it if asked. |
| **Children and crew off, then stop it** | The compromise every table finds. It works — and it means choosing, by name, off a manifest they are holding. |
| **Leave it running** | Everyone lives today. It fails inside a year with everyone aboard. A real option; the scenario logs it without punishing it. |
### If they ask Vane
> I filed a chart that said the line was somewhere it was not, because a man told me to
> and I wanted the work. That is all I did. It took six months of honest railwaymen
> repeating it to make it true. If you stop us I would call that fair, and I would ask you
> to put the children off first.
---
## Epilogue — the register *(~20 min)*
A Registry room, a form, and somebody writing down sixty-one names.
| Ending | What the register says |
|---|---|
| **All off, then stopped** | Sixty-one resettled in 1881, new papers, no way home. Two find their way into the department's files within the decade. |
| **Stopped with people aboard** | Closes clean. The file closes clean. Nobody is quite the same about a timetable. |
| **Partial** | However many were put off, put off. The rest are a number in a column, and the agent who chose is named. |
| **Left running** | Logged, monitored, inherited by whoever holds the case in a year. Defensible. Nobody thanks them. |
---
## Cast
| Who | Wants | Key line |
|---|---|---|
| **Marius Kell**, engineer | to be allowed to stop | *"You tell me when. I'll not do it before you say."* |
| **Elias Vane**, surveyor | to be asked, at last | *"I signed it. Six months of honest men made it true."* |
| **Hester Bligh**, conductor | her passengers accounted for | *"Sixty-one aboard. I have counted them every hour since."* |
| **Ada Reyes**, telegraph | an answer up the line | *"It never got to Fallon. I've sent it forty times."* |
| **Mr Bewley**, Registry | the chart, and the truth written down | *"Bring me the paper. The paper is the case."* |
---
## Do not resolve
- **Why the coal has not run out.** The wave sustains it; that is as far as anyone gets.
- **What is at the far end of the line.** Nobody aboard has reached it.
- **Whether the people put off in 1881 were saved or exiled.** Both. It should sit badly.
## GM notes
- **Play the passengers as people, hard.** The scenario dies the moment they become a
number, and it lives on the fact that they are not.
- **Standing Order 9 is the tutorial.** Do not soften it and do not apologise for it.
- **Cut first:** the town, down to the livery and the telegraph office. Never cut the
boarding.
- **If they crack it early,** run the passes — six of them, forty minutes apart — and let
the table feel the arithmetic they are betting on.
---
## Which scenario to run
| | Length | Combat | Sends them across |
|---|---|---|---|
| **LAST ADMISSION** | 2h | yes, one fight | no — prologue, civilians |
| **OPEN DAY** | 2h15 | no | no — prologue, civilians |
| **THROUGH TRAIN** | 3h30 | no | **yes** — the first real case |
| **BLACK PLATFORM** | 3–4h | yes, heavily | no — a modern-day action case |
Run a prologue, then THROUGH TRAIN. BLACK PLATFORM is the one to reach for when a table
wants a fight.
+55 -1
View File
@@ -13,6 +13,8 @@
"TYPES.Item.group": "Group",
"TYPES.Item.vehicle": "Vehicle",
"TYPES.Item.authority": "Authority",
"TYPES.Item.talent": "Talent",
"TYPES.Item.power": "Art",
"RINGBRP.Tab.Access": "Access",
"RINGBRP.New.authority": "New authority",
"RINGBRP.Scope.nothing": "nothing at all",
@@ -925,5 +927,57 @@
"RINGBRP.Gen.RolledChar": "{ch}",
"RINGBRP.Gen.TakeThem": "Keep these",
"RINGBRP.Gen.Nth": "agent {n}",
"RINGBRP.Dialog.Circumstance": "How are the circumstances?"
"RINGBRP.Dialog.Circumstance": "How are the circumstances?",
"RINGBRP.Item.TalentId": "Talent key",
"RINGBRP.Item.TalentCategory": "Category",
"RINGBRP.Item.TalentSummary": "In one line",
"RINGBRP.Item.TalentRule": "What it does",
"RINGBRP.Item.GrantsAction": "Grants action",
"RINGBRP.Item.UsesPerCase": "Uses per case",
"RINGBRP.Item.UsedThisCase": "Used this case",
"RINGBRP.Item.SkillBonusFam": "Bonus to skill",
"RINGBRP.Item.SkillBonusVal": "Bonus",
"RINGBRP.Item.ArtId": "Art key",
"RINGBRP.Item.ArtSummary": "In one line",
"RINGBRP.Item.ArtRule": "What it does",
"RINGBRP.Item.ArtRating": "Rating",
"RINGBRP.Item.PushCost": "Push cost",
"RINGBRP.Item.PushRule": "What pushing does",
"RINGBRP.Item.Origin": "Where it came from",
"RINGBRP.Panic.Label": "Panic",
"RINGBRP.Panic.Steady": "Steady",
"RINGBRP.Panic.SteadyNote": "You are all right. You are doing the job.",
"RINGBRP.Panic.Rattled": "Rattled",
"RINGBRP.Panic.RattledNote": "Your hands are not quite steady. No penalty — the first one is free.",
"RINGBRP.Panic.Shaken": "Shaken",
"RINGBRP.Panic.ShakenNote": "−5% to everything. You keep checking the way you came in.",
"RINGBRP.Panic.Losing": "Losing it",
"RINGBRP.Panic.LosingNote": "−10% to everything. You are talking too fast and not hearing answers.",
"RINGBRP.Panic.Overwhelmed": "Overwhelmed",
"RINGBRP.Panic.OverwhelmedNote": "−20% to everything. One more and you go.",
"RINGBRP.Panic.Broken": "Broken",
"RINGBRP.Panic.BrokenNote": "You freeze, run, or go at it. Choose one, take it, and come back at 3.",
"RINGBRP.Panic.Trig.WrongThing": "Something that should not be there",
"RINGBRP.Panic.Trig.Touched": "It touched you",
"RINGBRP.Panic.Trig.Body": "A body",
"RINGBRP.Panic.Trig.Known": "Somebody you knew",
"RINGBRP.Panic.Trig.Dark": "The lights went",
"RINGBRP.Panic.Trig.Wounded": "A major wound",
"RINGBRP.Panic.Trig.Named": "It said your name",
"RINGBRP.Panic.Relief.Steady": "Steady yourself (POW×5)",
"RINGBRP.Panic.Relief.Religion": "Religion — something older than the department",
"RINGBRP.Panic.Relief.Psychology": "Psychology — talk somebody else down",
"RINGBRP.Panic.Relief.Occult": "Occult — name it, out loud, for everyone who can hear",
"RINGBRP.Panic.Relief.Anchor": "Your anchor, once per case",
"RINGBRP.Panic.Gained": "{name} — panic {from} → {to}",
"RINGBRP.Panic.Eased": "{name} — panic {from} → {to}",
"RINGBRP.Panic.Breaks": "BREAKS — freeze, flee, or fixate. Choose, take it, come back at 3.",
"RINGBRP.Panic.NoChange": "No change.",
"RINGBRP.Flavour.Panicking": "panicking ({mod}%)",
"RINGBRP.Chat.ToEverything": "to everything",
"RINGBRP.Panic.Backfired": "Fumbled —",
"RINGBRP.Panic.SceneEnd": "Scene over",
"RINGBRP.Panic.SceneEndNote": "Panic clears when the scene does. Coherence does not.",
"RINGBRP.Party.Title": "The team, right now",
"RINGBRP.Party.Who": "Agent"
}
+4 -2
View File
@@ -6,8 +6,7 @@
"scripts": {
"build": "node tools/build-packs.mjs && node tools/update-readme.mjs",
"icons": "node tools/make-icons.mjs",
"dice": "node tools/make-dice-textures.mjs",
"check": "node tools/check-rules.mjs && node tools/check-kits.mjs && node tools/check-lang.mjs && node tools/check-behaviour.mjs",
"check": "node tools/check-rules.mjs && node tools/check-kits.mjs && node tools/check-lang.mjs && node tools/check-templates.mjs && node tools/check-behaviour.mjs",
"test": "npm run check",
"readme": "node tools/update-readme.mjs"
},
@@ -17,5 +16,8 @@
"type": "commonjs",
"dependencies": {
"classic-level": "^3.0.0"
},
"devDependencies": {
"handlebars": "^4.7.9"
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004
MANIFEST-000002
+1 -5
View File
@@ -1,5 +1 @@
2026/08/12-18:25:14.825703 1726ef000 Recovering log #3
2026/08/12-18:25:14.825849 1726ef000 Level-0 table #5: started
2026/08/12-18:25:14.825995 1726ef000 Level-0 table #5: 4591 bytes OK
2026/08/12-18:25:14.826236 1726ef000 Delete type=0 #3
2026/08/12-18:25:14.826263 1726ef000 Delete type=3 #2
2026/08/18-00:25:18.939914 16fcaf000 Delete type=3 #1
-1
View File
@@ -1 +0,0 @@
2026/08/12-18:25:00.997036 17328b000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004
MANIFEST-000002
+1 -5
View File
@@ -1,5 +1 @@
2026/08/12-18:25:14.845998 172efb000 Recovering log #3
2026/08/12-18:25:14.846266 172efb000 Level-0 table #5: started
2026/08/12-18:25:14.846391 172efb000 Level-0 table #5: 4129 bytes OK
2026/08/12-18:25:14.846614 172efb000 Delete type=0 #3
2026/08/12-18:25:14.846646 172efb000 Delete type=3 #2
2026/08/18-00:25:19.010528 1704bb000 Delete type=3 #1
-1
View File
@@ -1 +0,0 @@
2026/08/12-18:25:01.063788 173a97000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004
MANIFEST-000002
+1 -5
View File
@@ -1,5 +1 @@
2026/08/12-18:25:14.826938 171ee3000 Recovering log #3
2026/08/12-18:25:14.827153 171ee3000 Level-0 table #5: started
2026/08/12-18:25:14.827483 171ee3000 Level-0 table #5: 31614 bytes OK
2026/08/12-18:25:14.827723 171ee3000 Delete type=0 #3
2026/08/12-18:25:14.827764 171ee3000 Delete type=3 #2
2026/08/18-00:25:18.941763 16f4a3000 Delete type=3 #1
-1
View File
@@ -1 +0,0 @@
2026/08/12-18:25:00.998955 172a7f000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004
MANIFEST-000002
+1 -5
View File
@@ -1,5 +1 @@
2026/08/12-18:25:14.839430 1726ef000 Recovering log #3
2026/08/12-18:25:14.839716 1726ef000 Level-0 table #5: started
2026/08/12-18:25:14.840052 1726ef000 Level-0 table #5: 44390 bytes OK
2026/08/12-18:25:14.840621 1726ef000 Delete type=0 #3
2026/08/12-18:25:14.840694 1726ef000 Delete type=3 #2
2026/08/18-00:25:18.995451 16f4a3000 Delete type=3 #1
-1
View File
@@ -1 +0,0 @@
2026/08/12-18:25:01.048880 17328b000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004
MANIFEST-000002
+1 -5
View File
@@ -1,5 +1 @@
2026/08/12-18:25:14.812575 1726ef000 Recovering log #3
2026/08/12-18:25:14.814591 1726ef000 Level-0 table #5: started
2026/08/12-18:25:14.816158 1726ef000 Level-0 table #5: 225945 bytes OK
2026/08/12-18:25:14.816434 1726ef000 Delete type=0 #3
2026/08/12-18:25:14.816497 1726ef000 Delete type=3 #2
2026/08/18-00:25:18.976255 16fcaf000 Delete type=3 #1
-1
View File
@@ -1 +0,0 @@
2026/08/12-18:25:01.033123 172a7f000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004
MANIFEST-000002
+1 -5
View File
@@ -1,5 +1 @@
2026/08/12-18:25:14.822117 173707000 Recovering log #3
2026/08/12-18:25:14.822240 173707000 Level-0 table #5: started
2026/08/12-18:25:14.822383 173707000 Level-0 table #5: 3295 bytes OK
2026/08/12-18:25:14.822727 173707000 Delete type=0 #3
2026/08/12-18:25:14.822771 173707000 Delete type=3 #2
2026/08/18-00:25:19.005331 16ec97000 Delete type=3 #1
-1
View File
@@ -1 +0,0 @@
2026/08/12-18:25:01.058642 173a97000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000007
MANIFEST-000002
+1 -3
View File
@@ -1,3 +1 @@
2026/08/12-18:25:14.806453 172efb000 Recovering log #6
2026/08/12-18:25:14.806833 172efb000 Delete type=0 #6
2026/08/12-18:25:14.806857 172efb000 Delete type=3 #4
2026/08/18-00:25:18.961584 16ec97000 Delete type=3 #1
-5
View File
@@ -1,5 +0,0 @@
2026/08/12-18:25:01.665437 16f483000 Recovering log #3
2026/08/12-18:25:01.667727 16f483000 Level-0 table #5: started
2026/08/12-18:25:01.671815 16f483000 Level-0 table #5: 545293 bytes OK
2026/08/12-18:25:01.672145 16f483000 Delete type=0 #3
2026/08/12-18:25:01.672193 16f483000 Delete type=3 #2
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004
MANIFEST-000002
+1 -5
View File
@@ -1,5 +1 @@
2026/08/12-18:25:14.829681 173707000 Recovering log #3
2026/08/12-18:25:14.829858 173707000 Level-0 table #5: started
2026/08/12-18:25:14.830111 173707000 Level-0 table #5: 21613 bytes OK
2026/08/12-18:25:14.830352 173707000 Delete type=0 #3
2026/08/12-18:25:14.830387 173707000 Delete type=3 #2
2026/08/18-00:25:19.002226 16f4a3000 Delete type=3 #1
-1
View File
@@ -1 +0,0 @@
2026/08/12-18:25:01.055569 172a7f000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004
MANIFEST-000002
+1 -5
View File
@@ -1,5 +1 @@
2026/08/12-18:25:14.841492 1726ef000 Recovering log #3
2026/08/12-18:25:14.841753 1726ef000 Level-0 table #5: started
2026/08/12-18:25:14.842198 1726ef000 Level-0 table #5: 43953 bytes OK
2026/08/12-18:25:14.842469 1726ef000 Delete type=0 #3
2026/08/12-18:25:14.842504 1726ef000 Delete type=3 #2
2026/08/18-00:25:18.999210 16fcaf000 Delete type=3 #1
-1
View File
@@ -1 +0,0 @@
2026/08/12-18:25:01.052553 173a97000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004
MANIFEST-000002
+1 -5
View File
@@ -1,5 +1 @@
2026/08/12-18:25:14.831296 172efb000 Recovering log #3
2026/08/12-18:25:14.831754 172efb000 Level-0 table #5: started
2026/08/12-18:25:14.832094 172efb000 Level-0 table #5: 33484 bytes OK
2026/08/12-18:25:14.832323 172efb000 Delete type=0 #3
2026/08/12-18:25:14.832355 172efb000 Delete type=3 #2
2026/08/18-00:25:19.008595 16f4a3000 Delete type=3 #1
-1
View File
@@ -1 +0,0 @@
2026/08/12-18:25:01.062100 17328b000 Delete type=3 #1

Some files were not shown because too many files have changed in this diff Show More