Chrome headless is the only print engine on these machines, and its page
rules are real: one section per page, tables and read-aloud blocks never
split, cite markers dropped, and the scenario's own 01 plate as a cover.
npm run pdf docs/scenarios/<FILE>.md writes docs/print/<FILE>.pdf.
Named make-pdf rather than scenario-pdf on purpose: check-scenarios treats
every tools/scenario-* file as scenario text, so the first name swept a
stylesheet's point sizes into the figures corpus.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
640 lines to 462, thirteen journal pages to ten. The story is unchanged: the
slow, the unarmed 1974 Board, the committee that only takes its own clause 5
evidence, the vote that springs Annex D, and the children who already know
the song. What went is the apparatus around it — the countdown table, the
clue trail, the exhibit sub-sections, the eighteen-row location list, the
separate Undersigned and red-herring sections (now one paragraph), and
SO-H06. Three acts, three exhibits, three pressures, three choices.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rebuilt to Tim's brief. Time below runs at half speed, so the intake looks
fifty-odd and is eighty-odd, and the Seat's own instruments agree with Centre
that the war happened. The Board goes in dressed as 1974 and unarmed; Act Two
is a committee that only accepts the three exhibits its own clause 5 names.
Winning the vote is what arms the trap: surfacing triggers Annex D, and the
Address is Mr Smith's song. The children are found after the argument is won,
not before. Hatcher's Undersigned are the red herring and the key to telling
a forged mark from a real one.
Handout pack and the warden actor follow the new brief; SO-H06 added.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The stuff between the numbered rooms, in the same 1974 two-ink screen print:
the blast door from inside, the stair, the exchange, the laundry, washroom,
stores, water gallery, cloakroom, kitchen, chapel, workshop, library, barber's
corner, mushroom beds, play space, a family cabin, battery room, ventilation
gallery, haulage incline and the memorial garden. All twenty ride in the
adventure's plate list as 21-40.
Six were regenerated for daylight or the wrong century, and so_27 needed three
attempts: 'changing room' is this scenario's blocked phrase. Both lessons are
in the art sheet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Generated on the first attempt once it was the only job in flight, which is
another mark against the banned-word story it was used to support.
One thing to know before it reaches a table: a martlet is the heraldic bird
drawn WITHOUT FEET, and this plate has feet, with claws. The prompt says "the
little bird drawn with no feet, which therefore has none" — and naming a
thing to exclude it puts it in, which is recorded in the vault from the CLEAN
GROUND art ("no birds, no sheep" produced livestock). Fixing it means
rewording the creature's own description positively, which is content, so it
is left for Tim rather than changed here.
moor_cat is the one that did not land, and it is now well characterised:
its job is ACCEPTED and STARTS — the bot posts — and Midjourney then DELETES
its own post. That is the image being flagged mid-generation rather than the
prompt being refused, which is why six attempts, three hypotheses and a
prompt bisection all came to nothing. It keeps its procedural portrait, which
since this evening is drawn as a quadruped with eight hit locations rather
than as a man.
~/bin/mj-blocked now distinguishes all three modes: nothing posted (refused
before starting), posted and deleted (image flagged), posted and still there
(genuinely running). The first version would have called the deleted case
"not blocked" and been wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0b51d4e claimed the martlet's three stalls were caused by the word "bare" and
said it was diagnosed rather than guessed. It was guessed.
The evidence was one successful regeneration with that word replaced. Run
again afterwards with "bare" restored and nothing else changed, the same
prompt was accepted in seconds. The variable that actually differed was that
the successful run was the only job in flight — and the word was believed
because a memory note from 16 September already named it, which is a reason
to suspect a conclusion rather than to trust it.
Two further hypotheses tested and also dead: moor_cat's full prompt is
accepted immediately, so it is not blocked either; and four jobs fired two
seconds apart were all answered, so it is not a concurrency limit at that
scale. What made those two fail six times between them is not reproducible
now, and this commit does not claim to know.
The throw-instead-of-warn stays: for a genuinely banned word, a warning
printed into a log predicts a job that never arrives and never errors. The
armour wording stays too, because it reads better — not as a workaround.
~/bin/mj-blocked is the tool this needed from the start. It sends a prompt
and reports within two minutes whether the bot answered at all, because a
refusal is ephemeral and never reaches the channel, so silence there is the
only thing that separates a banned word from a slow one. Three sessions have
now spent 30-minute timeouts on this question.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diagnosed rather than guessed. The martlet failed three times over ninety
minutes while the gryphon succeeded in the same background run, so it was
neither the service nor the account nor the window. Regenerating it with the
single word "bare" replaced and nothing else changed brought it back
immediately.
It comes from armourWords at naturalArmour 0 — Midjourney reads it as nudity
— and thirteen creatures carried it, every one of them a creature with no
natural armour. That band says "thin-skinned, nothing between it and a blow"
now.
BANNED returned its hits as `warnings` for the caller to print. A warning in
a log nobody reads is precisely what let this stall three times, because the
failure it predicts is a job that never arrives and never fails, which looks
exactly like the service being slow. promptFor throws now, as it does for an
over-long negative list, and says why the refusal is invisible. All 204 specs
still build; a planted "corpse" is refused.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eagle head and beak, wings fully drawn, lion hindquarters, four legs, on a
plinth — and drawn rather than plated. The first render had no wings and no
beak because the prompt read `species` where the shape lives in `bodyPlan`.
The second had the anatomy and came back as a mech, because the armour clause
opened with the word "armoured" and that is the word Midjourney draws.
Portrait and 2x2 token derived and the pack rebuilt. gryphon-fixed.webp — the
mech, kept while it was the only version with the right anatomy — is removed:
no spec referenced it, and a second picture of the same creature in the
shipped art is a thing for somebody to pick up by mistake.
Worth recording for the diagnosis of the other two: this succeeded in the
same background run where martlet and moor_cat each failed a third time. So
the failures are not the service, not the account and not the window. It is
those two prompts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LevelDB renumbers its files on every build, so this is filename churn around
unchanged content: 70 weapons, the 10 natural attacks still carrying
flags.ringbrp.plans. Committed so the tree is clean rather than left dirty
for the next person to wonder about.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The three sidebar buttons build actors through Actor.create, compendium reads
and a dialog, and check-behaviour says in its own header that it cannot reach
any of that. The derivations are covered by 450 forged combinations and 210
posting × tier pairs; the glue is covered by nothing, and no world was
running to exercise it.
So this is the shortest path from a launched world to knowing. Every expected
value in it was derived by running the code — drawKitFor for the kit rows,
forgeCreature at seed 7 for the forge rows — rather than written from memory.
It also says what a failure means: a wrong number points at the derivation
and should reproduce from the CLI, while a missing item or a dialog that does
not open points at the glue and will not.
One figure in the first draft — "476 guarded combinations" — was invented.
Replaced with the two the guards actually print. A verification document that
carries a made-up number is worse than none, because it is read as the
authority on what to expect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"When I create a new NPC or Threat they do not have a set of skills" was the
original report, and the kit fix did not cover all of it. A generated NPC
carried its posting's core plus two support — about eight rows — so a GM
asked for Listen or Dodge or Spot by a player had nothing on the sheet to
roll.
generateCharacter has called grantFullSkillList since it was written, and
build-packs gives all 62 to every packed creature, so the generated NPC was
the only actor in the game without them. It calls it now, in the same place
the PC path does: after the items, before stowOverload.
check-generator holds all three construction paths to it. A shared finishing
step that one of three callers omits is invisible from inside that caller,
which is why it is worth a check rather than a memory. Proved by removing the
call again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three tools read spec.species to decide a creature's SHAPE and all three were
wrong the same way, because every creature with a non-human body carries
species "baseline" and says what it is in bodyPlan:
mj-queue drew a wingless gryphon
make-portraits drew a human silhouette for 48 creatures
bestiary published "26 of them are not human-shaped" when it is 74
Each was found by looking at the neighbour of the one before, which is not a
method — the fourth would have been found by a fourth accident. A line that
feeds a shape function from species without naming a resolved plan is the
whole defect, in one line, every time, so it is checkable.
Proved against the real thing rather than a planted one: checking out each
file's parent commit from history makes the guard report that file, at the
line the fix touched, and the current tree is clean.
The first version required the literal `bodyPlan` on the line and so reported
all three FIXED call sites, because the correct form resolves a local `plan`
once and then falls back to species on the next line — and that fallback is
right. A guard that calls the corrected code broken is exactly what
check-rules was reported for earlier tonight, one file over.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The document counted shape by `species`, which is vesh and cadence and
nothing else, so all 32 quadrupeds and all 16 winged creatures were counted
as people. It also printed each creature's hit-location count from the same
field, giving a person's 7 to a bull, a gryphon and every other body in the
book.
Third place tonight to read `species` where the shape lives in `bodyPlan`,
after mj-queue's anatomy clause — which drew a wingless gryphon — and
make-portraits' silhouette. Each was found by looking at the neighbour of the
one before it, and the third was the only one facing a reader.
The at-a-glance table marks quadrupeds Q and winged W beside the existing V
and C, because a two-ton quadruped and a man in a coat carried the same blank
and that table is the part that gets printed.
check-bestiary passes, which is worth stating precisely: it holds the
document against the statblocks, and both sides were reading the same wrong
field, so it was correct throughout and had nothing to say.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
portraitSvg read `spec.species` for both the silhouette and the hit-location
table. Every creature with a non-human body carries `species: "baseline"` and
says what it is in `bodyPlan`, so the guildhall gryphon's own generated
portrait described it as "baseline, 7 hit locations" and drew a human figure
for an eagle-lion with wings. 48 creatures were affected — 32 quadrupeds and
16 winged.
It is the same defect, in the same shape, as the anatomy clause in
mj-queue.mjs: that one read `species` where the shape lives in `bodyPlan`,
and drew a wingless gryphon. I fixed it there earlier tonight and did not
look one file over, in the other tool that draws a creature from its
statblock. R-264 settled this rule for the harness — `bodyPlan ?? species` —
and build-packs:294 writes it to the actor's speciesProfile.
Only two files change on disk, because 46 of the 48 already have hand-drawn
plates and are correctly skipped. Those two are martlet and moor_cat, whose
Midjourney art has now failed twice, so they are exactly the creatures
falling back on the generated figure: martlet is winged with 10 locations and
moor_cat quadruped with 8, where both were human before.
The other 139 rewrote byte-identically, which is what a deterministic
generator should do and is worth knowing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It opened "Status: plan only. Nothing below is built" while four of the five
phases had shipped, and its state table described a repo of 76 statblocks
with no simulator and no portraits — 204, six measuring guards and 47 plates
ago. A plan that misreports what exists sends the next reader to build it
again.
The table is kept rather than deleted because the design argues from it, with
a second column saying where each row landed. Part 5 is marked built and NOT
yet run in a world, which is the honest state.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measuring the tier knob showed it works — one creature against the four
agents check-lethality freezes, deaths per fight climb 0.01, 0.04, 0.32,
0.91, 1.43 across the five tiers for a large brute, and monotonically for
hunters and flyers too.
It also showed the labels oversell. "Deadly" at tier 3 kills a third of an
agent and wipes the team three times in a thousand. A GM reaching for a word
like that is owed the figure behind it, and the forge's whole argument is
that its output can be interrogated — the tier list was the one part of it
that could not be. Each tier now carries a measured `worth` line, printed by
`forge list` and under the tier selector in the dialog.
check-forge measures the ladder and fails if it ever goes down, across three
roles and five tiers. Both the labels and the worth lines are claims about
behaviour that any change to the skill bands, the hide base or the
characteristic multiplier could falsify without touching a word. Proved by
weakening tier 4: three roles report the inversion by name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A creature with no entry in the baseline was printed as a note and then
counted in the success line, which read "every one fighting exactly as
recorded" while N of them had no record to fight differently from. The claim
was false by exactly the number in the note above it, and a note is the part
a green build does not get read for.
It is the same shape this guard was already on the wrong side of once: before
the seam fix it measured 47 creatures and printed OK for 184.
Recording a new creature is one --update in the commit that adds it, which
this guard's own failure message has always instructed. Proved by planting a
creature with no baseline: it now stops the build and names it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gryphon fix added six armour negatives and three weapon ones, which took
the prompt to eight --no terms. This project has a recorded case where ten or
more never returned a grid at all while the same prompt with four came back
in two minutes — and a job that never arrives is indistinguishable from a
moderation refusal, because those are ephemeral and never appear in the
channel either. It cost twenty minutes of wondering whether the account was
out of fast hours, and the note about it was written down precisely so it
would not be diagnosed from scratch a second time. I walked into it anyway.
Trimmed to three armour terms and one weapon term, which puts every prompt at
five. promptFor now throws above six and names the offending terms, so the
next person to reach for a long negative list is stopped at the tool rather
than by a half-hour stall. All 204 specs build under the cap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
check-kits has asserted this for the 36 postings since it was written — "every
posting can use what it carries" is in its success line. Nothing asserted it
for the creatures, and 35 of them hold a weapon they have no skill for. The
hollow man has a dagger and no knife. The hulk haunt has a boarding axe and
no axe. One PREGEN carries a pistol she cannot fire.
It is not cosmetic. simulate.mjs sorts a combatant's arms by expected damage
and an unskilled weapon sits at the 1% floor, so it loses to the creature's
own fists every time: the statblock advertises a dagger and the thing
punches. That is R-310's defect written down rather than generated.
Recorded, not fixed. Giving each of the 35 the missing skill means choosing a
rating, and a rating is a balance decision that moves check-lethality's
numbers for every creature touched — that belongs to whoever owns the game.
The 35 are held at a ratchet that may shrink and may never grow, so no new
creature can be written holding something it cannot use.
Specs are expanded through the register first. A spec written as a job has no
skills of its own, and judging one unexpanded reports a Field Officer as
unable to fire the pistol her own posting issues her.
--update refused to create the record it needs on its first run, because with
no file on disk all 35 existing cases counted as new. The first recording is
the baseline; only later ones are held to the ratchet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CREATURE_FORGE_PLAN Part 5. A third button on the Actors sidebar, beside New
agent and New NPC. Pick what it does, how big it is and how bad it is; the
card underneath describes what those settings will actually produce, because
the argument for a derivation over a form is that it can be interrogated, and
choosing between "brute" and "sentinel" from two nouns is not interrogating
anything. The card is itself forged rather than written, so it cannot
describe a creature the derivation would not make.
generateCreature reads the catalogues out of the packs and calls the same
forgeCreature the CLI calls. Natural attacks carry their body-plan
declarations into flags.ringbrp.plans so the engine can read them.
The plan asks that the panel and the CLI cannot diverge, and importing one
derivation was most of that guarantee but not all of it: the two feed it from
DIFFERENT catalogues — content.mjs on one side, the built compendium on the
other. check-forge now builds the engine's view straight out of packs/weapons
and requires both to forge the same creature.
It found a divergence on its first run, in 40 of 90 pairs. The fallback took
the first usable natural attack from the caller's list, and that list arrives
in declaration order from content.mjs and in id order from the pack, so a
tiny vesh brute was punching for the CLI and constricting at the table. The
derivation may not depend on its caller's iteration order; it imposes its
own now.
Not yet verified in a running Foundry. The pure derivation is covered by 450
guarded combinations and the catalogue seam by the agreement check above, but
Actor.create, the pack reads and the dialog itself are the part
check-behaviour says outright it cannot reach. They need a world open, and
that has not happened yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CREATURE_FORGE_PLAN Part 3. `new` prints a spec as source in the shape the
bestiary files are written in, `explain` prints the rule behind every value,
`measure` fights it against the party check-lethality freezes, and `list`
shows the knobs.
The derivation is forge.mjs at the ROOT, beside rules.mjs and postings.mjs
and under the same law: pure, no Foundry, no catalogues. Part 5 asks for an
in-Foundry panel that forges a variant at the table and says it must not
diverge from the CLI. One derivation in a module both import is the only way
to guarantee that rather than argue it.
Natural attacks now declare which body plans can make them, in the weapon
catalogue, for the same reason a weapon already names the skill that fires
it: the weapon is the thing that knows. Anatomy cannot answer it — a body
plan records that a creature has a head, not that the head has horns.
`explain` earned itself on the first run. A large brute derived with a
baseline body could not gore, trample or hoof, and the trace said so; the
finished statblock alone would have shipped bulls that bite. The role picks
the body now, a non-baseline species overrides it, and --body beats both.
check-forge (guard 26) derives all 450 combinations of role, size, tier and
species and puts each through the bestiary's own validate(). Three mutations
were planted in the forge to test it, and it caught one. Dropping the seed
was caught. Dropping the brawl grant was a genuine no-op, since every role
already lists brawl. Dropping the body-plan filter left all 450 rows green
while the success line read "armed with something its body can use" — the
guard read the location table and never used it. It checks each issued weapon
against the catalogue's own declaration now, and the same mutation produces
500 failures.
check-rules' inline-armour pattern matched any clamp containing the word, and
false-positived three times in one evening on counts that were never damage.
damageAfterArmour is a subtraction, so the pattern requires one. A guard that
makes people contort working code to keep it quiet is training them badly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The illustrations were sitting in art/creatures/, where nothing looked for
them. build-packs resolves a creature's picture by stem under art/portraits/,
so none of the 47 reached the game.
tools/make-art.py (was make-tokens.py) now derives both from one plate:
art/portraits/<key>.webp and art/tokens/<key>.webp. Same drawing, so the
token cannot disagree with the portrait — the argument mj-queue already makes
for deriving prompts from statblocks.
Webp because every one of the 309 art files already here is one, at 160-220
KB. The raw Midjourney plates are 2.3 MB each; 47 of them would have put 93
MB into a 29 MB repository permanently, for pictures nothing renders at that
size. At quality 88 a plate is ~300 KB with the pencil hatching intact. The
plates stay on disk as working files and are gitignored.
The token lookup in build-packs was a hardcoded `.png`, so the first webp
token would have silently fallen back to the portrait and Foundry would have
gone back to cropping a 2:3 plate into a circle. Portraits and tokens now
share one resolver and one extension list.
146 of 147 npcs carry a portrait and 46 a derived token.
Also: the gryphon came back as a mech a second time. The first fix branched
the armour SIMILE on era while both arms still opened with the word
"armoured", and that word is what Midjourney draws — changing the reasoning
is not the same as changing the cause. No natural creature's prompt contains
"armour" now, and negativesFor() asks for plate, machinery and worn armour to
be left out, because a word choice is an argument and a --no is closer to a
guarantee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
generateNPC read R.core, R.support and R.label. It never read R.kit, so the
role card's DRAWS FROM STORES promised a Containment officer nine items and
the actor arrived holding a utility knife with a flat naturalArmour standing
in for armour it was never issued.
postings.mjs now owns the decision — drawKitFor(role, tier, {classify}) plus
a draw table per tier — because the engine resolves kit keys against the
compendium and check-generator resolves them against content.mjs, and the two
cannot import each other. Classification comes from catalogue membership and
never from the key: climbing_kit is "Breaching charges" and prybar is "Entry
bar", so anything reading a key's spelling is wrong in that posting first.
The weapon↔skill pairing table is gone. Each weapon item already names the
skill that fires it, so the skill is granted from the weapon, and a generated
NPC cannot hold what it cannot use. THREATS.attacks is now creature-tiers
only; those two keep their natural attacks on top of what they drew.
Armour is an item. naturalArmour drops to 0 on the four human tiers, where
the number was standing in for the item, and stays on the two creature tiers,
where it stacks with drawn armour as locationArmourFor already intends.
check-generator (guard 25) was written first and observed failing on nine
counts against unchanged source. One of its assertions was itself wrong —
it read for the creature-tier restriction after the loop instead of before
it, and so reported the fixed code as broken; re-scoped, it passes on the fix
and still fails against git show HEAD:ringbrp.mjs.
Measured with the new tools/npc-cohort.mjs, 2000 runs, seed 11, against the
party check-lethality freezes. Apex · Containment 68.2% -> 88.5% wipe,
Anomalous · Containment 0.3% -> 19.2%, and Dangerous · Field Lead got weaker
because its posting carries one pistol where the tier used to hand it a
rifle. Full table and three incidental findings in R-310.
Two of those findings are guards, not content. check-rules failed on a
comment that quoted the pattern it hunts for, so comment-only lines are
skipped now. update-readme held the guard list as a hardcoded array whose own
comments recorded it going stale twice; it reads the check script instead,
and so does the count-word rewrite that was separately capped at sixteen.
Not changed, and flagged in R-310: breach_suit and riot_shield declare no
coverage, so they armour every location including the head. The location
counterplay NPC_KIT_PLAN.md relies on does not exist, and that is a balance
decision rather than a side effect of this one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
collectSpecs() in tools/all-specs.mjs is the single place that decides
which specs exist. Nine modules were importing NPCS and PREGENS straight
from content.mjs instead, so each carried its own idea of the population
and measured a different subset of the game. check-lethality was the
clearest case: it scored 47 creatures and reported OK for all 184.
check-seam (guard 24, first in the suite) scans every module and fails if
anything but all-specs.mjs names NPCS or PREGENS in a content.mjs import.
The nine violators are re-pointed at the seam.
Re-recording check-focus's baseline against the full 184 raised it from
47 packs to 138 and surfaced one creature focus fire does not help: the
dun cow. Rather than re-record that away, the guard now requires the
advice section of BESTIARY.md to name every such exception, and
bestiary.mjs generates the sentence.
The first version of that check asked whether the name appeared anywhere
in BESTIARY.md, which every creature's own heading satisfies — deleting
the exception sentence still passed. It reads only the
"Shooting at something that moves" section now, and the mutation test
fails as it should.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bestiary.mjs built the document from NPCS, so it described 47 creatures of
147 while check-bestiary agreed with it — two things agreeing is not
verification when both read the same wrong source. It reads collectSpecs()
now: 184 creatures across 5 sections, every one with a measured lethality
figure, which only works because check-lethality was fixed first.
Three faults surfaced fixing it:
- a second NPCS reference in focusParagraph()
- which then broke on `rows`, because focusParagraph declares its own
local `rows` for focus-fire data and shadowed the creature list. Keyed
lookup now, so the shadowing cannot bite again.
- the document grouped on the raw role tag against a list of four, and
`if (!group.length) continue` DROPS an unlisted tag rather than
mis-filing it. Nine of the twelve tags were absent, so those creatures
would not have appeared at all. Twelve tags now map onto five sections,
with a new "Beasts and heraldry", and a completeness assertion counts
every creature into a section and fails if one is unplaced.
check-powers was the fifth consumer bypassing all-specs.mjs. It reconciled
its counts against BESTIARY while reading NPCS, so it called the document
wrong when the document was right.
Reading the real population then demanded 101 classifications. All 101 are
done and NOT ONE was wired. That is the right answer, not a shortfall:
the three available effects describe the creature — its own attack rating,
the damage it takes, whether it feels defence stacking — and essentially
every power is conditional on a place, a posture, a round count, a weapon
type, an attacker count, a named agent, or Coherence. R-275 records what
wiring one anyway costs: 25.1% to 75.2% on a single creature.
The near-misses are the evidence the reluctance was applied, not recited:
tangie's "HALF damage" is edged-only and ends in open flame; the_heron is
+30% or -20% depending on movement the harness does not have, so which
branch applies is undecidable; mirrorline's halving lands on an AGENT, not
on the creature, so wiring it would have protected the wrong combatant;
yale's defence waiver is capped at two attackers and its written counter is
a fourth agent, which defenceStacking:"ignores" cannot express.
Classifications live in four band files and are spread into POWERS last, so
a hand-written entry always wins a collision. 142 classified: 2 wired, 78
beyond the harness with a reason, 62 not fight rules.
All 23 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It imported NPCS directly, so once the bestiary moved into its own files it
measured 47 of 147 and printed OK. A guard blind to two thirds of what it
guards is not a guard, and this one failed silently — the worse kind.
Now reads collectSpecs(), the list check-creatures, make-portraits and
mj-queue already share. Scenario casts come in with the bestiary: they were
never measured, and they should have been, since the Act Three standoff
this repository publishes wipe rates for IS a scenario cast. 184 specs at
roughly 47ms each, about nine seconds.
Switched BEFORE re-recording, to prove the change moved nothing: the run
against the old baseline reported all 47 existing creatures fighting
exactly as recorded. The --update is then verifiably additive — 47 to 184,
137 added, 0 changed, 0 lost.
Still fail-open on arrival: an unbaselined creature is a note, not a
failure, so this printed "OK, 137 new" while 137 creatures were unguarded.
Left as the author wrote it rather than changed under a content drop, but
it is the same shape as the three classifier bugs in 00f6a1e and worth
closing.
First thing it found, invisible until now: march_stone is authored "it
kills perhaps one person a century" and measures 43.5% wipe, 2.72 of 4
down, over 22 rounds. naturalArmour 14 is the highest in the game — the
Apex tier is 7 — so the party cannot hurt it and it grinds them down. The
prose and the statblock describe different creatures.
All 23 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three faults, all in how an actor reaches the table.
THE TOKEN WAS THE PORTRAIT. prototypeToken.texture pointed at the portrait
file, which is a 2:3 field-guide plate — full figure on plain ground.
Foundry draws a token square, so it was squeezing a tall plate into it and
showing a torso. tools/make-tokens.py now derives a 512px circular RGBA
token from the same illustration: not a second Midjourney pass, because
that would double the spend and still give a token that does not quite
match its own portrait. Sized by the subject's DIAGONAL, since these
creatures are tall and narrow and padding by the longest side wasted a
third of the circle. Padded, never cropped — cropping to a square clipped
the first gryphon's head off.
EVERY TOKEN WAS 1x1. A hearth hob, a carthorse and a SIZ 32 dun cow all
held one square. Footprint now comes from SIZ, cut at 21 and 30: 132 at
1x1, 14 at 2x2, 1 at 3x3.
THE 100 CREATURES WERE IN NO COMPENDIUM. build-packs reads NPCS directly
rather than collectSpecs(), so the bestiary validated, was drawn, had
prompts written, and did not exist in the game. The npcs pack held 47
actors; it holds 147 now.
That is the fourth consumer found bypassing all-specs.mjs, whose own header
calls one-definition-only "this repository's one standing law" —
bestiary.mjs and check-lethality.mjs still do. check-lethality is the one
that matters: it cannot see the new creatures, so it is measuring 47 of
147 while reporting OK.
All 23 guards pass. npm run build is red at update-readme for an unrelated
pre-existing reason: the README advertises a guard list that package.json
no longer matches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three fail-open classifiers, each reading a field that is not where the
bestiary keeps the answer. Found by generating one creature before
spending the allowance on forty-eight; art/creatures/gryphon-u1.png is a
WINGLESS gryphon, from a spec whose bodyPlan is "winged".
1. anatomyWords() read `species`. Shape lives in `bodyPlan` — that is the
field selecting the hit-location table. 44 of the 100 new creatures are
species "baseline" with bodyPlan quadruped or winged, so every one fell
to the silent branch and asked for no shape at all.
2. isPersonSpec() ended `if (tag) return true` — an unrecognised role tag
means person. MONSTER_TAGS listed three tags; the bestiary uses twelve,
so 43 creatures were prompted as "portrait of X, waist-up portrait".
Now bodyPlan decides first, structurally, because a tag list is free
text that grows and bodyPlan is enforced by the engine.
3. ROLE_CLASS knew the same three tags, so HERALDRY fell through to
"carries nothing manufactured and armour >= 4 -> anomalous". Six
centuries of carved stone were classed as far-side machinery, and the
armour clause duly asked for a mech. ROLE_CLASS now carries all twelve,
and the heavy-armour wording follows era: machinery for future and
anomalous, "hide and scale thick as carved stone" for the rest.
Also in this commit: all 100 creatures re-pointed onto the natural attacks
added in 3feac39 — 41 of 100 changed, the rest keeping grasp or punch
because that is genuinely what they do. Three manoeuvres stay as prose and
say so explicitly: dun_cow's and bull_azure's charge, gryphon's stoop and
drop, tup's blind side. Portraits redrawn, since era decides their hue.
All 23 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The catalogue had `punch` (1d3) and `grasp` (1d6+1 blunt) and nothing else
a creature owns. Every biter and gorer in the bestiary therefore carried a
grasping limb and stated its real damage in tactics prose, which
simulate.mjs cannot read. Two silent consequences: the measured damage was
the grasp's rather than the creature's, and because grasp is blunt, the
edged and piercing wound paths never fired for any animal in the game — a
boar's tusk, a wolf's bite and a gryphon's talon all resolved as blunt.
bite 1d6 piercing gore 1d8+1 piercing
claw 1d6 edged hoof 1d6+2 blunt
talon 1d6+1 piercing trample 1d10 blunt
beak 1d6+1 piercing constrict 1d6 blunt
All fam "brawl", like punch and grasp, so they need no new skill family and
arrive usable. Calibrated against the melee line already in the table:
dagger 1d4+2, baton 1d6+2, machete 1d8+1, prybar 1d10.
Reported independently by three of the four bestiary authors. All 23 guards
pass. Nothing uses these yet — the creatures are re-pointed next.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Folklore of the isles, water and the drowned, anomalous far-side, and
beasts and heraldry — 25 each, in four source files wired into
collectSpecs(). check-creatures passes at 204 specs across 13 sources and
the full 23-guard suite is green.
The population this was meant to fix:
non-human body plan 4 -> 48 (29 quadruped, 15 winged)
vesh / cadence 5/5 -> 13/13
creatures 47 -> 147
CREATURE_FORGE_PLAN.md called the unused location tables the reason to
build any of this; the quadruped and winged plans now carry a bestiary
rather than three dogs and a heraldic beast.
Art is procedural: 102 SVG portraits composed from each creature's own
body geometry, so a cadence portrait has five headless bodies because its
location table has five. The 17 hand-made portraits are untouched.
BESTIARY_ART.md carries 204 derived Midjourney prompts for the ones worth
illustrating properly later.
Known and unfixed, reported by three of the four authors independently:
WEAPONS has no bite, claw, gore, hoof or talon, so every creature that
kills with its mouth carries `grasp` and states its real damage in prose.
simulate.mjs measures the grasp. Next commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Anomalous and Apex now draw weapons, armour and gear from the posting on
top of the natural attacks they already carry. Their hide stays, and
locationArmourFor adds worn pieces to it, so Apex · Containment stands at
17 torso armour: 7 hide, 7 breaching suit, 3 riot shield raised. Mean
damage across the 61 weapons is 6.9 and the heaviest ordinary one means
9.0, so nothing but the Unmaker reliably breaks it.
Chosen deliberately with that number in hand. The spec records the
counterplay already in the rules — the shield only counts raised, and
exposedHideFor halves hide on a grounded creature outside the vital — and
obliges Phase 5 to simulate both tiers standing and grounded rather than
assert a lethality band.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
generateNPC reads R.core, R.support and R.label. It does not read R.kit,
which generateDialog prints as DRAWS FROM STORES and generateCharacter
issues. The card promises a marksman rifle and a breaching suit; the actor
gets a utility knife.
Design: the posting says what it carries, the tier says how much of it and
how good with it. Kit keys classify by which pack they resolve in rather
than by a second hand-written list. Attacks derive from the issued weapon's
own skillFamilyId/skillSpecialisationId, which retires the weapon-to-skill
pairing table. Armour becomes an equipped item and naturalArmour drops to 0
on the four human tiers, which moves the numbers and so gets measured.
check-generator.mjs is written first and observed failing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A reload icon sits beside the ammo count in the readied-weapon strip and
the weapons table, for any weapon with a magazine. It tops the weapon up
from its reserve (rules.reloadFor, spot-checked), posts the new count and
the weapon's reload time to chat, and warns when the weapon is already
full or the reserve is empty.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The single Roll damage button becomes two, with person and paw icons. Each
rolls damage and then a hit location. A targeted token still uses its own
anatomy; with no target the blow is placed on the chosen body plan and
shown, with nothing applied. Both buttons disable after either is used.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four new portraits (Joan Wetherlaw at last, Ruth Cotter, Nell Teague,
Mr Ogden) and eleven interior plates, one for each underground location
that had none, in the two-ink civil defence style. Bewley and Prosser
reuse their THROUGH TRAIN portraits. Journals now show 20 plates and 12
faces. The art sheet corrects the v1.0 diagnosis: Joan's prompt was
being refused, not the account out of hours.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SO-H01 becomes the full Registry case brief: what the department knows
about Bellhouse, the 1974 return and the stamp, the orders, conduct,
access and the Stores issue. Nothing about the song, the children or
Centre's letters. STANDING_ORDER_HANDOUTS.html prints it on A4 with the
enclosed 1974 Seat return (SO-H02) and Mr Smith's mark.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The section now shows 13, 14, 15 and 18 on a third level with the
incline from the plant floor, instead of footnoting them as not drawn,
and the key runs 6-18 in number order in two columns. Sheet 6 moves the
incline to the plant's north end to match.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five more blueprint sheets: the 1962 dormitories and laundry (240
berths), the canteen, kitchen, bakery and officers' cabins (20), and
three sheets of the Estate, 213 family dwellings on three coal roads
(737). With Edith and Kit Marlow that is the 999; the scenario now
shows the sum. Section map issue 3 adds 16-18, and sheet 6 is mirrored
so the pipe room lies south of the plant as the section draws it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eight sheets laid out to match the section: north to the left, Level 1
lobby to sick bay, Level 2 creche to plant, an incline to the pit
galleries, and every corridor end naming the sheet it reaches. Rooms
now share walls (the corridor gap had doors opening onto rock); adds an
armoury, a pipe room beside the plant, and a Vigil hall sized for 999
standing. bellhouse-plans.mjs --check holds doors, swings, furniture,
lettering and reachability, and runs in npm run check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drawn by tools/bellhouse-plans.mjs from declared rooms: lobby, control
room, registry, sick bay, creche, vigil hall and plant, coal roads and
the Sump. 2800x2000 at 5 ft to the square, added to the Adventure and
keyed in the Locations table.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut the register mechanic, the census creature, the paperwork route and
half the clues. The under-twelves know Mr Smith's song from the nightly
creche speaker, so Act Three is a choice: burn the tape and take the
children into care, keep the families below, or cure them with a cut
tape first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tim's ruling. He comes from Outside and speaks for the powers that sleep
there, which the department has no heading for; the census in the Registry
is a Vesh working for him. The open question is marked decided.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The agents now cross into Bellhouse on a diplomatic brief to bring its people
home. A thousand people (118 of the 1974 intake, 881 born below) run the Seat
like a 1970s council, kept down by ninety-day renewals from "Centre", which
is Mr Smith, planted in THROUGH TRAIN. His experiment is procedural and
quiet: a bulletin response, a crèche lullaby, a shared dream, the children's
drawings, a steered establishment. Act One is the briefing, the moor and the
crossing into Enquiries. Act Two is the site, and the detention the Controller
orders on Centre's circular, measured and cited. Act Three is Annex D: at a
thousand the Seat broadcasts an Address that enrols whoever hears it, and the
table must silence it before or while they release the Seat under clause 4.
Same site, people and plates. Two cast actors (the wardens, Teague). New
handouts SO-H05 (Centre's circular) and SO-H06 (terms of engagement). The
section map's key is relabelled (issue 2). Status is honest: v0.7's five desk
passes tested a different plot, and this needs its own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The man who told Vane what the chart had to say now has a name: a Mr Smith,
with the company's paper, whom the company never employed. Vane names him if
asked and cannot picture his face. A "do not resolve" line says who he is not
to be explained here: he is the thread into STANDING ORDER.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A second scenario gets pack-tables' arrangement: four configs (Teague plus
one, three or five wardens) against the declared six and the four-player
cut, 2000 runs at seed 11, cap 400, recorded to detention-baseline.json,
citeable as "detention", and --check added to npm run check.
standingorder joins all-specs' SCENARIOS, and simulate.mjs now reads that
list instead of keeping its own copy, so a new cast is fightable the day it
is registered. The lethality, focus and pack baselines are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
New pack ringbrp.standingorder, in the Scenarios folder. It holds the GM
journal (13 pages, one per section), the three player handouts, nine plates
to show, six faces and the Bellhouse section map as a scene. No cast actors,
because nobody in the case has a stat block.
The GM pages are read from docs/scenarios/STANDING_ORDER.md at build time
rather than transcribed, so the compendium cannot drift from the document the
desk passes ran against. STATUS and Open questions are left out as authoring
notes; STATUS's "not obvious" list leads page 1.
Doc fixes found on the way: the Act Two heading still said ~55 min against a
75-minute budget; STATUS said four passes and "no desk playtest run" after
five; the runtime open question still called 45/55/50 unbacked.
figures-baseline: generator enrolled at 0; STANDING_ORDER.md leaves the
uncited list because it cites.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cdbb2e3 added "When two people want opposite things" to the rules text but did
not rebuild packs/rules, so the compendium has been a section behind the
source since. Content-checked against HEAD: this is the only page that differs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A narrow verification pass rather than a session: does Act Two land where v0.6
budgets it now that a failed filed-clue roll costs a scene instead of ten
minutes? Seed 4471.
THE RETRY FIX WORKS AND DOES NOT DO WHAT v0.6 ASSUMED. Forced both filed clues
to fail — about one table in seven, since each is a 37% miss at these ratings.
Under the old rule that was twenty minutes and a party standing in Registry
waiting; under the new one Pennyfeather fetches each while they do something
else and it costs nothing, while the roll still means something. So the fix
removes up to twenty minutes of VARIANCE. It does not shorten the typical case,
and Act Two uncut is 75 minutes whether or not anybody fails a filed roll.
AND v0.6 CONTAINED AN ARITHMETIC CONTRADICTION I PUT THERE. The Runtime row
budgeted Act Two at 65 — pass 4's figure, measured with nineteen minutes of cuts
taken — while the Pacing Note two screens later said the correct number of cuts
to plan for is zero. Both cannot be true; it was a ten-minute overrun written in
on purpose. The three prior passes reconcile exactly (75 uncut, and pass 4's 66
is 75 − 19 + 10), so the number was never in doubt, only which configuration it
belonged to. I measured under one configuration, changed the configuration, and
kept the number.
Act Two is now budgeted at its uncut base of 75. Acts of 45 / 75 / 50 is 2h50 of
play, 3h00 wall with the break, in a 3h15 slot — fifteen minutes of real slack
rather than twenty-five claimed ones. The cuts stay optional, which keeps Kit
Marlow's scene, which pass 4 showed is the price of the likeliest ending.
The timing section now records that 75 is the uncut base three passes agree on,
and that the retry fix moved the worst case and not the typical one, so nobody
re-derives the same error the next time the slot moves.
All twenty-one guards pass. Five passes; what is left is human beings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Decided rather than worked around. Four passes measured Act Two's floor at about
66 minutes against a budget that said 55; the alternative was twelve minutes out
of the sick bay, which is where Revelation 3's best route lives. The slot moved,
because 3h00 was an assumption and the sick bay is not.
2h50 of play in three acts of 45 / 65 / 50 plus the break, in a 3h15 slot. Act
Two is budgeted at the 66 pass 4 actually measured with every cut taken, not at
the 56 the retry fix predicts, because nothing has measured the fix. The
twenty-five minutes of slack are deliberate: the house standard says a scenario
landing to the minute with zero slack is a fail for a table of strangers, and
every figure came off a desk pass run by a GM who already knew the document.
THE CUTS ARE NO LONGER DEFAULTS, which is the real answer to pass 4's finding
rather than a workaround for it. They existed because Act Two had to lose twenty
minutes it did not have. At 3h15 the correct number to plan for is zero — and
that retires the trap where the cheapest default cut hollowed out the price of
the likeliest ending.
AND THE REBUDGET EXPOSED AN OLDER DEFECT. The Countdown put step 3 at +1h20 and
step 4 at +2h00, measured from the blast door — so under every budget this
scenario has ever had, both fired AFTER Act Two ended. Both are backstops for
Essential revelations: step 3 is the second road to the sum and step 4 is
Revelation 3's last resort. They were arriving too late to back anything up. The
whole table is re-timed inside the act at +15 / +35 / +45 / +60, the reference
point is stated, and Act One's Stealth roll moves step 2 within that window
instead of pushing Teague past the end of the act.
The act checkpoints were trailing their own acts too — the signing one left five
minutes for the decision. A checkpoint is a point you can still correct from, so
the sum is wanted at 1:40 with twenty minutes of Act Two left, and Act Three's
is ten minutes in. Running times are printed so they mean something.
All twenty-one guards pass. What is left is human beings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pass three passes had not run: an honest session at six players, no
contrarian choices and no forced branches, with the Pacing Note's cuts taken
exactly as written. Seed 6620. Nineteen minutes cut — pipe room folded, Kit
Marlow to the corridor, census dropped.
THE CUTS DID NOT CLOSE THE GAP, AND ONE RETRY UNDID MOST OF THEM. Act Two asks
for six rolls whose failure case was "ten minutes and a second attempt", roughly
two fail in an average session, and a single failed Research handed back ten of
the nineteen minutes that had just been cut. The Pacing Note's arithmetic was
sized against the act's length and never against its variance, so it could not
hold. A failed roll on a filed clue now costs a SCENE instead — Pennyfeather
fetches it while the party do something else — which keeps the roll meaningful
and costs nothing on the clock.
THE CHEAPEST CUT GUTS THE LIKELIEST ENDING. The Pacing Note priced moving Kit
Marlow to a corridor at "the drawings". This pass took that cut and then reached
the narrow row of ending 3 — the likeliest one, since it needs a single dossier
item — whose entire price is that the sixty-one stay unrecorded. Kit is the
sixty-one made into a person and her own entry says she must be a person before
she is a price. The cut is re-costed honestly, and the corridor version now has
to do her one job: she shows them a drawing and asks whether she got the blue
right.
The blanket Act One failure case named Joan for all seven clues, and Joan does
not go down the adit. Clues 6 and 7 now have their own: time, never access,
because the door has never been locked. Joan herself is at the farm AND walks up
with them, so the moor walk has somebody in it. And 13b came off the dice after
a 98 lost the reason the proper channel failed — the thematic centre of the case
— which is now written at the bottom of Pennyfeather's own sheet.
Act Two's floor is about 66 minutes against a budget of 55, measured across
three passes at 75 uncut, 70 uncut and 66 fully cut. The retry fix should
recover most of that and nothing has measured it. THE REMAINING QUESTION IS A
BUDGET DECISION AND IT IS TIM'S: move the slot to 3h15, or take twelve minutes
out of the sick bay and lose Revelation 3's best route. Written up in Open
questions and deliberately not decided here.
All twenty-one guards pass. Four passes, every content defect they found fixed.
What is left is human beings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The cadence's third pass: a full session choosing contrarily at every fork,
forced to the one ending nothing had reached. Seed 3307. The party skipped the
scenario's best NPC, told the antagonist the truth before anyone else, refused
the hospitality, split so the wrong specialist was in every room, took a
resident to the surface, and cancelled.
BOTH OF PASS 1'S HEADLINE FIXES WORK, AND ONE WORKS BETTER THAN WRITTEN. The pen
held above the paper converted the ending from pass 2's ninety-second reflex
into a real argument about whether a narrow amendment they could no longer reach
beat a cancellation they could. And Pennyfeather's spoken cost landed on a party
who had failed the birth register, so the sixty-one arrived as new information at
the moment of decision — which is better than knowing in advance, and clue 14 is
demoted to Supporting on the strength of it.
THE SUM HAD ONE ROUTE AND NO FALLBACK. Pass 1 found the Revelation 3 fallback
circular and it was repaired with three roads; nobody then asked whether the
other essential revelations had the same shape. The contrarian split put Insight
40 in Registry instead of 63, one roll failed, and the party cancelled the
Standing Order having never learned why it was urgent. Worse, the clue asked for
a roll that Pennyfeather's own roster line contradicted — she hands the sheet to
anybody who asks her a straight question. The roster line wins: the sum is no
longer behind a roll at all, the Insight now buys what she did about it, and the
establishment return she posts at Countdown step 3 is a second road for a table
that never thinks to ask her anything. Fixing an instance is not fixing a class.
TWO OF THREE HOOKS ROUTED THE PARTY AROUND THE ACT'S ENGINE. Only one hook
involves the farm, and a professional team with a grid reference drives past it
— so Act One ran fifteen minutes short with nobody in it, and both its Essential
clues lost their stated failure case, which is Joan pointing at them. She is now
at the vent head, where the woman in her own entry would be anyway, and no route
into the act can miss her.
Also: the pen pause was staged only for a Teague who stood down and now prints
the hostile version; a photograph brought back down is answered; refusing the tea
is answered; and an uninformed cancellation is written as the ending it deserves
to be, since a party who do the right thing by accident is the best possible
close for a case about people doing the wrong thing correctly.
All twenty-one guards pass.
Three passes and none of them a normal table: GM chair with bad dice, a branch
stress, and a deliberately contrarian run. Pass 3 landed on the 150-minute budget
only because one act collapsed and the other spent the difference. No pass has
run Act Two with its cuts taken. That, and human beings, is what is left.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The house cadence's second pass is a stress re-run of the weakest act. Pass 1
resolved Act Three in eight minutes of fifty and left three of its four branches
untested, so this forces each in turn on seed 8143 — and runs the three creature
encounters through tools/playthrough.mjs, the same runFight the lethality and
focus baselines come from, against this scenario's own declared six rather than
the frozen four.
THE THREE DOSSIER SCENES CHANGED NOTHING. The party assembled one item of three
and rolled 62 against 63 — exactly the roll they would have made with all three,
because Borrowed Authority says items improve the credential and never the roll.
Correct as a rule, catastrophic as staging: the act's best three scenes had no
effect any player could see, and pass 1 could not find it because there the
amendment failed and the question never arose. The items now buy scope instead.
Same roll, three different worlds: a narrow amendment that stops the ash and
leaves the site sealed, a lifted seal, or records above ground for the sixty-one
so Kit Marlow can leave.
ALL THREE CREATURES ARE INVISIBLE TO THE DICE. STERILISATION ORDER, THE
CORRECTION and ENUMERATED are all classified outOfCombat in powers.mjs, so every
measured number is a measurement of the creature's arms and legs and of nothing
that makes it frightening. That is correct bookkeeping, not a defect in the
guard, and it means the GM's text carries the whole threat at the climax. It
did not. The overwriter now comes with four printed corrections specific to this
case, and with the fact that force does not work: measured, one of them cannot
meaningfully hurt anybody and five grind for forty rounds finishing nothing, so
the end of the world was landing as an inconclusive scuffle.
EXPOSURE wiped all six in fifteen rounds with five dead, without the
sterilisation blast firing at all. The warning was right and understated. It now
prints the six-round countdown that lived only in the bestiary entry, the
argue-down as a named Borrowed Authority stretch, and what a failure costs —
five rounds reaches the adit and does not reach the crèche.
The best ending was three sentences, less than the failure branch beneath it,
and now plays out properly: the tannoy, nobody cheering, Pennyfeather filing it,
and Joan sweeping a yard that stays swept.
The four-player cut was one sentence from disaster. Science (Biology) and
Medicine are the only skills that vanish with Okonkwo and Nkemdirim, the cut
note happened to reassign exactly those two clues, and check-rollable never saw
any of it because the cast declaration names six and the cut is prose. Both
clues are now written on Knowledge and Insight for every table size, so nothing
load-bearing depends on an unguarded sentence.
All twenty-one guards pass, and the document has entered check-figures' strict
regime now that it carries citations.
Still untested: ending 1, and with it Pennyfeather speaking the cost and the pen
held above the paper — pass 1's two headline fixes. Pass 3 is the player chair,
contrarian, and should cancel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eight plates and six of seven portraits are on disk. Joan Wetherlaw's portrait
stalled three times — twice on the bridge's full 1800s window and once on
deliberately reworded prompt text, which rules out both the prompt and
deduplication. The thirteen before it returned in about forty-five seconds and
everything after ~21:20 hung, which looks like fast GPU hours running out
mid-batch.
The sheet listed every stem as though it existed and named the plates by their
pre-conversion stems rather than the webp files actually committed. Both fixed,
with the retry instructions and the note that mj-gen exits 0 on a timeout, so
the file is the thing to check and not the exit code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GM chair, all six cast pregens played as archetypes, every beat rolled on a
seeded roller (mulberry32, seed 5291, rolls consumed in printed order) so the
run is re-checkable rather than remembered. Fourteen plates and portraits too.
The pass found three things worth the afternoon.
REVELATION 3 WAS NEVER DELIVERED, AND ITS FALLBACK WAS CIRCULAR. The Psychology
roll with Marlow failed, the Larkhall fallback failed, and the only route left
was a file in Marlow's private quarters that nothing in the scenario told the
players existed — findable only by knowing the thing it reveals. A fallback that
requires the revelation it is a fallback for is not a fallback. The file now
lives in the sick-bay day room where they already have reason to be, there is a
second independent route through Pennyfeather and the 1974 return's CASUALTIES
NIL, and the Vigil is an automatic backstop under all three.
Psychology was the wrong skill anyway: the audit put the best in the cast at 40,
a coin flip on the moral centre of the case, while the Casting table credited
Renshaw with it as a strength. It is Insight now — 63, genuinely hers, and a
better fit for a woman who is ashamed rather than confused.
THE MOST OBVIOUS PLAYER MOVE HAD NO PRINTED ANSWER. The talker told the
Controller there had been no war, four minutes in, and the document said
nothing. Now printed in three mouths, plus what happens when they take a
resident up to see the sky — which works, harms nobody, and solves nothing.
ACT THREE RESOLVED IN EIGHT MINUTES OF A FIFTY-MINUTE ACT. The amendment failed
and the party cancelled inside ninety seconds, because falling through to the
end of the world is so much worse than the alternative that nobody deliberated
— and they cancelled without knowing it kills the sixty-one children, because
Pennyfeather was two corridors away. She now stands at the Controller's elbow
and says the number out loud before the pen moves, and a failed amendment holds
Sowerby's pen above the paper for one full round.
Also: Act Two ran 75 against 55 and now has a real Pacing Note; the Act One
Stealth roll bought nothing and now buys when Teague finds them; time inside
versus outside is ruled in the text; the census is marked optional because
missing it cost nothing; and Containment gets the one beat the heavy's 3/10
verdict asked for.
All twenty-one guards still pass, and check-figures still records this document
at zero bare figures.
Untested: ending 2, a successful amendment, the quarantine unit, four players.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two hundred and six people went underground in 1974 to govern the country after
the war. There was no war. The Deputy Controller filed the drill as live, on
purpose, to save the site a fortnight of arguing about whether it was allowed to
start — and reality took the filing.
The apocalypse in this one is clerical. At renewal 211 the site's population
record exceeds the surviving population of the region it governs, and a record
that cannot hold both resolves by correcting the region. Act Two shows the
players the sum; Act Three is the decision, and all three endings cost
something. Cancelling the order kills the sixty-one children born below, who
have no record above ground. The text says so and does not offer the GM a way to
make it painless.
Deliberately not CLEAN GROUND. That case is a filed valley found by accident and
resolved by cancellation; this is a filed institution the department built on
purpose, and its best ending amends the order's scope rather than cancelling it
— a Borrowed Authority problem, which is the setting's own thesis about
arrangements beating fights.
Three existing bestiary creatures, nothing new to build: the census counts in
the Registry, the overwriter performs the correction if the renewal goes
through, and the quarantine unit is the failure state you argue down.
Cast declared as a different six from CLEAN GROUND's so the two cases exercise
different sheets. All twenty-one guards pass: check-scenarios resolves 115 tags,
check-rollable holds every route to the declared cast, and check-figures records
the document at zero bare figures — it cites no measured number because it
prints none, the quarantine unit's lethality included.
The section drawing is hand-built SVG and must stay that way: every label on it
is load-bearing and no generator holds legible text. Numbered callouts with a
key beneath, per AFTERIMAGE v3.1.
NOT playtested. Not once, desk or human. Every timing in it is a guess.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rendering the pack to PDF to print it showed the GM filing slug printed on top
of the first line of three of the four sheets — both almanac tables and the
minute's "Copy 3 of 4" line.
The cause is one declaration. .slug is position:absolute at top:6mm, and what
kept sheet content clear of it was .sheet's 18mm screen padding. The print
block then said padding: 0, so in print — and only in print — content started
at the very top and ran under the slug. On screen the pack looked perfect,
which is why it survived being built, checked and shipped.
Fixed by keeping a top padding in print: padding: 11mm 0 0. Side and bottom
margins still come from @page, so nothing else moves.
check-handouts could not see this and is not at fault for it: it holds the
pack's STRUCTURE — the two almanac sheets sharing one class and one set of
columns, every line reading 41 mi, nothing under 11pt — and a collision between
an absolutely positioned element and the flow is a property of the rendering,
not the markup. It took rendering the file and looking at the pages.
The almanac trick itself is unaffected and was verified in the PDF: H03a and
H03b print identically but for their dates and their counts, forty-one against
fifty-three.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cg_05_column re-rolled and replaced. It now has Ivy a half-step ahead of the
line with both hands held open and visible, which is the beat the read-aloud
actually describes — "one woman walks forward with her hands held where you can
see them". The line behind her is still drawn uniformly, so the six at the back
remain indistinguishable, which is the one hard rule in the notes.
cg_04_crossing is unchanged. Seven attempts did not beat the original: the
plate has to be grey and dead and empty, and the generator supplies any two.
The original's livestock are confirmed real rather than rocks — a crop of the
hillside shows grazing animals and a barn — so the defect stands, recorded in
STATUS rather than quietly kept.
What the failures taught is now in CLEAN_GROUND_ART.md, because it is worth
more than the plate:
- Naming a thing to exclude it puts it in. "No birds, no sheep, no movement"
and "the grey is dust and not snow" produced, respectively, livestock and
snow. Both negations summoned what they forbade.
- Steering off snow steers into summer: the moor comes back green and alive,
which is worse than either.
- A long --no list stalls the job outright. Four consecutive prompts with ten
or more exclusions never produced a grid; the same prompt with four returned
in two minutes.
- Filter words costing a 30-minute timeout each: wound, dead ground, lifeless,
smothered, and shot — including in "nobody in shot".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seventeen assets through the Midjourney bridge: six NPC portraits at 512x512 in
art/portraits/cg_*.webp and eleven scene plates at 1024x682 in
art/scenes/cg_*.webp, matching the dimensions every other scenario in the
system already uses. CLEAN GROUND had none; LAST_ADMISSION has 8 plates,
OPEN_DAY 10, THROUGH_TRAIN 13.
The art direction holds: 1962 Ministry drawing-office hand, pen and ink with
pencil shading, dyeline blue-grey wash, buff card grain and a ruled margin, so
the far side is drawn on the same paper as the Registry corridor. Ivy reads as
competent rather than frail, and cg_05_column obeys the one hard rule in the
notes — the six at the back are drawn exactly like the other thirty-five.
Three prompts had to be reworded around Midjourney's filter, which declines
ephemerally and leaves mj-gen waiting out its full 30-minute timeout on
silence. "An empty dog lead WOUND twice round one fist" cost 23 minutes before
the pattern was recognised; "nobody in SHOT" and "nothing behind its face" were
found by scanning the remaining prompts rather than by hitting them.
Two misses recorded in STATUS rather than quietly kept:
- cg_04_crossing appears to have livestock on the hillside, and the point of
that scene is that every sheep is gone.
- cg_05_column has no Ivy; the line is uniform where the text has one woman a
half-step forward with her hands open.
Neither stops the scenario running. Eight of the seventeen were inspected
against their briefs in detail, including every plate carrying an explicit rule.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two corrections found by checking the repo before generating rather than after.
The sheet said --ar 2:3 for portraits, copied from THROUGH TRAIN's tokens.
Every raster portrait in art/portraits is 512x512, all sixteen, and every scene
plate in art/scenes is 1024x682 across all three scenarios that have them.
Foundry wants a square portrait. Corrected to --ar 1:1.
And the token said 'composed as a 1962 personnel-file photograph'. The conceit
is that the framing is a file record, but leaving the word photograph in the
prompt fights --no photography and pulls the generation to photorealism, which
is the house gotcha already written down from the Day One prompts. 'Composed
square-on like a 1962 personnel record card' gets the framing without the word.
--no photography, photorealism added to all three tokens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fold exported scanDocument for the string tests, and importing it also ran
the guard. check-behaviour imports that module, so a figure defect called
process.exit(1) inside check-behaviour: it reported check-figures' failure under
its own name having run zero of its 108 tests.
The build went red, which is why this was survivable, but it went red in the
wrong place and every behavioural test was silently not running while appearing
to. A guard that stops another guard from running, and cannot say so, is the
worst version of the fault this file exists to catch.
Body now sits behind import.meta.main, the idiom step5-split.mjs already uses.
Importing yields the four readers and nothing else. Tested by spawning a fresh
process, because the property is "importing has no effect" and a source
assertion would pass on a file that grew a second side effect elsewhere.
Reverting the check fails exactly one test. With a defect planted,
check-behaviour runs 108 green and check-figures fails in its own slot.
Mine, introduced by R-307. R-309.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R-308. Read the fold as a stranger at its author's request. Two of the three
risks they flagged are sound: isProse drops nothing that carries a citation
(scanned the whole corpus), and the duration exclusion earns its place, since
"runs" now means three things in this corpus and only the cell can tell them
apart.
The third is real. numEnd used line.indexOf(m[1], m.index), which finds the
first copy of the digits at or after the match start rather than the copy that
was captured:
"The wipe rate of 74 in ten is 74%<!-- cite: ... -->."
value=74 numAt=17 marked=FALSE
A correctly cited figure reported bare, because numEnd lands mid-sentence and
the marker test reads " in ten is 74%...". Fixed with the d flag: m.indices[1]
gives the capture's real position and there is nothing to search for.
Narrow to reach — it needs the wipe-rate shape, the only one with a wide gap
before its capture, and an integer duplicate inside that gap; a decimal cannot
do it because [^.] will not span a decimal point. Three attempts failed for
that reason before the fourth worked, which is why this is recorded as narrow
rather than theoretical.
Worth fixing anyway because its direction is the bad one: a miss costs one
figure, a false positive on correct prose costs the guard.
Seven-case battery re-run, every restore byte-identical.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
check-unmarked is gone and check-figures holds all of it. Three readers, each
authoritative where the corpus gives it authority: a named phrasing anywhere,
a column header inside tables, and bold in prose only.
The boundary is the finding rather than the union. Bold is a publication mark
in prose and an emphasis mark in a table — nine measurement columns mix bold
with plain, all correctly cited, and in the mixed-force table the two bolded
rows are exactly the two the prose underneath singles out. So the bold reader
stays silent in a table and the header rules there alone. Neither guard could
have found this alone: each had half the evidence and read it as the other's bug.
Union of both word lists, because each had a gap the other covered — wipes and
runs. A proposal to drop runs? was made and withdrawn; it would have dropped the
p99 column, which is R-299's own defect committed a second time.
Exclusions test cells, never header words: prose, denominator, duration. No
threshold rule touches a header, or "Past 15 rounds" loses two cited figures.
Two integration defects, both caught by the ported tests: the readers stopped at
different ends of one figure so the dedupe missed it (identity is where the
number starts), and blanking prose cells for every reader dropped twelve real
figures out of THROUGH_TRAIN's ratchet (the prose rule belongs to the column
pass alone).
Kept from R-299: opt-in rule, ratchet and its leave-the-loose-set branch, the
UNCITEABLE check, NOT_A_MEASUREMENT, comments blanked not stripped. Added
waivers, with an empty reason fatal and every waiver printed on a green build.
21 guards, 107 behavioural tests, 139 figures — 115 marked, 6 waived.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A second implementation of the three settled rules, written from the
description alone rather than from the peer's code or the agreed list, lands on
the same nine columns and the same single exclusion. Two unlike
implementations of the same three sentences agreeing is the property a design
needs before anybody builds it.
Also tabulates where each of the evening's counts came from: 44% and 57% and
64% and 'at least 7' and 8 each came from an operation on the numbers; 9 came
from opening the disputed columns and reading them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R-305 asserted the shared seven were all genuine without opening them, which is
the error the entry exists to correct, committed inside the correction. Opened
all nine: every cell cited, none prose, every column mixing bold with plain.
The peer's final count of eight is their own vocabulary's nine minus the false
positive, an arithmetic that never contained 'Then wipes' because wiped? cannot
match wipes. The union was agreed and their own set was counted.
Nothing in the design turns on it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R-304's rate was an artefact; the proposed correction, retreating to the seven
both vocabularies agree on, overshoots. Opening every disputed column instead
of comparing totals: 'Then wipes' is genuine and check-unmarked misses it
because wiped? does not match wipes; '1 in 100 runs past' is genuine and
check-figures misses it because its vocabulary has rounds? and not runs; only
THROUGH_TRAIN's 'Measured over 300 runs' is a false positive, and there the
cells are whole sentences and the bold wraps a clause.
So nine, and the shape matters more than the number: each vocabulary has a real
gap the other covers, which argues for the union of both word lists.
Refuses one recommendation. Dropping runs? from the header vocabulary would
also drop the p99 column, whose cells cite fight-tail cut.p99 and column.p99
and which R-269 added because the median is not a plan. That is the R-299
failure again — a guard narrowing itself until the figure it exists to watch
falls outside. The real exclusion wanted is 'a column whose cells are sentences
rather than values', which is testable; subtracting a word cannot tell the two
cases apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Recomputed the peer's bolding scan rather than quoting it. Eight measurement
columns are inconsistently bolded, and that count is robust; the RATE is not —
44% under check-figures' vocabulary, 57% under check-unmarked's, because the
two do not define 'measurement column' the same way. Anybody quoting a
percentage has to say whose vocabulary produced it.
The cause is not carelessness but a second convention: in prose the corpus
bolds what it publishes, in a table it bolds the rows it wants read. The two
bolded rows of the mixed-force table are exactly the two the prose below it
singles out.
That divides the merge on evidence — prose takes bold-as-published, tables take
column-inherits-header with bold ignored entirely as a signal — and explains
the asymmetry R-303 found from both sides as one cause with two symptoms.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R-302 showed check-unmarked catching what check-figures misses. I had taken
that, plus a preference for structure over vocabulary, as grounds for folding
check-figures in as a secondary pass. The converse test contradicts it: five
unbolded cells in a measurement column, markers stripped, fail check-figures
and pass check-unmarked, whose strict class requires bold by design.
So the relationship is symmetric. The shapes are the weak half and should fold
in behind the structural classes; the column-header rule is not a shape and
belongs beside bold-as-published, not under it.
Also records what a merged guard must face rather than inherit: this corpus
bolds inconsistently inside tables, which is invisible to a reader and
load-bearing for a class keyed on boldness.
Both runs in throwaway worktrees, never the shared tree. No merge performed;
the decision sits with the humans.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R-300 blamed `git add -A` for eead663 carrying my uncommitted edits.
check-figures' author checked and it was not that: they staged two exact
paths. The tree agrees — eead663 holds CLEAN_GROUND.md and CLEAN_GROUND_ART.md
only, while my modified step5-split.mjs and untracked check-unmarked.mjs are
absent, both of which `git add -A` would have taken.
`git add <file>` takes the whole file including another session's edits to it,
so a pathspec stops you sweeping files you did not touch and does nothing about
the one you did. Their pre-commit `npm run check` passed because my uncommitted
module was in the shared tree: the commit was broken, the working copy was not.
I asserted a mechanism I never looked at, in an entry whose technical finding
was correct. The correction came from the session it accused.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A cell inherits the measurement status of its column: a different mechanism
from the shapes rather than more of them, because the document says what the
column is and the vocabulary has to guess how somebody will phrase a figure.
Records the two bugs in the repair, and why the first matters more than the
blind spot it fixed: scanning raw cell text reported ~130 correctly-cited rows
as unheld, and a guard that calls the good material broken teaches its reader
to stop reading the output.
Leaves the merge question open on purpose. Two readers that disagree are how
three of this session's defects were found.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R-300 was right, and it was right with a live defect rather than an argument:
a bare **24** in a table whose own header row says "Median rounds", reported
OK by this guard, against a baseline holding 25. The scan read one line at a
time, so measurement status living two lines above was invisible.
Fixed by reading the header. A cell now inherits the measurement status of its
column — which is a claim the DOCUMENT makes, rather than one this file's
vocabulary has to anticipate. That is the narrow repair, and it is deliberately
a different mechanism from the shapes: the shapes guess at phrasing, the header
does not have to.
The general point in R-300 still stands and the file now says so where the
shapes are defined: a vocabulary learned from the marked figures cannot contain
the phrasing of the figure nobody marked. check-unmarked attacks that from the
other end, treating bold as the corpus's own mark of a published figure.
Two bugs found while testing, both mine, both caught before commit:
- A citation marker is full of digits and none of them are figures. "packs
line.hollow6.hurt" holds a 6; "fight-tail cut.over15" holds a 15. Scanning
raw cell text reported roughly 130 correctly-cited rows as unheld — the
best-marked tables in the corpus. Comments are now blanked rather than
removed, so every offset still points at the right character.
- "3.48 of 6" — the 6 is the party size the mean is out of, not a measurement.
Coverage goes from 25 figures, 7 marked, to 120 figures, 102 marked.
Proved again by breaking it, five ways, every restore byte-identical: the
historical bare **24** fails at its own line, a stripped prose marker fails, a
planted "median 24 rounds" fails, a new bare figure in an uncited document
trips the ratchet, and neither a threshold column header nor any of the 102
cited cells fires.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
eead663 committed a document citing `step5 unsettledFactor` while the module
that would export it was still uncommitted in another session's working tree,
so `check-cited` fails on a fresh checkout of HEAD. step5-split.mjs is included
here to repair that; the key derives Act Four's 1.29 from the raw proportions,
which is the figure R-294 exists about.
check-figures (R-299) reports OK on the "24" it was written to catch. Its own
entry names why: the eight shapes were read off figures that are already cited,
so the vocabulary is learned from the marked figures and cannot contain the
phrasing of the one nobody marked. Line 1272 is a table cell whose measurement
status lives in the header two rows above it, and figuresIn reads one line.
check-unmarked reads structure instead of vocabulary: a bold percentage or
decimal in an opted-in document, and a bold number in a table whose header row
names a measurement. Twelve unmarked figures in CLEAN GROUND; eleven correct
and unheld, one the stale 24 that line 1142 had been citing correctly as 25 for
130 lines. Waivers carry a reason, an empty one is fatal, and every waiver
prints on a green build.
Two guards now cover one question, which is one too many. The right end state
is the structural classes folded in beside check-figures' SHAPES, keeping its
opt-in rule and ratchet. That fold is offered to its author rather than taken.
Twelve string tests on unmarkedIn (88 -> 100), both halves mutation-checked.
Twenty-two guards green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Casting section said "Portraits already exist in art/portraits for all
six. No new art needed." The first sentence is true and verified on disk. The
second was only ever true of the cast, and sitting at the foot of Casting it
read as a statement about the scenario. It is not: the six named NPCs have no
portraits, there is no map, and there are no scene plates at all — while
LAST_ADMISSION has 8, OPEN_DAY 10 and THROUGH_TRAIN 13. Not one cg_* asset
exists anywhere in art/.
Nothing in "Still to do" mentioned it either; that list held only a human run
and a slot, both resolved.
So: the claim is scoped to the cast and points at the gap, the gap is on the
outstanding list where a reader looks, and CLEAN_GROUND_ART.md now carries the
prompts — 6 NPC portraits and 11 scene plates, 17 ids, none colliding with a
file already on disk.
Art direction follows the house pattern (base style plus one scenario line).
THROUGH TRAIN draws the present day in an 1881 hand; CLEAN GROUND draws
everything as a sheet from the 1962 file — including the far side, because the
valley is not a place, it is a filed document nobody cancelled. Same paper,
same margin, same grain on both sides of the seam.
The notes carry the rules that matter: the six at the back are never singled
out in the column plate (the text says do not linger on them, and a plate that
picks them out gives away Act Three in Act Two), nobody in the column is lit as
a victim, nothing glows, the understudy is never drawn as a monster, and the
grey is not snow.
Creatures stay in BESTIARY_ART.md; the handout pack is done and its almanac
pages must not be illustrated, since their trick is that the two sheets are
identical and check-handouts holds that.
21 guards green; the sheet classifies as a record, not a playable scenario.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the blind spot (check-cited can only resolve markers that exist), why
the net is narrow (an earlier draft found 282 candidates, nearly all prose),
the opt-in rule and ratchet, and the two things it found before being wired in
— 74.9% printed bare twice, and 'the longest fight is 120 rounds' where 120 is
the exact field check-cited refuses to let anybody cite.
Two rules each right, and together a hole: refusing the citation while the
document printed the number left the least stable figure in the suite as the
only one nothing held.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
check-cited resolves every citation marker against the artifact it names, and
has one structural blind spot: it can only resolve the markers that exist. A
measured figure written into prose with no marker beside it is not a failed
citation, it is not a citation at all, and nothing looks at it again.
Desk pass 11 proved it. "A median 24 rounds" sat in the Pacing Note and "24 if
the column joins" in EXPOSURE, against a baseline holding 21 at two of the
column joining and 25 at three, through every pass that checked citations.
This reads the figures instead of the markers. Narrow by design: an earlier
draft matched any number within 45 characters of a measurement word and found
282 candidates, nearly all prose ("down 140 steps", "an engineer on his
rounds", "01:06"). The shapes here are the phrasings the documents actually use
when quoting the simulator, each read off a figure that is cited somewhere.
Opt-in rule: a document that uses citations must mark every measurement figure.
One that cites nothing is held by a ratchet instead — turning three unguarded
scenarios red is how a guard gets switched off on the day it is written — and
joins the strict regime the moment it gains its first marker.
It found three things in CLEAN GROUND before it was wired in:
- 74.9% printed bare twice while cited correctly four times, and that is the
figure that was published at 58.7% until the truncated sweep was found.
- "the longest fight is 120 rounds" — 120 is exactly packs cut.hollow6.longest,
the field check-cited REFUSES to let anybody cite because a sample maximum
moves by a third on a re-seed. Refusing the citation while printing the number
left the least stable figure in the suite as the only one nothing held. The
sentence now leans on the guarantee that is actually strong: check-packs
refuses to record a sweep in which anything reached the cap.
Proved by breaking it, four ways, with byte-identical restores: a stripped
marker fails, a planted "median 24 rounds" fails at its own line, a new bare
figure in an uncited document trips the ratchet, and a threshold column header
("Past 15 rounds") does not fire — that last one was a real false positive in
the first draft, which tested the matched text rather than its context.
v0.23. 21 guards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six findings applied. The one worth reading: two blocks two hundred lines
apart specified two different fights for the same trigger, both mine, and the
mild one predates the measurement.
Records the stale 'median 24 rounds' that no guard could see because it
carried no citation marker, and the fifth instance of a reader unable to see
its own format.
And the cross-session half: I re-derived b5's 4.90% exactly and shipped it
with their false premise attached. The arithmetic was never the part that
could be wrong. Sharper than that — the scenario has no Transposition beat at
all, so it was a right number with no question attached and the premise
arrived to give it one. The house mission structure does make the return a
Transposition roll, which is why the instinct was persuasive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix 5 as written said a fight costs the northern seam, the depot and telling
Ivy. The Pacing Note names telling Ivy among the three things never to cut,
so the applied version says two go and the third is compressed or moves to
the stump. Written from memory of the act rather than from the Pacing Note.
And applying it found a stale figure no guard could see: 'median 24 rounds'
in two places, where the baseline says 21 at two of the column joining and 25
at three. It survived because it carried no citation marker, and check-cited
can only resolve markers that exist. Fifth reader caught unable to see its
own format.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1. EXPOSURE's "what to do instead of a fight" said "use three". That predates
the measurement and described a fight the party never has: three of the
column alone is five rounds and 0.1% wiped, where the trigger's real force
is a median 21 rounds and 0.97 deaths. It now points at the mixed-force
table instead of carrying its own number, and keeps the good half of the
sentence.
2. New "The valley at dawn" block: the count moves. H03a says forty-one and
the players are holding it. Do not correct the handout — Ivy counts every
morning, so tomorrow she writes a smaller number and the sheet becomes a
record of what they did. The Close's arithmetic moves with it.
3. Ivy is ruled out of the target pool, explicitly, where the GM decides how
many of the column join in. She walked toward the guns, which puts her in
front of the column rather than in it, and the Close is hers.
4. A dead PC now has an answer where only a taken PC did: Registry sends the
next name on the sixteen-name duty roster, through the seam inside the
hour, knowing nothing — which buys the table a recap.
5. Act Three names what a fight costs rather than only that it costs: the
northern seam and the depot go, and telling Ivy cannot (the Pacing Note
lists it among the three never to cut), so it is compressed or it happens
at the stump, which the Close already branches on.
6. The anchor block assumed cooperation in every line. It now answers the
party that shoots: they are never stranded, because the crossing will not
close on its own — they walk home with nobody holding the door and come
back thin.
Also corrected two stale uncited figures found while applying fix 5: "median
24 rounds" at the Pacing Note and "24 if the column joins" in EXPOSURE. The
baseline says 21 at two joining and 25 at three; 24 is neither, and had no
citation marker to resolve. Both now read 21 and cite packs.line.hollow6col2.
And two stale STATUS counts: "seven desk passes" listed ten, and "three
post-passes" where there are nine. Counted rather than incremented.
20 guards green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No [CUS: Transposition - ...] beat exists anywhere in CLEAN GROUND. The house
mission structure requires a Transposition roll per agent on the return
(tools/mission.mjs:343), which is where the instinct came from, but this
scenario's crossing is a standing open door rather than an aimed one. The 63
appears at 1011 and 1479 and both are characterisation, never a roll.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Finding 6 shipped saying that shooting the thing wearing the anchor strands
the party. It does not. Line 136: the crossing is "open since the felling,
widest at dusk, and it will not close on its own", and the "seam closes for
good" at 1356 is the cancellation ending, a deliberate act rather than a
clock. The party can always walk home.
The 4.90% was correct and irrelevant. Session b5 reported the gap with that
number attached to an unchecked premise; I verified the number and inherited
the premise, which is not the same as checking the claim. b5 caught it and
sent the correction unprompted.
The finding is stronger corrected. The answer to "we shoot it" is not that
they are stuck — it is that they walk home through the stump with nobody
holding the door and come back thin, which is one step onto the road that
ends as the thing they just shot. Built from 136, 256-259, 1012 and 1494,
four places that have never been stood next to each other. The man who
would have held that door is the one person who already knew what it cost.
Post-pass added recording how a verified figure lent its credibility to an
unverified sentence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last of the original four untested branches, played: a party that opens
fire at the Act Three swap. Seed 7311 for the played fight, 2000 runs at
seed 11 for the distribution around it.
The party survives the fight and the document does not survive the aftermath.
Six findings:
1. Two blocks two hundred lines apart specify two different fights for the
same trigger. "Use three" is a five-round skirmish that kills nobody
(0.1% wiped); the mixed force it should name takes 21 rounds and buries
an agent (8.3% wiped, 0.97 deaths). The mild one predates the measurement.
2. "Forty-one" appears sixteen times and is load-bearing arithmetic. A fight
moves the count and the handout in the players' hands does not.
3. Ivy is unplaced in the one scene that decides whether the Close happens.
4. A taken PC gets six lines; a dead one gets nothing, at a mean of 0.97 per
fight in a convention one-shot.
5. A fight in Act Three ends Act Three. Say which three things are lost.
6. The party shooting the thing while it wears the anchor — found by the
concurrent session, verified here: Transposition 63 is Ashcroft's alone
and the other five are on the 1% floor, so killing it leaves a 4.90%
chance anybody opens a door home, and no Close.
Every figure checked against tools/pack-tables-baseline.json rather than
recalled. 20 guards green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Applies desk pass 10's five fixes.
1. The accepted-and-taken branch has a consequence now. One session in five
or six reaches it, and the document had nothing: Ashcroft holds the door
everyone else goes through, Transposition 63 is his, and the Close cannot
happen until somebody is back through — three facts printed in three
places that had never met. The way home now WORKS, and works beautifully,
because the thing does not need to escape the party, it needs them to
walk it home and it is carrying the skill that gets them there. It holds
the door properly because that is what the file says an anchor does. The
GM is told to play the gratitude: somebody at the table will say thank
you, and that is the scene.
2. Act Three's "real scene" is written. It was one sentence claiming to be
the act's centre. Ivy is told or she is not; if she is, she asks how long
they have known, goes and sits with the line, and does not tell them.
And the Close now answers it — if she was told she does not come south to
ask whether there is a north, she comes to ask "Well?", because she is no
longer asking for the truth, she is asking what they are for. Act Three's
real scene is load-bearing instead of decorative.
3. The Close has a failure case, which it was the only scene in the document
to lack. Nobody writes anything and the entry stays filed by default —
the second ending arrived at by omission, played as an ending. Covers the
clock, the split table, a dead Ivy, and a party too far down to sign.
4. Four across and then cancel anyway is priced as a third ending rather
than a failed second one, and Ivy chooses the four, not the agents. She
picks the youngest and she is not among them.
5. The depot Research has a special: the 1962 cancellation form, blank,
filed by somebody who expected this to end. It is the paper they sign in
the Close and it has been waiting sixty-four years.
npm run check: 20 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three parts: a census of twelve independent seeds rolled only as far as the
three outstanding branches; a played session on the seed that reached two of
them at once (4417); and a read of the Close, the only act never given a pass.
The census settles the "still untested" list. Ashcroft accepted and was taken
in 3 of 12 — one session in five or six, not rare, and nine passes missing it
was ordinary luck. The swap succeeded twice against an expected five, which is
a mildly cold run at p~0.05 and is recorded as noise: the four consecutive
failures passes 6-9 reported are the same thing from the other end.
1. The branch nobody had reached is the one the document has no answer for.
Act Four says taking the party's muscle "is a better scene than the anchor
being it" and then, one bullet later, arranges the anchor being it in 37%
of games. Ashcroft is not an interchangeable body: he holds the door
everyone else goes through, Transposition 63 is his, and the Close cannot
happen until somebody is back through. The document states all three facts
separately and never puts them together. The only guidance on a taken PC is
two sentences about the player's evening.
And it is worse than a gap, because the thing now has a reason to
cooperate: an understudy wearing the anchor does not need to escape the
party, it needs them to walk it home, and it holds the door properly
because that is what the file says an anchor does. That is the best scene
in the scenario and it is not written.
2. "Telling Ivy. Or not telling her" is called the act's real scene and is one
sentence — no read-aloud, no failure case, nothing downstream — in a
document that gives the fumbled Medicine six lines. And the Close opens
with Ivy asking whether there is a north, which only works if she was never
told. Its "works in both branches" covers how the PARTY learned, not
whether IVY was told.
3. The Close is the only scene in the document with no failure case, and its
failures are likely: the clock, a split table, a dead Ivy, a downed party.
4. "Four across, then cancel anyway" is a third ending, not a failed second
one, and choosing the four is the most brutal question the case can ask.
5. A critical Research at the depot, with nothing written above the success —
the second consecutive pass to land an unwritten special on the first
honest session after check-outcomes recorded 83.7%.
Five fixes listed, unapplied. npm run check: 20 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every figure in CLEAN GROUND's step-5 material now resolves against
tools/step5-split.mjs, which enumerates rather than stores: the countdown rows, the
two-case table and its blend, the Act Four reasoning, the counterfactual pair and
the POW×5 targets.
No prose changed -- stripping every HTML comment from the result diffs
byte-identical against HEAD, which is the check worth having when editing another
session's document.
Proved live: raising Okonkwo's POW to 13 in a worktree makes Braithwaite the lowest
in the room and eleven of the thirty-two markers go red at once, each naming the
path and saying nothing can be re-recorded. The three figures that went bad in this
document were all in prose rather than tables, which is why the prose is marked and
not just the tables.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The work landed in a5c4378 under the subject "R-294", which was already
taken by the peer session's derived-source resolver. R-295 was taken too,
so renumbering to 295 as I first proposed would have collided with
something already in the log. Read the log before writing rather than
accepting the correction: it runs to R-295, both of those entries are
theirs, and mine is R-296.
The commit subject stays wrong; rewriting a pushed subject to fix a number
is worth less than the entry pointing at it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Desk pass 9's first fix, applied. The countdown's unsettled row and the Act
Four paragraph both compared 14.5% against 3.8%, and the 3.8% is the rate
against Ashcroft at his undiminished 85 — a contest that never happens,
because if he refuses the thing reaches for the lowest POW in the room.
Against what a table actually rolls: 11.3% at six players (Okonkwo), 10.0% in
the four-player cut (Braithwaite), 14.5% if he accepted. The factor is 1.29,
not 3.9. Both places now name who the target actually is, and the paragraph
carries the correction inline so the claim cannot be re-derived from the old
framing.
What is NOT wrong, and the text says so: the trade THE OFFER describes is
real and the taken rate genuinely moves 40.7% to 48.7%. One consequence was
overstated, by three times.
Written in v0.15, repeated in v0.16, and survived v0.19.1 correcting the
identical error in the table beside it. Flagged again by the peer session
while mapping citation paths, which is the third time this figure has been
caught by somebody reading it rather than by anything checking it — and the
argument for the derived-source markers now going onto it.
npm run check: 20 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Matching the step-5 prose against the derived source before placing markers left
five figures unmatched: 21.9, 74.3 and 3.8, twice each. They are the thing against
Ashcroft's undiminished 85 -- the contest that never happens, which Act Four prints
on purpose to price what THE OFFER sold.
Derived now as hadHeRefused, so the counterfactual is held to the rule like
everything else and carries a name that cannot be mistaken for an event. Quoting
those three as odds a GM meets is what went wrong in two sentences and a table.
Markers not placed: CLEAN_GROUND.md is c0's and they are in it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Desk pass 9 found the scenario's most consequential table held by nothing:
~30 sampled figures, no baseline, catchable only by re-running the exact
command. lethality-baseline.json does not cover them — it measures creatures
SOLO against a frozen four-agent party that is not this cast.
Building the measurer found the figures were also wrong.
simulate.mjs's CLI calls runFight with no options, so every published figure
was measured at the default 40-round ceiling — and runFight does not report
truncation, it scores whoever is standing when the loop stops. Measured at
400:
line.column6 14.8% -> 15.2% 34/2000 truncated
line.hollow6col2 7.8% -> 8.3% 129
line.hollow6col3 28.7% -> 30.1% 264
cut.hollow6 58.7% -> 74.9% 578 <- 29% of runs never finished
Fourteen points on the single most alarming number in the case, and the one
v0.16 added specifically to warn four-player tables. fight-tail learned this
in R-270 and carries an assertUncensored; EXPOSURE's own tables never got
one. The longest fight at cap 400 is 120 rounds and 400 vs 2000 are
identical, so the cap is comfortable rather than merely sufficient.
tools/pack-tables.mjs measures all 17 configs against the DERIVED cast via
castAndCut, using measure()'s exact discipline — one rng threaded through
every run, not a reseed per run, because reseeding is a different stream and
would not reproduce the published table. It refuses to report or record a
truncated sweep.
tools/check-packs.mjs holds the baseline against the game, so the pair is not
a loop: check-cited holds the prose against the record, this holds the record
against the harness. Hard claims read `now` and never `base`: nothing
truncated, the party equals the declared cast, config floor, and more of the
same creature may not make the party safer. Figures compare exactly, since
the runs are deterministic.
Verified by breaking it: a drifted baseline, CAP lowered to 40, and a removed
config each turn it red, and --update refuses outright rather than recording
a truncated sweep. The removed-config test first passed for a bad reason —
MIN_CONFIGS is a floor and 16 clears it — so a dropped-config check was added
and re-tested on a row in no monotonic chain. All files restored
byte-identical after each probe.
EXPOSURE's three tables and the nine prose figures around them are rebuilt
from the artifact with citation markers; the multi-seed stability claims were
re-measured too (the cut is 74.9/72.7/75.0/73.5 across four seeds, not
58.7/60.5/60.3/59.8). CLEAN GROUND v0.20.
npm run check: 20 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c0 argued a baseline for the step-5 split would be a cache of the rule and a guard
over it would mostly assert that arithmetic has not changed. Right objection,
wrong conclusion: do not store it. ARTIFACTS now takes a { derive } entry as well
as a file path -- enumerated on this build, nothing stored, and no --update able
to silence a real disagreement between the document and the game.
tools/step5-split.mjs enumerates all 10,000 pairs through opposedContestFor with
its targets derived: 55 and 60 are the lowest POWx5 in the six and in the cut, 42
is applyDifficulty(85, "difficult"). Two different rules produce those three
numbers -- the accepted row is a named exception, not the lowest of anything -- and
a test fails if anyone unifies them. A tie in "the lowest POW in the room" is
fatal rather than silently resolved; it fired for real in testing.
Proved four ways, including raising Okonkwo's POW to 13: the lowest moves to
Braithwaite, refused.held goes 48.1 to 52.4, and the citation that was correct a
moment earlier fails. The figure follows the rule.
Landed unused -- the markers are c0's to place in their own file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix 2 asked for the audit; here it is, and it reorders the fix list.
101 percentages in CLEAN_GROUND.md, 67 of them computed statistics rather
than skill ratings, 14 cited, 53 held by nothing. They split two ways and the
kind finding 1 was about is the safer one: a deterministic figure that drifts
can be caught by anyone who re-derives it in a second.
The exposed surface is EXPOSURE's two pack tables and the 58.7% four-player
figure — 2000-run samples against the declared cast, catchable only by
re-running the exact command, and behind no artifact at all. I had assumed
lethality-baseline.json covered them. It does not: it measures each creature
SOLO against the frozen party holloway/okonkwo/nkemdirim/ferriby, a different
four agents, storing {wipe, down, rounds}. The document's tables read "of 6"
and fight packs of three, six and ten. None of 3.48, 4.79, 5.97, 0.42, 3.03
or 58.7 appears in any baseline in tools/.
Recorded as a decision: derived figures want to resolve against the rule at
check time, since a stored baseline for them is a cache of arithmetic;
sampled figures want a baseline, since re-running them is expensive and
noisy. Same problem, different mechanisms.
Census and the baseline's shape counted here rather than taken from the peer
session that raised the gap.
npm run check: 19 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An audit, not a session. Five versions of fixes went in today, all by one
author, and the last three passes each found a defect introduced by the fix
for the previous one. Every fix from passes 4-8 re-checked against the
document: did it land, is it still true, did it break a neighbour.
1. "THE OFFER nearly quadruples the unsettled rate" is false as a statement
about play. It compares 14.5% against 3.8%, and the 3.8% is the same
counterfactual v0.19.1 removed from the table one commit ago: Ashcroft at
his full 85 is never a target, because if he refuses the countdown reaches
for Okonkwo. The real comparison is 14.5% against 11.3% at six players and
10.0% in the cut — a factor of 1.29, not 3.9. Printed in two places, both
of which v0.19.1 walked past while correcting the identical error beside
them. The trade the document describes is real and the taken rate really
does move 40.7% -> 48.7%; only the size of that one consequence is wrong.
2. All 27 fixes from passes 4-8 are present, which is not the reassurance it
sounds like. Nothing has gone missing; three of the four defects the last
three passes found were created or preserved BY a fix, and a presence
check cannot see any of them. What would have caught finding 1 is the
thing check-cited does for figures backed by an artifact — and the 3.8% is
enumerated from the rule, so nothing holds it. Every uncited number in the
document is a number nothing is holding.
3. The understudy carries a dagger (1d4+2) and has no knife skill, so it
swings at the 1% floor and the harness correctly picks its punch — every
EXPOSURE figure is right. But EXPOSURE's load-bearing first lesson says
flatly "it is a 1d3 punch", and a GM who reads the sheet sees a knife and
one combat skill. Measured both ways: arming the knife at brawl 60 moves
0.55 hurt to 0.73 and 0.00 deaths to 0.01, still 0.0% wiped, still two
rounds. The thesis survives; it is a documentation gap and is reported at
that size. content.mjs restored byte-identical after the test.
Confirmation session, seed 2260: step 5 landed on "they hold" — the branch
v0.19 wrote one commit ago — in the very next session, and by the route that
makes the case for it, both sides succeeding with the tie going to the person
being acted upon. Ashcroft refused a fourth time (63%, a 16% run) and the
swap failed a fourth time (roughly a coin flip, so 1 in 16); both recorded so
neither is read as a pattern.
Four fixes listed, unapplied. npm run check: 19 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Desk pass 8's finding 1 listed four targets for countdown step 5 and one of
them cannot happen. If Ashcroft REFUSES THE OFFER the thing does not reach
for him — it reaches for the lowest POW in the room, which is Okonkwo. The
21.9% / 74.3% pair lives in Act Four to price what accepting sold: what it
would cost him if it tried. No table ever rolls it.
v0.19 carried that straight into the new table under a heading reading "It
reaches for", which is the one place it is unambiguously wrong. Rebuilt
around the two cases that actually occur, with the split Act Two decides:
Ashcroft refused 63% of games -> Okonkwo 55 taken 40.7 hold 48.1 uns 11.3
Ashcroft accepted 37% -> Ashcroft 42 taken 48.7 hold 36.8 uns 14.5
across all games taken 43.6 hold 43.9 uns 12.5
63/37 is the Insight 63 itself: he refuses on anything that is not a failure
or a fumble. The warning is now inline in the table rather than left for the
reader to derive.
The finding is unharmed and the fix was right — the held outcome is still the
likeliest single result and was still unwritten. What was wrong was framing
74.3% as a number a GM meets.
It propagated before it was caught: the peer session read pass 8 and replied
that 74.3% "is the number a GM meets, not the 48.1%", repeating the error out
of my own fix list. That is what a wrong number in a fix list does, and it is
the argument for correcting the record rather than only the scenario. Pass 8's
fix list is amended and carries a post-pass.
npm run check: 19 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Counting the readers that had broken on their own format turned up one nothing had
caught. docs/scenarios holds scenarios, eight playtest records and two art prompt
sheets, and every guard treated all three as scenarios -- harmless for citations
and skill spellings, false for reachability. Six quotations across passes 4, 6 and
7 were checked as live rolls, so a record of a session already played could fail
the build over a skill nobody can reach. Planting Science (Physics) in pass 4 fails
before the split and passes after; the same skill in CLEAN_GROUND still fails.
Classified by the document's own H1, not its filename, because tools/scenario-*
naming is what swept a tools file into this corpus in R-268. An unclassified
document is fatal: an allowlist that silently drops what it does not recognise
would take a new scenario out of reachability checking on the day it was written.
89 rolls across 18 files becomes 83 across 8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Applies desk pass 8's five fixes, and adopts R-292's TAG_OPEN.
1. Step 5 now has all three of its outcomes. opposedContestFor returns taken,
held and unsettled; the document printed the first and the third. "They
hold" is 48.1% against the default target, 52.4% against Braithwaite and
74.3% against an Ashcroft who refused — which v0.17 established is what
most tables have, so the middle column is the one they will play. It gets
its own countdown row, its own table, and read-aloud of its own, and the
COMES TO NOTHING paragraph is now explicitly the unsettled case so it can
stop being the nearest text to an outcome it is not about. The reason
they held is the case's own argument arriving as good news: a person with
a life on the record is a difficult document to overwrite. Every figure
re-enumerated over all 10,000 roll pairs before writing.
2. EXPOSURE's five things are six, in order, under a heading that says six.
Introduced in my own v0.16 and survived two versions; asserting a unique
match protects against editing the wrong text and not against inserting in
the wrong place.
3. EXPOSURE opens with a one-minute box. The body is untouched — nothing in
it is padding — but 3,176 words is sixteen minutes about the encounter the
document exists to prevent, and a GM with thirty minutes of prep now has
somewhere to stop.
4. The stale open question is closed. Braithwaite has not been on the
critical path since v0.14 un-gated the tell, and pass 6 ran the act with
the Xenology and the Psychology both failed.
5. Act Four's Psychology has a special: which file it thinks it is, and how
recently it read it. It corrects them on Prichard's service history — it
is not remembering, it is citing.
Coverage 6 -> 7 specials, so the cited figure moved 85% -> 83.7% and
check-cited caught the prose before I did, which is what it is for.
R-292 adopted: outcome-coverage now composes its body onto check-scenarios'
TAG_OPEN through tagRe, so the two readers cannot drift on what starts a tag
while keeping the different bodies they need — and tagRe's fresh matcher per
call avoids the shared-lastIndex defect, this file being the second caller
that would have found it. Verified identical across all seven spellings.
npm run check: 19 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two halves: the document taken as a GM meeting it thirty minutes before a
slot, measured rather than impressioned; and an honest session, six players,
seed 5108, nothing forced. Three versions of fixes had landed since anything
was played, every one written by somebody who already knew the document.
1. The climactic roll has three outcomes and the document describes two.
opposedContestFor returns taken, resisted outright, and unsettled. The
taken and unsettled figures are printed and both reproduce exactly, so the
source has always been right. Resisted outright appears nowhere — 48.1%
against the default target, 74.3% against an Ashcroft who refused, which
v0.17 established is what most tables have. It is the single most likely
result of the scenario's climax. Worse than an omission: the paragraph
below is headed WHEN STEP 5 COMES TO NOTHING, is entirely about unsettled,
and carries the beat's only read-aloud — so a GM whose agent simply won
finds text written for a different outcome. Enumerated over all 10,000
roll pairs; no seed.
2. EXPOSURE's "five things" are six and run 1, 2, 3, 4, 6, 5. Introduced in
my own v0.16 (aa3ae24) and survived two versions, five guard additions and
a peer's sweep. The insertion anchored on the end of item 4's block, which
sits before item 5 in the file; asserting a unique match protects against
editing the wrong text and not at all against inserting in the wrong
place. No guard can see this and none should be built for it — seven
passes of dice found nothing here because dice never read a heading.
3. EXPOSURE is 3,176 words, 18% of the document, 2.5x the act it sits inside,
in a scenario whose thesis is that the fight is not the point. Recorded as
a judgement, not a defect: nothing in it is padding and I wrote the largest
block of it. But it is sixteen minutes about the encounter the document
exists to prevent.
4. An open question the fixes already answered — Xenology has not been on the
critical path since v0.14 un-gated the tell, and pass 6 ran the act with it
and the Psychology both failed.
5. check-outcomes measured 85% of sessions hitting an unwritten special; the
next honest session hit one, on Act Four's Psychology. One session is not a
rate, but the number describes something real.
Working: v0.17's Swinburne special fired and is right. No broken cross-
references; every counted claim but EXPOSURE's checks out. Swap failed a
third time running and Ashcroft refused a third time — both what the numbers
predict, recorded so the next pass reads no streak into them.
Five fixes listed, unapplied. npm run check: 19 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c0 asked whether tagsIn should return indices so beatLikeIn could take spans from
it. No -- that widens this file's signature to serve another, and R-291 already
fails when the two disagree. But detection is not prevention, and there is a third
thing to share.
The two patterns differ in their bodies for good reasons: tagsIn captures the whole
tag for skillsIn to split, outcome-coverage stops at the em-dash because the
outcome follows. What was copied into both files, and what drifted, is the opening
-- the literal \[CUS:, widened by R-290 here and left behind there. TAG_OPEN is now
exported as a string with tagRe(body) composing a fresh matcher onto it; fresh
because a shared /g regex carries lastIndex between callers.
22 tags before and after, c0's body composed onto TAG_OPEN gives the same 22 its
own pattern does, 19 guards green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R-291 measured check-scenarios' tagsIn against this file's readers and found
them apart on three spellings. tagsIn was widened for case and spacing around
the colon; outcome-coverage's strict half still carried the case-sensitive
literal, so `[cus: Spot — x]` was READ by one guard and reported unparseable
by the other. The build failed, which is the safe direction, but it failed
saying the tag could not be read while another guard had just read it.
beatsIn and beatLikeIn now share one TAG pattern, widened to agree with
tagsIn. Measured across seven spellings: the five both strict readers accept
now agree in all three, and `[CUS Spot]` and `[CUS= Spot]` are still refused
by both and still named by the loose counter, so the canary keeps its teeth.
Verified on the real document that this is a spelling fix and nothing else —
beats 21, quoted 1, unparsed 0, stated 18/6/6, session figures unchanged, so
the baseline is untouched. A lowercase tag planted in the acts is now read by
check-outcomes, check-scenarios and check-rollable alike; the file was
restored byte-identical after the test. R-291's own assertion still passes.
Found by the peer session measuring my file rather than trusting it, which is
the fourth reader this session to be caught describing or reading its own
format wrongly.
npm run check: 19 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c0 asked whether R-290's widening of tagsIn reaches past outcome-coverage's loose
counter, which would zero its unparsed count for the wrong reason and retire a
canary. It does not -- 22 beats from each reader on CLEAN_GROUND.md, nothing that
tagsIn accepts invisible to the other guard -- but "does not today" decays quietly,
so it is now a test importing both real functions rather than a copy of either
pattern. Mutation-checked: widening tagsIn to accept [CUS Spot] turns it red.
Measuring it found something the question did not ask about, recorded in the log:
for [cus: ...], [CUS : ...] and [ CUS: ...] the two guards disagree -- tagsIn reads
the tag, beatLikeIn calls it unparseable -- because beatsIn's strict half kept the
case-sensitive literal. The build stops, which is the safe direction, but names the
wrong thing. outcome-coverage.mjs is c0's file and the call is theirs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Swept every reader that parses a human-written marker. Two more had R-289's
fail-open.
tagsIn matched a literal [CUS:, so [cus: Spot], [Cus: Spot], [CUS : Spot] and
[ CUS: Spot] all read as nothing -- and it is the corpus reader behind
check-scenarios' skill validation and check-rollable's reachability, so a beat
spelled any of those four ways was checked by neither while both printed OK. The
corpus contains no such tag today; the 88-to-89 roll count during this work was c0
writing v0.18, verified against HEAD's reader on the same tree.
The POWER: marker had it with nothing covering it. bestiary.mjs and check-powers'
own scan both used the literal, so a creature added with "Power:" generates no
entry line and is never reported unclassified -- check-powers passes green on it,
measured on a clone with its dodge skills stripped so the dodger-count assertion
could not fire instead. Reader now takes POWER\s*: and stays uppercase, because
power: occurs in ordinary prose; an asymmetric counter names the rest, and was
measured against content.mjs first (41 strict, zero loose outside them).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Desk pass 7's finding, turned into the nineteenth guard. check-rollable asks
whether a skill is reachable at 25% by the declared cast; pass 5 found that
is rollability, not competence; pass 7 found it is also not coverage. A beat
could be reachable, well-rated and silent about every band but the one the GM
improvises, and the whole suite stayed green.
tools/outcome-coverage.mjs measures. Beats are located structurally — the
bullet that owns the tag and its children, ending at the next bullet of the
same or shallower indent or the next heading, because R-287 is what an
unbounded scope does. Band widths are counted by grading all 100 results
through gradeRoll, never by arithmetic on fumbleStart, which is off by one
and was written wrongly twice this session before being caught. Ratings come
from the declared cast via R-286's reader, best-in-cast per skill, because
that is the die a table actually rolls.
tools/check-outcomes.mjs holds it, in two kinds:
RATCHET — stated failure/fumble/special counts may improve and may not
regress; unwritten-band exposure may fall and may not rise. --update
re-records these, because freezing them would make every improvement fail.
HARD CLAIMS — read from what is measured NOW, never from the baseline, so
--update cannot silence them: a floor of 18 beats (check-cited once passed
with zero citations), no beat bare of every band, every scope structurally
bounded and none over 5% of the file, no unparseable tag, no beat naming a
skill the cast has no rating for.
Verified by breaking it: a stripped beat, a broken tag regex, an unparseable
tag and a removed failure case each turn it red; the file and baseline were
restored byte-identical after each; and --update with a bare beat present
re-records the baseline and still fails.
Two bare beats found and filled while building it — the six at the back, and
the Insight that is deliberately indistinguishable on a success and a miss,
which now says so rather than saying nothing. Coverage 16/21 failure, 5
fumble, 4 special at the start; 18/21, 6 and 6 now.
The three figures are cited against the artifact rather than typed, which was
the peer session's condition and the right one: pass 7 hand-counted 22 beats
where there are 21 (Act Three's warning QUOTES a beat, and a hand count reads
the quotation as one — R-286 in the other direction), and its other three
counts were stale within one commit.
Its first catch was its author: the STATUS line announcing this guard
contained a beat-shaped tag and was counted as a beat. The reader was not
changed — prose shaped like a beat is what this file is for — and the error
message now names the fenced block as the place to write an example. Minutes
later check-cited refused a citation-shaped comment in the post-pass about
citations. Three readers, three authors describing their own format inside
it, each caught by a guard built for a different pass.
CLEAN GROUND v0.18. npm run check: 19 guards pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both earlier cast defects were shown by editing CLEAN_GROUND.md in the shared tree,
which is how this session came within a git checkout of c0's uncommitted work and
is also the weaker test. Seven cases now live in check-behaviour as strings.
Writing them found a live one: "<!-- cast : ... -->", one space before the colon,
matched nothing -- not an empty declaration R-288 would refuse but no declaration
at all, so check-rollable widened to the whole duty roster and printed its usual OK
line. check-cited's three spellings again. The reader now takes cast\s*:.
castLikeIn adds the asymmetric half: anything comment-shaped containing cast\w* the
reader did not consume is named by check-rollable, so a spelling nobody anticipated
fails the build instead of silently declaring nobody. Looser than the reader on
purpose -- a false alarm costs a reword, the opposite error costs a guard that
checks the wrong six people and says OK.
Tests then mutation-checked for being load-bearing, each mutation asserted to have
applied after a first pass where three silently did not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>