e5dc9b529930b27081938148364520b1d0113733
95
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e5dc9b5299 |
Hide does not grow on membrane (R-96)
Option H, measured before it was built. Losing a wing already mattered
once it happened — a grounded supporter was 40.5% to be killed against
9.3% for one still flying, a swing of thirty points. It almost never
happened: the wings carried the creature's own six points of heraldic
hide, so folding one meant beating armour six eight times over, and it
occurred in 9% of fights. The rule was reachable, not weak, and every
option that made grounding HURT more moved the win rate by under five
points because you cannot spend a state you never reach.
So the lever is the wing, not the consequence. A location may now be
`bare`: worn armour still covers it — barding a wing works exactly as
before — but the animal's own hide stops at the edge of the membrane.
The supporter's wings are bare, and it gets the toughness back where a
six-hundred-year-old record should carry it: hide 6 -> 7, CON 20 -> 36,
so 22 hit points become 30. STR and SIZ are untouched, so the damage
modifier stays +2d6.
Measured over 20,000 fights against the same four agents:
grounded on borough off borough
before 9% 12.5% win 85.6% win / 14.4% wiped
after 67% 22.1% win 83.5% win / 16.5% wiped
The wing shot becomes the play. Fighting it on borough ground stays a
losing proposition, so ARGENT AND GULES is still the real answer and
grounding improves a bad fight rather than replacing the solution — and
the fight off borough ground keeps its teeth, marginally more lethal
than before rather than less. Bare wings ALONE was the trap: it made
grounding routine and turned the off-ground fight into a 96% walkover
with a 3.8% wipe rate, buying the text at the cost of the encounter.
check-anatomy gains the property this whole thread was about: a plan
whose behaviour turns on losing a wing must not armour that wing with
the creature's own hide, or the rule is decoration. Negative-tested by
putting the hide back.
Also re-records the lethality baseline, which was ALREADY stale: HEAD's
own code reproduced 27 of its 47 entries differently, because the
supporter joined NPCS in
|
||
|
|
77465baa30 |
A flyer with a hole in a wing is a quadruped (R-95)
The supporter's tactics have said this since it was written: "It is a flyer, and a flyer with a hole in a wing is a quadruped: disable either wing and it is on the ground for the rest of the scene, which is the fight the agents can actually win." Playing a full combat against it showed the sentence was decoration. `flightLost` was computed, printed on the NPC sheet as GROUNDED, and read by nothing. Both wings disabled cost 20% off PHYSICAL rolls, and Brawl is a MELEE category, so woundPenaltyFrom returned 0 and a grounded supporter attacked at exactly the 75% and exactly the 1d6+1+2d6 it had in the air. In the played fight it was grounded from round five and went on to kill one agent, knock out a second and leave a third dying. Losing a wing now changes the animal. A plan declares what it becomes — winged says `grounded: "quadruped"` — and hitLocationRoll drops the creature onto that plan the moment a wing is disabled or destroyed. The wings leave its hit chart and the six ranged faces they covered go back to the body: vital hits rise from 3 faces in 20 to 5. Every other wound moves across on the R-94 remap with its severity intact. The wing's own damage is deliberately dropped rather than carried, and cleared in its own update so the remap never sees it. A ruined wing must not arrive as a ruined foreleg — the creature is not lamed, it is earthbound, and the injury has already been paid out as the change of shape. check-anatomy gains a fifth property: a declared landing must exist, must have no wings of its own, must not itself be a flyer, must have room for every wound the flyer was carrying without two collapsing onto one location, and must not heal a disabled location on the way down. Three induced failures confirm it fires; the third — landing somewhere with too few locations — passed at first because I had not stated the capacity property, and the guard was sharpened until it caught it. Measured, and reported honestly: this does NOT deliver the fight the tactics text promises. Over 4,000 simulated fights the win rate moves 12.7% -> 12.1% on borough ground and 85.8% -> 86.3% off it, because grounding only happens in 9% of fights there and arrives late. What actually decides this encounter is ARGENT AND GULES: 11.4% win on borough ground against 85.4% off it. The wing rule is now real and mechanical, but the creature's advice still points the GM at the wrong lever. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4a80cc1eca |
Body plans keep what a wound MEANT, not just its number (R-94b)
1.7.1 carried wounds across a change of body plan by moving the raw points. Playing it showed that is the wrong invariant. A location is disabled at its maximum and destroyed at twice it, those maxima come from `frac`, and the plans disagree — an arm is 0.35 of the body and a foreleg 0.42 — so moving the number silently changed the meaning. Measured across the 20 plan changes: 234 severity flips, 138 of them turning a location that was merely disabled into a destroyed one. A destroyed arm arrived as a working-ish foreleg; two disabled arms stacked onto one Vesh grasping limb and arrived destroyed. So severity is now the invariant. A hurt location arrives hurt, a disabled one disabled, a destroyed one destroyed; the points are rescaled to the destination's own maximum to keep that true, and two wounds sharing one location take the worse rather than the sum, because relabelling a creature's anatomy must not destroy a limb that nothing in play destroyed. Correspondence is now stated rather than inferred. Every location gains a plan-independent `slot` (lowerR, upperL, vital, head...), and the remap matches on slot first. The previous version inferred it from `kind` plus table order, which inverted left and right on every crossing: a wounded right arm came back as the opposite hind leg. Where a destination has no matching slot the fallback prefers the same side and the same height, so a wing becomes a foreleg rather than a hind leg and a left wing becomes a LEFT arm. The vesh and the cadence are not bilateral — one grasping limb, five interchangeable units — so their slots carry no side, which is a fix to my own first pass: giving them sided slots was a category error the side guard caught. check-anatomy now proves all four promises over every ordered pair and severity band: nothing is stranded, severity is preserved, left never becomes right and fore never becomes hind between bilateral plans, and no update deletes a wound it is also setting. 432 carried wounds, 0 flips, down from 234. Five induced failures confirm each assertion fires; two of my first attempts at those did NOT fire and were sharpened until they did. Played through in a live world: a church grim filed as a humanoid with a destroyed right arm, a disabled left arm and a hurt chest becomes a dog with a destroyed right foreleg, a disabled left foreleg and hurt forequarters, and converts back to exactly the wounds it started with. Its penalties correctly stop being manipulation and start being movement — a man with two ruined arms drops his weapon, a dog with two ruined forelegs cannot run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a90c4f3e71 |
Carry wounds across a change of body plan (R-94)
locationDamage is keyed by location id and prepareDerivedData reads only
the keys belonging to the current plan, so changing speciesProfile on a
wounded actor stranded every wound it had. Found on a live Barghest that
had been shot in the leg and chest as a humanoid and then switched to
quadruped: 5 points under `legR` and 7 under `chest`, exactly its 12
missing hit points, sitting under keys no quadruped location answers to.
It read on the sheet as a badly hurt dog with eight pristine locations.
Stored and never read — the same defect this system keeps producing.
rules.mjs gains remapLocationDamage, which carries wounds across by KIND,
because kind is what the rules act on. Exact-kind matches are claimed
first in table order, so the obvious pairings land before any fallback
competes for them; that ordering is what makes the arms become the
forelegs going one way and the forelegs become the arms coming back.
Overflow past a location's destroyed ceiling is reported, not dropped.
It also gains locationDamageReplacement, which exists because the first
version of this fix had the bug it was fixing. Foundry merges object
updates so the outgoing keys must be deleted by name (R-93), but a
deletion and an assignment of the SAME key do not both apply — the
deletion wins. quadruped and winged share every location but the wings,
so switching between them emitted `-=hindLegR` alongside `hindLegR: 5`
and threw the wound away. Caught in live testing, not by reading.
Guards, both negative-tested before being wired in:
- check-anatomy now walks all 20 ordered plan pairs and asserts every
wound lands on a location the destination has, the arithmetic closes,
the round trip does not leak, and no update both sets and deletes the
same key.
- check-rules gains 13 spot-checks over both new rules.
Verified in a running Foundry: baseline -> quadruped -> winged ->
quadruped -> baseline conserves all 12 points with no orphans, returns
to exactly {legR: 5, chest: 7}, and whispers the GM a receipt of what
moved each time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ddc4f99ff4 |
Hit locations reach ordinary combat, and creatures get a body
THE DEFECT. locationFor had ONE caller in the engine, hitLocationRoll, and hitLocationRoll was called from fall, burn, detonate and burstAttack. Falling, fire, explosions and automatic fire. Nothing else. rollWeaponDamage rolled dice, printed a number and returned it, so the chat Damage button, the character sheet's damage button and the item sheet's all stopped there. A grenade knew which arm it took off. A sword did not. Every individual piece was already correct - the tables, per-location armour and coverage, disable and destroy thresholds, Major Wound, dying, death, the figure on the sheet. They simply were not joined up, which is why this reads as a wiring change rather than a new subsystem. - rollWeaponDamage now carries through to hitLocationRoll: it rolls the location, applies that location's armour, applies the wound, and handles the rest through the machinery that already owned it. Nothing here re-implements any of that - locationModeFor picks the melee or ranged column from the weapon's class. The two columns have always differed and nothing was choosing between them - with no target it does exactly what it did before and SAYS SO on a card, rather than failing quietly - burstAttack opts out with locate:false - it rolls its own location per round, and letting both fire would have applied every round of a burst twice BODY PLANS. Two new tables: quadruped (four legs, fore/hindquarters, neck, a head that is hard to reach) and winged (that, plus wings). Forequarters are the vital. DISABLE EITHER WING AND IT CANNOT FLY, which is the fight a party can win. Barghest, Kelpie and Church grim were all species "baseline" - a black dog, a horse and a grave-hound, each hit-located with two arms and a chest. Fixed. Added "The supporter", a winged heraldic beast, so the new table is exercised by real content instead of existing unused. Body plan is a NEW FIELD, not a species. A bear is not a playable people with characteristic dice and talents, so creature specs take bodyPlan and the playable species list is untouched. THE NPC SHEET had no hit locations at all - 102 lines against the character sheet's 627 - while every NPC in the game had them derived and wired to consequences. It now carries the body-plan selector, the figure, the per-location table, the condition and a GROUNDED flag. The sheet class already extended the character sheet, so it already HAD the context; the template never used it. FOUND IN PASSING, by the new guard: kind "body" - the abdomen, the trunk, the hindquarters - did nothing whatever. Its printed effect has always promised "-30% to all Physical actions and bleeding 1 hit point per round until First Aid" and the code delivered none of it, on every body plan including the humanoid default. It now bleeds 1, or 2 destroyed, exactly as bodyX says. NEW GUARD: check-anatomy. Every body plan must cover 1-20 exactly once in BOTH modes, every location must be drawn and every drawn shape must be a location, and every kind must be one locationEffectsFor acts on, has effect text for, and has a destruction outcome. Verified it fails on a d20 gap, a shapeless limb and an unhandled kind before wiring it in. Ninth guard. VERIFIED IN A RUNNING FOUNDRY, not just unit-tested: melee bar reads the Melee column and the rifle reads Ranged on the same creature; forequarters graded as a vital hit; a maxed wing set flightLost and the sheet said GROUNDED; the real chat Damage button rolled 1D20 15, found the right foreleg, applied 12 through armour, disabled the leg and took the dog from 16 to 4; and with nothing targeted nothing was touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
748e061b56 |
CLEAN GROUND: an outline where the department authored its own monster
A v0.1 rough outline, freely adapted from A Hollow Tree (2023) — the walk north that never arrives, and a predator that imitates. Everything else comes out of the system: a 1962 reserve entry that was never cancelled, forty-one people looping eleven duplicated miles, and Borrowed Authority as a detector that can only tell filed from unfiled. Uses existing bestiary entries only. Marked not-runnable: no pregens, read-aloud, handouts, Act Four exposure model or playtest yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
339d5dd8dc |
test runs the guards through bun, not through npm
`test` was "npm run check", so reaching bun meant shelling out to npm first. It is "bun run check" now — one runner, and npm is out of the path. Not the eight-guard chain copied into `test`: that is the same shape of mistake the rule formulas made before rules.mjs existed, a list written twice and changed once. `check` stays the only place the guards are named. Verified: npm test exit 0, bun run test exit 0, bun run check exit 0. Worth knowing: bare `bun test` does NOT run this. It is bun's own test runner, it matches no files here, and it exits with an error. The ways in are `npm test`, `bun run test` and `bun run check` — which is what the README already tells you to use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
54a759e057 |
Run the guards under bun, including the two new ones
Rebased onto the creature-forge work, which had added check-creatures and check-lethality to the chain and had `check` back on node. Taking either side whole would have been wrong: the node side loses bun, the bun side silently drops the two new guards. So the runner is bun and the guard list is upstream's, all eight of them. Only `check` and `readme` move. build, icons, portraits, bestiary, mj and simulate stay on node — they were never part of this change, and build writes LevelDB packs through classic-level. Verified before committing: each guard run individually under bun, then `npm run check` end to end, exit 0. check-rules 103 rules, 33 files check-kits OK check-lang 995 keys, no collisions check-templates 19 templates compile check-behaviour 67 behavioural tests check-scenarios 9 files, 62 mechanic tags check-creatures 101 actor specs across 8 sources check-lethality 46 creatures, none drifted more than 6 points Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0c0996858f |
A GM screen taken from the rules that actually run
The figures a GM reaches for mid-scene, on one page: the order of operations, the circumstance ladder, how a roll bands, reaction, the combat round, hit locations, wounds and bleeding, both tracks, the way home, the stations, and the environment. Every number was checked by running the function rather than by reading it — 58 of 58 against rules.mjs, including the eight that moved out of ringbrp.mjs. That matters more than usual here, because `design/` still carries four rulings the code has overtaken: hit points as CON+MAS, a Major Wound at max(9, HP/3), "no hit locations, final", and marksmanship style fixed by home-world gravity. rules.mjs is the authority and the page says so at the top. It is also written to drop straight onto a Loom board: 34 cards, 23 links, nothing swallowed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
322389bc8a |
A bestiary a GM can actually read, and the defect it found immediately
Forty-six creatures and no way to look at them. They live in content.mjs, which is source code, and in the compendium, which shows one actor at a time behind two clicks. A GM prepping a case can browse neither, so in practice the bestiary was whatever the GM happened to remember writing. docs/BESTIARY.md is generated, never hand-written, for the reason the rules journal is generated from rules.mjs: a reference that can disagree with the thing it references is worse than no reference. It opens with a one-page table sorted by how much of a party each creature puts on the floor - the part that gets printed - then an entry each with characteristics, attack, the POWER line pulled out of the tactics, and the measured figure read straight from the lethality baseline. Every number is derived, so the document cannot drift, and check-lethality already refuses the build if the measurements move. IT FOUND SOMETHING IN ITS FIRST RUN. The generic heavy natural attack is keyed `grasp` and was NAMED "Vesh grasp limb". Twenty-two creatures carry it and five are Vesh, so the at-a-glance table cheerfully printed "Vesh grasp limb" against a black dog, a kelpie, a church grim and an apple tree. Renamed to "Grasping limb"; the key is untouched, so every kit reference and every built pack still resolves. That defect was invisible for as long as nobody could see the bestiary in one place, which is the argument for the document. The caveats are given more room than the numbers, deliberately. The measured column is SOLO against ONE frozen party, and the document says so three times: encounter lethality moves with how many there are and, much harder, with who the players brought - the same fight runs 0% to 100% across party choice. It points at --spread rather than pretending a single figure is an answer. Also records the real remaining gap: 21 creatures fight with nothing but their own bodies and still carry human anatomy, so the barghest can be shot in the left arm. The machinery to fix that is built and measured; what is missing is anatomy tables for the shapes they actually are - a quadruped, an immobile object, a swarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4b718595fe |
check-lethality: guard eight, and the README stops advertising a subset
Seven guards checked that content is WELL FORMED. None checked what it DOES, so a change to a damage modifier, a hit-point formula or the armour value on a service vest could double a creature's lethality without touching one line of that creature - and nothing in the build would notice, because the creature did not change. Built as a regression test rather than the hand-declared bands the creature-forge plan described. Bands are the wrong shape here: declaring "keepers: dangerous" across forty-six creatures means inventing forty-six judgements, and after --spread it is clear the interesting question is not "is this dangerous" - that has no single answer - but "is this the same as it was". A recorded baseline answers exactly that, needs no authoring, and cannot be argued with. Every creature is measured solo against a FROZEN party of four, two armed postings and two trades, at a fixed seed, so the only thing that can move a number is a change to the rules or to the creature. Tolerances are 6 points of wipe rate and 0.35 agents, which is outside the noise floor of 200 runs - a guard that cries wolf gets deleted. Verified by tampering: told the baseline the nuckelavee was harmless and the guard caught it at +16.5 points and +1.67 agents down, exit 1. Runs in half a second, so it joins the pre-build checks rather than being something to remember to run. Also: the README's guard block was a hardcoded list of four while the suite was seven. It had silently stopped mentioning every guard added after it was written, including check-creatures and check-scenarios. Now all eight, and the prose says eight. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
414dbd7037 |
simulate --spread: one number was the wrong instrument
THROUGH TRAIN Act Three publishes a single figure for its standoff - "the party is wiped in 24% of runs" - and tells the GM not to soften it. Measured against every party the duty roster can field, that fight runs from 0% to 100%. It is not that the published number is wrong. It is that no single number can be right for this fight, because the answer was settled at character selection: four armed postings walk it, four trades are massacred, and the GM reading one figure is reading somebody else's session. --spread measures the encounter against every C(16,4) party - 1820 of them, exhaustive rather than sampled, about a minute - and reports the floor, the median and the ceiling with the parties that produce them. Exhaustive on purpose: a GM planning a session wants the actual worst case, not an estimate of it. It also prints each agent's effect on the wipe rate averaged over every party they appear in, which is the line that gets used at the table. For the Act Three standoff: okonkwo -43.2 points ashcroft +16.1 sandoval -17.7 nkemdirim +13.5 holloway -15.9 ferriby +12.4 Okonkwo is worth forty-three points of wipe rate on his own. That is a scene-shaping fact about the encounter that no amount of re-measuring the median would surface. The published tables are NOT rewritten here. What to do about a 100-point spread is a design decision - constrain the party, print a range, or rebalance the fight - and it belongs to whoever wrote the scenario. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2d4f7f18be |
Twenty-four new creatures, and the anatomies finally get used
The bestiary goes 22 to 46: eight more FOLKLORE, eight more HORROR, eight more FORWARD. Ten of the forty-six are now Vesh or Cadence, up from two. That ratio is the point. Both anatomies have had complete hit-location tables, body geometry, species talents and location effects for the whole life of this project and nothing used either, so none of it ran outside the character generator. They are now written to be WORTH the anatomy rather than to wear it: - The relay is five Cadence bodies holding a crossing open, and the crossing closes as they are destroyed. No vital, no head, 10% off everything per body lost - it cannot be dropped and cannot be ignored, which is the species argument entire. - The courier is a Vesh that fights at half rating while its one grasp limb is full. Disable the limb and it drops the consignment and fights properly. The whole encounter turns on a trade the party has to work out. - The census cannot be usefully fought at all: no head means no killing shot, so the fight lasts until somebody opens the ridge, by which time it has counted everyone. - Knockers are the only folklore Cadence, and the committee is a Cadence because a committee IS several bodies and one decision. The rest is written to the house line that most of these are arrangements rather than fights, because a bestiary where every answer is a gun has one page. The washer at the ford will not fight and cannot be made to; the apple tree man wants a drink it has been owed since 1961; the waiting room's exit was never locked and nobody stands up. Measured, 250 runs against four duty-roster agents, seed 11. Nothing accidental turned up: the non-combat entries sit at 0.0-0.1 agents hurt, the fighters at 1.4-2.3, and the only creature over a 10% solo wipe is the stanchion at 14.4% - which is a fixed object with 12 armour whose own tactics say it is a demolition problem and not a fight. The harness can only force a fight, so read that number as "the party punched the pillar". Art regenerated: 24 new portraits, and the eight new non-humans verified to draw exactly as many body parts as their location tables have entries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
94479641b3 |
simulate: locate the damage
The harness measured a game in which every blow went to a general pool. That was
tolerable while every creature was a baseline human and merely wrong once the bestiary
had a Cadence in it: a creature whose entire mechanical identity is being whittled down
body by body was being measured as a person with an odd portrait, and the number it
printed was about nobody.
locationFor and resolveLocationHit move from ringbrp.mjs to rules.mjs, and the
category mapping inside woundPenaltyFor becomes woundPenaltyFrom beside them, for the
reason applyDifficulty and resolveBands moved before them: node cannot load the engine,
and the harness may not own a second copy of a rule. All three arrive with the
spot-checks they never had - 103 rules now, 346 formulas verified.
What the loop does now: 1d20 against the DEFENDER's own species table, melee finding
limbs and shooting finding centre of mass; damage to that location and to the general
pool, so the two agree; disabled at the location maximum and destroyed at twice it,
cumulatively. Then the consequences actually apply - a destroyed head is unconscious
immediately (conditionFor has always accepted that flag and was never passed it, so a
headshot used to leave the target swinging), a ruined arm costs -30 to every attack
including shooting, a ruined leg costs -30 to dodge, and each Cadence body lost costs
the whole creature -10 to everything.
Verified as mechanics rather than as numbers that moved: no species can return a
location it does not have across all 40 rolls; vesh and cadence have no head at any
roll and cadence has no vital either; a vesh ridge is hit 3/20 where a human head is
1/20, which is the "long target rather than a small one" the tables were designed for.
A built Cadence has five bodies at 4 points each and no vital; a built Vesh has six
locations with the ridge the largest.
Measured consequences, seed 11, 400 runs, four duty-roster agents:
keepers x6 31.0% -> 36.5% wiped
the_choir x11 0.3% -> 0.0% wiped, and now degrading as bodies go rather than
being a person who cannot be shot in the head
long_walker 42.0% wiped, which nothing had ever measured
Still no bleeding, panic, Coherence, cover, range bands or fire modes. Still a floor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f88c31649c |
The bestiary's first non-human creatures
vesh and cadence have had complete hit-location tables, body geometry, species talents and location effects since they were written, and no creature had ever used either. So none of that had run outside the character generator, and the whole bestiary was hit-located as a person - including a black dog and a stack of paper. THE CHOIR IS A CADENCE. It always was: its tactics say "run them as ELEVEN separate bodies with one initiative between them", which is Distributed and Quorum described in prose because the species that models it had no users. Retagged, and given the two talents. No scenario prose refers to it, so nothing else moves. PRECEDENT is new, and is a Vesh - the first. Built to exercise what makes the species different rather than to be another sack of hit points: no head, so no fight against one ends on a lucky roll, and a sensory ridge for a vital that runs the length of its back and is correspondingly easy to hit. It is also not hostile, which is the point; the encounter is a conversation with something that answers questions about an event that has not happened yet. Two things left deliberately unfixed and written down rather than fudged: - The choir is eleven in the fiction and five in the location table. Reconciling it properly means giving cadence a per-creature body count, which stops the table being a constant and makes the paper doll follow it. That is the right fix and it is not a small one. A note on the spec says so. - Precedent gets unblinking but not out_of_step, though SPECIES.vesh grants both. An npc actor has no Coherence track, and out_of_step costs 1 to use. Also corrects a comment in creature-schema.mjs claiming Out of Step is vesh-locked. It is not - five postings and a roster agent offer it to humans, and locking it would take it off all six. SPECIES[x].talents is what a species is GRANTED; TALENTS[].species is what only that species may HAVE. Two different fields, and only the second locks. Simulated: Precedent 0.68 of 4 hurt over 3 rounds, no wipes. The choir alone is trivial; at the eleven its own tactics call for, 3.05 of 4 hurt, median 18 rounds, 0.3% wipes. Read both as floors - the harness does not model hit locations, so a Cadence's entire mechanical identity is exactly what it cannot yet measure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
bbe7b92459 |
Stop tracking LevelDB's LOG; it churns on read, not on change
LOG is LevelDB's diagnostic text — "Delete type=3 #1" and compaction notes — and it is rewritten every time a pack is OPENED. Not rebuilt: opened. So all eighteen went dirty whenever anything merely read the compendia, Foundry and any verification script alike, and pack churn kept turning up in commits that had not touched any content. Verified disposable before ignoring rather than assumed: a copy of packs/npcs with LOG and LOG.old deleted opens cleanly, reads back all 21 actors, and LevelDB writes itself a fresh LOG on open. CURRENT and MANIFEST-* stay committed, deliberately. They are how a fresh clone finds the data, and ignoring them would ship compendia nobody can open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
77e9aadc79 |
Rebuild packs; every actor now carries a portrait
76 of 76, up from 16. The npcs pack goes from every creature on the generic silhouette to all 21 with art, and each token carries the same face as its sheet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
187a317a0a |
mj-queue: the illustrated upgrade path, without a self-bot
The Mac bridge posts /imagine to Discord with a user token. It is not ported here and should not be: Midjourney's terms since 27 May 2026 forbid automated access, there is no official API, and the bridge's own memory note records Discord silently dropping its interactions from 22 July — which is what enforcement looks like from inside. A Windows clone would be a second self-bot on the same account. So this does everything except talk to Discord, which turns out to be most of the work. `next` derives a prompt from the creature's spec and puts it on the clipboard; you paste it yourself; `file` or `watch` renames the download to the canonical name and retires the generated placeholder. One at a time, because the memory note is explicit that concurrent jobs in one channel steal each other's grids. Prompts are derived, not written — anatomy from the location table, scale from SIZ, build from STR against SIZ, plating from naturalArmour — so they cannot drift from the statblock. Three things learned by reading the data rather than assuming it: - tactics is written for a GM, not an illustrator. "Advances by bounds, 15 m per round" put a rate of fire in an art prompt. role is the one-line essence and is already the sentence you would hand an artist. Sentences carrying mechanics are dropped; dates and ages are kept. - era is the wrong test for whether something is a person. It classified Mr Bewley of the Registry and everyone in 1881 Cordera as monsters, because their kit is old. - 18 creatures are species "baseline" and are not human-shaped — a black dog, a case file that eats case files, a machine that has decided it is the site. The prompt refuses to describe a body it cannot verify, and `list` names them. That is a statblock to write, not something to paper over here. `sheet` emits a prompt sheet that the existing gen-art.mjs parses unchanged, so if the bridge ever works again the whole bestiary drives from it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9ddea43720 |
make-portraits: every creature gets art it cannot disagree with
Sixty of the seventy-six actors had no portrait. The sixteen that did are the hand-made duty roster; the whole bestiary and every scenario NPC fell back to a generic silhouette. The point is not that this is free. A portrait composed from anatomy.mjs cannot contradict the statblock, because it is drawn from the numbers the hit-location roll uses: silhouette from BODY_ART, height from SIZ, build from STR against SIZ, plating from naturalArmour, a mark for the primary weapon's class. A Cadence would get five bodies because its table has five. Nothing has to be kept in step. Colour carries what a GM wants off a thumbnail — whether the thing is a person, folklore, recovered machinery, or the far side. That reads the shouted category the role lines already carry (FOLKLORE, HORROR, FORWARD), which nothing was using, and falls back to structure for the ones with no tag. build-packs resolves portraits by stem and extension rather than an exact .webp filename, so a creature gains art without an edit to content.mjs and a hand-made raster always beats the generated diagram. A spec that NAMES a portrait and has none is still a hard build failure. These read as diagrammatic field-guide plates and will not match the Midjourney agent portraits. That split is deliberate: the agents are the cast, the bestiary is case material. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
24b183642f |
all-specs: one place that knows where the actors are
check-creatures collects every spec in the repo, including the rule that a scenario cast is any exported array whose entries carry characteristics — so a new scenario is covered the day it is written. That rule is a definition, and two more tools are about to need it. Lifted into tools/all-specs.mjs and imported back. check-creatures reports the same 76 specs across the same 8 sources; this is a move, not a change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
255b146753 |
Species anatomy moves where node can read it
RINGBRP.locationTables and RINGBRP.bodyArt lived inside ringbrp.mjs, which no tool can import — it touches game, Hooks and foundry at load. So nothing outside Foundry could ask how many hit locations a Cadence has, or what shape a Vesh is, which is exactly what art generated from a statblock needs to know. Moved byte for byte to anatomy.mjs and assigned back, the same move rules.mjs got when the simulator needed the combat rules. One definition, two readers. check-rules matched those tables with a regex against ringbrp.mjs and went blind the moment they moved — it reported "could not read locationTables" and the species check silently stopped running. It imports the value now. A guard that parses source text breaks when the source is refactored; one that imports it cannot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0633589061 |
Rebuild packs for the posting talent pools
Roster agents carry no talents of their own; they expand from their posting at build time, so changing the pools left the compendia a version behind the register. Rebuilt with Foundry stopped. Verified by reading the built pack back: 36 packed actors, none carrying Unblinking, Distributed or Quorum. No lost/ directories, so the packs took the write without needing repair. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b0981008b3 |
No posting may grant a species
Eighteen postings offered vesh or cadence biology to any agent who took them: unblinking nine times, quorum seven, distributed three. That is the root cause behind the six characters check-creatures caught — they were not rogue, they had taken talents their own postings advertised, and every newly generated agent would have done the same. Unblinking is the clearest case. It is cat: species, species: vesh, and its own rule text reads "A Vesh has no face to read and does not need to read yours". Nine postings offered it, including Interview, Pathology and the doctor trade. Each of the nineteen slots is replaced with a talent drawn from what the posting actually does rather than from what was nearest alphabetically: Registry gets Requisition, the Anchor gets Echo Sense, Disposal gets Clean Scene, Signals gets Chain of Custody and Dead Reckoning. No pool gained a duplicate, and no talent is left unused — every legal talent is now offered by at least two postings. The guard lives in check-kits, which is the file that validates postings, and it imports TALENT_SPECIES from creature-schema.mjs so there is one definition of which talents are locked rather than two. check-creatures was looking at characters; nothing was looking at the pools they were picked from. Verified by reintroducing unblinking on the doctor trade and watching it fail, not merely by watching it pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
85c1062106 |
Pause overlay: tighten the caption, lift the field grid
Both found by looking at the thing render, which nothing had done before — the earlier work on this overlay was verified through the CSSOM because the browser pane would not composite frames, so the geometry and timing were confirmed and the LOOK was not. Michroma is already a wide face, and .4em on top of it set GAME PAUSED at 176px against a 190px field: 93%, running bracket to bracket, so the readout was competing with the lock for the same width. .22em measures 154px, 81%, leaving 18px clear at each bracket. text-indent moves with it so the word stays optically centred. The field grid was drawn at opacity .3, which composited over the void band is 1.44:1 against its own ground. On a dark table map that is not a faint grid, it is no grid at all. .45 measures 1.80:1 — present as texture, still far below the 3.1:1 --rb-line is chosen for, so the amber lock stays the only thing asking for attention. Both measured in a browser with Michroma confirmed loaded, not estimated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8d7508077c |
Rebuild packs, and correct the guard count in the README
The talent fixes changed source without the compendia following, so the packs said Braithwaite had Unblinking while roster.mjs said Clean Scene. Rebuilt with Foundry stopped. Verified in the built pack: no agent carries a vesh talent, and Ashcroft keeps Out of Step beside Borrowed Hour and Not Quite Here, which is now legal because it is anomalous rather than species. The README's guard box regenerated with the new totals, and its prose said "five guards" in three places when there are seven. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4ded53f9b5 |
Fix: npm run build has never worked on Windows
Both spawners built their argument with a file URL's .pathname, which on Windows is "/C:/Users/...". Node resolves that against the drive root, so the build spawned "C:\C:\Users\...\check-rules.mjs" and aborted on a module it could not find. update-readme.mjs had the same bug and swallowed it in a bare catch, so instead of failing it silently wrote a README whose guard box was empty — "0 guard lines" where there should be four. A Windows build therefore could not happen, and if it had, it would have quietly deleted the guard output from the README. Same root as the packs/ CRLF bug: this repository has only ever been built on macOS and Linux, where .pathname happens to be a usable path. Also adds check-creatures to the pre-build guards, which it was missing, so the build and npm run check now refuse the same things. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2c547efe72 |
simulate: a lethality harness that uses the real rules
THROUGH TRAIN publishes "MEASURED, 400 runs each" and a 24% wipe rate; the starter publishes its own table. Nothing in this repository could produce either number, so both were claims rather than results and neither could be re-checked after a rule moved. The harness knows no rules of its own. Everything comes out of rules.mjs, because a harness with its own copy of the combat loop measures a game nobody is playing and drifts silently the first time a rule changes. Two things had to move for that to be possible: applyDifficulty, resolveBands and gradeRoll -> rules.mjs. The whole of d100 resolution lived in ringbrp.mjs, which node cannot import. All three are pure, so they moved and the engine re-exports them; no caller changes. check-rules then refused them for having no spot-check, so they now have fifteen, covering the 1% floor, the 96-99 rule and 00-always-fumbles. They had none in their entire life inside the engine. expandFromRegister -> tools/expand-spec.mjs. A roster agent is written as a job and a rank and carries no skills or kit; everything it can do is derived at build time by a function locked inside build-packs.mjs, which cannot be imported because importing it runs the build. A simulated agent expanded by a second copy of that logic is not the agent that gets packed. Moved verbatim; both sides import one copy. Deterministic throughout: same creature, party and seed, same numbers, which is what lets a published table be verified rather than remembered. What it finds: for the Act Three standoff the seed barely matters (0.5-2.8% wipe across five seeds, eight rounds throughout) and party composition dominates everything. Four support agents against the six miners wipe 92.8% of the time; swap in the heavy and the warden and it is 0.3%. The published 24% is not a property of the encounter, and no single figure can be. Models Reaction re-rolled each round, banding, graded defences, the stacking defence penalty, damage by band, major wounds and the dying clock. Does NOT yet model hit locations, bleeding, panic, cover, range bands or fire modes, all of which make a losing fight worse. Read the output as a floor, never a ceiling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c3002e93c9 |
check-creatures: validate every actor spec, and fix the vesh talents it found
Nothing validated creature specs. check-kits validates postings and check-scenarios validates prose; buildActor throws on an unknown skill or item key and is silent about everything else, so species, marksmanship style, characteristic blocks, species-locked talents and misspelt field names could all be wrong and still build, pack and play. Every packed actor once shipped as species "hominid" and the whole cast was hit-located as something the rules do not contain. tools/creature-schema.mjs derives every allowed value from the catalogues rather than retyping them: skills from SKILL_CATALOGUE, kit from the item catalogues, species from SPECIES, stats from CHARACTERISTIC_DICE, and the two marksmanship styles out of the language file. It reports every problem with a spec rather than dying on the first, and names the nearest real key. It also reports fields buildActor never reads, because "armours:" is not an error, it is an actor that silently equips nothing. On its first run it found six baseline humans carrying vesh biology. "unblinking" is cat: species, species: vesh, and its own rule text reads "A Vesh has no face to read". Out of Step is reclassified as anomalous with a Coherence cost, which is what every talent in that category has and what the Anchor who is three years older than his birthday says always wanted to be; the other five are swapped to agency and field talents that fit them. Not fixed here: the root cause is upstream in postings.mjs, where twelve posting talent pools still offer unblinking, distributed or quorum to any agent. Picking replacements across those is a design decision, not a mechanical one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
10f588b666 |
Plan: the creature forge
Five parts - schema and validator, lethality harness, forge CLI, procedural portraits, Foundry importer - built on three findings: the species machinery (vesh, cadence) is fully implemented and completely unused, the published measured lethality tables cannot be reproduced by anything in the repo, and RINGBRP.figures already holds the per-species geometry procedural portraits would need. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c6924d6ea8 |
THROUGH TRAIN: art prompt sheet, and a runner for it
Prompts for all 43 assets - 16 duty-roster portraits, 14 NPC portraits from TT_CAST, and the 13 scene plates, none of which had a recorded prompt, so none of them could be regenerated. Art direction is frontier newspaper engraving, and the present-day Bureau material is drawn in the same 1881 hand as Cordera with modern people composed as tintype portraits, so the two halves read as one document. Three rules are written into the sheet because they protect meaning rather than style: nothing modern is drawn on the 1881 side, the train is absence rather than a glowing spectre, and nobody is lit as a villain. tools/gen-art.mjs parses the sheet rather than keeping a second copy of the prompts, expands the style tokens and drives mj-gen. Dry run by default, because --run fires paid generations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b31b1028a9 |
Rebuild the pause overlay as a containment hold
The uniselector rotated forever, which is the one thing a hunting stepping switch never does, and it was carrying two outright bugs: the motion trail pointed the way the arm was going rather than the way it came, because conic angles run clockwise from the origin and the tip was declared first; and the hub pip ran on a free 2.6s sine against a 7.7s arm, so the light drifted permanently out of phase with the mechanism and meant nothing. Replaced with an instrument that acquires, locks, reports and lets go: a static field, four corner brackets that close and snap, a scan line reading down the field at exactly cycle/3 so its third pass always lands on the lock, and a readout welded to the same cycle. Every layer reads --rb-pause-cycle, so nothing can drift. The field offset is derived from the column rather than measured and pasted in. Also ignores the impeccable hook cache and keeps its config, which records why broken-image is suppressed for a stylesheet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2332b449ba |
Refresh package-lock
The committed lockfile predated the handlebars devDependency and carried the wrong name and licence metadata. Regenerated by npm install; no dependency actually changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f0edd54e83 |
Mark packs/ binary so Windows checkouts stop corrupting the compendia
core.autocrlf=true rewrites packs/*/CURRENT from "MANIFEST-000002\n" to "MANIFEST-000002\r\n" on checkout. LevelDB then tries to open a manifest whose filename ends in a carriage return, fails with "The filename, directory name, or volume label syntax is incorrect", and Foundry runs RepairDB across all eighteen packs on first launch. The packs are therefore broken from the moment they are cloned, and the only visible symptom is a pile of lost/ directories in git status. Linux and macOS checkouts are unaffected, which is why this went unnoticed. The committed blobs are already LF, so no renormalisation is needed: this only affects the smudge side. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
214e207cc6 |
THROUGH TRAIN: north bench plate without the park bench
Midjourney read "bench" as furniture in the first version. Regenerated with the word removed and "no furniture" stated: two coated figures from behind, empty ground, a dry gully, the town's water tank on the far horizon. Emptiness is the subject, which is what the scene is for - they arrived where the paperwork says the railway is. All thirteen plates are real art and there are no placeholders left. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8a4d093a13 |
THROUGH TRAIN: the Bureau plate, and no placeholders left
tt_11_bureau regenerated after the first attempt timed out, converted to the house plate format. All thirteen plates are now real art. A version of tt_12_northbench without the word "bench" in the prompt is still rendering; the current one is good apart from the park bench Midjourney put in the desert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
67b6f61c64 |
THROUGH TRAIN: two of the three new plates are real art now
tt_12_northbench and tt_13_standoff replace the placeholder copies. Converted to the house plate format, 1024x682 webp, to match the other ten. The Bureau plate timed out in Midjourney at 1800s waiting for a grid and is still a placeholder; it is regenerating. Known flaw in tt_12_northbench: Midjourney read "bench" as furniture and put a park bench in the Nevada scrub. Everything else about the plate is right - two coated figures, empty ground, no railway, the town smoking on the horizon - so it ships while a version without the word regenerates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
973b4a53aa |
THROUGH TRAIN rewritten into five acts
Act One is now an induction at the Bureau, Act Two puts the players through the crossing and makes them find the train, Act Three is a town-side obstacle, Act Four is the boarding, Act Five is the four minutes. 4h00 in five acts, was 3h30 in three. The restructure earns one thing the old shape could not: THE CROSSING OPENS WHERE THE RECORD SAYS THE LINE IS, which is an empty bench of scrub on the wrong side of the wash. The agents arrive where the paperwork put the railway, not where the railway is, so the fraud is the ground under their feet and finding the train is real work. Registry's file has this wrong and Bewley believes the file. - Act Three: Rance Doyle and five unpaid miners take the agents for company detectives, and they are not wrong to. Resolves on what the players SAY - no roll makes it go away, no roll makes it a fight. Losing costs them the 1881 issue, not their lives. His brother Tom is aboard and dies at minute two of every pass, which is the wiring between the acts - new cast: Bewley, Prosser, Rance Doyle, Shard, Tom Doyle, Cordera man - new handouts: the company notice, and the six names in Ambrose's hand - 8 GM pages, 10 handouts, 15 actors, 13 plates MEASURED, 400 runs each: the Act Three standoff is 0.4/4 hurt from cover and 1.25/4 in the open, both ending early - but FOUGHT TO A FINISH it puts 1.75 of 4 down and wipes the party in 24% of runs. The early stop is what makes the act survivable, not the miners being weak. My first draft of that table was guessed and wrong in all three rows. FOUND AND FIXED: there is no Ride skill in this game and never has been. The boarding set-piece has asked for "[CUS: Ride, then Athletics]" since it was written, through a playtest, which is an instruction to roll a thing nobody has. Horses are now handled by a local and the agent rolls Athletics. NEW GUARD: check-scenarios resolves every [CUS: Skill] tag in every scenario against SKILL_CATALOGUE and fails the build on a skill that does not exist. Verified it fails on the real bug before wiring it in. Sixth guard. NOTE: the three new plates (Bureau, north bench, standoff) are placeholders in this commit - Midjourney is still rendering them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
24b1aaa682 |
Development: skills improve from what beat you, not what worked
Reversed experience. A skill is marked when it FAILS - failure or fumble - and never when it works, and the improvement roll is the same reversal: roll OVER your rating and the skill goes up 1d6. Nobody learns anything from the lock that opened first time. The two halves pull against each other on purpose, so ratings converge instead of running away. Simulated over 2000 campaigns: a 10% and an 85% skill start 75 points apart and are 11 apart after 60 cases. - rules.mjs owns it: DEVELOPMENT, developmentMarksOn, developmentImproves, developmentCeiling, developmentGainFor - trainingCap has been on the skill schema and the item sheet since it was written and nothing has ever read it. Development reads it now: a skill stops at its own cap, and everything else stops at DEVELOPMENT.ceiling, because a d6 gain could otherwise push a 98% skill past 100 - GM opens the phase from a GM-only toggle in the token controls; it posts a card naming every agent and their marked count, and puts a Development button on each sheet. Players press their own - the toggle re-reads the setting on change, since getSceneControlButtons only fires at load and the button would otherwise lie if the phase were opened by the API or a second GM - rules journal and player primer rewritten from the tables; the old text described the pre-reversal rule on both halves - 7 spot-checks, 4 behaviour tests; verified end to end in a running Foundry Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
99e78f440a |
"Somebody you knew" costs one point, not two
At two, the closing beat of a hard case broke four agents in five at the same moment. A whole party freezing or bolting on the final scene reads as arithmetic rather than grief, which is the opposite of what the trigger is for. Found by playing THROUGH TRAIN end to end. Replayed the same beat from the same party state — Holloway 3, Okonkwo 2, Bhattacharya 4, Nkemdirim 3, Finch 2 — with the new cost: one agent breaks, the rest carry it at 3 and 4 with the penalties that implies. That is the shape it wanted. "It said your name" stays at two, and is now the only two-point trigger in the list, which is right — it should be the singular worst thing that can happen to somebody. The rules journal and the player primer both generate their trigger tables from PANIC_TRIGGERS, so both already print the new number. Version 1.4.2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c05d488b9d |
Play THROUGH TRAIN end to end; fix three scenes the dice could not reach
Ran the whole case with five agents — briefing, kit swap, Mercy Draw, the
crossing, Cordera, the water tower, the four minutes, the locomotive, the
brakes, the register. It completed, and it exposed one serious defect and two
smaller ones. All three are the same shape: the scenario put dice in front of
things the fiction says should simply happen.
1. THE ENDING WAS UNREACHABLE. Convincing Kell to stop was gated on Persuade
or Status. Only THREE of the ten duty-roster agents have either trained —
the Field Lead has Persuade 15 and Status 15. Three separate attempts on
him all failed and the scenario had no way to end; I narrated it as working
because otherwise there was no ending, which is exactly the improvisation a
scenario is supposed to prevent.
It now takes NO ROLL if the party can tell Kell one true thing about his own
train — the coal, the four minutes, the north bench. He has waited a
hundred and forty-five years for somebody who knows what is wrong to say
so. Only a party that knows nothing rolls, at average, and a failure costs
a pass rather than the ending.
2. THE PAPERS NEEDED PERSUADE. Showing one side the other's document is the
move the scenario is proudest of, and it sat behind Persuade at −30%:
15% became 1%. Both men WANT their paper read — Merritt is proud of his
letter, Ambrose's whole grievance is that nobody has looked at his writ.
No roll, between passes.
3. THE BOARDING SORTED ITSELF BADLY. Athletics runs 25–60% across the roster.
Putting all five on the water tower left the doctor and the Registry officer
missing twice each, carried across on the department's plank, passengers in
their own set-piece, two passes gone. The text now names which route suits
whom before they choose.
What worked, and is worth recording: the automatic clues absorbed two failed
rolls in Act One without the trail stalling; Standing Order 9 caught Okonkwo
checking a wristwatch in an 1881 street and cost a Coherence and a clock tick;
the fumble-is-a-fall rule fired on a 100 at the tower for five damage; the
exposure table held — one hit across the whole fight, on the agent who left
cover to work on Doyle; and the reset landed exactly as intended, with Doyle
standing up at the top of the car and Nkemdirim's hit staying where it was.
Version 1.4.1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
391c6a3679 |
Rebuild packs cleanly
LevelDB LOG timestamps only — no content change. Committed so the tree matches a build made with Foundry stopped, which is the state the published archive should carry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5bc46879c7 |
THROUGH TRAIN: put the gunfight inside the loop
The loop is no longer just a train going round. It is FOUR MINUTES with a
gunfight in it, and those four minutes have run about two million times.
Two sides, and neither is the villain. Cass Merritt and three hired men have a
signed letter of engagement from the company telling them to surrender the
payroll to nobody "upon any pretext whatsoever, including the presentation of
any paper". Josiah Ambrose and four Cordera miners have a sealed writ of
attachment from a territorial judge naming that same payroll against eleven
weeks of unpaid wages. Both documents are genuine. Neither man has seen the
other's. The company sold the same money twice and is not aboard. Both are
handouts now, and they only mean anything held in the same hand.
The mission is restated everywhere it appears — briefing page, case brief, GM
essentials: CLOSE IT. Closing the crossing does not kill anybody, it lets them
go. The loop ends, 3 November 1881 resumes from 11:44, and everyone aboard walks
forward into the life they would have had — including Ambrose and a miner named
Doyle, who did not survive the real four minutes. The agents are not choosing
who lives; they are choosing to stop holding history still, having met the
people it lands on.
The standing order is louder in three places: NOTHING MODERN GOES THROUGH.
Two things the playtest found, both now written into the text:
- THE FIGHT WAS A TPK GENERATOR. Modelled with all nine shooters firing at the
party it wiped them 171 times in 200. The model was wrong — the two sides
are shooting at EACH OTHER — so the scenario now carries a measured exposure
table: head down draws nothing, acting from cover costs half a party member
and no wipes over 300 runs, moving in the open costs one, and going for the
payroll box wipes more than half of all parties. The GM is told to say that
last row out loud BEFORE anybody commits to it.
- THE LOOP RESETS AND THE AGENTS DO NOT. Everyone who belongs to 1881 is back
up unhurt on the next pass. The agents keep their wounds. Nothing said so,
and a player who assumes otherwise takes risks that kill them. It is now
stated in capitals in both documents, with the played pass that prompted it
— two of four down at 0 hit points, one untouched because he stayed on the
floor.
Nine cast members, eight handouts, ten plates.
Version 1.4.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
c595bf5a33 |
Tidy the compendium, and release 1.0.0
Three things were untidy, and one of them was a stale declaration of exactly the
kind this project keeps producing.
- packFolders named ten packs of seventeen. Talents, the Quiet Arts, the
primer, the rules and all three scenarios were sitting loose in the sidebar
because the declaration was written when there were ten packs and never
revisited. It now covers all seventeen in four subfolders under one parent
— Scenarios, Characters, Equipment, Rules & reference — and the build
asserts the folder list and the pack list are the same set, so it cannot
drift again.
- Labels were off-pattern in three places ("Transport", and the two prologues
reading "Custodians: Prologue — X"). Everything is "The Custodians · X".
- Thirty-six pregens in one flat list. The pack now carries three compendium
folders — Duty roster (16), Before the department (16), Original pregens
(4) — and every actor is filed. Nothing unfiled.
Worth knowing, and verified against Foundry's own source rather than guessed:
packFolders only assigns a pack that has NO existing entry in the world's
core.compendiumConfiguration, and the routine only runs when the set of active
packs changes. So a world that already has this system installed keeps whatever
layout it had; the new one applies to a new world. The local world was brought
into line by writing the configuration directly.
Version 1.0.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
1bf8d7f6fb |
Playtest LAST ADMISSION: five fixes, one new guard
Played it through in Foundry with the six on their civilian sheets. Five
defects, all of the house's favourite kind — a number written down that nothing
reads.
1. Pollard and Agyeman were armed and armoured as CIVILIANS. The police trade
kit carries a revolver and a stab vest, which is right for a serving
officer and wrong for a British civilian, and it ended the scenario's only
fight in a single round. expandFromRegister's civilian mode now strips
firearms and body armour from trade kit as well as induction and standing
issue, and the build throws if one gets through. Wren Finch keeps her
knife — a knife is not an issued weapon.
2. Old Marrow's shears were a utility knife. The page said 1d8+1; the actor
in the pack held 1d4+2, so a GM running off the token had a different
fight from the one written down. He now carries a billhook — an actual
garden tool that is already in the catalogue — and the page says 1d8+2,
which is what a billhook does.
3. He was too soft to be the fight the scenario promises. At the original
numbers he died in an average of 2.4 rounds and in 6 runs of 20 he was
dead before he ever swung. Retuned over thirty runs to HP 20, armour 2:
about four and a half rounds, somebody hurt in 28 of 30, somebody dropped
in 9 of 30. The verification run took four rounds and left Pollard on
eight hit points.
4. Two stat lines disagreed with the generated sheets — Marrow 16 against 15,
Enderby 13 against 12.
5. Two rolls had no failure written: the body, and Sammy's ankle pull. The
body is now marked "no roll" outright, because there is no better answer
to buy. The pull has a fumble result.
New guard: check-behaviour parses the scenario's own fight line out of the page
HTML and compares it to the actor the build will generate — hit points against
hitPoints(CON,SIZ), armour against naturalArmour, and shear damage against the
catalogue entry for whatever he is actually holding. Verified it bites by
reverting the damage to 1d8+1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
d063b3c016 |
Add LAST ADMISSION, a two-hour haunted-house prologue
OPEN DAY was too complex for a first night: a fixed timetable the GM narrates minute by minute, three things that must be left unresolved, ten serial approach scenes, and no combat. This is the replacement opener — same premise engine, a third of the moving parts, and something to hit. A cheap scare attraction on the last night of its six-week run, in a real empty hall, with paying public still inside. Three of the ghosts on the poster were invented in an afternoon by a marketing agency, printed on the tickets and the wall panel and the call sheets, and performed forty times a night to twelve thousand people who agreed to believe them for eighteen pounds each. They are in the building now. They are not ghosts, they are entries, and they have exactly as much inside them as was written down — a page each, which they do, and nothing else. That makes them simple to run and simple to stop: hurt them, or break the script. Both work; the script is faster and cheaper, and a table that finds it should feel clever. The fourth name on the panel was copied off a memorial in the parish church because the layout needed four. Edith Crane was real, died in a real fire here in 1963, and nobody wrote her a page because nobody had to. She never attacks, cannot be hurt, and will not speak until she is asked her name. She wants her name off a poster. The night ends on whether it comes down tonight or on Monday — and Monday is two performances away. Written for the second intake specifically, on their civilian sheets, with each one's job as their way in: Renshaw is IN the show and knows the running order, Ferriby wired it, Sharrock built the sets, Pollard is on the door, Braithwaite and Ashcroft paid to get in. Each shortcuts a different part of the middle and none of them is required. Art: eight plates commissioned, seven kept. The wall panel plate was cut after two attempts both produced three frames instead of four — a picture that contradicts a count the scenario turns on is worse than no picture, so the panel is a handout the GM shows rather than describes. The freed green-room plate went onto the Act One page, where the call sheet is. OPEN DAY stays in the repo as the longer, quieter alternative; both scenarios say plainly which is which and that nothing depends on the choice. Verified in Foundry: the adventure imports, six cast, seven GM pages, five handouts, plates load. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f576c7977d |
Add a second intake: six more agents, with portraits
Written for the group that actually plays. Three postings and three trades
again, and none of the six repeats a posting or a trade already on the sheet,
so the roster now covers sixteen different jobs and no two players can pick the
same character by accident.
Gareth Pollard Night Warden Field Officer
Dominic Ashcroft Anchor Officer Senior Field Officer
Neil Braithwaite Pathology Officer Field Officer
Kate Renshaw Interview Officer Senior Field Officer
Danny Sharrock Construction Worker Probationary
Colin Ferriby IT Specialist Probationary
Each also gets a before-the-department entry, because without one they cannot
play OPEN DAY — the prologue builds its civilians off the BEFORE table, and the
build throws on a roster agent that has no entry in it. Two of the six were
already trades, so their civilian self is the same job with none of the kit.
Two guards had to be corrected rather than satisfied, both for the same reason:
they had encoded an accident of the roster being exactly ten.
- "the ten cover all ten trades exactly once" cannot survive a sixteenth
agent. The property that actually matters is that no trade is missing, so
that is what it asserts now.
- "the roster should be half trades" would mean picking characters to satisfy
a ratio. It now asserts both halves are present and neither dwarfs the
other, and separately that no job appears twice — which is the check with
real teeth, and it passes.
The portrait pass caught an arithmetic slip of mine: Sharrock came back looking
early forties against a sheet that said 35, and his biography says twenty years
on sites, which would have him starting at fifteen. He is 42.
Verified in Foundry: 36 pregens in the pack, all twelve import, sheets render
with their portraits.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f185c92088 |
Add OPEN DAY, the recruitment prologue
Ten civilians, one decommissioned telephone exchange, and eleven minutes during
which the building went back to being on the street it was built on. Nothing
attacks anybody. Nothing wants anything. That is the horror, and it is the whole
design.
The premise was already in the pregens. Wren Finch's biography said she was
inside a building "on the night it stopped being on the street it was on", and
Soraya Rahimi's said she talked a caller down on air "for eleven minutes, and
the recording does not play back the same way twice". Those are two views of one
afternoon, so the scenario is built at the intersection and the other eight
"How they found her/him" lines are the same afternoon from eight more jobs.
Nothing in the prologue contradicts a sheet, because the sheets came first.
The reconciliation pass, which is most of the work:
- Finch's line said "night". The open day is an afternoon. One word changed on
the pregen, and both are true. Her line also says she was the only witness
who could describe the inside, so she is the one who goes into number 11 —
written into the location note rather than left to chance.
- Rahimi's line says she was ON AIR. So the disconnected telephone that rings
at 15:45 is patched through to her outside broadcast, which makes the canon
line and the scene the same event instead of two coincidences.
- Okonkwo carries two men out; Nkemdirim's patient has a forearm that healed
crooked over a break he took six minutes ago. Both seams needed an injury in
Act Two and there was none, so 15:50 and 15:51 now have one.
- Sandoval's forty minutes of traffic sit inside an eleven-minute window. That
is the anomaly, so it is on the handout in those words.
- "Prine" collided with an NPC already in the world. The engineer is Kearsley.
Structure: 2h15 against the house 3h30, stated in the design intent, because a
prologue exists to make the first real case land. No combat, no antagonist, no
Coherence — the last one is thematic, not a simplification: Coherence is what it
costs to keep looking once you know what you are looking at, and these ten do
not know yet. The Briefing is the first time the word is said aloud.
Ships as docs/scenarios/OPEN_DAY.md (the full GM document) and a Foundry
Adventure in a new `prologue` pack: 8 GM pages, 7 handouts, 5 NPCs, and ten
generated plates. The PCs are the "(before)" pregens already in the Pregens
pack — a second copy here would give a GM two sets of the same ten people and
no way to know which one their notes were on.
Verified in Foundry: the adventure imports, both journals render branded, the
countdown table reads as a table, and all ten plates load.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
26ad00174f |
Add the same ten before the department found them
Ten civilian counterparts, one per roster agent: same person, same
characteristics, same face, no induction and nothing ever issued to them. A
player can hold the two sheets side by side and read exactly what the job did.
Keyed off ROSTER rather than written out again, so the pair cannot drift apart.
The five who joined straight into a posting get the trade they came out of, and
between them the ten now cover all ten trades exactly once — which makes this
set a demonstration of the whole trade half of character creation as well as a
set of characters.
expandFromRegister grows a `civilian` mode: no induction skills, no standing
issue, no tier issue, no trade cant, and nothing anomalous or Coherence-costing.
Three things the build guard caught, each of which would have shipped:
- Holloway was "carrying issued kit: notebook". Wrong: an interpreter owned a
notebook long before anyone issued her one. The rule is now kit that could
ONLY have come from the department.
- Same again for skills — the linguist's core list contains trade cant,
because it is a language and she is a linguist.
- Wren Finch was carrying the black mirror, which is era "anomalous", from a
transposed tradition, and costs Coherence to look into twice. That is
something you acquire after the night the department notices you, not
before. It is the only anomalous item in any trade kit.
The guard runs over the built objects rather than trusting the flag that made
them, because the flag is three call-frames away from the result.
Verified in Foundry: 24 pregens, the before sheets show the civilian job in
place of rank, no armour, nothing readied but fists, and the right face.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
4101221eb1 |
Fill the empty space in the actor sheet header
The header was one flex row — identity, then vitals pinned to the top right. The identity column is tall (name, rank, marksmanship, the two style hints, two age fields) and the vitals row is short, so everything under the vitals was empty blue: roughly a third of the header. Split it into two columns and stack the status column: vitals, then the derived numbers, then the readied weapons. Both of those already existed as full-width bands directly below, so nothing new is on screen — it is the same content arranged to use the space, and it puts damage modifier and readied weapons next to hit points, which is where they are read during a fight. Two sizing traps, both found by measuring rather than looking: The status column is an auto track, so its width came from max-content — the readied-weapons line, whose length is whatever the weapon names happen to be. That crushed identity into a 190px gutter with the name truncated to "Marta Hollow". width:0 + min-width:100% keeps those panels out of the measurement so the column is sized by the vitals and they stretch to match. The vitals row is wide enough on its own to do the same thing, so the columns are proportional (1fr / 1.6fr) and the vitals now wrap. Measured at 1020px: header 367px tall before, 266px after, columns 241 and 235 so they end level. At 1400px: 214px tall, columns 189 and 158, vitals unwrapped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c15b13c3b9 |
Add the duty roster: ten ready-to-play agents with generated portraits
Five postings and five trades, so a group choosing from this sheet meets both halves of character creation without anyone having to explain them. Skills and kit are not written out per agent. They are expanded from the register at build time by the same helper the starter scenario uses, which is hoisted out of the BLACK PLATFORM block for the purpose — two copies of that expansion is two places for the induction rule to drift. Ratings sit at the middle of each band rather than being rolled, because a pregen that varies between installs is not a pregen. Portraits are generated art, resized to 512 and stored as webp (12MB of PNG became 210KB). buildActor now takes a portrait and gives the same face to the prototype token: a pregen you can recognise on the map is worth more at a convention table than a correct one you cannot tell apart. It throws on a portrait that is not on disk, because a broken image on a pregen sheet is the sort of thing that is first noticed on a projector. Guard: check-behaviour gains a test that every roster agent names a real posting or trade and has its portrait on disk, verified to bite. It caught "archive", which is not a posting — Bhattacharya is Registry. Verified in a running Foundry: 14 pregens in the pack, sheets render with the portraits, coherence and hit points fill from the template, no system errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7f8355f0d0 |
Add the player primer and cheat sheet, and brand every journal
Ten pages a player reads before session one, in their own compendium at OBSERVER ownership: nine of setting, one cheat sheet. The draft called the organisation "the Bureau"; everything else in the game calls it the department or the agency, so that is what it is called. Thin areas filled rather than padded: the seven character principles as a list, the real Coherence band table instead of a paragraph describing one, and "what continuity is not" pulled out of the middle of a page because it is the most important sentence for a new player. The cheat sheet's numbers are computed from rules.mjs and lang/en.json at build time. A hand-typed one would have been the sixth time this project shipped a number that disagreed with the code, and the one the players were holding. R-250: the design tokens were never on :root. Journals rendered as default parchment, and four things were wrong in sequence — v14's hook is renderJournalEntrySheet; its `element` is the node, so `[0]` returns the first CHILD and put the class on a header button; there is no .journal-page-content in v14; and then the real one: with the selector matching, font-size, line-height and padding all applied while every colour did not, because the palette is declared on .ringbrp-sheet/.ringbrp-chat/.ringbrp-window and never on :root. A journal inherited no tokens, so every var() silently became nothing. A rule that HALF applies is not a specificity problem — I spent three attempts treating it as one, including a version bump on a cache theory that was wrong. Branded: primer, rulebook, starter GM text and handouts. Scoped by flag, so other modules' journals are untouched. Version 0.9.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d9bce12c27 |
Replace the starter with BLACK PLATFORM, and make the dialogs ask questions
The starter is now an action case: a train running through a station whose rails were lifted in 1976, two engineers aboard, eleven minutes until it returns. Descend, take the platform, board, rescue, fight forward along a moving train, stop it. Simpler than PAPER WINDOW on purpose — one clue scene, no social subsystem — which also means it cannot reproduce R-248, because there are no groups or standings to leave unwired. Every agent can fight, deliberately: floors of firearm 42%, melee 40%, Brawl 40%, Dodge 40% so nobody is a spectator on their first evening. Floors only RAISE, so a posting already better stays better — Callum's shield lands at 53, not the floor's 45. The builder now handles combat floors, extra weapons issued for a case, and a scenario with NO Borrowed Authority at all; the last was assumed to exist and would have thrown. Two new tests: every floor names a real skill, and every agent clears the fighting floors the handout promises. Sixty-two dialog strings reworded. They were naming mechanics at people: "Situational modifier %" is now "Anything else helping or hurting? (%)", "Target cover" is "Is the target behind anything?", "Difficult (÷2)" is "Difficult — half your skill". Labels ask the question; hints say what it does to the number; notifications say what to do next rather than what went wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4e28de2a64 |
Play PAPER WINDOW with the shipped pregens; fix four silent defects
Ran the starter end to end. It closed, and it found four defects in what I shipped an hour earlier — all in the scenario data, all silent. R-247: the Borrowed Authority did not work. I wrote scope "operate", which is not one of the five scope tiers; an unrecognised scope ranks 0 and every action came back "impossible" with no error. The first scene of the starter is the Borrowed Authority tutorial. Fixed, guarded in check-kits, and covered by a test that documents the failure mode. R-248: the contact subsystem was unreachable in the scenario built around it. The case file carried a standing for the Returned Clerks and no GROUP ITEM; resolveContact returns null without one, so READ, SIGNAL and OFFER did nothing. Groups are data now, and a test asserts every standing on a shipped case file has a group behind it. R-249: legs wrote `kindId` where resolveLegDialog reads `leg.kind`. It worked only because the terrain text happens to begin with "Extraction". Not a defect: phase four went Difficult because Resources hit 0 and destitution makes every remaining phase Difficult. Best beat in the session. All four phases failed and the case still closed — Containment 8 to 2, Resources 8 to 0 — which is what the scenario is built to survive. Two of six closing requirements succeeded and the settlement still completed. The Cancellation Man opened with a critical against a raised riot shield and went through 10 points of armour, teaching the shield and graded-defence lessons in one roll. Also documented: nobody on the starter team can aim the return, because only the Anchor Officer trains Transposition. The extraction page now says to narrate it rather than roll six dice that will all fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
49c2e9fc7b |
Add PAPER WINDOW as the starter scenario Adventure
The six-player introductory case ships as a Foundry Adventure in a new `starter` pack, because a scenario that arrives as nine loose documents a GM has to assemble is a scenario that gets read rather than run. One import brings in: the GM scenario in twelve pages; the seven player handouts as their own journal at OBSERVER ownership so they can be revealed one at a time without the GM editing permissions first, while the GM text stays hidden; the six pregens built through the same buildActor path the shipped pregens use, so they arrive with full skill lists, kit, hit points and linked tokens; the cast, including the Cancellation Man; the Borrowed Authority as a real credential with scope, Trust 2 and its three red flags; and the case file already at Containment 8 / Resources 8 / Clearance 6 with its four phases loaded at the stated difficulties and modifiers. Pregen ratings are fixed at mid-band rather than rolled — a pregen that varies between installs is not a pregen. The expansion from posting or trade into a sheet reuses the register and the induction rule rather than restating them, so the starter cannot drift from the generator. Verified by importing it: no errors, the roster resolves through stable keys, handouts arrive player-visible, Nia has the team's highest Tradecraft as the text requires, Callum's shield is slung so raising it is still a decision, the three probationers show the trade/induction split, and phase one runs out of the box. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
43508259ac |
Consolidation pass after a Codex review
Four real defects, each verified against the source before acting on it. Trade expertise was coupled to agency rank. A probationary burglar came out at 35-50% and a veteran at 65-80%, as though fifteen years of picking locks were something the department conferred. TRADE_BANDS is now fixed at 50-65/30-50 at every rank, and INDUCTION scales with service instead (20-32 probationary to 45-60 veteran) because that half genuinely is the department's. The two axes were the wrong way round. This is the thing I flagged myself after PAPER HARBOUR and then left alone — flagging a defect is not the same as fixing it. Bonus points were sprayed across every skill in the game. grantFullSkillList runs immediately before the loop and puts all fifty-nine skills on the sheet, and the loop picked from actor.items, so a veteran's ninety points landed anywhere. The comment two hundred lines above has always said "over the core skills"; the code never did. "Random species" passed "baseline" into generators that support random perfectly well. No behavioural tests. The strongest point in the review: the three guards are static and none of them can tell whether a rule is READ, which is the only kind of defect this project has ever shipped. tools/check-behaviour.mjs now runs 28 deterministic tests, gated into the build, covering R-231/232/243/245/246, graded defences and the lamp rule. Proved it bites by re-breaking R-246. The register moved to postings.mjs, a Foundry-neutral module the engine, rulebook, guards and tests all import normally — they were previously pulled out of the engine with a regex and eval, which worked and was a trap. My first version of that used `export … from`, which re-exports without local bindings, so ROLES was undefined and the system threw on init: node --check passes that, loading it in Foundry does not. Also: the contact doc comment still said "only OFFER moves standing"; check-kits accepted mas/app, which are the DISPLAY names of siz/cha, so a posting declaring one would have passed and produced an undefined characteristic; package.json disagreed with the manifest; README counts were three revisions stale and are now generated at build. Deferred with reasons: splitting the 6,700-line engine into modules. Right, and a multi-session refactor whose risk is exactly the silent breakage demonstrated above. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c60485de72 |
Play PAPER HARBOUR with the criminal, and fix R-246
A pier that closed in 1974, a beacon still transmitting, and a probationer recruited out of a city with a caution the department has quietly lost. The generator's gap — somebody uses that agent's name before it is given — landed on him in phase 2, and he held it on Fast Talk 14 against 48 while the Field Lead missed the read. The case turned on a warrant card: the keepers of the ground want relieving by somebody official enough to take the duty on, and every agent carries exactly that. Bargain 27, favourable, rolled 33 — impression -1 to +1. The one unambiguously his moment was the log the first team locked in behind them: 11 against Sleight of Hand 50. R-246: a fumbled greeting cost nothing. contactOutcomes has said fumble/signal -1 since the social pillar was built, and impression was adjusted only inside the `offer` branch — so the -1 was computed on every botched greeting and thrown away. Impression now falls on a fumble at any stage and still only rises on an offer; the narrower fix matters because read's delta counts FACTS LEARNED, not standing, so applying the delta always would have paid the team impression for doing research. Also logged: a probationary criminal is out-sneaked by a career Field Lead (47 to 60) because trade skills use the agency tier bands — defensible, flagged, not changed. And one invented step of my own: I rolled Transposition to get them home after the extraction phase had already put them out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b8430c284b |
Play PAPER MARGIN with a trade character
A farm in Angus, a person in two places, and a probationary doctor recruited from A&E alongside two career officers. The generated case turned out to be its own best argument for trades: the truth is that there is no original left, both are copies, and telling two identical people apart is a medical question — Medicine on that team reads 42, 1 and 1. Both halves of the trade design showed up. She was the only one carrying the department's own vocabulary (Anomaly Lore 37 against 0 and 0, Tradecraft 31 against 5 and 5), and she was hopeless at closing: Persuade 23, rolled 95, read on Insight 60. The examination failed by three and the bloods came back critical on 02 — both samples are the same sample. Neither career officer could have found it. R-226 fired visibly: trained Tradecraft 31 lost the phase lead to trained Navigate 34. The riot shield issued to Containment two commits ago turned up unprompted and took Kobe from 7 everywhere to 10 on torso and arms. Coverage applied to the opposition too — the duplicate died through its bare leg. One probe error recorded, not a defect: endOfCase returned nothing because my ad-hoc rolls passed no skillFamilyId, so nothing ticked — the same omission R-222 found in resolveLegDialog, reproduced by hand. Named properly, Medicine ticked and improved 42 -> 48%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
79acc8227f |
Ten trades, an induction layer, and rolling the stats yourself
TRADES: ten ordinary jobs the department recruits from — construction worker, doctor, professor, engineer, law enforcement, IT specialist, scientist, linguist, entertainer, criminal. They are not postings. A posting is what the department made of you; a trade is what you were before any of it. A trade character is built in two halves. The trade is genuine expertise on the same bands a posting's skills use — a doctor is a proper doctor. INDUCTION goes on top at 25-40%: Tradecraft, Anomaly Lore, First Aid, Firearm (Pistol), Dodge, plus a sidearm, a vest, cordon kit, a cover identity and a ward. The band is low on purpose; six weeks is not a career, and that gap is most of what it feels like to play one. Induction never demotes: a former police officer keeps pistol 58 from the trade rather than being re-taught it at 26. The creation dialog gains a "Recruited as" switch with its own trade list and card (which shows both halves), and check-kits now holds trades and induction to the same standard as postings — both new guard paths verified by breaking them. Rolling them yourself: a tickbox that walks the eight characteristics one at a time, each roll announced to chat so the dice actually fall, "Take them" locked until all eight are down. A hand-rolled character is taken exactly as it fell — no best-of-three on the key stat, no species shift. Verified both ways. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
53eb72c100 |
Issue riot shields to Containment and the Night Warden
Both postings draw a riot shield, and both are trained to use it — issuing one without the skill would have been R-212 again with a different item, the Antiquities Officer who drew a flintlock and rolled 5% with it. melee_weapon:shield is SWAPPED into each support list rather than added, so five core and five support are preserved and the shield arrives without a free power bump. Containment drops stealth (a posting whose job is being the thing in the doorway was always the least stealthy in the department); the Night Warden drops navigate (they sit with one site all night). check-kits now requires the Shield skill for an issued shield, the same way it has always required training for an issued weapon. Verified by removing the skill and watching the build refuse. Four generated agents: shield skill at 25-30%, nobody encumbered, though a Containment officer lands at 32.9 of 33 kg. The shield ships slung — raising it is the choice, and for the Warden it is the difference between 2 points of armour and 5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0c2636d543 |
Play the bell tower stair: shields, and what they cost
Two officers, identical kit — riot shield, stab vest, 12 hp, Shield 65% — and one difference: one holds the shield up, one keeps it slung and shoots. Raised it is most of their armour (torso 5, arms 3 against torso 2, arms 0 with it down). Four ordinary blows turned aside cost the shield nothing, which is the right call: at a point per parry a 5-wear riot shield would have been kindling before the fight started. Round 8 a CRITICAL halberd was parried, dragged down one step, and the shield took 2 wear for absorbing it while the reduced blow still landed on the covered arm for 7. Both officers were killed through the gaps a riot shield does not cover — one through both legs, one through the head for 10 against a location maximum of 5, dead outright rather than dying (R-227 holding). The ranged penalty then decided it. Shield up, the survivor shot at 65% less 20% and rolled 93, 89, 73, 65. That last number is the rule in one figure: 65 hits at 65% and misses at 45%. The redcap was on 3 of 13 and walked away. Nothing needed tuning. Full log in docs/REVIEW_LOG.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c61700566c |
Shields: armour you raise, and the only armour you can defend with
A shield is the one piece of armour you hold, and it protects you exactly as much as you are currently holding it up. Raised, its points apply to what it covers and it can parry on a new Shield skill. Lowered, it is weight. The price of keeping it up is that it occupies a hand and you are shooting around it: -20% to your own ranged attacks, printed on the card like everything else. Only one shield goes up at a time. Seven of them, 2-point buckler to 6-point ballistic shield; the pavise and barrier plate cover legs too because they are walls you carry. `degradation` finally does something — it has been on every armour item since the packs were first built and was read by nothing. A blow costs the shield a point of wear for every step it stood above an ordinary success, so an ordinary hit costs nothing, a special 1 and a critical 2. At its limit it comes apart, drops, and will not be raised again. Two defects found by driving the real functions rather than reading the diff, both introduced by this change: - Every shield defence quietly rolled DODGE. "shield" passed through defenceTypeAllowed unchanged, so neither parry branch matched and it fell through. Invisible in the card; it showed up as eight rolls at a supposed 85% returning six failures. Taking a blow on the shield IS a parry, so it is now a flag on a parry rather than a defence type of its own. - The wear rule was unreachable: it fired only on a clean stop, but under graded defences only a critical fully stops a critical, so a shield would never have taken a scratch. Wear is for what the shield ABSORBED — dragging a critical down to a special is exactly when shields splinter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
016ea1c4ab |
Play the chantry ruin: coverage measured against points
Two officers from the same pregen, same hit points, same Sword and Dodge, armoured so that the one carrying MORE points carried it in FEWER places: a breaching suit at 7 points on all seven locations against a cuirass and helmet at 9 points on three. Against a redcap with an 80% halberd, where fourteen of twenty melee locations are limbs. The cuirass and helmet stopped fifteen points across three connecting blows and it did not matter: the two hits that found a bare leg did every point they rolled, destroyed the leg (12 against a maximum of 6), and started a six-round dying clock. Under the old rule that leg carried 9 points and both blows would have done nothing. The suited officer took the same weapon on a limb for zero and went nine rounds untouched — then a CRITICAL halberd against a successful dodge landed one step down as a special, halved the armour, and put 9 through a 7-point suit. Full coverage is resilience, not immunity. Full log in docs/REVIEW_LOG.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6ed9ded362 |
Armour covers what it covers, and Coherence moves to the vitals
Asking for the type of armour per location turned out not to be a display request. Every equipped piece added its points to EVERY location, so a Brodie helmet protected your shins and a cuirass protected your skull — with a catalogue containing sallets, lobster-tail helmets and three cuirasses, that could not be printed without the sheet saying something visibly false. Armour now declares coverage as a named set rather than a list of locations, because a piece has to fit whatever body wears it: all, torso, torsoArms, torsoLimbs, head. The sets expand to location KINDS, so a cuirass covers a baseline torso and a vesh trunk and ridge without either knowing the other exists. Unstated coverage is `all`, so anything that does not declare behaves as everything did before. Eighteen of the twenty-five catalogue pieces are not whole-body and now say so. The trap was double counting: hitLocationRoll subtracted the global worn total PLUS the body's own armour at that location, which was right while armour was global and becomes a double count the moment a location knows its own pieces. It reads the location's total and nothing else, verified by firing fourteen hits of 10 damage across every location and asserting damage applied == 10 - that location's armour. A single ARMOUR number no longer exists, so the header shows the torso with "0-8 by location" beside it when the rest of you is not that. This is a real difficulty change: limbs are frequently bare and limbs are 12 of 20 on the melee table. Coherence moves out of the derived strip into the vitals at hit-point size, with its band under it and the box reddening as it falls. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3421ed4a2e |
Reorder the combat tab, and make weapons readyable
There was no way to ready a weapon. The strip under the characteristics reads `equipped`, and the only toggle-equipped control on the sheet was on the ARMOUR list — a weapon could only be readied by opening its item sheet and finding the tickbox there. The weapons table now carries the same toggle, the row is tinted when readied, and a line above the table says what the control does. The tab runs in the order it is used: weapons, armour, defend, reaction, body last. Weapons lead with 40px art and a display-face name, having previously sat below two screens of anatomy and reaction arithmetic. The figure is a partial, drawn in three places — header thumbnail, combat tab, anatomy tab. Three inline copies would have been the template-layer version of the defect check-rules exists to prevent: a location present in one drawing and missing from another. The header carries a 34px figure and every location with its damage and the armour over it, so "how much armour is on the part that just got hit" is answered on the main page without changing tab. Also fixed: `.ringbrp-sheet a` outranks a bare `.rb-equip`, so an unequipped toggle was painted white and read as lit — true of the armour toggle too, since it was added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4bfa7110d0 |
A lamp lights the people standing in it
The goods yard fight made the case: three agents inside the same pool of light shot at -50% while the fourth, four metres away, shot at -10%. A lamp now has a reach, per item rather than global, because a weapon lamp and a vehicle searchlight are not the same offer — 4 m for cold light strips, 6 m for a weapon lamp, 10 m for the issue field lamp, 30 m for a roof searchlight. Anyone inside it is in the light and the card names whose lamp it is, so both halves of the rule travel together: the pool is one position and the whole group is standing in it. Measured from the tokens. The shot that started this — no lamp of her own, four metres from somebody else's, in pitch dark — goes from 20% to 60%, while an agent twenty-five metres out still eats the full -50%. An UNKNOWN distance is explicitly not lit: Number(null) is 0, so an unmeasured distance would otherwise have read as standing on top of the lamp and lit any scene with no tokens placed. The dialog carries a tick instead, pre-set when the canvas could measure it and the GM's answer when it could not. Note: template.json is read when Foundry loads the system, so a deployed server needs a restart and not just a pull before the migration can stamp the new field. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5c640d65cf |
Fix a fast action firing twice and paying once
Ammunition was charged once before the shot loop rather than once per shot, so the Webley put two rounds out and the cylinder went down by one. That is the exact dishonesty the burst rules exist to avoid, and it landed on the mode that is supposed to cost the most. Charged per shot now, and the loop stops when the weapon runs dry part way through the action. Found by playing the four modes side by side in the goods yard — one agent per mode, all four at 60%, so the fight read the mode and not the shooter. Full log in docs/REVIEW_LOG.md, including two findings left open: burst options are close to unselectable once the light is bad, and a lamp currently lights only its carrier while three agents stand in the same pool of it at -50%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2e10ecced9 |
Fire modes, fitted sights and lamps, and a tab rail
Selectable fire, with the modifiers on the choice Every weapon has carried a fireMode since the packs were first built and nothing read it: a musket could rip a ten-round burst and "rapid" bought a weapon nothing. FIRE_MODE now gates the burst rules and carries its own attack modifier — slow +10% and then you work for the next one, fast a second shot at -20% to both, rapid bursts, beam +5% and it can be held on the target for damage instead of hits. The gate lives in burstAttack too, so no macro can route round it. `fast` was on five catalogue weapons and was not in the system's list of modes at all, so re-saving one of them silently blanked it; the list is derived from the rule now. burstAttack shipped complete and was reachable from nothing. It is the resolution path the selector chooses, and takes the dialog's whole stack rather than charging the player twice for the same burst. Sights and lamps that apply themselves A sight is graded by range band, so a telescopic sight is a -10% liability inside ten metres and worth +20% at extreme, and it only counts if it fits. The band is already known when the modifier is wanted, so nothing is selected and nothing is forgotten. The dark costs something now; a carried lamp buys it back to dim, never to daylight, and the card says it shows them exactly where you are. A migration fits gear that predates the fields, matched on the stamped key rather than by name. The damage card The last card still wearing Foundry's stock dice styling — it posted a bare Roll and let the core template dress it. It renders through the module's own card now, with the Roll still on the message so dice stay inspectable. A rail instead of a strip Eight tabs across an 820px sheet compressed to 11px type. They run down the side now, one line each, with counts as badges; the sheet is 152px wider to pay for it. Also fixed: any weapon with a Coherence cost could not be attacked with AT ALL — the handler re-read the weapon after an await, when currentTarget is already null, and the attack died on a TypeError with nothing shown. And the duplicate-rule guard failed an honest table because FIRE_MODE and speedBands share two key names; it wants a majority now, and prints partial overlaps rather than swallowing them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d4442a55f0 |
Play SALT COUNTY with the new bestiary
A records office, a revenant as the briefed symptom, and the carrion file as the cause "elsewhere in the same location". The revenant was resolved without a shot off its own leverage line. AMENDMENT then removed two trained skills and a talent permanently, with no roll offered — which is the whole point of that entry. Burning the file exercised the fire rules on a thing rather than a person: armour counted on the round it caught and never again. The team burned too. Closed at Containment 8, Resources 1, Clearance 5; the failed extraction advanced the window clock and every crossing took the -10%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ee9e1f5b73 |
Codex review pass: three real faults corrected
R-228: Ambidextrous was a compulsory build. 0/-20 gave a full-strength main hand AND a cheap second attack. It now buys consistency, not a free attack: -20 in both hands against -20/-40 untrained. R-229: full auto was a firing squad. Ten independent hits off one critical, each with its own damage, location and Major Wound check. Hits capped at 3/5/7, and a burst is ONE wounding event however many rounds connect. R-230: Engines deleted the pressure it existed to create. Guns +2 and Helm +1 against a vent of 3 made a fighting round heat-neutral, so Heat only mattered when the engineer failed. Venting is now 2/3/4. Also: falling halving no longer stacks (soft + controlled quartered the drop); blast has one formula floored once, so the answer no longer depends on call order. And the structural note — "giving everyone a roll is not the same as giving everyone a decision". Sensors, Damage Control and Medical now have mutually exclusive modes that bite in the engine: mark/spoof/read, patch/brace, restore/steady. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
226f853ee6 |
Hazards, automatic fire, two weapons, a bestiary, and space combat
Stress test first: 150 characters across every posting, tier and species against fourteen invariants each, then the edges (characteristics at 1/3/21, every location destroyed, twelve pikes carried, hp set to -99). R-227: a destroyed head was a nap. The printed effect has always read "For anything that keeps its brain there, this is death" and the code set unconscious and stopped. destructionOutcomeFor() now says what destruction means. THE WORLD: falling (a die per 3 m, armour does not apply), drowning (CON rounds then a point a round, straight past the Major Wound threshold), fire (a condition with a size; armour helps on the round you catch and never after), explosives (one roll at the centre, shared by band, cover beats armour). AUTOMATIC FIRE: one roll, the success level says how much of the burst arrived, and the rounds are spent either way. TWO WEAPONS: -20/-40, or 0/-20 with the new Ambidextrous talent; the off hand takes the lighter weapon and nothing over 2 kg. BESTIARY: 21 entries in three families — folklore, horror, and what comes back from further along — each with characteristics, natural armour, tactics and a POWER written as a rule with a cost. SPACE: eight stations, each on a skill somebody already has, so the Physician has a chair. Range is a band; the helm moves one a round, in or out. Heat is the pressure and took two rounds of playtest tuning to stop being a countdown. 64 rules, 198 spot-checked formulas, a 12-page rulebook in reading order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
79caf143bd |
R-226: "Tradecraft first" was too literal
My own R-221 fix overshot. A Containment Officer whose Tradecraft had caught a stray bonus point sat at 12% against a base of 5 — technically trained — and led every phase ahead of a Rigging Officer on 53% Navigate. Tradecraft still wins ties and wins when genuinely better; it no longer wins when plainly worse. The Containment phase went 6% -> 26% on the same team. Found playing GLASS LANTERN with the historical gear: salvaged polearms and slings correctly arrive at 5% because nobody trains them, and maille over brigandine (AR 8) reduced four utility-knife hits to zero damage each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e2d0ed5bb2 |
A player briefing, and the historical range
The generator was already writing the timeline, the cast and the texture and filing all of it behind the GM screen. New PLAYERS page: a read-aloud opening, what it is like there, what is known so far, who is there and what they want — but never what they lie about. Page ownership is now declared by the page rather than guessed from its index, which would have hidden any second player page. Historical range: 38 weapons (from 6) and 14 armours (from 2), each with its period stated, from arming swords and longbows through matchlocks and Brown Bess to the Lee-Enfield and a trench club. Five new skills to hang them on, including Firearm (Muzzle-loader), because a muzzle-loader is not a rifle. 275 icons; every weapon maps to a real skill and every item has art. Catalogue is 206 items across four eras. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
22a5067153 |
R-225: the extraction spends the way home, not hit points
Every case carries a "window closes" clock whose stated cost is THE WAY HOME and whose steps are "the margin narrows / the return is Difficult / the return is aimed at -30%". Nothing ever read it, so the phase whose job is getting out had no consequence of its own and the GM had to invent one. Extraction now advances that clock: failure and fumble narrow the window, a critical opens it back up, and returnCrossing() applies the penalty alongside the classification modifier and the Coherence band. 0/-10/-20/-30, Difficult from two steps. Also: legs now store their `kind`. The extraction check matched on the terrain prose and worked only because the string begins "Extraction —". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2d0f70329e |
Play IRON MARGIN: closed socially, no casualties
Four phases, no weapon drawn. The arrangement with the quiet neighbours (standing 0 -> 2) is the win condition on this case. Nobody lost a hit point. A fumbled Transposition on 99 cost 3 Coherence and delivered an Art, which is the only way one ever arrives. Together with LOW VERGE this brackets the death rules honestly: they bite when the fiction has already gone badly, not by default. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5c114601a9 |
R-224: a body at zero absorbed damage for free
Hit points clamp at zero, so further damage to somebody already there changed nothing — and a STABILISED agent absorbed it forever, because the count was held and there was nothing else left to move. You could empty a magazine into someone bleeding out and the system would not notice. Damage at zero now breaks the stabilisation and costs a round of the count, and hitLocationRoll calls died() when that meets the limit. Found by playing LOW VERGE until it killed somebody: two agents who were medically stable died in the water on a failed tidal extraction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
89ce557355 |
Death at zero, and a case brief that answers the useful questions
R-223: nothing happened at 0 hit points. An agent was marked down and lay there forever; the only mention of death in the system was flavour text on a destroyed head. Zero now starts a clock whose length is the agent's own Major Wound threshold, ticked from the same hook that charges blood loss. First Aid or Medicine stabilises and stops the count; healing above zero clears it; heal() refuses the dead. On death, Coherence decides what the record says — from a proper death in service down to not being recorded as dead at all. Case generator: four phases instead of five (the arc drew two interchangeable middles that read as padding). The brief now leads with Where / When / Classification / Phases, states the opposition in full, and has a "How you get back" section. place and duration were generated and never printed. The same facts are on the case file itself. 300 seeds checked for object leaks; one found and fixed (group taboo/desire are objects with .text). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a360d77b83 |
Play SHORT ORCHARD on the retuned ramp
Phases read 41/36/31 before the fumble; the fight with the rival desk cost two agents; the last two phases resolved at 11% and 1% and the card says why — Wounded: -20%. The wound pipeline reaches case phases, which is correct and now legible. Closed at Containment 6, Resources 3, Clearance 5. Confirms R-222: every agent finished with experience ticks from phase and combat work, where before the fix a case produced none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1b44fccb79 |
Retune the case ramp, and let leading a phase earn experience
The ramp made every phase after the first Difficult (halving the lead's skill) and stacked a cumulative -5% on top. Separated in phaseRampFor() in rules.mjs: difficulty belongs to the phase (only Containment is Difficult, and it does not also ramp), the modifier is attrition (-5 a phase, capped at -15). lead 55% was 55 -> 22 -> 17 -> 12 -> 7 now 55 -> 50 -> 45 -> 27 -> 40 Over 20,000 simulated cases with destitution modelled, phases passed roughly double and destitution incidence roughly halves. R-222: resolveLegDialog passed no skillFamilyId, so the choke point could not resolve the skill item and leading a phase never earned a tick — Tradecraft, the skill the job most uses, could never improve. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1a0bdbd2df |
R-221: the phase-lead fallback could never fire
bestIn("tradecraft") ?? bestIn("navigate") never reached Navigate, because every
character carries every skill and skillRating() returns the printed base rather
than null — so the first lookup always found somebody. A team of postings that
train Navigate and not Tradecraft was led by whoever had the highest UNTRAINED
Tradecraft, which is 5%, putting phase three at 1%.
TRAINED now means above the printed base. Measured on a Pathology / Overwatch /
Warden / Materials team: lead 5% -> 34%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
cc23fdb7bd |
Rulebook and compendium refresh
Audited every exported rule in rules.mjs against what the rulebook says and closed the gaps: - Dodge starts at DEX x 2 — newly evaluated, and stated nowhere until now - Encumbrance: free carry, and which skill categories it actually penalises - Bleeding rates and what stops each kind - The merge ladder for Borrowed Authority, as a table - A new page listing all 25 postings, generated from ROLES The rulebook is now 9 pages and every one is generated from the code it documents. All packs, 231 icons and the dice textures rebuilt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1734612c06 |
v0.7.0 — vehicles, Borrowed Authority, and a door for the case generator
Since 0.6.1: - VEHICLES as their own Item type: crew, seats, cargo, hull, armour, range, terrain and the skill that works them, with speed as a band. 14 of them, from the pool car and the Custodian van to a sealed crawler and the carriage that is already parked outside wherever you were going to go. - STORES roughly doubled to 118 gear items across four eras — standing issue and paperwork, transport stores, and the forward stores Registry will not hand a probationary officer. Catalogue now 175 items, 231 icons. - Agents leave creation properly equipped: standing issue, the posting's kit, what seniority adds, and one personal effect that is theirs and not the department's. - All 25 postings now describe themselves in the creation dialog. - BORROWED AUTHORITY, the access subsystem. No hacking skill and nothing to break into: access is a named credential with a scope, a Trust that erodes, and red flags describing how its real owner behaves. Inside scope there is no roll. Two tiers beyond is impossible at any skill. Coherence widens a record, and pushing one past what its Trust will carry merges your file with theirs. - Defences graded by level, and damage now rolls at the level a blow landed at. - Dodge's DEX x 2 base is finally evaluated; it read 0% for most postings. - Brawl is a fallback rather than a peer of the weapon in hand. - check-lang guard: one key that was both a value and a branch silently un-localised the entire system. - The case generator is reachable from the Actor Directory, and no longer puts every character in the world on a new case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d0144f744e |
The Custodians v0.6.1 — full system
A Foundry VTT game system: Basic Roleplaying d100 in a drab British agency that deals with crossings, where reality keeps a record of what belongs where and can be made to get it wrong. Contents: - rules.mjs as the single authority for every rule formula, with 40 rules and 132 spot-checked values. Nothing may restate a formula it defines. - 25 postings, each with a key characteristic, trained skills, talents and its own kit; a 175-item catalogue across four eras; 231 generated icons. - Reaction Lanes initiative, re-rolled every round including the first. - Coherence, and the Quiet Arts, which only ever arrive from a crossing gone wrong. - Graded defences: only a critical stops a critical. - Borrowed Authority — access as a record rather than a hacking roll. - A case generator, and a rules journal generated from rules.mjs so the book cannot drift from the code. Built compendia are committed so a remote Foundry needs only the files. node_modules is not; `npm install` restores it for rebuilding content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |