Commit Graph
100 Commits
Author SHA1 Message Date
slaguru666andClaude Opus 5 cdbb2e34ee R-273: the opposed roll, built where the authority lives
The content has called for 'opposed POWx5' since before the rule existed -- the
hollow man's tactics, CLEAN GROUND twice, slice-text three times -- and rules.mjs
defined none. A scenario carried a local ruling that said in its own text it was a
ruling and not a rule.

Generalises that ruling rather than inventing another: both sides roll, the better
band wins, only the ladder the game already has. Ties go to whoever is being acted
upon, which is what defenceOutcomeFor has always said; the scenario's 'favour the
agent' gave the same answer only because no agent ever initiates one. Neither side
succeeding leaves the contest unsettled rather than won, which the two beats need
in opposite directions.

Two exports at the scenario session's request: opposedOutcomeFor compares graded
levels and carries both, so a caller can price a fumbled attempt without this file
deciding what a fumble costs; opposedContestFor runs it from ratings and rolls with
per-side difficulty, so 'resists at Difficult' does not put applyDifficulty back
into a document. Spot-checked against the real Act Four beat: 55 against 85 at
Difficult, which is 42.

Page 1 states it by asking it -- the tie-break and the margin are computed from the
rule at build time, so the book cannot drift from the engine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:42:17 +01:00
slaguru666andClaude Opus 5 d03b3208e5 check-cited: the counter watching for invisible markers was itself case-blind
Fourth variant of the same defect, found by the peer session probing sideways.
Neither CITE nor ANY_CITE carried the `i` flag, and ANY_CITE required a literal
"cite:" with no space before the colon. So three plausible spellings were
invisible to the reader AND to the counter that exists to catch invisible
markers:

  <!-- Cite: first-blood cut.swing -->     OK 25, exit 0
  <!-- CITE: first-blood cut.swing -->     OK 25, exit 0
  <!-- cite : first-blood cut.swing -->    OK 25, exit 0

Each of those was verified carrying a citation printing 41 against an artifact
holding 40, and each reported OK with a count byte-identical to a clean tree.
The count check could not see them because it was looking for the same literal
the reader was. Capitalising the first word of a comment is not an exotic
mistake.

The two patterns are now deliberately asymmetric, which is the actual fix:

  CITE      tolerates case and spaces around the colon, so those spellings
            simply work when correctly placed.
  ANY_CITE  stays looser still, so a spelling neither of us anticipated is
            counted and named rather than skipped.

The reader accepts only what the format specifies; the counter recognises
anything a person might have meant as a citation; the difference is reported.
The failure text now covers misspelling as well as misplacement, since the
unread set can be either.

Matrix verified, exit codes read directly: a wrong value fails under all four
spellings including no-spaces; a right value passes under all of them, counting
26; a misplaced-and-capitalised marker fails; a marker missing its path fails;
clean tree OK 25 with no false positive.

Four iterations of this guard, four variants of one defect -- something that
exists and is never read -- and all four were found by the session that did not
write it.

npm run check: 16 guards, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:33:17 +01:00
slaguru666andClaude Opus 5 0de463c470 check-cited: make the regex match its own error message, and name the right lines
Two residual defects in the marker check added at 4d7d93a, both found by the
peer session that found the original.

1. THE REGEX WAS LOOSER THAN ITS DOCUMENTATION. The failure text has always
   promised the comment must follow its figure "immediately, with nothing but
   markup between", but CITE's separators were \s*, which spans newlines — so
   a marker on the line after its number resolved and was checked, contrary to
   the stated rule. Tightened to [ \t] throughout.

   Verified safe before changing it: matched under the loose pattern 25, under
   the tight pattern 25, so no citation in CLEAN GROUND relies on crossing a
   newline. There is no finding in the document.

2. THE OFFENDER REPORT WAS RIGHT ABOUT THE COUNT AND WRONG ABOUT THE LINES.
   It filtered line by line, so a valid cross-line citation was printed as an
   offender whenever some other marker was genuinely unread — a maintainer
   told "line 15 is broken" would have edited a working citation. That is the
   headline defect fixed an hour ago one level down: correct verdict, wrong
   reason.

   Offenders are now located by position across the whole text, so the lines
   named are exactly the markers no CITE match covers. This is the fix that
   matters independently of the regex: a marker spanning lines would still be
   counted once by ANY_CITE and named nowhere, so tightening alone leaves a
   narrower version of the same bug, and position-based reporting stays
   correct if anyone ever loosens the separator again.

Placement battery, exit codes read directly: clean tree OK 25 no false
positive; one word between number and marker fails; bare marker alone fails;
two on a line with one attached fails naming only the unattached; cross-line
now fails rather than silently passing.

Three iterations of this guard, three variants of one defect — a marker or a
message that exists and is not read — and all three were found by the session
that did not write it.

npm run check: 16 guards, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:29:45 +01:00
slaguru666andClaude Opus 5 4d7d93abc8 check-cited: a citation marker nothing reads now fails the build
A misplaced marker failed open. CITE requires the comment to sit immediately
after its figure; put one word between them and the citation is silently
skipped while the run still reports OK with the same count as a clean tree:

  The longest fight seen ran **67** rounds.<!-- cite: fight-tail cut.longest -->
  check-cited: OK — 25 cited figures ...

That marker names a field the guard is supposed to refuse outright, and the
guard never saw it. Worse than an absent citation, because whoever wrote it
believes they added a check — and worse still in a guard written yesterday to
catch exactly this shape of defect.

Every `<!-- cite:` occurrence is now counted and compared against what the
parser actually read; any difference fails and prints the offending line.
Verified on both placement failures: a marker one word from its number, and a
marker on its own line with no number at all. The failure headline was also
wrong for this class -- it claimed a figure mismatched its artifact -- and now
covers both.

Found by a peer session writing a bad citation by accident while testing.

npm run check: 16 guards, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:24:32 +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 14c34449e3 R-271: measure the tail, and a round cap that scores a long fight as a draw
Fourteen guards all average, so none can see how long a fight might run. 6000
fights per configuration: the cut runs a median of 11 and a 95th of 22, the
six-a-side line a median of 14 and a 95th of 32, and the longest seen are 71 and
79. The line is the LONGER fight, because more bodies means more of them fighting
on at reduced skill rather than dropping. Length predicts death: 61.6% of cut
fights past fifteen rounds are wipes against 42.0% of shorter ones.

maxRounds = 40 is the default every guard runs at and a fight reaching it is
scored as neither wipe nor win -- censoring 0.2% of cut fights and 1.87% of the
line's. Measured before proposing anything: uncensored, the swing moves 39.8 to
39.9 and 14.8 to 15.1, against a recorded noise of 2.1. The cap stays, documented
rather than corrected, because the cure is four re-recorded baselines.

Also R-270 addendum: the hit table is weighted toward the locations where the two
thresholds coincide -- 12 of 20 melee results, 15 of 20 ranged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:08:09 +01:00
slaguru666andClaude Opus 5 74e45b15e7 R-270: a rule I invented, and the coincidence that hid it
R-266 said a fighter goes down at half maximum hit points and called it one rule
in both directions. conditionFor puts someone down at hp <= 2; majorWoundFor is
ceil(hp/2) and feeds only how long the dying last; a location is disabled by
resolveLocationHit at its own locationMaxHp capacity.

The reason it survived reading: for a 10-point neighbour majorWoundFor is 5 and a
leg, abdomen or chest holds exactly 5, and for a 12-point agent both are 6. On the
locations that get hit most the invented rule returns the real one's answer, and
the narration prints MAJOR WOUND and disabled on the same line. Arms and heads are
where they part, and I had not looked at an arm.

Found by the scenario session going to rules.mjs to verify a different correction
of mine and reading the next function along. Both errors made the fight look
easier than it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:03:34 +01:00
slaguru666andClaude Opus 5 6e46ad0909 CLEAN GROUND v0.11.1: correct the mechanism under the first-blood swing
Two wrong mechanisms in one sentence of my own v0.10 prose. The measured swing
of 40 is unaffected -- what was wrong was the explanation I gave for it, which
I wrote from intuition rather than from rules.mjs.

1. "every disable removes an attacker" -- it does not. locationEffectsFor
   (rules.mjs:461) charges 30 points off physical for a ruined leg or
   manipulation for a ruined arm, and simulate.mjs:305 applies the
   manipulation penalty to melee and ranged attacks alike. A 40% attacker
   disabled is a 10% attacker, not an absent one: still swinging, still
   drawing attacks, still a target. A slope, not a cliff.

2. "both sides go down at half their maximum hit points" -- also wrong, and
   this one nobody flagged. Half maximum hit points is majorWoundFor, the
   MAJOR WOUND threshold, which feeds dyingLimitFor and decides how long a
   dying character lasts. It has nothing to do with leaving the fight.
   conditionFor (rules.mjs:1630) puts somebody down at 2 hit points or on a
   destroyed head, and that is the only thing that stops them acting.

The paragraph now states the real loop and warns the GM off the wrong one,
because playing disables as removals gets the fight wrong in the party's
favour -- the direction that makes a party-ending encounter feel survivable.

Credit where due: the first error was caught by a peer session correcting
prose it had sent me twice. I found the second only because I verified the
first against rules.mjs instead of taking it on assertion.

npm run check: 14 guards, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:01:11 +01:00
slaguru666andClaude Opus 5 765b3518db R-269: the fourth quadrant, and a disable does not remove anybody
Seed 16 is the missing corner -- party takes first blood in round 1 and is wiped
anyway -- and it runs 24 rounds. Its last five are Neil dropped by a critical
through armour, then Dominic alone at 1%, failing three times, then dying. That
is what three effective attackers costs at a table when a fight goes long.

