Commit Graph
52 Commits
Author SHA1 Message Date
slaguru666andClaude Opus 5 09e33907fa Forge a creature from inside Foundry
CREATURE_FORGE_PLAN Part 5. A third button on the Actors sidebar, beside New
agent and New NPC. Pick what it does, how big it is and how bad it is; the
card underneath describes what those settings will actually produce, because
the argument for a derivation over a form is that it can be interrogated, and
choosing between "brute" and "sentinel" from two nouns is not interrogating
anything. The card is itself forged rather than written, so it cannot
describe a creature the derivation would not make.

generateCreature reads the catalogues out of the packs and calls the same
forgeCreature the CLI calls. Natural attacks carry their body-plan
declarations into flags.ringbrp.plans so the engine can read them.

The plan asks that the panel and the CLI cannot diverge, and importing one
derivation was most of that guarantee but not all of it: the two feed it from
DIFFERENT catalogues — content.mjs on one side, the built compendium on the
other. check-forge now builds the engine's view straight out of packs/weapons
and requires both to forge the same creature.

It found a divergence on its first run, in 40 of 90 pairs. The fallback took
the first usable natural attack from the caller's list, and that list arrives
in declaration order from content.mjs and in id order from the pack, so a
tiny vesh brute was punching for the CLI and constricting at the table. The
derivation may not depend on its caller's iteration order; it imposes its
own now.

Not yet verified in a running Foundry. The pure derivation is covered by 450
guarded combinations and the catalogue seam by the agreement check above, but
Actor.create, the pack reads and the dialog itself are the part
check-behaviour says outright it cannot reach. They need a world open, and
that has not happened yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 00:28:57 +01:00
slaguru666andClaude Opus 5 d7def0b31e The forge: derive a creature, and say which rule made each number
CREATURE_FORGE_PLAN Part 3. `new` prints a spec as source in the shape the
bestiary files are written in, `explain` prints the rule behind every value,
`measure` fights it against the party check-lethality freezes, and `list`
shows the knobs.

The derivation is forge.mjs at the ROOT, beside rules.mjs and postings.mjs
and under the same law: pure, no Foundry, no catalogues. Part 5 asks for an
in-Foundry panel that forges a variant at the table and says it must not
diverge from the CLI. One derivation in a module both import is the only way
to guarantee that rather than argue it.

Natural attacks now declare which body plans can make them, in the weapon
catalogue, for the same reason a weapon already names the skill that fires
it: the weapon is the thing that knows. Anatomy cannot answer it — a body
plan records that a creature has a head, not that the head has horns.

`explain` earned itself on the first run. A large brute derived with a
baseline body could not gore, trample or hoof, and the trace said so; the
finished statblock alone would have shipped bulls that bite. The role picks
the body now, a non-baseline species overrides it, and --body beats both.

check-forge (guard 26) derives all 450 combinations of role, size, tier and
species and puts each through the bestiary's own validate(). Three mutations
were planted in the forge to test it, and it caught one. Dropping the seed
was caught. Dropping the brawl grant was a genuine no-op, since every role
already lists brawl. Dropping the body-plan filter left all 450 rows green
while the success line read "armed with something its body can use" — the
guard read the location table and never used it. It checks each issued weapon
against the catalogue's own declaration now, and the same mutation produces
500 failures.

check-rules' inline-armour pattern matched any clamp containing the word, and
false-positived three times in one evening on counts that were never damage.
damageAfterArmour is a subtraction, so the pattern requires one. A guard that
makes people contort working code to keep it quiet is training them badly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 00:22:39 +01:00
slaguru666andClaude Opus 5 4dd72c6248 Generated NPCs draw their posting's kit
generateNPC read R.core, R.support and R.label. It never read R.kit, so the
role card's DRAWS FROM STORES promised a Containment officer nine items and
the actor arrived holding a utility knife with a flat naturalArmour standing
in for armour it was never issued.

postings.mjs now owns the decision — drawKitFor(role, tier, {classify}) plus
a draw table per tier — because the engine resolves kit keys against the
compendium and check-generator resolves them against content.mjs, and the two
cannot import each other. Classification comes from catalogue membership and
never from the key: climbing_kit is "Breaching charges" and prybar is "Entry
bar", so anything reading a key's spelling is wrong in that posting first.

The weapon↔skill pairing table is gone. Each weapon item already names the
skill that fires it, so the skill is granted from the weapon, and a generated
NPC cannot hold what it cannot use. THREATS.attacks is now creature-tiers
only; those two keep their natural attacks on top of what they drew.

Armour is an item. naturalArmour drops to 0 on the four human tiers, where
the number was standing in for the item, and stays on the two creature tiers,
where it stacks with drawn armour as locationArmourFor already intends.

check-generator (guard 25) was written first and observed failing on nine
counts against unchanged source. One of its assertions was itself wrong —
it read for the creature-tier restriction after the loop instead of before
it, and so reported the fixed code as broken; re-scoped, it passes on the fix
and still fails against git show HEAD:ringbrp.mjs.

