R-272: guard the tail, and stop prose drifting from its artifacts

Three things Tim asked for together: measure the long end of a fight, make the
swing check bite on stale prose, and record in rules.mjs the coincidence that
hid an invented rule from two readers.

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

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

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

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

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

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

RULES.MJS — one comment, no rule change.

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

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

npm run check: 16 guards, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
slaguru666
2026-09-13 10:20:25 +01:00
co-authored by Claude Opus 5
parent 14c34449e3
commit 8279cdfda8
10 changed files with 649 additions and 10 deletions
+70
View File
@@ -6090,3 +6090,73 @@ cure, and that a later measurement which cares about the tail must pass its own
no document cites is a maintenance obligation protecting no claim — and this suite has
fourteen guards precisely because each one was built when something started being asserted.
If the tail reaches a page, it gets a guard the same day.
---
## R-272 — the tail reached a page, so it got a guard; and two guards to stop prose drifting
R-271 ended "if the tail reaches a page, it gets a guard the same day." Tim asked for the
tail, the citation fix and the rules.mjs note together, so this is that day. Guards fifteen
and sixteen, and one comment.
**check-fight-tail (15), on `tools/fight-tail.mjs`.** The suite could not see a long fight
because every guard in it averages. EXPOSURE printed medians for the same reason.
The cap is the measurement here, so the tool passes its own — 400, not `runFight`'s default
40 — and `--update` refuses to record anything that reaches it. Proved by lowering it back
to 40: the cut loses 20 fights to the ceiling and the six-a-side 117, with 107 ending
neither won nor wiped, and the recorder stops rather than writing a truncated distribution.
That is R-271's defect turned into a refusal.
The claim checks read a fresh measurement rather than the baseline, so `--update` cannot
silence them. Two claims, chosen because the obvious one does not discriminate — "the long
end is twice the median" is true of both encounters and so distinguishes nothing:
- **Length predicts death.** A fight past fifteen rounds ends in a wipe materially more
often than a shorter one. A GM can act on that: a fight still running is not a stalemate,
it is one the party is losing slowly.
- **The six-a-side fight is the LONGER one**, median 14 against 10.7, which is R-270 showing
up as duration. A disabled fighter does not leave, they fight on 30 points down, so more
bodies means more people swinging badly for longer. The cut is shorter because it is
decisive, not because it is safer.
**check-cited (16), on `tools/check-cited.mjs`.** check-firstblood and check-attackers catch
the game changing; neither reads the document. Re-record after a re-cast and the artifact
updates, the guard goes green, and the paragraph goes on printing the old number with a
citation under it saying where the new one lives — the citation making it worse, because it
tells a GM the figure is checked.
So the citations are machine-readable: `**40**<!-- cite: first-blood cut.swing -->`, resolved
against the named JSON on every build. 25 of them now. It earned its place immediately by
failing three times on its first runs — a config keyed `column` that the prose called `line`,
two figures rounded from 32.3 to 32, and a vacuous pass when zero citations were found, which
is now fatal in its own right.
**It also refuses to let a field be quoted at all.** `fight-tail.longest` is recorded for
drift and may not reach prose: same party, same seeds, same runs, and renaming a config from
`line` to `column` moved it from 71 to 90 rounds, because `seedFor` derives the stream from
the id. Measured across seven labels — median spread 0, p95 1, p99 3, **longest 21**. A
sample maximum reads exactly like a bound on the encounter and is a property of the label.
EXPOSURE states the long end as p99 instead, which is stable to about three rounds.
The same discipline caught the deadlier ratio: 2.42 at six a side with a seed spread of 1.1,
forty-five per cent of its own value. The document states the direction of that effect and
explicitly declines to quote its size; `deadlierNoise` is recorded so the next reader can see
why.
**rules.mjs.** A comment over `resolveLocationHit` recording that its two thresholds are
unrelated and usually agree. `disabled` is a fraction of the pool per location; `majorWound`
is ceil(hp/2) and feeds only `dyingLimitFor`; neither takes anybody out of a fight, which is
`conditionFor` at 2 hit points or a destroyed head. For a 10 hp human, leg, abdomen and chest
capacities are 5 and majorWoundFor is 5 — and those locations take 12 of 20 melee results and
15 of 20 ranged. Two readers independently reconstructed a rule that does not exist, checked
it against the log, and were confirmed by it, because the agreeing case is most of the hits.
The note says: if you are about to describe either threshold from watching a fight, test an
arm. It is the only place the difference is visible.
**What this pass is really about.** Four of the defects found today were inside the guards
rather than the game. A guard is a claim we have agreed to stop checking, which is its whole
value and exactly why an assumption inside one is the least likely thing in the repo to be
questioned. Both new guards are therefore built to refuse rather than to record: one will not
write a censored measurement, the other will not let an unstable figure be cited. Neither can
be satisfied by re-recording.
+44 -4
View File
@@ -737,8 +737,8 @@ losing. Both of the first two are live in this scenario.
| Measured | Party strikes first | Then wipes | If it does not |
|---|---|---|---|
| **Three of the column vs the cut** | 50.3% (mean round 1.8) | **25.5%** | **65.5%** — a swing of **40** |
| Six of the column vs the full six | 54.1% (mean round 1.3) | 7.8% | 23.2% — a swing of 15.4 |
| **Three of the column vs the cut** | 50.3%<!-- cite: first-blood cut.firstShare --> (mean round 1.8<!-- cite: first-blood cut.meanRound -->) | **25.5%**<!-- cite: first-blood cut.wipeIfParty --> | **65.5%**<!-- cite: first-blood cut.wipeIfColumn --> — a swing of **40**<!-- cite: first-blood cut.swing --> |
| Six of the column vs the full six | 54.1%<!-- cite: first-blood column.firstShare --> (mean round 1.3<!-- cite: first-blood column.meanRound -->) | 7.8%<!-- cite: first-blood column.wipeIfParty --> | 23.2%<!-- cite: first-blood column.wipeIfColumn --> — a swing of 15.4<!-- cite: first-blood column.swing --> |
First blood is very nearly a coin toss, and in the cut it is worth **forty points of wipe
rate**. Against the full six it is worth a third of that, because one fighter is a sixth
@@ -769,7 +769,8 @@ losing. Both of the first two are live in this scenario.
second agent to connect in a round is worth more than the first — that is what
`check-focus` measures across the bestiary, and it holds here.
- **Recorded, and it agrees:** the three land **16 to 18%** of their attacks —
Bhattacharya 17.8, Renshaw 17.3, Braithwaite 16.2 — which is *above* the one-in-eight
Bhattacharya 17.8<!-- cite: attackers bhattacharya.landed -->, Renshaw 17.3<!-- cite: attackers renshaw.landed -->,
Braithwaite 16.2<!-- cite: attackers braithwaite.landed --> — which is *above* the one-in-eight
the arithmetic gives for a fresh dodge, precisely because most blows in a fight are not
the first of their round. `tools/attackers-baseline.json`, held by `check-attackers`.
- **This is the other half of why the cut is a different game.** Three attackers cannot
@@ -780,6 +781,44 @@ losing. Both of the first two are live in this scenario.
passed hand to hand with both parties off the ground rather than marksmanship. The
column being uncatchable is the point; see their tactics line.
#### How long it runs, which is not the median
**Every figure above is a middle, and a middle is not a plan.** The table prints median
rounds because that is what the suite could measure; it is the wrong number for a slot.
| | Median | Past 15 rounds | Past 20 | Past 30 | 1 in 100 runs past |
|---|---|---|---|---|---|
| Three of the column vs the cut | 10.7<!-- cite: fight-tail cut.median --> | 19.9%<!-- cite: fight-tail cut.over15 --> | 7.3%<!-- cite: fight-tail cut.over20 --> | 1.3%<!-- cite: fight-tail cut.over30 --> | 32.3<!-- cite: fight-tail cut.p99 --> rounds |
| **Six of the column vs the full six** | **14**<!-- cite: fight-tail column.median --> | **40.3%**<!-- cite: fight-tail column.over15 --> | **21.6%**<!-- cite: fight-tail column.over20 --> | 6.5%<!-- cite: fight-tail column.over30 --> | **45.3**<!-- cite: fight-tail column.p99 --> rounds |
Three things a GM should take off that table.
- **The six-a-side fight is the LONGER one**, which is the opposite of what more guns
suggests. It is R-270 showing up as duration: a disabled fighter does not leave, they
fight on at 30 points less, so more bodies means more people swinging badly for longer.
**The cut is shorter because it is decisive, not because it is safer.**
- **One six-a-side fight in five runs past twenty rounds, and one in a hundred past
forty-five.** Act Four is 45 minutes. That fight is the whole act and the Close is what
pays for it — the same warning point 2 makes about hollow men, now with the column's
numbers under it. *The last column is the honest way to state a worst case: the longest
fight in any one sample is not a bound and moves by a third of its value on a re-seed, so
`check-cited` refuses to let this document quote it.*
- **A fight still going at fifteen rounds is not a stalemate. It is one the party is losing
slowly.** Long fights end in a wipe materially more often than short ones, in both
encounters. *The size of that effect is not quoted here on purpose: at six a side it
varies by nearly half its own value across seeds, so only its direction is trustworthy.*
`check-fight-tail` holds the direction and fails the build if it reverses.
**If the clock says the fight has gone long, it has already gone wrong.** Cut to the
consequence rather than rolling it out — the party is not going to turn it around, and
twenty more rounds of dice is how a convention slot dies.
*Recorded in `tools/fight-tail-baseline.json`, re-checked on every build by
`check-fight-tail`; read it with `node tools/fight-tail.mjs`. Measured with the round
ceiling raised to 400 because the harness default of 40 truncates exactly this
distribution — at the default, one six-a-side fight in fifty is cut off rather than
finished, and the 99th percentile reads 40 because 40 is the wall.*
#### What to do instead of a fight
- **Act Four's tension is a test, not a threat.** The question is which of two people is
@@ -1064,7 +1103,8 @@ twice.*
large part of why EXPOSURE says violence at four players is party-ending. Substituting
Agyeman fixes the arithmetic as well as the lane.
- **He is not sidelined in the fight — he is busy and ineffective.** He takes his
actions — **9.2 attacks across a fight, of which 1% land** (`check-attackers`) — and
actions — **9.2<!-- cite: attackers ashcroft.attacksPerFight --> attacks across a fight, of
which 1%<!-- cite: attackers ashcroft.landed --> land** (`check-attackers`) — and
**one attack in a thousand disables anybody**. That plays worse at the table than
being obviously out of it: a player who cannot act knows to do something else, and a
player rolling once a round for an hour thinks he is fighting. **If a fight starts and Ashcroft is in it, give him something that is not