Corrects a mechanism I had written twice: a disabling hit does not remove an
attacker, it charges 30 points off physical or manipulation per locationEffectsFor.
Bhattacharya keeps swinging at 10 for four rounds. A slope, not a cliff.

Neither guard fired and neither was wrong: the fight sits in the 25.5% the swing
does not cover, and no guard measures the tail because all of them average.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:59:06 +01:00
slaguru666andClaude Opus 5 dc801f7e9f CLEAN GROUND v0.11: Dodge 68, and Ashcroft is busy rather than sidelined
Two holes a peer session's per-agent measurements pointed at. Their scratch
figures are not used; both edits are sourced to published numbers or to the
guard that now holds them.

EXPOSURE gains a fifth point: the party's sheets read about twice as effective
as they are, and the number doing it is the column's Dodge 68. That is
arithmetic off two published values, not a measurement -- Dodge 68 on The
Quiet Neighbours (tools/content.mjs, printed by the bestiary) and one defence
spent per incoming blow, so a 40% attack against a fresh dodge connects about
one time in eight. Nothing in the document said so, and grep for "expect to
hit" / "hit rate" / "lands about" returned nothing at all.

The point carries its own way out: DEFENCE_STEP is -30, so a defender who has
dodged once meets the next blow at 38%, then 8%, then the floor. Spend their
dodges rather than out-shooting them. That is also the other half of why the
four-player cut is a different game -- three attackers cannot spend three
defenders' dodges and six can, so the missing attacker is the one who would
have made everybody else's shots land.

The recorded landing rates agree and are now citeable (16-18%, above the naive
one-in-eight because most blows are not the first of their round), so the
sub-bullet cites tools/attackers-baseline.json rather than asserting.

The scaling note's Ashcroft warning gains the sharper reading: he is not
sidelined, he is busy and ineffective -- 9.2 attacks a fight of which 1% land
and 0.1% disable anybody, both held by check-attackers. A player who cannot
act knows to do something else; a player rolling once a round for an hour
thinks he is fighting. So the note now says what to give him instead, using
only what is on his sheet: First Aid 40, Dodge 63, Insight 63, the anchor.

Verified rather than relayed: Dodge 68 at tools/content.mjs, DEFENCE_STEP -30
and the per-round reset in rules.mjs and simulate.mjs, the three fighters'
skills at 40/40/35, and the per-attack rates re-measured independently across
three seeds before the baseline existed (17.6-18.3 / 17.4-17.6 / 15.5-16.2 /
1.0%, against the recorded 17.8 / 17.3 / 16.2 / 1.0).

npm run check: 14 guards, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:58:14 +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 a0d15ad7a9 CLEAN GROUND v0.10: EXPOSURE reframed on the first disabling blow
The old EXPOSURE paragraph read the fight at round five and quoted bucket
figures no guard held. Round five is where the outcome becomes legible; round
two is where it is decided. Reframed on the first disabling blow and cited to
tools/first-blood-baseline.json, so every figure in the paragraph is one the
build re-measures:

  cut   50.3% first blood at mean round 1.8, 25.5% vs 65.5% wipes, swing 40
  line  54.1% at round 1.3, 7.8% vs 23.2%, swing 15.4

An earlier draft carried 43 for the cut swing from a single seed. Three seeds
give 40.0. The note stays in the document.

Also adds a Casting coupling warning: the declared cast in the `cast:` marker
is load-bearing on check-rollable and check-firstblood, and re-casting
invalidates the recorded swing. Verified rather than assumed — swapping
braithwaite for agyeman in the marker fails both guards and the build, and
check-firstblood names the fix:

  "The recorded swing describes the old cast; re-record with --update,
   do not revert the cast."

npm run check: 13 guards, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:50:07 +01:00
slaguru666andClaude Opus 5 60995b28c2 R-267 addendum: read the cast, do not copy it
I told the scenario session that this guard turns a silent re-cast into a build
failure, and it accepted the coupling on that basis. The cast was hardcoded, so
a re-cast would have left it measuring the old six and reporting success.

Reads the <!-- cast: --> marker check-rollable established, drops the two the
scaling note drops by name rather than by position, and refuses to run if the
marker is gone. Verified by re-casting CLEAN GROUND in a throwaway worktree and
watching the guard name the change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:45:38 +01:00
slaguru666andClaude Opus 5 996bd248d3 CLEAN GROUND: one status block, and the end of the build archaeology
A stand-in GM opening this had no single place to start: the state of the document
lived across three playtest logs, twenty commit messages, a status paragraph and a
checklist at the foot. Worse, the two written accounts had drifted apart — the
status paragraph still said the scenario "wants a stress re-run of Act Four, a
player-chair pass", both of which were done and committed, and the foot checklist
still said v0.8 in a v0.9 document with a sentence broken in half.

STATUS now sits at the top and answers the questions a GM actually arrives with:
what version, can I run it (yes, from this file alone), is it convention-ready (no —
never played by human beings, no print pack), what playtesting exists and at which
seeds, how long it runs and where the cuts are, how many players.

Then the three things that are not obvious, as pointers rather than restatements,
because GM ESSENTIALS is where rules live: opposed rolls use a scenario-local ruling
since rules.mjs has none; Ashcroft cannot fight and his player must be told before
the game; and the lethal encounter is the column in Act Two, not the monster in Act
Four, which cannot hurt anybody.

The foot checklist is deleted rather than corrected. Every item on it was either
struck through or now in STATUS, its history is in the logs and the commits, and
keeping a second account of what is outstanding is what let the first one drift.
Open Questions stays as the single home for decisions and STATUS says so in a line
instead of holding a copy — the same rule this repository applies to rule formulas,
applied to prose about state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:42:39 +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 8e5a30a263 CLEAN GROUND v0.9: the anchor cannot fight, and the cut is three
Reading two narrated fights from another session showed Dominic attacking at
`vs 1`, four times in one of them. Confirmed against the sheet rather than the
log: weapons is empty and his only combat skill is Firearm (Pistol) 43, with
nothing to fire. So the four-player cut fights with Renshaw and Bhattacharya on
compact pistols and Braithwaite on a knife — a quarter of the party out of any
fight, and a large part of why EXPOSURE already said violence at four players is
party-ending without knowing that was the reason.

A defect I introduced. "He carries no weapon, and the GM must not fix it" was
written as characterisation about an anchor having both hands full, and was never
checked against a fight.

Not fixed by arming him, which would cost the character to solve what is really a
briefing failure. Said out loud instead: his entry now states the 1% plainly and
instructs the GM to tell his player BEFORE the game that Dodge 63, First Aid 40,
Insight 63 and the anchor are his set-piece. A player who is not told will roll at
1% for twenty minutes and conclude the sheet is broken, which is the actual harm
here. The scaling note carries the three-attacker arithmetic and points at Agyeman
as fixing the sum as well as the lane; EXPOSURE names the cause in its own prose.

Also recorded in the pass-3 log, deliberately NOT applied: the bimodality now has a
mechanism — both sides disable at half max HP, so each disable thins the return fire
and makes the next likelier, and first blood is a true coin worth 43 points of wipe
rate in the cut against 14 at six-vs-six. It reproduces exactly. It stays out of the
scenario because it comes from a reading tool with no guarded baseline, and a
GM-facing page may not carry a figure nobody can re-derive. The duller published
sentence is the checkable one. If that measurement gets a guarded home the paragraph
should be reframed around the first disabling blow, which is the better frame.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:39:22 +01:00
slaguru666andClaude Opus 5 19ed0b5f0f R-266: the coin is the first disabling blow, and it lands in round two
Reading both ends of the four-player cut — seed 5's sweep and seed 2's wipe —
says the fight is not decided by round five but by whoever lands the first
disabling hit, on average in round two. Both sides disable on one good blow, so
each one thins the return fire and makes the next likelier; nothing pulls a
fight back toward the middle.

tools/first-blood.mjs measures it: 49.7/50.3 on who strikes first, and 23.1% vs
66.5% wipes on either side of that. Six against six is the control at 8.6% vs
22.4%. A reading tool, not a guard — it records nothing, and no figure from it
goes on a generated page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:36:05 +01:00
slaguru666andClaude Opus 5 6b95a2c5a5 CLEAN GROUND v0.8: the player chair found the refusals, and both needed deciding
Desk pass 3 is committed alongside — full session from the player chair, choosing
against the scenario at every junction, seed 5580 with one corrected branch at
7731. Every failure case and every pass-2 fix held. What it found instead were the
two things players do that no GM-chair pass can reach, because both are refusals
rather than rolls.

"We are not going through." Three acts live on the far side and the document had
no line about declining to cross, which is the most professionally sensible choice
a party can make after reading a coroner's note about a man who walked himself to
death. Answered now with a reason and not a compulsion: the entry is pegged to a
tree that no longer exists, so a seam with no peg does not sit still — waiting does
not make it safer, it makes it unfindable, and the forty-one go with it. Registry
says that once and leaves the choice alone. A party that still refuses gets a real
ending in ten minutes rather than four railroaded hours.