Measured with the new tools/npc-cohort.mjs, 2000 runs, seed 11, against the
party check-lethality freezes. Apex · Containment 68.2% -> 88.5% wipe,
Anomalous · Containment 0.3% -> 19.2%, and Dangerous · Field Lead got weaker
because its posting carries one pistol where the tier used to hand it a
rifle. Full table and three incidental findings in R-310.

Two of those findings are guards, not content. check-rules failed on a
comment that quoted the pattern it hunts for, so comment-only lines are
skipped now. update-readme held the guard list as a hardcoded array whose own
comments recorded it going stale twice; it reads the check script instead,
and so does the count-word rewrite that was separately capped at sixteen.

Not changed, and flagged in R-310: breach_suit and riot_shield declare no
coverage, so they armour every location including the head. The location
counterplay NPC_KIT_PLAN.md relies on does not exist, and that is a balance
decision rather than a side effect of this one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 00:08:12 +01:00
slaguru666andClaude Opus 5 3d83fbde93 R-280: the band replaced by the test it was standing in for
MEASURABLE = [15,85] proxied for "can this fight move at all". The direct form,
in the units the guard already uses: the spread rate must stand clear of both
ends by more than its own noise. Deliberately blind to the gain -- admitting the
sizes where focus fire clears its noise would make the guard's claim true by
construction. Size is still picked on nearest-an-even-fight.

31 measurable became 38. the_arrears returns at 6.3, and switchboard arrives at
8.1 -- the second largest gain in the artifact, thrown away for being one point
past a round number. Four of the eight carry effects larger than most rows the
band already admitted.

Two of them do not clear their own noise: the_choir has 0.7 points of headroom
and used 0.2, the_stanchion has six and used 0.2. Above-noise falls 31/31 to
36/38 and the bestiary prints 36. That is two measurements reporting no
detectable effect, which the band suppressed by refusing to take them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 14:27:13 +01:00
slaguru666andClaude Opus 5 a7eca91a9c R-278: raising SCAN_RUNS found that SCAN_RUNS had never been read
Asked to raise the scan until the redcap's pack size stopped flipping. Measured
the threshold -- unstable at 3000 and 4000, stable across twenty seeds at 6000 --
raised it, and the re-record took fifteen seconds, which was impossible.

winRate takes three parameters and pickSize passed SCAN_RUNS as a fourth.
JavaScript discards it, so every scan has always run at RUNS and SCAN_RUNS has
never been read by anything. The fix I was asked to make was inert in the same
way as the thing it was fixing.

winRate takes runs now. The redcap is still n=3 with gain 6.3, arrived at stably
rather than luckily; the_arrears drops out of the measurable band at an honest
scan, 31 packs to 30; the_committee moves 2 to 6 and stays pinned. Claim check
still passes, bestiary regenerated.

The only signal was a number being too small. A fifteen-second re-record is good
news, and good news is what nobody investigates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 14:18:46 +01:00
slaguru666andClaude Opus 5 32ca982369 R-275: the harness had never read a creature's power
41 statblocks carry a POWER in their tactics and simulate.mjs read none of them,
so a redcap that ignores the cumulative defence penalty has been measured as a
creature that tires -- in check-lethality, in check-focus, and in every figure
published about it. The defect was not that the powers were unimplemented, it was
that nothing said they were not.

powers.mjs classifies all 41: 2 wired, 14 notSimulable with a stated reason, 25
not fight rules. check-powers refuses an unclassified POWER and refuses a
notSimulable without a reason -- and it does not test that the harness imports a
power, it fights the creature with and without and requires the two to disagree.

Moved: the courier 9.4% to 1.0% wiped (it attacks at half while carrying), the
redcap 0.7% to 0.9% (small, because these fights rarely spend a second defence).

ARGENT AND GULES was wired and then un-wired: it tripled the supporter's wipe rate
to 75.2% because the harness has no ground and applied the borough-ground condition
unconditionally. Same reason THE PULL is not wired. I had wired one and refused the
other on identical facts.

Lethality and focus re-recorded, bestiary regenerated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 11:05:11 +01:00
slaguru666andClaude Opus 5 9f3a63174f CLEAN GROUND v0.12: convention one-shot, the print pack, and the real opposed roll
Tim settled the last two questions: convention one-shot, and rules.mjs gains a
real opposed roll (landed by a peer session at R-273). Both decisions change
the document, and the one-shot unblocked the print pack.

THE PRINT PACK — docs/scenarios/CLEAN_GROUND_HANDOUTS.html, guard 17.

Four A4 sheets, self-contained, no external fonts or assets: H01 the coroner's
note on white, H02 the 1962 committee minute on cream with photocopy grain,
H03a and H03b the almanac on ruled paper. 11pt floor throughout.

The binding constraint is that H03a and H03b must print identically or the
almanac trick dies, so that is structural rather than careful: they share one
`.almanac` class and every dimension comes from a variable defined once. There
is no selector anywhere that names one page and not the other, and
check-handouts fails the build if one appears, if their markup structures
diverge, if their columns differ, if any row stops reading "41 mi", if the
counts stop being forty-one now against fifty-three then, or if the pack and
the scenario drift apart.

It earned itself immediately: its first run failed my own pack for five rules
at 10.5pt, under the house 11pt floor. Rendering was checked visually too,
which caught two things no guard would have — the "TO CLEAN GROUND" header
colliding with NOTES, and the writing crossing the red margin rule instead of
starting right of it.

THE ONE-SHOT. Countdown step 6 said "and this is a campaign", which the
decision contradicts. Rewritten, and the Close's "leave it filed" ending now
says how to land it tonight: do not end on "you'll be back", because the table
never will and a hook they cannot take reads as an unfinished scenario. Name
the next agent who gets sent, and have Registry thank them.

THE OPPOSED ROLL. The scenario-local ruling is deleted; GM ESSENTIALS points
at the game. Both beats were re-priced against the real rule by enumerating
all 10,000 roll pairs -- exact, no seeds -- and two things fell out:

  THE OFFER IS PRICED. Ashcroft refusing is taken 21.9% of the time, the
  safest file at the table. Accepting: 48.7%, past Braithwaite's 37.6%.
  Accepting does not make him a bit more vulnerable, it makes him the easiest
  person in the room, and the countdown reaches for the easiest.

  STEP 5 CAN COME TO NOTHING, 14.5% of the time against an Ashcroft who
  accepted, and that row fires once and is marked permanent. Previously a
  silent gap in a climactic beat. It is now a written outcome with read-aloud
  text: the reach fails, it wears the wrong face for a moment, the party learns
  what it is and cannot prove it, and the clock still turns to step 6.

Also: update-readme's count-word list ran out at sixteen, one guard after its
own comment warned about hardcoded lists going stale. It failed loudly rather
than silently, so it is an inconvenience and not a defect. Extended.

npm run check: 17 guards, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:50:03 +01:00
slaguru666andClaude Opus 5 8279cdfda8 R-272: guard the tail, and stop prose drifting from its artifacts
Three things Tim asked for together: measure the long end of a fight, make the
swing check bite on stale prose, and record in rules.mjs the coincidence that
hid an invented rule from two readers.

GUARD 15 — check-fight-tail, on tools/fight-tail.mjs.