"Can we cancel it from in here." A Research fumble and an Anomaly Lore success both
landed on a question the document could not answer either way. Decided rather than
fudged: cancellation is a REGISTRY ACT ON OUR SIDE, because the entry is a record
and the record is held here. So it cannot be done from inside, somebody has to be
back through before it happens, and anyone still in the duplicate when it goes goes
with it, having never been filed as being there. The Close now costs not only what
they choose but who is standing where when it takes effect, and if they are bringing
anyone through those two questions collide.

Also: Ray Teasdale, the clearance foreman the timeline always implied and the roster
never carried — a Persuade success in pass 3 bought an interview with a man who did
not exist. Act One's failure case no longer assumes the party is at the farm, since
pass 3 spent the act twenty miles away and the automatic notch sat uselessly on the
other side of the county. The re-cut is answered on a success as well as a fumble,
with the reason: the entry is pegged to the older authority and there is no older
tree. Fix 7's conditional target moved onto the countdown row, because I missed it
myself eight minutes after writing it. A special for reading the child.

And the bimodality paragraph, which took three attempts to earn its place: held when
its citation failed, verified after another session found the cause — three separate
random generators, so a seed named a different fight in each tool — and corrected
once more when its illustration turned out to compare targeting modes rather than
the two ends. Published now as 23% nobody down against 45% everybody, decided by
round five, with the focus-fire claim removed.

Twenty-three fixes across three passes. The cadence is complete; what remains is the
print pack and two decisions that are not a scenario's to make.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:34:37 +01:00
slaguru666andClaude Opus 5 e84c85be02 R-265: name the denominator the two-down row is zero out of
The round-five race line read "zero in 2000 fights", which invites the reading
that no fight in the run was a wipe. It is zero out of the 84 that reach round
five with two of the three down — a small bucket, and the sentence should say so
before the figure goes to print in a scenario.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 01:17:51 +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 796bb7df73 CLEAN GROUND: the second EXPOSURE table, and four ratios that moved with it
Another session re-pinned the main exposure table at 8f3e1ff after the harness
correction, and left the surrounding prose to me, which was the right split. Two
things were still outstanding.

The four-player cut carries its OWN table, nested inside a numbered list well below
the main one, and it was still on pre-correction figures: 3.24 / 2.09 / 40.2% / 0.85
and 4.00 / 3.98 / 99.1% / 1.45. Re-measured to 3.25 / 2.31 / 45.0% / 0.98 and
4.00 / 3.99 / 99.8% / 1.46. Easy table to miss and worth naming in the provenance so
the next reader does not have to work out which tables were in scope.

Four prose claims quoted ratios that no longer held. Six of the column wipe one party
in seven rather than one in twelve; ten wipe nine in ten rather than five in six; at
four players three wipe nearly half rather than two in five; and six hollow men take
a median thirteen rounds, not twelve. "Three of them is survivable" now also says
somebody dies in about one run in six, because deaths per run went 0.11 to 0.16.

Independently confirmed rather than taken on trust: my own run of the section's
published command, at its own declared cast of six and seed 11, matched the other
session's figures exactly on every comparable row — understudy 0.55 / 0.01 / 0.0% /
2 rounds, six of the column 4.79 / 3.03 / 14.8% / 1.55 / 14 rounds, ten 5.97 / 5.87 /
90.8% / 2.75, six hollow men 3.48 / 0.42 / 13 rounds. Two sessions measuring
separately and agreeing is better evidence than one measuring twice.

The argument the section exists to make is unchanged and slightly stronger. The
understudy still cannot hurt six agents; the column is deadlier; the steep stretch is
still between six and ten bodies. The cause is nothing about the column — a torso
vest no longer protects a head, so every unhelmeted agent is easier to hurt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:59:45 +01:00
slaguru666andClaude Opus 5 6e3ea8fa19 playthrough: read a scenario's own encounter, against its own cast
The tool could only narrate the guard's fight — the frozen four, at the pack size
focus-baseline records. CLEAN GROUND's EXPOSURE table measures something else entirely: a
declared cast of six at counts of 1, 3, 6, 10 and 15, which is the fight a GM running that
case will actually referee and the one whose numbers we have just re-pinned.

--creature, --count, --party, --seed and --mode now mirror tools/simulate.mjs, so a row
measured there can be read here in words. Positional form still works.

  node tools/playthrough.mjs --creature quiet_neighbours_npc --count 6 \
    --party ashcroft,bhattacharya,renshaw,braithwaite,pollard,okonkwo --seed 11

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:55:45 +01:00
slaguru666andClaude Opus 5 8f3e1ff48e CLEAN GROUND: re-pin EXPOSURE against the corrected harness
The table was measured at 5450521 and marked due for re-measurement, because R-264 changed
what simulate.mjs measures: hit location by body plan rather than species, armour per
location rather than one scalar, and flyers grounded by a lost wing. Re-run at 6f33525
with the cast, counts, runs and seed the section already declares.

The finding the section exists to publish is unchanged — the understudy still cannot hurt
six agents (0.55 hurt, 0.01 down, 0.0% wiped) and the curve between six and ten of the
column is still the steep part. What moved is the cost of fighting the column, and the
reason is the armour fix rather than anything about the column: a torso vest no longer
protects a head, so every unhelmeted agent is easier to hurt.

  six of the column   8.6% -> 14.8% wiped, 2.38 -> 3.03 down, 1.18 -> 1.55 deaths/run
  ten of the column  83.5% -> 90.8% wiped, 5.69 -> 5.87 down
  six hollow men      0.15 -> 0.42 down, 12 -> 13 rounds

Both spot-checked by running the published command rather than my own script, which is the
claim the section makes to a reader.

This file is timevans-c0's work and the re-measurement was its open note; it committed v0.7
first, so this lands as a separate commit on top rather than entangled with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:52:09 +01:00
slaguru666andClaude Opus 5 6f33525b3a CLEAN GROUND v0.7: pass 2's seven fixes, and an opposed roll the game has not got
Desk pass 2 stressed Act Four across five contrarian branches at seed 9314 and is
committed alongside. The fixes from pass 1 held — beat 3's ungated tell worked and
the four-beat spine stopped the sprawl — but the stress found seven more holes.

The serious one: rehearsal step 5 is beat 3's guaranteed fallback, and in branch C
both sides rolled successes, which rules.mjs has no way to resolve. It defines no
opposed roll at all, while the hollow man's tactics text, slice-text.mjs and this
scenario all ask for one. Same class as Scent and Science (Botany): content naming
a mechanic the engine cannot perform.

GM ESSENTIALS now carries a SCENARIO-LOCAL ruling so the case is playable — both
roll, better band wins, ties to the agent, both fail and nothing happens — using
only banding the game already has, labelled a local ruling rather than a rule.
rules.mjs is untouched: whether the system gains a real opposed roll is not a
scenario's call, it is logged as an open question, and the text says to delete the
local ruling if that rule ever lands.

Verified against rules.mjs's own banding rather than by eye. Branch C now resolves
to Okonkwo resisting on equal bands. Branch D, with fix 7, has the understudy
prefer Ashcroft if he accepted THE OFFER, resisting at Difficult —
applyDifficulty(85,"difficult") is 42, the system's own grade and not a number I
invented — which finally makes the fiction and the numbers agree, because a
compromised anchor was previously the hardest man in the room to take.

Also in: the early-kill branch, which pass 2 measured at ten minutes of act and
thirty-five of dead air, now continues as Braithwaite's twenty minutes over a body
wearing Prichard's face, with nobody having yet asked where the real Prichard is;
a miss for Psychology, since the best line in the document cannot sit behind one
40% roll; a critical result for Borrowed Authority, which happens one roll in
twenty-five; step 5's target named as the lowest POW in the room, Okonkwo at 55%,
because a thing that has practised thirty years takes the easiest file; and one
sentence saying the hollow man's anchor immunity does NOT transfer, set up by
Ashcroft being skipped in Act Three so that he draws the wrong conclusion.

The EXPOSURE table is marked correct-as-of-5450521 and still needs re-pinning: the
harness rewrite landed at ad5fd57 and moved two of its four creatures. Not done
here, because it was a note in pass 2 rather than one of the seven.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:50:20 +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 104b588e86 R-262: the second R-261, renumbered, deferring to the one that was committed
Two sessions numbered an entry against the same committed log, which ended at
R-260, and both picked R-261. Mine was written first but was uncommitted, so the
other session could not have seen it and had every reason to think the number was
free. When it staged docs/REVIEW_LOG.md my entry was sitting in the file, so it
went out inside 1d915c5 under that commit's message, and HEAD reached origin with
two R-261 headings in it.

Mine renumbers to R-262 and moves below theirs so the log stays monotonic. Theirs
keeps R-261 because theirs is the one that was committed; this diff moves and
renumbers nothing but my own prose, and their entry appears in it only as context.

The hazard is now a matter of record in both directions: their note at the foot of
R-261 caught check-rollable arriving from another session mid-build and handled it
without clobbering anything, and this is the same collision on a file whose
convention is to append to the end. A shared working tree makes the end of
REVIEW_LOG.md the likeliest place for two sessions to meet, and an uncommitted
entry there has no claim on its own number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:16:04 +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 48216bae10 Focus fire across all 47, and the same confound one turn later (R-260)
Measured in simulate.mjs. For each creature, the pack size whose spread-fire win rate
lands nearest 50% — a fight at 0% or 100% cannot show an effect, which is why R-259's
first sample was three-quarters useless — then focus versus spread, 2000 fights across
three seeds.

31 of 47 creatures had a pack size that could show anything. Focus fire helped 31 of 31,
by more than that row's own noise 31 of 31 times, mean +12.8 points. Best is 6x The
margin, 40.4% -> 61.1%. The other 16 are unmeasurable rather than unaffected: eleven are
trivial six at a time, five are hopeless in pairs.