Fourteen guards measured this scenario's combat and none could see a long
fight, because every one of them averages. The cap is the measurement here, so
the tool passes its own (400, not runFight's default 40) and --update refuses
to record anything reaching it. Verified by lowering it back to 40: the cut
loses 20 fights to the ceiling, the six-a-side 117, 107 of those ending
neither won nor wiped, and the recorder stops.

Two claims, both read from a fresh measurement rather than the baseline, so
--update cannot silence them. "The long end is twice the median" was rejected
as a claim because it is true of both encounters and so distinguishes nothing.
Instead: a fight past 15 rounds wipes the party materially more often than a
short one, and the SIX-A-SIDE fight is the longer one (median 14 vs 10.7) --
R-270 showing up as duration, since a disabled fighter keeps fighting 30
points down. The cut is shorter because it is decisive, not safer.

GUARD 16 — check-cited, on tools/check-cited.mjs.

check-firstblood and check-attackers catch the game changing; neither reads
the document. Re-record after a re-cast and the artifact updates, the guard
goes green, and the prose keeps printing the old number under a citation
saying where the new one lives. So citations are now machine-readable --
**40**<!-- cite: first-blood cut.swing --> -- and resolved on every build. 25
of them. It failed three times on its first runs, all real: a config keyed
"column" that the prose called "line", two figures rounded 32.3 -> 32, and a
vacuous pass on zero citations, now fatal in its own right.

It also refuses citation of unstable fields. fight-tail.longest may not reach
prose: same party, same seeds, same runs, and renaming a config moved it 71 ->
90 rounds, because seedFor derives the stream from the id. Across seven
labels -- median spread 0, p95 1, p99 3, longest 21. A sample maximum reads
like a bound and is a property of the label. EXPOSURE states p99 instead.
Same discipline on the deadlier ratio: 2.42 with seed spread 1.1, so the
document gives its direction and declines to quote its size.

RULES.MJS — one comment, no rule change.

Over resolveLocationHit: its two thresholds are unrelated and usually agree.
disabled is a fraction of the pool per location; majorWound is ceil(hp/2) and
feeds only dyingLimitFor; neither removes anyone from a fight, which is
conditionFor at 2 hit points or a destroyed head. At 10 hp, leg/abdomen/chest
capacity is 5 and majorWoundFor is 5, and those locations take 12 of 20 melee
results and 15 of 20 ranged -- so two readers reconstructed a rule that does
not exist, checked it against the log, and were confirmed by it. The note
says to test an arm, the only place the difference shows.

Guards verified to bite, not assumed: drift, re-cast, censoring, the longest
refusal, the rounding catch and the vacuous-pass catch were each forced and
each failed the build with the right guidance, then restored.

npm run check: 16 guards, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:20:25 +01:00
slaguru666andClaude Opus 5 0b86c5ffc3 R-268: guard the sentence that names a player's character
CLEAN GROUND prints that the four-player cut is three effective attackers and
that Ashcroft is not one. Nothing checked it, and a GM reads it aloud to decide
who a real player spends four hours being.

effective-attackers.mjs measures per attack, not per fight: counted per fight
Braithwaite leads on disables, but only because his armour buys him a third more
swings -- per attack he is the weakest of the three. Both rates are recorded and
only the per-attack one is reasoned from. Asserts exact drift, then the sentence:
three clear 5% of attacks disabling, one does not, and that one is Ashcroft.
Re-recording does not silence the claim check; verified in a worktree.

declared-cast.mjs holds the cast marker reading both scenario guards need, rather
than a copy in each. It was briefly named scenario-cast.mjs, which check-scenarios
sweeps into the scenario corpus -- its own example marker was read as a real cast.

update-readme dropped any guard that exited non-zero, so check-rollable vanished
from the README and the count word fell to thirteen while fourteen guards ran. A
missing line is now fatal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:56:01 +01:00
slaguru666andClaude Opus 5 ce1103ec0d R-267: a guarded home for the first disabling blow
Another session verified R-266's measurement, agreed the frame was better than
its own, and declined to publish it because the figure had no guarded lineage a
doubter could re-derive. It was right, and the fix costs 0.7s of build time --
which is the number I should have checked before calling it a reading tool and
not a guard.

first-blood.mjs gains --update/--check and is now both the reader and the
measurement of record; check-firstblood.mjs is a thin wrapper over it, the same
shape as check-bestiary. Compares the recorded figures exactly, then asserts only
what a page would claim: first blood lands within 5 points of even in the cut,
and its swing exceeds the six-a-side line's by more than both noises.

Recording it moved the swing from the scratch run's 43 to 40 against a
seed-to-seed spread of 2.1 -- high by more than its own noise, which is the
argument in miniature. update-readme's COUNT_WORD could not reach thirteen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:42:31 +01:00
slaguru666andClaude Opus 5 8627fa3536 One generator, so a citation names one fight (R-265)
tools/playthrough.mjs exists to show a reader why a measured number is what it is. I cited
it to another session — read seed 2, see the wipe the 45% row is made of — and the
reproduction failed in front of them. They reported different outcomes at both seeds and
guessed the cause correctly from outside: the two tools were not drawing from the same
stream.

They were not. simulate.mjs used a private mulberry32 makeRng; playthrough.mjs had its own
LCG written to look like it. Both deterministic, both reproducible alone, and "seed 2"
named a different fight in each — which breaks the only thing the tool is for. Its own
comment claimed a seed here names the same fight there. check-focus carried a third copy of
that LCG, so the two guards described the same game with different dice.

makeRng is exported and both files use it. A playthrough seed is now exactly the first
fight of simulate.mjs --seed <n>: --runs 1 --seed 2 and the playthrough give 9 rounds, 4 of
4 down, 1 dead, both.

The other half was my citation rather than the code: the command I sent omitted --mode, so
it plays both targeting arms and prints two fights. They read the last line, I quoted the
first. The summary line now names the arm.

check-focus re-recorded under the shared stream; figures move a point or two. What it buys
is that the bimodality analysis now reproduces the published means exactly — 2.31, 3.03,
0.40, 0.42 against the four rows CLEAN GROUND publishes. Under the old LCG it agreed to
within a decimal, which looked like corroboration and was two experiments landing near each
other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 01:11:41 +01:00
slaguru666andClaude Opus 5 ad5fd57568 The harness was fighting a different animal (R-264)
Reading a narrated supporter fight showed it being hit on armR and head. It has wings; it
has never had arms. Three defects, all in the pipeline every published number comes from.

1. It located hits by species, not body plan — simulate.mjs read defender.species where
   the game writes spec.bodyPlan ?? spec.species ?? "baseline" into speciesProfile
   (build-packs.mjs:294). The barghest, kelpie and church grim were fought on two legs
   with arms, and the supporter with no wings, so the fight its tactics call the one the
   agents can win could not occur in a measured fight.
2. Armour was one scalar for the whole creature, and the harness's locations had no
   armour field. Bare wings, the grounded halving and the vital exemption — R-254 and
   R-255 — were invisible to every number. Worn armour was summed the same way, which put
   a stab vest on a cleaner's head.
3. Nothing was ever grounded: a wing could be ruined and the creature kept flying.

Fixed in the game's order — locate, then apply what that location carries, every term
imported from rules.mjs. A ruined wing calls groundedPlanFor and the wounds carry across
by severity through remapLocationDamage, the function _preUpdate uses.

A bug of mine no guard would have caught: remapLocationDamage returns { damage, moved,
rescaled } and my first draft passed the whole object where a damage map was expected, so
every wound a creature carried was forgiven the moment it came down. check-lethality would
have passed it — fewer wounds means a longer fight, which reads as a number moving, and
this commit moves numbers. Found by probing a landing by hand.

20 of 47 creatures moved, 18 deadlier and 2 less. The supporter goes 69.3% -> 25.1% wiped,
3.38 -> 2.16 down, second deadliest to fourth: it was being measured as a 30-hit-point
creature in uniform armour 9 that could not be grounded. The small rises elsewhere are the
party's armour no longer covering locations it never protected.

check-focus then caught the page overclaiming, which is what it is for: focus fire still
helps in all 30 but only 28 clear their own noise where 31 of 31 did. The guard was
asserting more than the page needs — the bestiary prints that count from the artifact and
cannot overstate it — so it now checks only what the page asserts outright, and the page
rewrote itself to "in 28 of them".

Both baselines re-recorded. Minor version, not a patch: the published numbers changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:42:52 +01:00
slaguru666andClaude Opus 5 5dcaaf519a The simulator can be read now, not just totalled (R-263)
Every guard here reports a number per creature, and a number cannot say why a fight went
the way it did. That gap is what produced R-258 through R-260: each written from a side
harness built to watch a fight, each modelling something slightly different from the game,
two of the three wrong in ways no guard could catch because no guard was involved.

runFight takes an optional `say` sink. It reports what the simulator already decided — the
attack roll and its target, a defence and the penalty it was made at, the landing level
after a dodge downgrades it, damage against armour before and after armourAgainst, the
location, major wounds, disablement, death. It never touches the generator, so a narrated
fight and a silent one are the same fight; check-lethality and check-focus both still
match their baselines exactly with the hook in place.

tools/playthrough.mjs (npm run play) is its consumer, in the same commit deliberately: a
hook with no reader is the exact defect this project keeps finding in its own rules, and
adding one to the measurement pipeline with only a scratchpad file calling it would have
been committing the thing I have spent the session removing. Pack size comes from
focus-baseline.json so the fight you read is the fight check-focus measures; creatures
recorded as pinned are played solo.

Three redcaps, seed 20260913, same seed both ways. Spread fire: wiped in 10 rounds, 3
dead, and all three redcaps still standing — 39 hit points spread three ways so that none
of it finished anything. Focus fire: same opening, diverging at one target choice in round
1, party wins in 20 with two up. The +13.2 points check-focus records, seen once instead
of averaged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:30:14 +01:00
slaguru666andClaude Opus 5 1d915c55cf check-focus: the focus-fire claim is an artifact, not a sentence (R-261)
R-258 put a tactic on a GM-facing page from an unguarded harness, R-259 found it worth
nothing, R-260 found the replacement right for the wrong reason. Three corrections, each
landing as prose with nothing checking it — the arrangement that let ddc4f99 describe a
game nobody was playing.

tools/focus-baseline.json records, per creature, the pack size and the spread-fire and
focus-fire win rates. check-focus re-measures all of it on every build and compares
exactly, with the party, seeds, run count and seeding scheme recorded alongside so numbers
taken under different conditions are refused rather than compared. The bestiary READS the
artifact instead of restating it, and check-bestiary refuses a page that has fallen behind
it. Page, guard and simulator cannot disagree.

The pack size is recorded rather than re-chosen: a fight at 0% or 100% cannot show an
effect, and a guard that picked again each run would let a changed creature move quietly
to a different question and pass. 31 of 47 creatures land in the measurable band; the
other 16 are recorded as pinned, with the rate that pinned them.

It guards the claim as well as the numbers. The page says focus fire helps in every fight
in doubt; check-focus fails if any row's gain reaches zero or stops clearing its own
noise. That failure means rewrite the page, not re-record the baseline.

The run count was chosen by evidence. 1000 x 3 seeds costs 8.5s and takes the suite from
2.5s to 13.7s. I tried 500 to halve it and the claim-check failed — at 500 runs one row
no longer clears its noise, so "without exception" is not supported by that much
sampling. Recording twice at 1000 gives byte-identical files.

Negative-tested four ways, all firing: a creature quietly made nimbler (redcap dodge
75 -> 85, spread 40.6% -> 27.6%), a baseline under different seeds, a hand-edited page,
and the claim failing at 500 runs.

Guard eleven (check-rollable) arrived from another session mid-build; this is twelve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:13:28 +01:00
slaguru666andClaude Opus 5 555fa761c0 check-rollable: guard eleven, because a real skill is not the same as a route
check-scenarios resolves every skill a scenario NAMES against the catalogue, which
is a different question from whether anybody present can roll it. CLEAN GROUND
shipped four commits with three clues gated on Track (base 10), Navigate (base 10)
and Science (Botany) — base 1, so the 1% floor was the whole of it — and every
guard passed, because all three are perfectly real skills. A desk playtest found
it by auditing the acts against the sheets the scenario casts. That audit is
mechanical, so it belongs in the build.

Two tiers. Corpus-wide, some roster agent must reach VIABLE for every skill any
scenario names. Per scenario, a document that DECLARES its cast is held to that
cast instead, and that is the tier that catches this defect class.

The cast is declared rather than inferred, and that is the interesting part. The
first version scraped pc_ keys out of the prose and swept up the substitutes named
in CLEAN GROUND's player-count scaling — a cast of nine instead of six, which put
Sandoval and his Track 35 in scope and made the guard pass the very bug it was
written for. A guard that guesses the cast is worse than none, because it reports
success. CLEAN GROUND now carries a cast comment and the guard reads it off the raw
text, since scenarioText strips HTML comments.

Verified load-bearing rather than assumed: re-injecting the original Track tag into
the real CLEAN_GROUND.md fails the guard, naming the skill, the base chance and the
declared cast. Worth recording that the corpus-wide tier would never have caught
it — Lindqvist trains Botany at 40, so it is rollable by the roster and simply not
by the six who were cast. The first fixture test passed for that reason and misled
me; only the declared-cast tier finds this.

VIABLE is 25 and is justified, not picked: it is the commonest base chance in the
catalogue, what an untrained agent brings to Spot, Listen or Brawl, so it is the
level the game itself treats as worth attempting. Below it a clue is not gated, it
is buried.

check-scenarios exports its tag parser rather than growing a second copy, behind
the invokedDirectly pattern simulate.mjs already uses; a duplicated parser is
exactly what this repository's one standing law forbids. update-readme then caught
me fairly — it cross-checks the advertised guard list against npm run check — so
the guard is registered there too and the README advertises eleven in all three
places.

No REVIEW_LOG entry: the log is clean at R-260 and is being appended to every few
minutes by concurrent work, so the end of that file is the likeliest place to
collide. Left for whoever next touches it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:10:00 +01:00
slaguru666andClaude Opus 5 995f3fa94e The firing order was worth nothing, measured properly (R-259)
R-258 put a tactic in the bestiary on the strength of a table-side harness — cheapest
weapon first, best weapon last, 10.7 points of win rate across 24 orderings. Measured in
simulate.mjs, the pipeline every published number comes from, it is worth +0.27 points,
with per-seed differences of -1.5 to +1.8 against 2.6 points of noise on a single arm.
Zero.

The toy lied for two reasons, both mine. Its party had one uniquely valuable weapon (an
anti-materiel rifle the borough does not own, so unhalved, worth double anything else) so
there was something to sequence around; the frozen party is 4.09, 3.98, 1.43, 1.43 —
two near-identical pairs. And the toy fixed the order every round, while the game
re-rolls Reaction every round and has no rule for holding an action, which I checked
before measuring. The order is not a decision the rules offer, and I had written table
advice for it.

The page is corrected: the ladder stays, because it is derived and true, and it now says
you cannot choose who goes first.

What is worth doing, measured the same way: committing a round's attacks to one target,
in fights that can actually move (a fight at 0.2% or 100% cannot show an effect and three
of my first four samples were pinned there). Against dodge <= 30% focus fire gains 7.6
points, which is just killing attackers sooner; against dodge >= 55% it gains 12.2, so
about 4-5 points is the defence ladder and the rest is arithmetic unrelated to dodging.
Separating them needed contested fights at both ends of the dodge range — without that
control I would have credited all 13.8 points against three redcaps to the ladder, which
is R-255's mistake again.

runFight gained two hooks, off by default and unused by the baseline: partySequence
rearranges the party within the slots initiative already gave them, leaving enemies where
they fell so the experiment isolates agent order from who acts before the creature; and
partyTargets "focus" concentrates fire. Both roll the same dice, and check-lethality
confirms all 47 creatures still fight exactly as recorded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:58:22 +01:00
slaguru666andClaude Opus 5 f932a79121 The firing order, in the bestiary, generated (R-258)
DEFENCE_STEP is -30, so a creature's Dodge is not a percentage it has for a round — it is
one it has once or twice. The median dodge in the book is 45% and goes 45 -> 15 -> 1
inside a single round. No player-facing document said so, which hid a real decision:
what order the party shoots in.

The bestiary now has a section on it. Only a landing hit spends a defence, so a miss
strips nothing; each landing hit makes the next attack of that round easier; therefore
the cheapest reliable weapon fires first and the one you most need to land fires last —
against the supporter, the weapon the borough does not own.

Everything it states is generated from DEFENCE_STEP, resolveBands and the statblocks: the
step, the ladder, the median, the spread (13 of 47 creatures stripped by one landing hit,
25 by two, 9 by three) and the list of the hardest to strip. Negative-tested by setting
the step to -15 and -20; the page rewrites itself both times. No win rates are published:
the ordering measurements come from a table-side harness that ignores wound penalties,
bleeding and dying, so the page states the mechanism and quotes no percentages.

My first draft used the dodgiest creature in the book as the worked example — 80%, three
landing hits to strip — directly beneath a claim that the first attack of the round eats
the dodge. True of a modest dodge, false of a good one, and both were on the same page.

tools/bestiary.mjs --check has existed since the document did, with the comment "(for the
guards)", and no guard ever ran it: a check written and wired to nothing, the same shape
as every rule found computed and never read. That was tolerable while the page restated
statblocks and is not now that it derives from rules — change DEFENCE_STEP and the
document lies to a GM. check-bestiary.mjs is the tenth guard, negative-tested both ways
it can fail: a rule change that staler the page, and a hand-edit of the page.
update-readme flagged it as "run by the build, not advertised" before I listed it, and
the README's count word went from nine to ten by itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:51:04 +01:00
slaguru666andClaude Opus 5 c3a337314f One armourAgainst, in the authority, shared with the harness (R-257)
Left open by R-256. Two rules decided how a hit meets armour — armourAgainst (a critical
ignores it, a special halves it, rounded down) and damageAfterArmour (what is left never
goes below zero). Both lived in ringbrp.mjs, which is the game. Neither lived in
rules.mjs, which is the authority, so the guard that forbids redefining a rule had
nothing to forbid. tools/simulate.mjs applied armour from its own inline copies of both,
as it had since it was written.