R-259's decomposition was confounded, and I wrote it while correcting a confound. It
credited the defence ladder with 4-5 points by comparing dodge <=30% against dodge >=55%
— but the low-dodge samples were PAIRS and the high-dodge ones TRIOS. Held at a fixed
pack size, dodge is worth 1-2 points. Pack size is the driver: 8.0 points for a pair,
14-15 for three or more, then flat. What focus fire buys is that a dead creature stops
attacking and a half-dead one does not attack any less.

Two failures, one cause: comparing groups that differ in more than the thing being
measured. The control is cheap and I did not run it until the third time.

The bestiary advised concentrating fire and gave the ladder as the reason — right advice,
wrong reason. It now gives the reason that survives measurement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:03:02 +01:00
slaguru666andClaude Opus 5 bf8db68383 CLEAN GROUND v0.6: the eight fixes desk pass 1 earned, and its log
Desk pass 1 is committed alongside, seed 4271 and the roll stream printed, so
the run is re-checkable rather than remembered. It failed the scenario, which is
what it was for.

The pre-flight audit did the real damage before a die was rolled: Track, Navigate
and Science (Botany) were named by the acts and rollable by nobody in the cast,
and Botany is base 1 — the same unusable-test defect fixed in Act Four two
commits earlier and left standing in Act One. check-scenarios cannot see this; it
resolves skill names and has no opinion on whether anyone present can roll one.

Then the dice found the rest. Xenology (Baseline) failed and Act Four had no
route left, which is the concentration risk logged as an open question arriving
as a dead end on the first run. A fumbled Anomaly Lore had no written result and
I invented a ruling at the desk. Act One's failure case backstopped arrival
rather than the peg. The act ran 4:25 against 3:55 on three lucky rolls.

Fixed: the notch is automatic and the three broken routes moved onto Spot,
Knowledge and Anomaly Lore; every roll in the document now carries a written
miss; the Act Four tell is ungated, with Xenology buying it early and privately
instead of holding the only key; the fumble is answered by the re-cut delusion,
tried once, failing in silence; Act Four has a four-beat spine because with no
mechanical threat the structure has to be in the text; and THE OFFER finally
fires Ashcroft's seam, priced at 2 Coherence he must accept before being told.

Verified by re-running seed 4271 through the rewritten acts, so the identical
rolls that broke the run were put back through the fix. Every one now has an
answer, and Act One's three must-land clues all land despite two failures.

Timing is improved and NOT solved: cuts recover 20-25 minutes, landing near
4:00-4:05 on a 3:55 budget, and on-the-minute is a fail for strangers. A third
cut is likely needed and the candidate is named. Whether Act Four's spine holds
45 minutes, and whether THE OFFER plays as a decision, cannot be settled from a
desk and are recorded as such.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 00:00:29 +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 15da21d265 CLEAN GROUND: the understudy cannot hurt them and the refugees can
Measured rather than asserted: tools/simulate.mjs, 2000 runs a row at seed 11
against the named cast of six, at a clean tree, with the command in the section
so any figure can be re-checked after a rule moves. Re-ran two rows to confirm
determinism before publishing them.

The understudy alone is 0.48 of six hurt, no wipes, median two rounds — a 1d3
punch at 60%. Six of the column wipe one party in twelve and ten wipe five in
six, with the curve between them almost vertical. So the lethal encounter in
this scenario is not the monster in Act Four, it is the refugees in Act Two,
which is the case's moral argument turning out to be its combat maths too.

Four consequences written up for the GM: do not buff the understudy, because the
horror is that it passed every test the party had; adding hollow men buys rounds
and not danger, and six of them cost a median twelve rounds that the Close then
pays for; the column must never become a threat, for the second reason that the
table will lose; and the four-player cut is a different game, where three of the
column already wipe two parties in five.

Every row is a floor — the harness models no bleeding, Panic, Coherence, cover
or fire modes, and Panic and Coherence are both live here. Act Two now carries a
pointer to the section, since that is where the dangerous encounter is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:45:01 +01:00
slaguru666andClaude Opus 5 5450521ed2 CLEAN GROUND: four sheets of paper, one of which is a trick
CG-H01 is a coroner holding something he cannot file — unfiled man, ninety-year
-old joints on a forty-year-old body, and boots resoled nine times by hand,
which is the premise in one detail and is not pointed at.

CG-H02 is the 1962 minute. Paragraph 4 has the Registrar warning that a place
recorded as the place the displaced go must afterwards be furnished with
displaced persons, and the Committee noting it without regarding it as an
obstacle. Paragraph 5 orders a review that was never held. It is minuted by
A.E.W., Clerk (Junior), who is Bhattacharya's predecessor, so that sheet goes
into his hand and nothing is said.

CG-H03a and CG-H03b are one almanac page each, eleven years apart, same hand
and same ruled paper. Seven days of weather and miles walked, and a column
headed TO CLEAN GROUND reading 41 mi on every line of both. Laid side by side
there is no roll and no explanation: the table says it out loud. The counts
carry the cost — fifty-three then, forty-one now.

Numbers checked against the rest of the document: 41 survivors including the
six hollow men, 11 duplicated miles, and the 41-mile figure inherited from 1962
when it was true on the real map and untestable from inside ever since.

Still outstanding: a print-ready A4 pack at 11pt. H03a and H03b must print
identically or the trick dies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:38:13 +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 bb10613992 CLEAN GROUND: three things the GM says out loud, and the words they must not
The crossing, the first sight of the column, and Ivy at the stump — placed in
their acts rather than collected in a section, so the GM meets each one where
it is needed. Four sentences, three and three.

The notes around them carry more than the prose does. The crossing must not say
nuclear, 1962 or ash: the Geiger says the first, Bhattacharya says the second,
and ash is a word a player should reach for unaided. The column's six at the
back are the hollow men and are one item in a list, not a pause. Ivy's question
is reported rather than quoted so it survives both branches, and she does not
argue, plead or bargain — anything that makes her pitiable cheapens a decision
that has to be taken by people who respect her.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:30:06 +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 ee4e687c6c CLEAN GROUND casts six from the duty roster, and Scent was never a test
No new sheets: Ashcroft, Bhattacharya, Renshaw, Braithwaite, Pollard and
Okonkwo, cast for six non-overlapping lanes with an act each. Skill figures
read out of expandFromRegister rather than retyped, so the compendium stays
the authority.

Two defects the casting found. Scent's base chance is 0, so the 1% floor was
the whole of it and Act Four's "only test that works" was a roll nobody could
make; it is Xenology (Baseline) now, which is what the skill is for. That puts
the one reliable test on Braithwaite's sheet alone, so the scaling note refuses
to let him be dropped and the risk is logged as an open question.

Bhattacharya's private box now carries RESERVE ENTRY, which hands the
briefing-or-discover decision to a player instead of the GM.

Still an outline: no read-aloud, handouts, Act Four exposure model or playtest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:14:23 +01:00
slaguru666andClaude Opus 5 ca040b7926 The belly is not armoured (R-254)
R-95 made losing a wing change the animal. R-96 took the hide off the
wing so losing one was possible. Separately each was right and each was
measured. Together they gave the party a soft target and deleted it the
moment the party hit it:

  winged     6/20 ranged faces land on armour 0 (a bare wing)
  quadruped  0/20

A service round on borough ground averages 3.75 after halving — 3.75
through a bare wing, 0 through hide 7 — so grounding the creature took
the party's chance of winning from 44.9% to 1.4%, while its own tactics
told the GM that grounding it was the fight they could actually win.

Found by measuring something else. Every aimed-shot configuration made
the party worse, and the most aggressive was the worst: aiming at a wing
at -20% grounds it in 99% of fights and HALVES the win rate to 10.1%.
Buying the objective reliably was the fastest way to lose, which only
makes sense if the objective is a trap. So no aimed-shot rule; the
problem was never the lack of one.

It also retires the "+31 points" I reported for grounding at 1.7.3. That
was win-rate-if-grounded against win-rate-if-not — selection bias, since
the parties that grounded it were the ones shooting well. Forcing the
grounding gives the causal value and it was -43. A correlation of +31
and a causation of -43 out of the same mechanic.

The fix: a heraldic beast is armoured the way it is drawn, across the
back and the flanks. On its belly half that hide is no longer in the
way, so exposedHideFor halves natural armour while system.grounded is
set. Hide 7 becomes 3.

                      flying   grounded   worth
  on borough ground    45.3%     51.7%    +6.4
  off borough ground   90.6%     97.9%    +7.3

Modest deliberately. Grounding should help, not decide — the borough is
still the real answer. Losing its damage modifier was worth +26.5 and
would have made the wing the whole fight.

check-anatomy gains a sixth property: a flyer must not be harder to hurt
on the ground than in the air. The first version probed at a single
4-point round and declared the fix broken, because 4 sits inside the one
band where a bare wing beats a halved hide — below 5 damage the creature
really is better off grounded, above it much worse. Integrating across
1-12 gives 2.83 flying against 3.75 grounded and reads it correctly.
Negative-tested against 1.7.8's behaviour, which it names.

The lethality baseline is unchanged: its simulation never grounds
anything, so system.grounded never comes up there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:57:31 +01:00
slaguru666andClaude Opus 5 97fe2704e8 Measure creatures over 2000 runs, not 200 (R-253)
R-252 established that a recorded figure carried several points of slack
at 200 runs and that only more runs narrows it. This is that.

Measured by taking every creature under four different base seeds and
looking at how far the four answers sat apart, which is the noise a GM
reading the bestiary is unknowingly trusting:

                   mean spread    worst creature
  200 runs          1.28 points     11.5 points
  2000 runs         0.36 points      2.7 points

The worst case is what mattered. One creature's published wipe rate
could be eleven points from the same creature rolled with different
dice, on a figure printed in docs/BESTIARY.md for somebody to plan an
evening around. Under three now.

It corrected 19 of 47 published figures, mean 0.35 points: the arrears
6% -> 8.9%, the supporter 44% -> 46.8%, the nuckelavee 13.5% -> 16%.
Each was a sampling artefact printed as a property.

The cost is the part worth recording, because it is why this was never
done. I guessed "maybe a minute of build time" when I suggested it,
which was wrong by a factor of thirty and would have been a fair reason
to say no:

  check-lethality alone   0.29s -> 1.88s
  whole nine-guard suite           2.55s

BESTIARY.md regenerates from the baseline, so its run count follows
automatically; its header now also says the seeds are derived per
creature, since "at seed 11" stopped being the whole truth in R-252.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:33:40 +01:00
slaguru666andClaude Opus 5 2dbea88d4b Seed the lethality sim per creature (R-252)
Each creature now derives its own seed from its key, FNV-1a mixed into
the base seed, instead of every one of them being measured against seed
11.

This is the second reading of the request and the one I had not tested.
What I checked before was position independence — whether a creature's
numbers depend on its neighbours — and that was already true and still
is: inserting a creature ahead of the barghest changes 0 of 47 existing
rows under the new scheme, same as the old. What was NOT true is that
each creature had its own dice. All forty-seven faced the same two
hundred sequences.

Measured, because the argument for doing it is better than the result:

                                  shared 11   per-creature
  mean wipe rate across bestiary     5.67%        5.67%
  mean agents down                   0.538        0.522
  rows changed                         --        34 of 47

Seed 11 was not biasing the book. There was no systematic luck to
remove, and the aggregate is unmoved to two decimal places. What the
change buys is decorrelation: the error in each row no longer comes from
the same draw as every other row.

The useful number fell out of the comparison rather than the change.
Individual creatures moved up to four points of wipe rate purely from
being handed different dice — the courier 3.5% -> 7.5%, the long walker
66.5% -> 62.5%, quarantine unit 84.5% -> 88.5%. That is the sampling
noise inside any single recorded figure at 200 runs, and it means these
numbers are an exact regression fingerprint and a loose description of a
creature at the same time. Only more runs narrows the second; more seeds
does not. R-252 says so on the page.

seedMode is recorded alongside the numbers and checked, because changing
how a seed is derived moves every row without changing SEED itself. A
baseline from the old scheme is now refused rather than compared against
this one silently and wrongly — verified by running the new code against
the old file before re-recording.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:28:45 +01:00
slaguru666andClaude Opus 5 e54ce9130d R-251: measure what ddc4f99 actually did, four commits late
The baseline needed no re-recording — e5dc9b5 already captured these
numbers, it matches HEAD's code exactly, and it differs from 4b71859's
in precisely the 26 entries ddc4f99 moved. What was missing was any
record of WHAT that change did, since it was folded silently into a
commit about wings.

Isolated by replaying 322389b and ddc4f99 against the same party, seed
and counts, so the figures are ddc4f99's own and not contaminated by the
four commits after it:

  creatures whose numbers moved          26 of 46
  deadlier                                3
  less deadly                             8
  unchanged wipe rate, moved elsewhere   15
  mean change in wipe rate               -0.17 points

Routing every blow through a hit location REDISTRIBUTED lethality
without raising it. Creatures with one big attack got slightly less
dangerous, because a blow that used to come off a single pool now has to
beat the armour covering wherever it landed; creatures that attack often
got slightly more dangerous, because they now accumulate damage in a
limb and disable it. Across the bestiary those cancel to within a fifth
of a point. Largest moves: the courier 6% -> 3.5%, the arrears 7.5% ->
9.5%, the stanchion 26% -> 28%, the long walker 68% -> 66.5%.

The log entry also records the two corrections I owe this thread, both
from explaining before measuring: the seeding was always per creature,
and the claim in check-lethality's header that I called untrue was true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:24:34 +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 77465baa30 A flyer with a hole in a wing is a quadruped (R-95)
The supporter's tactics have said this since it was written: "It is a
flyer, and a flyer with a hole in a wing is a quadruped: disable either
wing and it is on the ground for the rest of the scene, which is the
fight the agents can actually win."

Playing a full combat against it showed the sentence was decoration.
`flightLost` was computed, printed on the NPC sheet as GROUNDED, and
read by nothing. Both wings disabled cost 20% off PHYSICAL rolls, and
Brawl is a MELEE category, so woundPenaltyFrom returned 0 and a grounded
supporter attacked at exactly the 75% and exactly the 1d6+1+2d6 it had
in the air. In the played fight it was grounded from round five and went
on to kill one agent, knock out a second and leave a third dying.

Losing a wing now changes the animal. A plan declares what it becomes —
winged says `grounded: "quadruped"` — and hitLocationRoll drops the
creature onto that plan the moment a wing is disabled or destroyed. The
wings leave its hit chart and the six ranged faces they covered go back
to the body: vital hits rise from 3 faces in 20 to 5. Every other wound
moves across on the R-94 remap with its severity intact.

The wing's own damage is deliberately dropped rather than carried, and
cleared in its own update so the remap never sees it. A ruined wing must
not arrive as a ruined foreleg — the creature is not lamed, it is
earthbound, and the injury has already been paid out as the change of
shape.

check-anatomy gains a fifth property: a declared landing must exist,
must have no wings of its own, must not itself be a flyer, must have
room for every wound the flyer was carrying without two collapsing onto
one location, and must not heal a disabled location on the way down.
Three induced failures confirm it fires; the third — landing somewhere
with too few locations — passed at first because I had not stated the
capacity property, and the guard was sharpened until it caught it.

Measured, and reported honestly: this does NOT deliver the fight the
tactics text promises. Over 4,000 simulated fights the win rate moves
12.7% -> 12.1% on borough ground and 85.8% -> 86.3% off it, because
grounding only happens in 9% of fights there and arrives late. What
actually decides this encounter is ARGENT AND GULES: 11.4% win on
borough ground against 85.4% off it. The wing rule is now real and
mechanical, but the creature's advice still points the GM at the wrong
lever.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:02:30 +01:00
slaguru666andClaude Opus 5 4a80cc1eca Body plans keep what a wound MEANT, not just its number (R-94b)
1.7.1 carried wounds across a change of body plan by moving the raw
points. Playing it showed that is the wrong invariant. A location is
disabled at its maximum and destroyed at twice it, those maxima come
from `frac`, and the plans disagree — an arm is 0.35 of the body and a
foreleg 0.42 — so moving the number silently changed the meaning.
Measured across the 20 plan changes: 234 severity flips, 138 of them
turning a location that was merely disabled into a destroyed one. A
destroyed arm arrived as a working-ish foreleg; two disabled arms
stacked onto one Vesh grasping limb and arrived destroyed.

So severity is now the invariant. A hurt location arrives hurt, a
disabled one disabled, a destroyed one destroyed; the points are
rescaled to the destination's own maximum to keep that true, and two
wounds sharing one location take the worse rather than the sum, because
relabelling a creature's anatomy must not destroy a limb that nothing
in play destroyed.

Correspondence is now stated rather than inferred. Every location gains
a plan-independent `slot` (lowerR, upperL, vital, head...), and the
remap matches on slot first. The previous version inferred it from
`kind` plus table order, which inverted left and right on every
crossing: a wounded right arm came back as the opposite hind leg. Where
a destination has no matching slot the fallback prefers the same side
and the same height, so a wing becomes a foreleg rather than a hind leg
and a left wing becomes a LEFT arm. The vesh and the cadence are not
bilateral — one grasping limb, five interchangeable units — so their
slots carry no side, which is a fix to my own first pass: giving them
sided slots was a category error the side guard caught.

check-anatomy now proves all four promises over every ordered pair and
severity band: nothing is stranded, severity is preserved, left never
becomes right and fore never becomes hind between bilateral plans, and
no update deletes a wound it is also setting. 432 carried wounds, 0
flips, down from 234. Five induced failures confirm each assertion
fires; two of my first attempts at those did NOT fire and were sharpened
until they did.

Played through in a live world: a church grim filed as a humanoid with a
destroyed right arm, a disabled left arm and a hurt chest becomes a dog
with a destroyed right foreleg, a disabled left foreleg and hurt
forequarters, and converts back to exactly the wounds it started with.
Its penalties correctly stop being manipulation and start being
movement — a man with two ruined arms drops his weapon, a dog with two
ruined forelegs cannot run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 16:42:57 +01:00
slaguru666andClaude Opus 5 a90c4f3e71 Carry wounds across a change of body plan (R-94)
locationDamage is keyed by location id and prepareDerivedData reads only
the keys belonging to the current plan, so changing speciesProfile on a
wounded actor stranded every wound it had. Found on a live Barghest that
had been shot in the leg and chest as a humanoid and then switched to
quadruped: 5 points under `legR` and 7 under `chest`, exactly its 12
missing hit points, sitting under keys no quadruped location answers to.
It read on the sheet as a badly hurt dog with eight pristine locations.

Stored and never read — the same defect this system keeps producing.