They agreed, which is the whole point. Nothing published was wrong and no guard could
have said they were two things rather than one. But every number in BESTIARY.md, every
row of the lethality baseline and every measurement quoted from R-251 onward comes out of
that harness: change how a special hit meets armour and the game changes, the numbers
describing the game do not, and all nine guards still pass. Same shape as ddc4f99, with
no tolerance to blame and no reason it would ever have surfaced.

Both rules now live in rules.mjs. ringbrp.mjs imports and re-exports them because they
are public API at game.ringbrp. The simulator imports them. Verified live in the world
that game.ringbrp.armourAgainst and the rules.mjs export are the same function object,
not two that agree.

Proof it changed nothing: check-lethality replays 47 creatures over 2000 fights each and
every one fights exactly as recorded — no tolerance, no drift — across roughly four
million resolved attacks, all of which now go through the moved rule.

check-rules fails any file outside rules.mjs that halves armour or subtracts it inline,
naming file and line. Negative-tested by restoring the harness's original three lines
verbatim (both patterns fire) and by redefining armourAgainst in ringbrp.mjs, which the
name-based guard now catches because the rule finally lives somewhere it belongs to.
Arriving in rules.mjs also tripped the spot-check requirement immediately: eight new
spot-checks, and what a special does to hide 9 is now written down once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:33:58 +01:00
slaguru666andClaude Opus 5 f847cb2171 The sheet header said 9 while the body said 4 (R-256)
Verifying R-255 on a live sheet found the rule working everywhere it was built and
contradicted in the first place a GM looks. The header's ARMOUR field is the hide the
creature HAS; on a grounded beast the hit-location table under it read 4 and the header
still read 9. Nothing computes off that field, so nothing in the code was wrong. It was
wrong at the table.

The field stays editable and stays 9 — that is the creature's hide and what a GM types
into. Under it, when system.grounded is set, the sheet now says what is in play:
DOWN: 4 · CHEST 9, derived from exposedHideFor rather than from a second opinion, with
the reason on the tooltip. Verified live: no badge in the air, and grounded the badge and
the location table agree at 4 with the forequarters at 9.

check-rules forbids redefining a rule by NAME, which misses how this goes wrong in
practice — nobody redefines exposedHideFor, they write Math.floor(hide / 2) in the sheet
because it is cheaper than an import. I wrote that version first and check-rules passed
it. It now fails any file outside rules.mjs that halves a hide inline, naming file and
line; check files are exempt because stating the expected value independently is what a
test is for.

That guard also turned up something I have not fixed, recorded in R-256: armourAgainst
lives in ringbrp.mjs rather than rules.mjs, and tools/simulate.mjs has its own inline copy
of it. They agree today so no published number is wrong, but every figure in the bestiary
and the lethality baseline comes out of that simulator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:29:41 +01:00
slaguru666andClaude Opus 5 c0d33ee81d Hide 9, grounded 4, vital exempt — the borough is worth 56 points again
R-254 shipped the belly rule and called +6.4 points "modest on purpose". That read the
wrong statistic. +6.4 measured whether grounding helps; the question was whether the
borough still matters, and the gap between fighting the supporter on borough ground and
off it had fallen from 60.6 points to 44.9. Half damage from every departmental weapon is
meant to be the fight's central problem, and I had quietly taken sixteen points off it
while reporting a small number about something else. Same error as R-251.