rules.mjs gains remapLocationDamage, which carries wounds across by KIND,
because kind is what the rules act on. Exact-kind matches are claimed
first in table order, so the obvious pairings land before any fallback
competes for them; that ordering is what makes the arms become the
forelegs going one way and the forelegs become the arms coming back.
Overflow past a location's destroyed ceiling is reported, not dropped.

It also gains locationDamageReplacement, which exists because the first
version of this fix had the bug it was fixing. Foundry merges object
updates so the outgoing keys must be deleted by name (R-93), but a
deletion and an assignment of the SAME key do not both apply — the
deletion wins. quadruped and winged share every location but the wings,
so switching between them emitted `-=hindLegR` alongside `hindLegR: 5`
and threw the wound away. Caught in live testing, not by reading.

Guards, both negative-tested before being wired in:
  - check-anatomy now walks all 20 ordered plan pairs and asserts every
    wound lands on a location the destination has, the arithmetic closes,
    the round trip does not leak, and no update both sets and deletes the
    same key.
  - check-rules gains 13 spot-checks over both new rules.

Verified in a running Foundry: baseline -> quadruped -> winged ->
quadruped -> baseline conserves all 12 points with no orphans, returns
to exactly {legR: 5, chest: 7}, and whispers the GM a receipt of what
moved each time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 16:29:16 +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 748e061b56 CLEAN GROUND: an outline where the department authored its own monster
A v0.1 rough outline, freely adapted from A Hollow Tree (2023) — the walk
north that never arrives, and a predator that imitates. Everything else
comes out of the system: a 1962 reserve entry that was never cancelled,
forty-one people looping eleven duplicated miles, and Borrowed Authority
as a detector that can only tell filed from unfiled.

Uses existing bestiary entries only. Marked not-runnable: no pregens,
read-aloud, handouts, Act Four exposure model or playtest yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 13:25:44 +01:00
slaguru666andClaude Opus 5 339d5dd8dc test runs the guards through bun, not through npm
`test` was "npm run check", so reaching bun meant shelling out to npm
first. It is "bun run check" now — one runner, and npm is out of the
path.

Not the eight-guard chain copied into `test`: that is the same shape of
mistake the rule formulas made before rules.mjs existed, a list written
twice and changed once. `check` stays the only place the guards are
named.

Verified: npm test exit 0, bun run test exit 0, bun run check exit 0.

Worth knowing: bare `bun test` does NOT run this. It is bun's own test
runner, it matches no files here, and it exits with an error. The ways
in are `npm test`, `bun run test` and `bun run check` — which is what
the README already tells you to use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 17:44:16 +01:00
slaguru666andClaude Opus 5 54a759e057 Run the guards under bun, including the two new ones
Rebased onto the creature-forge work, which had added check-creatures
and check-lethality to the chain and had `check` back on node. Taking
either side whole would have been wrong: the node side loses bun, the
bun side silently drops the two new guards. So the runner is bun and the
guard list is upstream's, all eight of them.

Only `check` and `readme` move. build, icons, portraits, bestiary, mj
and simulate stay on node — they were never part of this change, and
build writes LevelDB packs through classic-level.

Verified before committing: each guard run individually under bun, then
`npm run check` end to end, exit 0.

  check-rules       103 rules, 33 files
  check-kits        OK
  check-lang        995 keys, no collisions
  check-templates   19 templates compile
  check-behaviour   67 behavioural tests
  check-scenarios   9 files, 62 mechanic tags
  check-creatures   101 actor specs across 8 sources
  check-lethality   46 creatures, none drifted more than 6 points

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 17:41:27 +01:00
slaguru666andClaude Opus 5 0c0996858f A GM screen taken from the rules that actually run
The figures a GM reaches for mid-scene, on one page: the order of
operations, the circumstance ladder, how a roll bands, reaction, the
combat round, hit locations, wounds and bleeding, both tracks, the way
home, the stations, and the environment.

Every number was checked by running the function rather than by reading
it — 58 of 58 against rules.mjs, including the eight that moved out of
ringbrp.mjs. That matters more than usual here, because `design/` still
carries four rulings the code has overtaken: hit points as CON+MAS, a
Major Wound at max(9, HP/3), "no hit locations, final", and marksmanship
style fixed by home-world gravity. rules.mjs is the authority and the
page says so at the top.

It is also written to drop straight onto a Loom board: 34 cards, 23
links, nothing swallowed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 17:18:51 +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 414dbd7037 simulate --spread: one number was the wrong instrument
THROUGH TRAIN Act Three publishes a single figure for its standoff - "the party is
wiped in 24% of runs" - and tells the GM not to soften it. Measured against every party
the duty roster can field, that fight runs from 0% to 100%.

It is not that the published number is wrong. It is that no single number can be right
for this fight, because the answer was settled at character selection: four armed
postings walk it, four trades are massacred, and the GM reading one figure is reading
somebody else's session.

--spread measures the encounter against every C(16,4) party - 1820 of them, exhaustive
rather than sampled, about a minute - and reports the floor, the median and the ceiling
with the parties that produce them. Exhaustive on purpose: a GM planning a session
wants the actual worst case, not an estimate of it.

It also prints each agent's effect on the wipe rate averaged over every party they
appear in, which is the line that gets used at the table. For the Act Three standoff:

  okonkwo   -43.2 points        ashcroft  +16.1
  sandoval  -17.7               nkemdirim +13.5
  holloway  -15.9               ferriby   +12.4

Okonkwo is worth forty-three points of wipe rate on his own. That is a scene-shaping
fact about the encounter that no amount of re-measuring the median would surface.

The published tables are NOT rewritten here. What to do about a 100-point spread is a
design decision - constrain the party, print a range, or rebalance the fight - and it
belongs to whoever wrote the scenario.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 21:17:28 +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 bbe7b92459 Stop tracking LevelDB's LOG; it churns on read, not on change
LOG is LevelDB's diagnostic text — "Delete type=3 #1" and compaction notes — and it is
rewritten every time a pack is OPENED. Not rebuilt: opened. So all eighteen went dirty
whenever anything merely read the compendia, Foundry and any verification script
alike, and pack churn kept turning up in commits that had not touched any content.

Verified disposable before ignoring rather than assumed: a copy of packs/npcs with LOG
and LOG.old deleted opens cleanly, reads back all 21 actors, and LevelDB writes itself
a fresh LOG on open.

CURRENT and MANIFEST-* stay committed, deliberately. They are how a fresh clone finds
the data, and ignoring them would ship compendia nobody can open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:44:38 +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 187a317a0a mj-queue: the illustrated upgrade path, without a self-bot
The Mac bridge posts /imagine to Discord with a user token. It is not ported here and
should not be: Midjourney's terms since 27 May 2026 forbid automated access, there is
no official API, and the bridge's own memory note records Discord silently dropping
its interactions from 22 July — which is what enforcement looks like from inside. A
Windows clone would be a second self-bot on the same account.

So this does everything except talk to Discord, which turns out to be most of the
work. `next` derives a prompt from the creature's spec and puts it on the clipboard;
you paste it yourself; `file` or `watch` renames the download to the canonical name
and retires the generated placeholder. One at a time, because the memory note is
explicit that concurrent jobs in one channel steal each other's grids.

Prompts are derived, not written — anatomy from the location table, scale from SIZ,
build from STR against SIZ, plating from naturalArmour — so they cannot drift from the
statblock. Three things learned by reading the data rather than assuming it:

- tactics is written for a GM, not an illustrator. "Advances by bounds, 15 m per
  round" put a rate of fire in an art prompt. role is the one-line essence and is
  already the sentence you would hand an artist. Sentences carrying mechanics are
  dropped; dates and ages are kept.
- era is the wrong test for whether something is a person. It classified Mr Bewley of
  the Registry and everyone in 1881 Cordera as monsters, because their kit is old.
- 18 creatures are species "baseline" and are not human-shaped — a black dog, a case
  file that eats case files, a machine that has decided it is the site. The prompt
  refuses to describe a body it cannot verify, and `list` names them. That is a
  statblock to write, not something to paper over here.

`sheet` emits a prompt sheet that the existing gen-art.mjs parses unchanged, so if the
bridge ever works again the whole bestiary drives from it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:33:16 +01:00
slaguru666andClaude Opus 5 9ddea43720 make-portraits: every creature gets art it cannot disagree with
Sixty of the seventy-six actors had no portrait. The sixteen that did are the
hand-made duty roster; the whole bestiary and every scenario NPC fell back to a
generic silhouette.

The point is not that this is free. A portrait composed from anatomy.mjs cannot
contradict the statblock, because it is drawn from the numbers the hit-location roll
uses: silhouette from BODY_ART, height from SIZ, build from STR against SIZ, plating
from naturalArmour, a mark for the primary weapon's class. A Cadence would get five
bodies because its table has five. Nothing has to be kept in step.

Colour carries what a GM wants off a thumbnail — whether the thing is a person,
folklore, recovered machinery, or the far side. That reads the shouted category the
role lines already carry (FOLKLORE, HORROR, FORWARD), which nothing was using, and
falls back to structure for the ones with no tag.

build-packs resolves portraits by stem and extension rather than an exact .webp
filename, so a creature gains art without an edit to content.mjs and a hand-made
raster always beats the generated diagram. A spec that NAMES a portrait and has none
is still a hard build failure.