Three changes that only work together:

  - hide 7 -> 9, restoring the weight on the axis that should hurt
  - grounded still halves it, 9 -> 4, which an ordinary round beats
  - exposedHideFor now takes the location's kind and leaves `vital` unhalved: a beast on
    its belly is not presenting its chest to the floor

Measured: 28.4% on borough ground flying, 68.4% grounded, 84.6% off it — a 56.2 point gap
against 44.9 before. Predicted 28.4 / 84.3 / 55.9 before building.

The composition of the three hide functions lives in prepareDerivedData where no test
could reach it, and check-anatomy's guard 6 is deliberately one-sided so it would not
have caught the vital losing its exemption. check-behaviour now composes the same
expression from the real supporter spec and the real tables. Negative-tested four ways:
removing the exemption, un-baring the wings, removing the halving, and inflating the hide
past what a round can beat each fail it with the right message.

Cost: the supporter goes from fifth to second deadliest creature in the book (2.84 -> 3.38
down, 46.8% -> 69.3% wiped). check-lethality refused the build until that row was
deliberately re-recorded.

BESTIARY.md printed the new hide and said nothing about it halving, because the body-plan
blurb predates the rule. The README said "eight guards" in three sentences above a
generated block listing nine. Both are the signature defect in prose: a number written
down that nothing reads. Both now generate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:15:35 +01:00
slaguru666andClaude Opus 5 4cfacd47fe check-lethality compares exactly, because it already seeded per creature (R-97)
I asked for this on a false premise of my own: I reported that adding a
creature shifted a shared random stream and perturbed every other
creature's recorded number. That is not true and the code never did it.
measure() builds its own mulberry32 from the seed on every call, and
check-lethality calls it once per creature, so a creature's numbers do
not depend on its neighbours or its position. Measured rather than
argued: inserting a creature ahead of the barghest changes 0 of 47
existing entries. The file's own claim — "the only thing that can move
the number is a change to the rules or to the creature" — was accurate
all along, and my last commit message says otherwise. It is wrong.

The real cause, found by replaying each commit against the baseline as
committed at 4b71859:

  4b71859  baseline recorded            0 of 46 differ
  322389b  bestiary                     0 of 46 differ
  ddc4f99  hit locations reach combat  26 of 46 differ   <-- here
  a90c4f3 .. e5dc9b5                   26 of 46 differ

ddc4f99 routed every ordinary blow through the hit location table. That
is the largest change the combat system has had and it moved 26 of 46
creatures, which is correct and expected. What is not correct is that
nobody noticed for four commits: each creature moved by one or two
points, the guard allowed six, and it reported OK while describing a
game nobody was playing.

So the tolerance goes. It exists for sampling noise and there is no
sampling noise here — same party, same seed, same counts, and two
recordings of unchanged code are byte-identical. Anything that moves is
a real change, which is the entire point of the file. `rounds` is now
compared too; it was recorded and then never read, so a creature could
take a round longer to kill forever without a word.

Because exactness only means something if the measurement is exact, the
guard now proves it instead of assuming it: one creature measured twice
must come back identical, and it says so plainly if a future change
reaches for Math.random.

Negative-tested. A 2% change to locationMaxHp now trips 8 creatures at
+1.5 and +0.5 points of wipe rate — every one of which the old tolerance
would have passed. Breaking determinism is caught and named.

Also fixes update-readme, which advertised 8 guards while the build ran
9: check-anatomy was added without touching the list, which is precisely
what the comment above that list already warned had happened once. The
list is no longer trusted — it is checked against the `check` script in
package.json, and refuses to write a README advertising a different set
than the build runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:22:19 +01:00
slaguru666andClaude Opus 5 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 ddc4f99 and shifted the shared random stream
for every creature measured after it. Every drift sat under the guard's
six-point tolerance, so it passed while being wrong. Worth knowing that
the file's claim — "the only thing that can move the number is a change
to the rules or to the creature" — is not currently true: adding any
creature perturbs the rest. Seeding per creature would fix it and is
not done here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:16:54 +01:00
slaguru666andClaude Opus 5 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>
2026-09-12 15:46:11 +01:00
slaguru666andClaude Opus 5 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>
2026-09-01 16:43:39 +01:00
slaguru666andClaude Opus 5 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>
2026-08-31 21:20:09 +01:00
slaguru666andClaude Opus 5 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>
2026-08-31 21:11:04 +01:00
slaguru666andClaude Opus 5 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>
2026-08-31 21:05:25 +01:00
slaguru666andClaude Opus 5 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>
2026-08-31 20:57:17 +01:00
slaguru666andClaude Opus 5 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>
2026-08-31 20:33:32 +01:00
slaguru666andClaude Opus 5 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>
2026-08-30 21:15:45 +01:00
slaguru666andClaude Opus 5 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>
2026-08-19 19:25:20 +01:00
slaguru666andClaude Opus 5 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>
2026-08-18 23:23:03 +01:00
slaguru666andClaude Opus 5 774e95dea8 THROUGH TRAIN — the crossing starter, and two more playtest fixes
A train has been running the same four miles of track since 3 November 1881. It
has never stopped. Sixty-one people are aboard and all of them are alive.

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

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

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

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

Two fixes from playing it:

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

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

Version 1.3.0.

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

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

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

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

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

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

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

Version 1.2.0.

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

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

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

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

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

Version 1.1.2.

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

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

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

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

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

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

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

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

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

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

Version 1.1.0.

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

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

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

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

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

Version 1.0.7.

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

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

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

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

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

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

Version 1.0.5.

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

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

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

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

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

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

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

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

Version 1.0.2.

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

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

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

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

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

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

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

Version 1.0.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 18:42:05 +01:00
slaguru666andClaude Opus 5 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>
2026-08-12 18:21:45 +01:00
slaguru666andClaude Opus 5 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>
2026-08-12 17:50:52 +01:00
slaguru666andClaude Opus 5 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>
2026-08-12 17:03:49 +01:00
slaguru666andClaude Opus 5 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>
2026-08-12 16:19:50 +01:00
slaguru666andClaude Opus 5 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>
2026-08-12 11:06:04 +01:00
slaguru666andClaude Opus 5 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>
2026-08-12 10:19:41 +01:00
slaguru666andClaude Opus 5 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>
2026-08-12 00:08:07 +01:00
slaguru666andClaude Opus 5 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>
2026-08-11 23:39:43 +01:00
slaguru666andClaude Opus 5 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>
2026-08-11 23:25:10 +01:00
slaguru666andClaude Opus 5 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>
2026-08-11 22:57:20 +01:00