These read as diagrammatic field-guide plates and will not match the Midjourney agent
portraits. That split is deliberate: the agents are the cast, the bestiary is case
material.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:32:53 +01:00
slaguru666andClaude Opus 5 24b183642f all-specs: one place that knows where the actors are
check-creatures collects every spec in the repo, including the rule that a scenario
cast is any exported array whose entries carry characteristics — so a new scenario is
covered the day it is written. That rule is a definition, and two more tools are about
to need it.

Lifted into tools/all-specs.mjs and imported back. check-creatures reports the same
76 specs across the same 8 sources; this is a move, not a change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:32:39 +01:00
slaguru666andClaude Opus 5 255b146753 Species anatomy moves where node can read it
RINGBRP.locationTables and RINGBRP.bodyArt lived inside ringbrp.mjs, which no tool
can import — it touches game, Hooks and foundry at load. So nothing outside Foundry
could ask how many hit locations a Cadence has, or what shape a Vesh is, which is
exactly what art generated from a statblock needs to know.

Moved byte for byte to anatomy.mjs and assigned back, the same move rules.mjs got
when the simulator needed the combat rules. One definition, two readers.

check-rules matched those tables with a regex against ringbrp.mjs and went blind the
moment they moved — it reported "could not read locationTables" and the species check
silently stopped running. It imports the value now. A guard that parses source text
breaks when the source is refactored; one that imports it cannot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:32:30 +01:00
slaguru666andClaude Opus 5 0633589061 Rebuild packs for the posting talent pools
Roster agents carry no talents of their own; they expand from their posting at build
time, so changing the pools left the compendia a version behind the register.

Rebuilt with Foundry stopped. Verified by reading the built pack back: 36 packed actors,
none carrying Unblinking, Distributed or Quorum. No lost/ directories, so the packs took
the write without needing repair.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:34:46 +01:00
slaguru666andClaude Opus 5 b0981008b3 No posting may grant a species
Eighteen postings offered vesh or cadence biology to any agent who took them: unblinking
nine times, quorum seven, distributed three. That is the root cause behind the six
characters check-creatures caught — they were not rogue, they had taken talents their own
postings advertised, and every newly generated agent would have done the same.

Unblinking is the clearest case. It is cat: species, species: vesh, and its own rule text
reads "A Vesh has no face to read and does not need to read yours". Nine postings offered
it, including Interview, Pathology and the doctor trade.

Each of the nineteen slots is replaced with a talent drawn from what the posting actually
does rather than from what was nearest alphabetically: Registry gets Requisition, the
Anchor gets Echo Sense, Disposal gets Clean Scene, Signals gets Chain of Custody and Dead
Reckoning. No pool gained a duplicate, and no talent is left unused — every legal talent
is now offered by at least two postings.

The guard lives in check-kits, which is the file that validates postings, and it imports
TALENT_SPECIES from creature-schema.mjs so there is one definition of which talents are
locked rather than two. check-creatures was looking at characters; nothing was looking at
the pools they were picked from. Verified by reintroducing unblinking on the doctor trade
and watching it fail, not merely by watching it pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:34:37 +01:00
slaguru666andClaude Opus 5 85c1062106 Pause overlay: tighten the caption, lift the field grid
Both found by looking at the thing render, which nothing had done before — the earlier
work on this overlay was verified through the CSSOM because the browser pane would not
composite frames, so the geometry and timing were confirmed and the LOOK was not.

Michroma is already a wide face, and .4em on top of it set GAME PAUSED at 176px against
a 190px field: 93%, running bracket to bracket, so the readout was competing with the
lock for the same width. .22em measures 154px, 81%, leaving 18px clear at each bracket.
text-indent moves with it so the word stays optically centred.

The field grid was drawn at opacity .3, which composited over the void band is 1.44:1
against its own ground. On a dark table map that is not a faint grid, it is no grid at
all. .45 measures 1.80:1 — present as texture, still far below the 3.1:1 --rb-line is
chosen for, so the amber lock stays the only thing asking for attention.

Both measured in a browser with Michroma confirmed loaded, not estimated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:27:17 +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 4ded53f9b5 Fix: npm run build has never worked on Windows
Both spawners built their argument with a file URL's .pathname, which on Windows is
"/C:/Users/...". Node resolves that against the drive root, so the build spawned
"C:\C:\Users\...\check-rules.mjs" and aborted on a module it could not find.

update-readme.mjs had the same bug and swallowed it in a bare catch, so instead of
failing it silently wrote a README whose guard box was empty — "0 guard lines" where
there should be four. A Windows build therefore could not happen, and if it had, it
would have quietly deleted the guard output from the README.

Same root as the packs/ CRLF bug: this repository has only ever been built on macOS and
Linux, where .pathname happens to be a usable path.

Also adds check-creatures to the pre-build guards, which it was missing, so the build
and npm run check now refuse the same things.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:15:33 +01:00
slaguru666andClaude Opus 5 2c547efe72 simulate: a lethality harness that uses the real rules
THROUGH TRAIN publishes "MEASURED, 400 runs each" and a 24% wipe rate; the starter
publishes its own table. Nothing in this repository could produce either number, so both
were claims rather than results and neither could be re-checked after a rule moved.

The harness knows no rules of its own. Everything comes out of rules.mjs, because a
harness with its own copy of the combat loop measures a game nobody is playing and
drifts silently the first time a rule changes. Two things had to move for that to be
possible:

  applyDifficulty, resolveBands and gradeRoll -> rules.mjs. The whole of d100 resolution
  lived in ringbrp.mjs, which node cannot import. All three are pure, so they moved and
  the engine re-exports them; no caller changes. check-rules then refused them for having
  no spot-check, so they now have fifteen, covering the 1% floor, the 96-99 rule and
  00-always-fumbles. They had none in their entire life inside the engine.

  expandFromRegister -> tools/expand-spec.mjs. A roster agent is written as a job and a
  rank and carries no skills or kit; everything it can do is derived at build time by a
  function locked inside build-packs.mjs, which cannot be imported because importing it
  runs the build. A simulated agent expanded by a second copy of that logic is not the
  agent that gets packed. Moved verbatim; both sides import one copy.

Deterministic throughout: same creature, party and seed, same numbers, which is what
lets a published table be verified rather than remembered.

What it finds: for the Act Three standoff the seed barely matters (0.5-2.8% wipe across
five seeds, eight rounds throughout) and party composition dominates everything. Four
support agents against the six miners wipe 92.8% of the time; swap in the heavy and the
warden and it is 0.3%. The published 24% is not a property of the encounter, and no
single figure can be.

Models Reaction re-rolled each round, banding, graded defences, the stacking defence
penalty, damage by band, major wounds and the dying clock. Does NOT yet model hit
locations, bleeding, panic, cover, range bands or fire modes, all of which make a losing
fight worse. Read the output as a floor, never a ceiling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:10:05 +01:00
slaguru666andClaude Opus 5 c3002e93c9 check-creatures: validate every actor spec, and fix the vesh talents it found
Nothing validated creature specs. check-kits validates postings and check-scenarios
validates prose; buildActor throws on an unknown skill or item key and is silent about
everything else, so species, marksmanship style, characteristic blocks, species-locked
talents and misspelt field names could all be wrong and still build, pack and play.
Every packed actor once shipped as species "hominid" and the whole cast was hit-located
as something the rules do not contain.

tools/creature-schema.mjs derives every allowed value from the catalogues rather than
retyping them: skills from SKILL_CATALOGUE, kit from the item catalogues, species from
SPECIES, stats from CHARACTERISTIC_DICE, and the two marksmanship styles out of the
language file. It reports every problem with a spec rather than dying on the first, and
names the nearest real key. It also reports fields buildActor never reads, because
"armours:" is not an error, it is an actor that silently equips nothing.

On its first run it found six baseline humans carrying vesh biology. "unblinking" is
cat: species, species: vesh, and its own rule text reads "A Vesh has no face to read".
Out of Step is reclassified as anomalous with a Coherence cost, which is what every
talent in that category has and what the Anchor who is three years older than his
birthday says always wanted to be; the other five are swapped to agency and field
talents that fit them.

Not fixed here: the root cause is upstream in postings.mjs, where twelve posting talent
pools still offer unblinking, distributed or quorum to any agent. Picking replacements
across those is a design decision, not a mechanical one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:09:43 +01:00
slaguru666andClaude Opus 5 10f588b666 Plan: the creature forge
Five parts - schema and validator, lethality harness, forge CLI, procedural portraits, Foundry importer - built on three findings: the species machinery (vesh, cadence) is fully implemented and completely unused, the published measured lethality tables cannot be reproduced by anything in the repo, and RINGBRP.figures already holds the per-species geometry procedural portraits would need.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:08:36 +01:00
slaguru666andClaude Opus 5 c6924d6ea8 THROUGH TRAIN: art prompt sheet, and a runner for it
Prompts for all 43 assets - 16 duty-roster portraits, 14 NPC portraits from TT_CAST, and the 13 scene plates, none of which had a recorded prompt, so none of them could be regenerated.

Art direction is frontier newspaper engraving, and the present-day Bureau material is drawn in the same 1881 hand as Cordera with modern people composed as tintype portraits, so the two halves read as one document. Three rules are written into the sheet because they protect meaning rather than style: nothing modern is drawn on the 1881 side, the train is absence rather than a glowing spectre, and nobody is lit as a villain.

tools/gen-art.mjs parses the sheet rather than keeping a second copy of the prompts, expands the style tokens and drives mj-gen. Dry run by default, because --run fires paid generations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:08:36 +01:00
slaguru666andClaude Opus 5 b31b1028a9 Rebuild the pause overlay as a containment hold
The uniselector rotated forever, which is the one thing a hunting stepping switch never does, and it was carrying two outright bugs: the motion trail pointed the way the arm was going rather than the way it came, because conic angles run clockwise from the origin and the tip was declared first; and the hub pip ran on a free 2.6s sine against a 7.7s arm, so the light drifted permanently out of phase with the mechanism and meant nothing.

Replaced with an instrument that acquires, locks, reports and lets go: a static field, four corner brackets that close and snap, a scan line reading down the field at exactly cycle/3 so its third pass always lands on the lock, and a readout welded to the same cycle. Every layer reads --rb-pause-cycle, so nothing can drift. The field offset is derived from the column rather than measured and pasted in.

Also ignores the impeccable hook cache and keeps its config, which records why broken-image is suppressed for a stylesheet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:08:23 +01:00
slaguru666andClaude Opus 5 2332b449ba Refresh package-lock
The committed lockfile predated the handlebars devDependency and carried the wrong name and licence metadata. Regenerated by npm install; no dependency actually changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:08:22 +01:00
slaguru666andClaude Opus 5 f0edd54e83 Mark packs/ binary so Windows checkouts stop corrupting the compendia
core.autocrlf=true rewrites packs/*/CURRENT from "MANIFEST-000002\n" to "MANIFEST-000002\r\n" on checkout. LevelDB then tries to open a manifest whose filename ends in a carriage return, fails with "The filename, directory name, or volume label syntax is incorrect", and Foundry runs RepairDB across all eighteen packs on first launch.

The packs are therefore broken from the moment they are cloned, and the only visible symptom is a pile of lost/ directories in git status. Linux and macOS checkouts are unaffected, which is why this went unnoticed.

The committed blobs are already LF, so no renormalisation is needed: this only affects the smudge side.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 21:29:11 +01:00
slaguru666andClaude Opus 5 214e207cc6 THROUGH TRAIN: north bench plate without the park bench
Midjourney read "bench" as furniture in the first version. Regenerated with the
word removed and "no furniture" stated: two coated figures from behind, empty
ground, a dry gully, the town's water tank on the far horizon. Emptiness is the
subject, which is what the scene is for - they arrived where the paperwork says
the railway is.

All thirteen plates are real art and there are no placeholders left.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 19:34:51 +01:00
slaguru666andClaude Opus 5 8a4d093a13 THROUGH TRAIN: the Bureau plate, and no placeholders left
tt_11_bureau regenerated after the first attempt timed out, converted to the
house plate format. All thirteen plates are now real art.

A version of tt_12_northbench without the word "bench" in the prompt is still
rendering; the current one is good apart from the park bench Midjourney put in
the desert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 19:33:59 +01:00
slaguru666andClaude Opus 5 67b6f61c64 THROUGH TRAIN: two of the three new plates are real art now
tt_12_northbench and tt_13_standoff replace the placeholder copies. Converted to
the house plate format, 1024x682 webp, to match the other ten.

The Bureau plate timed out in Midjourney at 1800s waiting for a grid and is still
a placeholder; it is regenerating.

Known flaw in tt_12_northbench: Midjourney read "bench" as furniture and put a
park bench in the Nevada scrub. Everything else about the plate is right - two
coated figures, empty ground, no railway, the town smoking on the horizon - so it
ships while a version without the word regenerates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 19:32:55 +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 99e78f440a "Somebody you knew" costs one point, not two
At two, the closing beat of a hard case broke four agents in five at the same
moment. A whole party freezing or bolting on the final scene reads as arithmetic
rather than grief, which is the opposite of what the trigger is for. Found by
playing THROUGH TRAIN end to end.

Replayed the same beat from the same party state — Holloway 3, Okonkwo 2,
Bhattacharya 4, Nkemdirim 3, Finch 2 — with the new cost: one agent breaks, the
rest carry it at 3 and 4 with the penalties that implies. That is the shape it
wanted.

"It said your name" stays at two, and is now the only two-point trigger in the
list, which is right — it should be the singular worst thing that can happen to
somebody.

The rules journal and the player primer both generate their trigger tables from
PANIC_TRIGGERS, so both already print the new number.

Version 1.4.2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:38:45 +01:00
slaguru666andClaude Opus 5 c05d488b9d Play THROUGH TRAIN end to end; fix three scenes the dice could not reach
Ran the whole case with five agents — briefing, kit swap, Mercy Draw, the
crossing, Cordera, the water tower, the four minutes, the locomotive, the
brakes, the register. It completed, and it exposed one serious defect and two
smaller ones. All three are the same shape: the scenario put dice in front of
things the fiction says should simply happen.

  1. THE ENDING WAS UNREACHABLE. Convincing Kell to stop was gated on Persuade
     or Status. Only THREE of the ten duty-roster agents have either trained —
     the Field Lead has Persuade 15 and Status 15. Three separate attempts on
     him all failed and the scenario had no way to end; I narrated it as working
     because otherwise there was no ending, which is exactly the improvisation a
     scenario is supposed to prevent.

     It now takes NO ROLL if the party can tell Kell one true thing about his own
     train — the coal, the four minutes, the north bench. He has waited a
     hundred and forty-five years for somebody who knows what is wrong to say
     so. Only a party that knows nothing rolls, at average, and a failure costs
     a pass rather than the ending.

  2. THE PAPERS NEEDED PERSUADE. Showing one side the other's document is the
     move the scenario is proudest of, and it sat behind Persuade at −30%:
     15% became 1%. Both men WANT their paper read — Merritt is proud of his
     letter, Ambrose's whole grievance is that nobody has looked at his writ.
     No roll, between passes.

  3. THE BOARDING SORTED ITSELF BADLY. Athletics runs 25–60% across the roster.
     Putting all five on the water tower left the doctor and the Registry officer
     missing twice each, carried across on the department's plank, passengers in
     their own set-piece, two passes gone. The text now names which route suits
     whom before they choose.

What worked, and is worth recording: the automatic clues absorbed two failed
rolls in Act One without the trail stalling; Standing Order 9 caught Okonkwo
checking a wristwatch in an 1881 street and cost a Coherence and a clock tick;
the fumble-is-a-fall rule fired on a 100 at the tower for five damage; the
exposure table held — one hit across the whole fight, on the agent who left
cover to work on Doyle; and the reset landed exactly as intended, with Doyle
standing up at the top of the car and Nkemdirim's hit staying where it was.

Version 1.4.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:34:23 +01:00
slaguru666andClaude Opus 5 391c6a3679 Rebuild packs cleanly
LevelDB LOG timestamps only — no content change. Committed so the tree matches a
build made with Foundry stopped, which is the state the published archive should
carry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:27:52 +01:00
slaguru666andClaude Opus 5 5bc46879c7 THROUGH TRAIN: put the gunfight inside the loop
The loop is no longer just a train going round. It is FOUR MINUTES with a
gunfight in it, and those four minutes have run about two million times.

Two sides, and neither is the villain. Cass Merritt and three hired men have a
signed letter of engagement from the company telling them to surrender the
payroll to nobody "upon any pretext whatsoever, including the presentation of
any paper". Josiah Ambrose and four Cordera miners have a sealed writ of
attachment from a territorial judge naming that same payroll against eleven
weeks of unpaid wages. Both documents are genuine. Neither man has seen the
other's. The company sold the same money twice and is not aboard. Both are
handouts now, and they only mean anything held in the same hand.

The mission is restated everywhere it appears — briefing page, case brief, GM
essentials: CLOSE IT. Closing the crossing does not kill anybody, it lets them
go. The loop ends, 3 November 1881 resumes from 11:44, and everyone aboard walks
forward into the life they would have had — including Ambrose and a miner named
Doyle, who did not survive the real four minutes. The agents are not choosing
who lives; they are choosing to stop holding history still, having met the
people it lands on.

The standing order is louder in three places: NOTHING MODERN GOES THROUGH.

Two things the playtest found, both now written into the text:

  - THE FIGHT WAS A TPK GENERATOR. Modelled with all nine shooters firing at the
    party it wiped them 171 times in 200. The model was wrong — the two sides
    are shooting at EACH OTHER — so the scenario now carries a measured exposure
    table: head down draws nothing, acting from cover costs half a party member
    and no wipes over 300 runs, moving in the open costs one, and going for the
    payroll box wipes more than half of all parties. The GM is told to say that
    last row out loud BEFORE anybody commits to it.

  - THE LOOP RESETS AND THE AGENTS DO NOT. Everyone who belongs to 1881 is back
    up unhurt on the next pass. The agents keep their wounds. Nothing said so,
    and a player who assumes otherwise takes risks that kill them. It is now
    stated in capitals in both documents, with the played pass that prompted it
    — two of four down at 0 hit points, one untouched because he stayed on the
    floor.

Nine cast members, eight handouts, ten plates.

Version 1.4.0.

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

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

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

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

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

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

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

Version 1.1.3.

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

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

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

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

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

Version 1.1.2.

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

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

One thing I got wrong and one thing worth knowing:

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

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

Version 1.1.1.

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

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

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

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

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

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

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

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

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

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

Version 1.1.0.

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

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

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

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

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

Version 1.0.7.

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

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

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

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

Version 1.0.6.

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

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

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

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

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

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

Version 1.0.5.

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

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

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

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

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

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

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

Version 1.0.4.

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

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

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

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

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

Version 1.0.3.

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

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

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

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

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

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

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

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

Version 1.0.2.

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

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

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

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

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

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

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

Version 1.0.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 18:42:05 +01:00