Files
RingBRP/docs/REVIEW_LOG.md
T
slaguru666andClaude Opus 5 89ce557355 Death at zero, and a case brief that answers the useful questions
R-223: nothing happened at 0 hit points. An agent was marked down and lay there
forever; the only mention of death in the system was flavour text on a destroyed
head. Zero now starts a clock whose length is the agent's own Major Wound
threshold, ticked from the same hook that charges blood loss. First Aid or
Medicine stabilises and stops the count; healing above zero clears it; heal()
refuses the dead. On death, Coherence decides what the record says — from a
proper death in service down to not being recorded as dead at all.

Case generator: four phases instead of five (the arc drew two interchangeable
middles that read as padding). The brief now leads with Where / When /
Classification / Phases, states the opposition in full, and has a "How you get
back" section. place and duration were generated and never printed. The same
facts are on the case file itself. 300 seeds checked for object leaks; one found
and fixed (group taboo/desire are objects with .text).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 22:13:21 +01:00

3863 lines
219 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RingBRP — design review log
## Round 1 — Codex review of RINGBRP_RULES_SPEC_v0.1.md
**Date:** 7 August 2026 · **Reviewer:** Codex (GPT-5.4) via `codex exec` · **Author:** Claude (Opus 5)
### Verdict headline
> "The draft has a sound BRP chassis, but it currently fails its own Ringworld test. The generic
> five-tier technology rule and generic progress-clock research system replace distinctive
> Ringworld procedures with generic sci-fi mechanics."
> "Without those changes, this remains competent generic BRP science fiction wearing Ringworld nouns."
That is exactly the failure mode the spec asked Codex to hunt for, and it found it.
### Factual errors found — VERIFIED against the source corpus
Nine of Codex's checkable claims were re-checked by Claude against `~/RingBRP/extracted/`.
**All nine were correct.** Claude's spec was wrong in each case.
| # | Codex's claim | Source check | Verdict |
|---|---|---|---|
| 1 | 1984 general HP = **CON + MAS**, not an average | Explorer Book: "hit points equal to the total of his CON + his MAS" | Codex right; `R-09` halved it |
| 2 | 1984 has **seven** hit locations (L/R arm, L/R leg, abdomen, chest, head) + general HP = 8 live values | Explorer Book: lists all seven by name | Codex right; spec said "six pools" |
| 3 | POW doubles = **open-ended reroll-and-add** for Earth humans under 100, attributed to Puppeteer breeding | Explorer Book, POW entry | Codex right; spec said "may indicate psi potential" |
| 4 | Default age die is **1D8**; 1D10 reserved for NPCs/special cases | Explorer Book: "roll 1D8 … ordinarily the gamemaster should not allow such ancient explorers" | Codex right; spec said 1D10 |
| 5 | `Ringworld` skill covers **macrostructure and physical relationships only** — explicitly "equivalent to Planetology" — not artefact identification | Explorer Book, RINGWORLD (S) | Codex right; spec's "Cthulhu Mythos slot" was a genericisation |
| 6 | BRP difficulty ladder is **Automatic / Easy (×2) / Average / Difficult (÷2) / Impossible** | BRP:UGE p.113 and glossary | Codex right; spec's +50/+20/−20/−50/×⅕ was invented |
| 7 | BRP: cannot mix Parry and Dodge in one round unless completely defensive; cumulative −30% per extra defence | BRP:UGE Dodge entry | Codex right; `R-19` omitted the restriction |
| 8 | Stunners resolve on **energy output vs target MAS**, not CON vs potency | Technology Book, STUNNERS | Codex right |
| 9 | Variable sword **cannot** cut scrith, GP hull or stasis fields, and cuts dense alloys slowly | Technology Book, variable knife entry | Codex right; spec said "ignores all non-powered armour" |
Additional errors accepted without independent check (internally consistent and checkable later):
- `R-01` fumble formula does not reproduce BRP. Correct form:
`fumble_start = 101 - ceil((101 - clamp(effective,1,100)) / 20)`
(verified by arithmetic against BRP's own worked example: skill 60 → 98–00.)
- `R-22` rounding: BRP requires `ceil` for HP, Major Wound and Experience Bonus; spec used
`round`/`floor`/`floor`.
- Major Wound is caused by **one blow** meeting the threshold, not by crossing a current-HP boundary.
- `R-22` cross-references `R-09`, which is about age, not HP. Broken reference.
- `R-14` unfamiliar-specialisation transfer is catastrophically broad — would grant
Science (Nuclear Physics) at 60% off Science (Biology) at 80%.
- `R-15` xeno-cap as written needs hidden per-skill learning-source provenance — i.e. it
reintroduces the parallel economy it claimed to delete.
- `R-16` contains 36 rows, not 35; omits Navigate and Research.
- `R-23` encumbrance base should be **STR** kg (spec said MAS), with local-gravity multiplication.
- `R-24`/`R-26` directly contradict each other on whether tier-gap-3 needs research.
- `R-27` advancement omits the Experience Bonus from the improvement roll and mis-states the
over-100% rule.
- `P-3`/`P-5` overstated the diagnosis: xenological activation spent ordinary occupation points,
not a separate currency, and was expressible on a flat sheet.
### Decision Record verdicts
| DR | Codex | Note |
|---|---|---|
| DR-01 delete impulse combat | **AGREE** | Add opposed Athletics/Pilot chase exchange for the second-by-second beat |
| DR-02 keep MAS/APP names | **AGREE-WITH-CHANGE** | Canonical engine IDs `siz`/`cha`; MAS/APP as display labels in the setting layer only |
| DR-03 consolidate skills | **AGREE-WITH-CHANGE** | Keep families but require real specialties; add Navigate and Research; publish a finite launch catalogue |
| DR-04 move ranged to Combat | **DISAGREE** | Keep firearms/energy under Perception; bows/thrown under Combat. The 1984 placement was deliberate — marksmanship as concentration, not agility |
| DR-05 xeno table as training cap | **AGREE-WITH-CHANGE** | Cap `Xenology (Species)` only; Xenology then augments other skills |
| DR-06 TECH_TIER_GAP | **DISAGREE** | Replace with familiarity: familiar 0 / analogous −20 / no analogue −50 / Builder locked until a named Discovery, then −20. Materials and weapons get explicit traits |
| DR-07 retain Research | **AGREE-WITH-CHANGE** | Rebuild as 1–5 **typed sequential steps** (field, library, laboratory, analysis). Novel alien subjects require ≥1 field step. Completion grants a Discovery, not automatic skill dice |
| DR-08 ×5 attribute rolls | **AGREE** | Lethality is controlled by HP/damage/armour, not by Idea/Luck/Stamina |
| DR-09 keep Ringworld Lore | **AGREE-WITH-CHANGE** | Keep it, but restore the narrow macrostructure remit |
| DR-10 separate mechanics/setting layers | **AGREE** | Worth the naming overhead |
### Open Question positions
| Q | Codex position |
|---|---|
| Q-01 multiplier | ×5, BRP standard |
| Q-02 hit locations | **Pools off; roll a location only when a blow causes a Major Wound**, then apply a concise consequence. Preserves severing, autodocs and variable-sword horror without seven HP tracks |
| Q-03 defence | BRP default: unlimited same-type at cumulative −30%, no mixing parry/dodge, keep missile-dodge restrictions |
| Q-04 encumbrance | STR kg; each 10 kg over: −1 MOV, −5% Physical/Manipulation/Combat; multiply by local gravity |
| Q-05 opposed rolls | Full BRP, including the loser downgrading the winner's degree |
| Q-06 playable species | Human, Kzin, Puppeteer at launch. Native guide as an NPC template; native PCs later with a warning that they change the premise |
| Q-07 automatic effects | Yes — a formal `resolution: automatic` category with explicit immunity/counter tags. Not GM fiat |
| Q-08 scale | **"Yes; this is the largest missing subsystem."** Light expedition procedure: journeys split into 3–6 legs, each with time cost, resource cost and one Navigation/Pilot test. Track exactly three expedition resources: **Supply, Power, Vehicle Integrity** |
| Q-09 Foundry | Skills as embedded Items with `familyId`/`specialisationId`/`categoryId`/`base`/`value`/`trainingCap`/`transferGroup` fields — **do not parse names**. Research as a **shared Project Item on a party/expedition Actor**, not per-PC |
| Q-10 missing | First-contact procedure; Navigate and Research skills; expedition travel; vehicle/starship scale and damage; material traits; technological traditions; the young-Earth POW rule; simweb training; gravity adaptation and zero-g; and actual species/equipment/pursuit data |
### Over-deletion (put it back)
- The four 1984 research **modes** (field, library, laboratory, armchair/analysis) — especially the
rule that major research generates a field expedition.
- `Ringworld` skill's narrow macrostructural remit.
- Young-Earth open-ended POW; simweb training; local-gravity encumbrance; gravity adaptation.
- Location **consequences** — losing them guts variable swords, autodocs and transplant resistance.
- The shared handling group linking flashlight laser and variable sword.
- Publication and assimilation of xenological discoveries back in Human Space as a downtime reward.
### Under-deletion (still too complex)
- Full BRP characteristic-based category modifiers — six calculated sheet values. Use BRP's
Simple Skill Category Bonuses, or drop category bonuses entirely.
- `R-14` — recreates root-skill leakage in a worse form.
- 36 skills plus unbounded free-text specialties can exceed the original sheet burden.
- `R-25` research — up to 50 progress points, repeated rolls, two difficulty mechanisms.
- Five universal tech tiers — "an unnecessary taxonomy with little setting truth".
- Pursuit packages plus separate allocation pools. Give each pursuit a fixed package plus at
most three player-selected increases; do not reopen point-buy per career.
- Use BRP's "remove statements" option as the default combat mode: declare and resolve on DEX rank.
### Foundry blockers
1. `R-24` — contradictory permissions, no defined unlock scope.
2. `R-25` — no step durations, difficulty modifiers, progress floor or ownership.
3. Species, pursuits, equipment, armour, weapons, defects and vehicles are referenced but have
no schemas and no data.
### Agreed direction for v0.2
1. Delete `TECH_TIER_GAP` as written; replace with technological-tradition familiarity plus
explicit material/weapon traits.
2. Rebuild Research as typed, expedition-generating steps producing Discoveries.
3. Restore `Ringworld` skill's real remit.
4. Add the expedition/travel procedure (Supply / Power / Vehicle Integrity).
5. Add a first-contact procedure.
6. Fix all arithmetic and BRP-conformance errors listed above.
### Outstanding disagreements from round 1, needing the human owner's call
- **DR-04** (ranged weapons: Perception vs Combat category) — Claude proposed Combat for BRP
conformity; Codex wants Perception preserved for setting fidelity. Genuine design split.
- **DR-06** (tech gap model) — Claude's tier taxonomy vs Codex's familiarity model. Codex's is
better; noted for adoption unless the owner prefers the simpler tier table.
---
## Round 2 — Codex review of RINGBRP_RULES_SPEC_v0.2.md
**Date:** 7 August 2026 · **Reviewer:** Codex (GPT-5.4) · **Author:** Claude (Opus 5)
### Headline — the round-1 verdict is reversed
> **"No, it is no longer generic BRP sci-fi wearing Ringworld nouns."**
Evidence Codex cited, all mechanical rather than cosmetic: open-ended Earth-human POW expressing
Puppeteer breeding (`R-05a`); age/longevity/multi-pursuit generation producing Known Space
characters (`R-09`, `R-29`); Megastructure Lore with its exact 1%-on-arrival macrostructural
role (`R-17`); Ring distance consuming Power the local civilisation cannot replace (`R-31`);
field expeditions producing Discoveries that unlock specific Builder functions (`R-25`/`R-26`);
publication creating a return-to-Human-Space loop (`R-26a`); scrith, GP hull and stasis fields
changing what weapons can physically do (`R-24c`).
> "The distinctive spine exists. The problem is now execution, not identity."
### CRITICAL — v0.2 defeats the owner's own ruling
`R-12a` made **Simple Category Modifiers the default**, and BRP's simple modifiers use the
**primary characteristic only**. So the default Ranged Combat bonus is `DEX/2` — and **POW and
CON contribute nothing**. The owner ruled that ranged weapons use DEX *and* the Perception
characteristics. The full-modifier table honours that; the default does not.
Codex's fix, adopted: **Ranged simple bonus = `ceil((DEX + POW + CON) / 6)`**, and by symmetry
Melee = `ceil((STR + DEX) / 4)`. Must be corrected in v0.3.
### Regression check — round-1 errors
**FIXED:** fumble formula · difficulty ladder · opposed downgrade · POW rule · HP source and
rounding · broken cross-reference · `R-14` transfer breadth · `R-15` xeno-cap provenance ·
`R-17` Ringworld remit · `R-19` parry/dodge exclusivity · `R-23` STR base · `R-24`/`R-26`
contradiction · research architecture and reward · stunner and variable sword · `P-3`/`P-5`
retraction · monofilament transfer group · publication reward.
**NOT FIXED / NEW PROBLEMS:**
| Issue | Detail |
|---|---|
| `R-12a` | Defeats the owner's ranged-weapon ruling (above). **Highest priority.** |
| `R-27` simweb | Invokes "special failure", but `R-01` has no special-failure band. Map to fumble or the rule is unusable. |
| `R-27` experience | Allows experience on "any skill" while also saying Research is the only route past 100% for Mental skills. Add per-skill `experienceEligible`. |
| `R-25` library hours | Should be **100 hours**, not 200. *(Verified against source: "Normally, the time needed is 100 hours per step.")* |
| `R-25` multi-researcher | "ANY success completes / ANY fumble does not" is self-contradictory. Ruling: the success completes the step, the fumble still imposes its complication. |
| `R-25` new specialisation | "(highest related − 0)" is unclear and far too generous. Start new specialisations at printed base. |
| `R-10` | Promises location consequences but supplies **no table**. Rolling a location then rolling BRP's Major Wound table can contradict itself (location "arm", result "facial scar, lose CHA"). Write the seven-location consequence table. |
| `DR-01` chase | Still no opposed Athletics/Pilot pursuit procedure. Expedition legs are not combat chases. |
| `R-08a` / `R-24d` | Zero-g and vacuum are asserted as "familiarities" with no effects, acquisition rules or schema. |
| `R-30` | Automatic-effect powers have labels but no rule for counter matching, precedence, or where immunity tags live. |
| §8 | Character familiarity state, species, pursuit, weapon, armour, defect and vehicle schemas still absent. Deferring the *data* to v0.3 is fine; deferring the *schemas* is a v0.2 blocker. |
### The four new subsystems
**`R-24` Tradition and familiarity — right mechanic, wrong granularity.**
Numbers endorsed: keep 0 / −20 / −50 / locked (at skill 60 that reads 60% / 40% / 10%, a legible
spread). But analogy and unlocking belong to a **function**, not a whole device — a Builder
console can be analogous for *operate*, alien for *diagnose*, impossible for *repair*. Also, one
tradition per device cannot model hybrid Known Space tech. Restructure to
`functions.{operate,diagnose,repair}: {analogueIn, requiredDiscoveryIds}` with `traditionIds` as
an array.
**`R-25` Research — generates play, calendar-heavy but not table-heavy, internally inconsistent.**
Codex's timing analysis: a four-step solo project at skill 50 averages ~1,600 hours (160
ten-hour days); three researchers at 50% give an 87.5% per-step success chance, cutting it to
~92 days. Defensible if it is downtime. Needs: per-step `difficulty` (default Average, currently
referenced but never stored), a cap on participating rollers, stated objectives for field steps
(`resolveInPlay: true` is not an objective), and five as the maximum *initial* length with
fumble-inserted setback steps permitted beyond it.
**`R-31` Expedition — light enough, but costs and skill roles are broken.**
- "−1 supply, −1 power; vehicle legs −1 power instead of −1 supply" is ambiguous. Ruling: foot leg
−1 Supply; vehicle leg −1 Supply **and** −1 Power. People do not stop eating inside vehicles.
- Allowing Pilot *instead of* Navigate lets a good driver find a rumoured destination without
navigating. Navigate governs every overland leg; Pilot mitigates hazards or a failed Navigate.
- Failure currently lets the party take extra time whenever time is cheap, dodging resource
pressure. Make failure cost baseline **plus one resource** and +50% elapsed time.
- Fumble: pay baseline, no progress, −1 Integrity, hazard.
- Critical should find a Site only on an **unexplored** leg, or players farm Sites on repeat runs.
- Recovery has no numbers: one day, one roll — success +2, special +3, critical +4.
- One Integrity track cannot describe several vehicles. Either name it abstract or track per asset.
**`R-32` First contact — generates a scene, but READ is weak and Standing moves far too fast.**
The maths: at skill 60, only a special-or-better READ reveals the fact that makes OFFER Easy —
**12% of attempts**. The other 48 points of success merely report disposition. That contradicts
the claim that READ is the highest-value action. Fix: success reveals one taboo *or* desire;
special reveals both; critical reveals both plus +20% to a later stage. Remove Standing changes
from READ — silently understanding someone should not make them like you. Cap permanent Standing
change at **±1 per encounter**; +3 should need at least three contacts. And OFFER needs
communication gating: if SIGNAL failed with no shared language, verbal Bargain is unavailable,
though a nonverbal gift or ritual may be attempted at Difficult (Easy if READ found the form).
### §0.3 — the hit-points pushback
**Accepted, with a caveat that matters.** Codex agrees HP must halve because the location pools
are gone, and that keeping ~26 HP with no locations would remove the danger. But it rejects the
framing that this is an *equivalent* trade:
> At HP 13 / Major Wound 7, a 1D20+5 variable sword causes a Major Wound on **95%** of hits and
> deals 13+ damage on **65%**. Against the original 26 general HP its base damage could never
> empty the pool in one hit, though it would devastate a location. v0.2 therefore converts
> frequent mutilation into frequent total incapacitation.
Ruling: keep the formula, do not sign off the combat balance until signature weapon damage,
armour values and the missing location-consequence table are tested together.
### Foundry schema failures (12 found)
Highlights: no character familiarity state anywhere; device modelling too coarse for
function-level analogy; Discovery has two competing authorities (`unlocks` is function-specific,
`scope` is tradition-wide) with no canonical matching rule; `holders` contradicts party
ownership and raw Actor UUID arrays break on import/duplicate; numeric `base` cannot express
`DEX×2` or `EDU×5`; projects omit difficulty, remaining hours, researcher caps and setback
links; the scrith material entry silently drops `deep_radar`, `hyperwave` and neutrino
absorption; `ALL` has no tag semantics; no weapon schema exists although materials expect
penetration tags; expedition data lacks leg records, explored status and multiple assets;
combat defence state has no defined owner document.
### Highest-value change for v0.3
> Make v0.3 a **complete vertical-slice playtest**, not a broad data release: one four-leg
> expedition with exact costs and hazards, leading to one native first contact, one three-step
> field research project, and one Builder device with function-level familiarity and Discovery
> data. Run that chain end to end **without GM invention or hand-edited Foundry state.** Fix
> every rule and schema that blocks it.
> "That single slice will test all four new subsystems — and reveal more than fifty pages of
> untested equipment data."
### Verification note
Ten of Codex's checkable factual claims have now been re-verified against the source corpus
across two rounds. **All ten held.** The only claim that initially failed to verify — library
research at 100 hours — turned out to be truncated by Claude's scratch deduplication script;
the un-deduped extraction confirms it.
---
## Round 3 — Codex review of RINGBRP_RULES_SPEC_v0.3.md
**Date:** 7 August 2026 · **Reviewer:** Codex (GPT-5.4) · **Author:** Claude (Opus 5)
### Arithmetic audit — Claude's published table was wrong in four places
Claude independently recomputed every P(Major Wound) with an exact-enumeration script
(`scratchpad/mw.py`) rather than trusting either party. Codex's recomputation and Claude's agree
exactly. **The v0.3 §3.4 table contained four errors.**
| Weapon | v0.3 published | Correct (MW 9) |
|---|---|---|
| Club / baseball bat | ~25% | **33.3%** |
| One-handed sword | ~30% | **37.5%** |
| Crossbow | ~25% | **18.8%** |
| Two-handed sword | ~60% | **76.0%** |
| Medium pistol | ~20% | **25.0%** |
Confirmed correct: punch 0%, dagger 8.3%, heavy pistol 40.0%, heavy rifle 58.3%, shotgun close
74.1%, hand beamer 50.0%, variable sword 85.0%, laser pistol/rifle 100%.
**The design conclusion survives** — the ladder still reads correctly from fists to lasers — but
the table must be republished with exact figures.
### Major Wound threshold — floor it at 9
Codex: `ceil(HP/3)` gives a light human (HP 23) a threshold of 8, and the step from 9 to 8
**triples** the dagger result (8.3% → 25.0%) and doubles several others. Verified independently.
Lower HP already makes small characters easier to drop; the extra wound cliff is unearned.
**Adopted: `MAJOR_WOUND = max(9, ceil(HP/3))`.**
Also: Claude's claim that 9 is "almost exactly BRP's absolute number" is **false** — BRP's is ≈7.
Nine is justified because it produces the right weapon ladder, not by BRP parity. Rationale text
must be corrected.
### Marksmanship Style — two dead rules and a trap
1. **"Reflex: first Dodge ignores the multiple-defence penalty" is a no-op.** The first defence in
a round never carries that penalty. Dead rule. If a benefit is intended, it must be *"the first
Dodge does not increment `defencesThisRound`."*
2. **"Deliberate: may aim across consecutive rounds, maximum +30%" is a no-op.** Deliberate
already reaches +30% from a single round of aiming. Dead rule.
3. **Gravity does not actually drive the choice.** Heavy-gravity characters roll DEX 2D6+6 *and*
POW 2D6+6 — both average 13. There is no mechanical pull toward Deliberate; the link is pure
flavour. Codex: make gravity **determine** the governing characteristic (light = DEX,
heavy = POW, normal = choice) rather than merely suggest it.
4. **Deliberate becomes a trap at high skill.** Codex modelled both styles at weapon 60 and found
a genuine trade (Reflex +31 moving/short; Deliberate +9 long, +7 aimed). But around weapon 90
the bonuses hit the 95% success ceiling, so Deliberate's advantages stop paying while its −20%
move-and-fire penalty persists — and style is permanent. Fix: give Deliberate something that
survives the ceiling, e.g. *a full round of aim treats long range as medium*, or *an aimed hit
improves one success level*.
### Major Wound consequence table — not ready
- Results 1–2 make **20% of all Major Wounds** merely "lose one action". Result 3 is nearly the
same. Too much of the table is nothing.
- Results 4–5 and 8 have the attacker choose arm or leg — that **is** location consequence, so
the "effects, not anatomy" claim is false. Assign 4 to manipulation impairment and 5 to
mobility impairment; remove the choice.
- "Hand unusable" and "two-handed actions at −30%" contradict each other — two-handed use should
be impossible.
- Disabled leg halves MOV but says nothing about Dodge, Athletics, falling or standing.
- Result 7 never says which skills, who picks the sense, or the treatment difficulty and time.
- Result 8's "permanent if untreated" conflicts with the Luck roll, which only governs
characteristic loss.
- Result 9 makes the GM invent the characteristic and never defines "out of the fight".
- **Repeated Stamina rolls have no stopping condition.** At CON 13 a character has only
`0.65³ = 27.5%` chance of still being conscious after three checks. Successful First Aid must
stop the sequence.
- §3.2 says the table fires "at most once per fight"; §3.5 says it fires on every Major Wound.
Contradiction with no state to back either.
- "Permanent" characteristic loss contradicts the later statement that an autodoc reverses it.
- Variable sword shifting only 8–10 → 10 yields amputation on ~28.5% of hits. If the intent is
the source weapon's mutilation rate, shift **4–10 → Catastrophic** (~66.5% of hits).
### Round-2 carry-over: 20 closed, 11 reopened
**Closed:** simweb trigger · library hours · mixed research results · new specialisation base ·
step difficulty · step cap · field objectives · maxResearchers · leg cost · Navigate vs Pilot ·
critical-on-unexplored · READ · Standing cap · OFFER gating · function-level analogy · holders ·
experienceEligible · sensor properties · `ALL` removal · chase procedure.
**Reopened / partial:** gravity enforcement and high-skill balance (`R-12b–d`) · Major Wound table
internals (`R-10`) · which resource failure consumes and who chooses (`R-31`) · recovery-fumble
target (§5 and §8 disagree) · two competing Integrity authorities · `unlocks` cannot resolve
because devices have no `deviceId` · `skill.base` formula grammar undefined · weapon schema
exists but is not buildable · **vacuum still has no effects or acquisition rule** · counter vs
immunity conflated with unrepresented precedence · species/pursuit/equipment/defect/vehicle
schemas **falsely marked closed — still absent**.
### Schema failures (worst)
No `deviceId`, so `device.cziltang_brone.operate` is a dangling reference · raw unlock paths need
string parsing and are not import-safe (use `{documentId, functionId}`) · derived HP and MW are
stored with no recalculation rule and will drift from CON/MAS · `activeWounds` can encode only
Bleeding · `rateOfFire: "1/3"` is an uninterpreted string with no round semantics, ammo cost or
reload · `shots` conflates capacity with current ammunition · shotgun has two competing damage
authorities · weapon `traditionId` singular vs device `traditionIds` array · armour lacks equipped
state, degradation and damage-type interaction · material `immuneTo` and weapon `penetrationTags`
are different namespaces with no matching rule · device functions lack governing skill, difficulty,
effect, duration and power cost · project steps are not a discriminated union · expedition legs
lack difficulty, duration, cost and table references · these are `system` fragments, not Foundry
documents — no `name`, `type`, `_id`, schema version, migrations or validation.
### Vertical slice — not buildable
> "No. The slice in §6 is a requirements list, not runnable content."
Missing: four actual leg records · the six-entry site and hazard tables · which resource a failure
consumes · Hanging People group statistics · their true taboo, desire, correct offer form and
rewards at Standing +1/+2/+3 · complete READ/SIGNAL/OFFER procedure (v0.3 has only amendment
summaries) · three complete research-step records · the brone's per-function skills, difficulties,
effects and power costs · Machine People raider statistics, ammunition, tactics and encounter
geometry · pregenerated explorers · firearm rate-of-fire and reload rules · executable wound and
treatment rules.
**Document-level contradiction:** the scope line says unchanged rules "carry over" from v0.2, but
the acceptance test permits *only this document*. A playtest spec must either contain the complete
procedures or formally import a versioned rules core.
### Verification note
Claude verified the arithmetic independently rather than accepting it. Codex's recomputation was
correct and Claude's published table was not. Across three rounds, every checkable Codex claim
that has been tested has held.
---
## Round 4 — Codex acceptance test of RINGBRP_v0.4_PLAYTEST_SLICE.md
**Date:** 8 August 2026 · **Reviewer:** Codex (GPT-5.4), running §15 as a GM · **Author:** Claude (Opus 5)
### Verdict
> **"FAIL. v0.4 is not runnable with zero GM invention."**
This is the correct and useful outcome of a first acceptance test. The rules survived three rounds
of review; they did not survive contact with actual content. That is what the slice was for.
Codex played the scenario start to finish, logging every point where it had to invent. It also ran
a 100,000-fight Monte Carlo model of the combat encounter.
### The three hard blockers
**1. The scenario gate is mathematically shut.** The control plate — the object the entire research
chain depends on — is a Standing **+3** reward (§11.4). But permanent Standing is capped at **±1
per encounter** (§6, a cap adopted from Codex's own round-3 advice), and the slice contains exactly
one contact. **+3 is unreachable.** Falling back to the random Site table, the plate requires a
critical Navigate on an unexplored leg followed by a 2 on 1D6:
> `1 − (1 − 0.02/6)² = ` **0.666%**
Two rules that were each individually correct combined into a scenario that cannot be completed.
**2. Combat cannot be resolved consistently.** There are **no general range modifiers anywhere in
the system** — so a pistol at 60 m fires at its full rating, directly contradicting §12.3's "their
weapons are useless at 60 m". Also missing: the hand beamer's range bands, the raiders' gravity
and marksmanship styles, movement distance while firing, DEX-tie resolution, a definition of
"down" for the morale trigger, and a minimum of zero on post-armour damage.
**3. The device is unusable even on success.** The brone is stored at Integrity 0. Research
unlocks `operate` but nothing repairs it — unlocking and repairing are different states with no
transition. Worse: complying with the raiders (an outcome §12.3 explicitly endorses) costs 3
Power, leaving 3, and `operate` costs 4.
### §12.4's Major Wound claim is false
The slice advertises that the heavy rifle exercises the Major Wound table at **58.3%**. Against the
party's actual armour:
| Target | P(Major Wound) per hit |
|---|---:|
| Unarmoured, MW 9 (the quoted figure) | 58.3% |
| Dunn / Tomas, armour 4 | **8.3%** |
| Ceyla / Rakhi, armour 6 | **0%** |
Fix: give the two rifle raiders Deliberate style and have them alternate aim and fire; their aimed
shots ignore 4 points of armour and would genuinely exercise the table.
### Combat model — neither style dominant, and Claude's suspicion confirmed
Sustained party output: Reflex pair 5.04 HP/round at entry, 6.44 at short range; Deliberate pair
6.22 HP/round. Deliberate is marginally better at entry, Reflex at close quarters.
> "Neither style is indispensable... If the beamer is ruled out of range, Reflex becomes clearly
> dominant. **The missing beamer range controls the conclusion.**"
Claude had independently flagged that the geometry might not test the styles as designed. Confirmed:
the pneumorifle's 200/400/600 bands mean the 60 m engagement is *short* range, where Deliberate's
+10% long-range bonus never triggers.
Monte Carlo (100,000 fights): party forces morale in 99.3–99.96% of runs, but casualty rate swings
from **14.5% to 81.2%** purely on whether the raiders focus fire or spread — an unstated NPC ruling,
not player tactics.
### Claude's three self-found defects — all confirmed by Codex
| Defect | Codex |
|---|---|
| §13.2 applies the facility modifier after the difficulty multiplier, contradicting §1 | Confirmed `[CONTRA]`. §1 gives 26%/20%, the worked example gives 16%/10% |
| §14 familiarity example says "5%" then "1%" | Confirmed `[CONTRA]` — "calls the result effective 5%, then says the floor applies, then says the floor does not apply, finally resolving it at 1%" |
| Physical category governed by STR, not DEX | Not raised by Codex, but stands — it makes Dodge, Athletics and Stealth strength-based, penalising the light-gravity pilot by 4 points |
### Pregens — verified sound
Codex independently recomputed every derived value for all four explorers. **All correct**, matching
Claude's own audit exactly. Skills judged "face-plausible" for the stated roles. Two caveats:
legality cannot be audited without an age/pursuit allocation procedure and a printed skill-base
catalogue; and Tomas carries a "minidoc" which §3.5's autodoc list (deskdoc/shipdoc/megadoc) does
not define.
### Research timing
Claude's 24.4% combined figure is arithmetically right *under the published (wrong) modifier order*,
but the calendar claim is not: expected **82 days**, not 60–80. Under §1's correct order it is
**49 days**. The cracked-plate penalty is catastrophic — a further −20 under difficulty-first
ordering drives both researchers to ~1%, giving **~1,005 expected research days**.
> "This is dead time, not play: identical rolls occur after 16–20 day gaps with no intervening
> decisions, threats or resource pressure."
Fix: replace the cracked penalty with a fixed 100-hour Average Repair step; make the 200 hours a
one-time investment with failed retries costing 40 hours and granting +10% cumulative calibration.
### Other confirmed contradictions
- **§1 fumble ordering.** The fumble test runs before the "96–99 always fails" clause, so at
effective values up to 80 some of 96–99 are fumbles rather than failures. Rewrite as: *"96–99
cannot be an ordinary success; they are failures unless the fumble range includes them."*
- **§5** Pilot may re-roll a failed Navigate "once per leg" without restricting it to vehicle legs —
Dunn can use Pilot (Flycycle) while walking.
- **§5** recovery fumble must damage a *different* track, but rolls 4–6 damage Integrity even when
Integrity was being repaired.
- **§§10, 11.4** contact happens *after* leg 3, so the leg-3 rewards are retroactive.
- Ceyla has no Athletics rating despite hazards and the aperture requiring it. Suggested 32%.
- Site 3 (Ghoul waystation) is a legal random result with no statistics, taboo, desire or rewards.
### Round-3 closure audit
17 of 22 closed. **Reopened:** result-8 permanence vs the universal Luck roll · full Foundry
documents ("incomplete JSONC specimens, not a creatable slice"). **Partial:** result-9 mapping for
multi-tag weapons · simultaneous wound sequences · the `majorWoundThisCombat` flag has no
mechanical purpose or reset · finite beam cells contradict "no reload".
### Required for v0.5
1. Move contact **before** the leg-3 roll; make immediate rewards depend on *impression* within the
encounter while permanent Standing still moves only ±1.
2. Add a general range modifier ladder: short +0, medium −10%, long −30%, extreme −50%, beyond
impossible. Retain the style +10%s. Movement with an offensive action = half MOV (15 m).
3. Give every weapon range bands in its own gear entry.
4. Give NPCs gravity origin and marksmanship style.
5. Make research completion set the brone's repair to Average at −20 familiarity; operation failure
costs 1 Power, not 4.
6. Fix the §1 modifier-order authority and apply it consistently to every worked example.
7. Physical category → DEX.
---
## Round 5 — Codex acceptance test of v0.5 · **FAIL**
**8 August 2026.** 32 of 47 closed · 14 partial · 1 open · **10 newly broken**.
**The decisive finding: v0.5's reachability claim was arithmetically false.**
Codex computed the informed contact route: READ 58 (special-or-better 12%), climb 48%,
best SIGNAL 29%, OFFER Easy 78%. Result: **P(Impression +2) = 1.697%, P(+3) = 0.174%.**
Modal outcome +1 at 55.1%. The random Site-2 fallback gave 0.499% (0.998% with a guide).
**The control plate remained gated.** Two versions had now failed on the same thing by
different routes.
Other confirmed defects: 30-item acceptance log including undefined critical/special attack
effects, incomplete action economy, cover that both penalised and blocked, pistol positions
contradicting the bounds route, no rifle aim cadence (without which the Major Wound table never
fires against armour 6), incomplete targeting ties, unpublished printed bases (so §1's 5% floor
could not be applied — and repair's true floor is 5%, not the 1% published), unpublished item
masses (so the eye-storm hazard could not resolve), and the post-compliance Power budget leaving
the brone inoperable after a diagnose.
Corrected maths Codex supplied and Claude accepted: research **93.5% by day 32, expected 25.9
days** (not "20–32"); step 3 expected 1.65 attempts; repair is a **reflecting walk to net
Integrity 3**, expected **8.48 attempts / 33.9 hours** (not "three successes, eight attempts").
Codex's combat simulation (20,000 fights/case): in cover shooting the rifles first — 50% chance
someone goes down, 0.2% TPK. Out of cover — 88%. Shooting the nearest raider instead of the
rifles — **48% TPK**. The tactical difference is enormous and was undocumented.
---
## Round 6 — v0.6 submitted
**8 August 2026.**
### The structural fix — `R-37`
> **A scenario-critical object must never sit behind a probability gate.**
> Rewards accelerate; they never gate.
The dead flyer is now a **fixed location**. Contact determines only speed and cost: +2/+3
immediate escort, +1 one-day detour, 0 three-day detour and −1 Supply. Contact keeps real weight
through a new reward — researching as the band's guests costs **no Supply**, against ~9 Supply
otherwise across a 45-day project.
### Also in v0.6
`R-38` project-hours not researcher-hours · `R-39` critical ignores armour, special halves it ·
`R-40` complete action economy (movement is free; cover, reload, aim and First Aid each consume
the action) · `R-41` cover penalises without blocking · `R-42` the immediate Stamina roll is the
first of three; unconsciousness 1D6×10 min · `R-43` targeting: nearest → lowest HP → lowest DEX,
and the down are not legal targets · `R-44` one OFFER per encounter · `R-45` margin defined ·
`R-46` climb failure · `R-47` diagnose costs 0 Power after the Discovery and reveals the exterior ·
`R-48` fumble Power cost · `R-49` net Integrity 3 · `R-50` abstract rations · `R-51`–`R-52` Ghoul
route completed · `R-53` complication 6 statted · `R-54`–`R-55` stationary Supply and hazards ·
printed bases and item masses published.
### Implementation
The ruleset now exists as a **working Foundry VTT system** at
`~/FoundryVTT/Data/systems/ringbrp` (v0.6.0), verified in a live Foundry v14 instance with zero
load warnings. Rules verified in-engine against the spec: fumble bands 98 at skill 60 and 96 at
20; 96–99 never succeed and 00 always fumbles; difficulty-before-situational (72 Difficult −20 =
16); HP and the floored Major Wound; the range ladder; critical/special/full armour; repair 5%
pre-Discovery and 38% post; diagnose 1%; aimed rifle 70% at 60 m.
---
## Round 6 — Codex review of v0.6 · **FAIL**, but the gate is closed
**8 August 2026.** Of the 30 round-5 acceptance items: **21 closed, 6 partial, 3 open.**
### R-37 worked — and then the same bug appeared twice more
> "R-37 closes the control-plate discovery gate. It does not remove every single point of failure
> from the critical path."
**1. The eye-storm could permanently delete the cziltang brone.** Rakhi's heaviest carried item
*is* the brone, at 12.5 kg — exactly what the hazard takes. Codex's arithmetic: 19.25% per
stationary hazard check, **57.5% cumulative across four**. The scenario would have died on a dice
roll more than half the time during the research phase.
**2. Power could be exhausted to a dead end.** The rim-wall site was not declared a Power-recovery
installation, and each failed operation costs 1 Power at a 48% failure rate.
**3. The "exactly enough, once" Power budget was false.** The now-guaranteed flyer yields 1D3
Power, so the post-compliance budget is **5–7**, not 4.
**4. The impression table was still wrong.** SIGNAL special (+1) plus OFFER special (+1) is **+2**,
not +3. Third time that arithmetic has been published incorrectly.
### Code-vs-spec divergences found (14)
Stale RollTable wording · pistol route contradicting the journal · Dodge base 0 vs DEX×2 · Ghoul
weapon mismatch · missing configured expedition/project/discovery · device fields absent from the
schema · rifle cadence and corrected pistol positions stranded in the Foundry journal and never
moved into the written spec.
---
## v0.6.1 — the response
`R-56` **THE CRITICAL-PATH INVARIANT**, promoted from an ad-hoc fix to a standing rule:
> *Nothing on the critical path may be destroyed, exhausted or made unreachable by a random
> result.*
- Hazards never take mission-critical equipment. Rakhi loses the toolkit, not the brone. Non-critical
losses are recoverable on a Spot the next day.
- The rim-wall site **is** a Power-recovery installation. Running dry costs days and Supply, never
the scenario.
- Power budget corrected to 5–7.
Plus `R-57` cell accounting · `R-58` the impression matrix, correct at last (+3 requires a
critical) · `R-59` rifle cadence and pistol positions moved into the spec · `R-60` the −3 archer
branch given a real procedure instead of a scope exemption · `R-61` scavenger theft procedure.
**Foundry synced to v0.6.1** and verified live: 9 compendia including a fully configured expedition
Actor carrying the three-step project, the discovery with its structural unlock reference, and the
contact group with its taboo; Dodge base as a formula; hazard tables rewritten; Ghoul hook.
### The pattern worth keeping
Three versions failed on the same class of defect in three different disguises — a gate, then a
destructible object, then an exhaustible resource. The lesson is not any of the individual fixes.
It is that **a critical path needs an invariant, not a patch**, and that only playing the thing
end to end reveals which disguise it is wearing this time.
---
# Round 7 — Strike Rank (Option 1) implementation + statline audit
*8 Aug 2026. Brief: `reviews/BRIEF_r7_strikerank_statlines.md`. Raw: `reviews/r7_codex_review.md`.*
Owner chose **Option 1, Deterministic Strike Rank**. Built, then reviewed. Codex returned
seven Part A findings and six Part B findings. Every claim below was checked against the
running system before being accepted or rejected.
## Verified true and FIXED this round
`R-57` **The expedition sheet was printing the answers to the first-contact challenge.**
The contacts tab rendered every taboo, every desire and `correctOfferForm` verbatim off the
embedded group item, while the expedition's own `knownTaboos` / `knownDesires` were empty and
`pidginEstablished` was false. The sheet displayed *"A gift of 0.5 kg of salt, passed hand to
hand, with both parties off the ground"* — the exact solution — to anyone who could open it.
Now: players see only what the expedition has actually learned, plus "Nothing learned about
this group yet."; the GM sees everything, marked GM ONLY with a yellow rule.
**This is the most severe defect found in seven rounds** — it silently defeats a scenario
pillar rather than producing a wrong number. Verified from both the GM and the player path.
`R-58` **The PC-before-NPC tie-break never fired.** It tested `actor.hasPlayerOwner`, which is
false for every pregen in a world with no players yet assigned — so all four PCs were being
treated as NPCs. Now tests `actor.type === "character"`. Verified: Ceyla (DEX 12) correctly
precedes the DEX 12 raiders she ties with.
`R-59` **Simultaneous combatants sorted alphabetically.** Replaced with insertion order, so the
result is stable and reproducible rather than an artefact of naming.
`R-60` **Strike Rank went stale, so P-10 did not actually work.** Nothing recomputed initiative
when load, weapon, skill or DEX changed, so "carrying loot changes your action economy
mid-scene" was inert. Now recomputes on actor/item change. Verified live: dropping the 12.5 kg
cziltang brone takes Rakhi from **SR 8 to SR 6** unaided, and picking it up restores it.
`R-61` **Training modifier applied to actions with no governing skill.** Move, Ready and Operate
Device were each taking the −30% "Untrained" +1. T is now only applied where the action has a
governing skill.
`R-62` **Melee defaulted to the equipped ranged weapon.** Rakhi declaring melee was scored on her
Pneumorifle. The default weapon is now filtered to match the declared action.
## Verified true, NOT yet fixed — these need an owner decision or are a separate build
| # | Finding | Status |
|---|---|---|
| A1 | `E` uses `ceil` per `R-23` "each 10 kg **or part**". Tomas is **1.0 kg** over his free carry and pays a full step of MOV, skill *and* SR. Codex recommends `floor` — rejecting all four of my proposed fixes — which drops the PC mean E from 1.5 to 0.5 and removes the cliff, at the cost of departing from the 1984 rule. | **Owner decision.** Changes `R-23`, which also governs MOV and skill penalties, not just initiative. |
| A6 | `T` spread of −2…+1 equals the entire beam-to-slow weapon spread, so 79→80% skill can outweigh weapon choice. Codex recommends −1/0/+1. Neither 1984 nor BRP:UGE coupled skill to action order; it is wholly our addition. | **Owner decision.** Changes agreed Option 1. |
| A2 | **The declared action is not enforced.** SR is computed from the declaration, but the attack controls never check it and nothing marks the offensive action spent. A combatant can declare Reload, take the Reload ordering, and then fire anyway. SR is currently advisory. | Needs action-spent state. Separate build. |
| A3 | **Aiming is free.** The attack dialog offers an unrestricted "Aiming" checkbox that grants the bonus and armour bypass with no earned aim state. Confirmed at `ringbrp.mjs:1269`. | Interlocks with A2. |
| A4 | `reloadRounds` is duration being spent as ordering. A 3-round reload delays within the round instead of occupying three. There is also no reload resolution that transfers reserve ammunition. | Separate build. |
| B1 | The Discovery gate is not enforced, and cannot be until acquisition is representable: the expedition already **owns** the discovery item whose text says "Not yet earned", so presence ≠ acquired. `unlocks` and `requiredDiscoveryIds` are inverse copies that can disagree. | Needs an `earned` state + one canonical authority. |
| B3 | `material.immuneTo` has no invocation point — the damage button rolls a formula with no target and no material, so it cannot know scrith was struck. Also: spec v0.3 says `penetrationTags`, the code says `damageTags`. | Needs target-aware damage. |
| B4 | Device status is derived as functional only at full Integrity, ignoring per-function `requiresIntegrity`. Needs per-function `locked / damaged / unpowered / ready`, plus a `modifiedByDiscoveryId` distinct from hard prerequisites. | Carried from round 2. |
| B6 | `group` has no shape in spec v0.3 at all — it was invented for the Foundry build. Needs structured `offers[]`, reward IDs, negative-standing consequences, language gating for SIGNAL, and GM-only ownership of the definition. | Design work. |
## Codex's ranked enforcement cut line for the statlines
| Priority | Fields | Treatment |
|---|---|---|
| **P0** | `requiredDiscoveryIds`/`unlocks`, `immuneTo`, `materialId` | Hard gate or immunity, explicit GM override |
| **P1** | `skillBonus`, `powerDraw`, `blocksSensors` | Apply and show in the roll; debit resources |
| **P2** | `slowTo`, `neutrinoAbsorption` | Warn only — neither has a quantified meaning, so automating would invent a rule |
| **GM reference** | `correctOfferForm`, `rewardsByStanding` | Never auto-adjudicate prose; hide unrevealed, prompt the GM |
## Correction to our own published work
`INITIATIVE_OPTIONS.md` stated `E = floor(...)` and that Rakhi was the only encumbered
character. Both were wrong: the rule is `ceil`, and all four explorers are over their free
carry. The worked example has been replaced with the order the built system actually produces,
verified in Foundry 14.361. Codex also correctly noted the round-7 brief itself repeated the
stale `floor` — the brief was written from the doc, not the code.
**Scoreboard.** Round 7 is the first round where the most serious finding was an information
leak rather than an arithmetic error, and the first where a defect (`R-57`) would have been
invisible in play until it had already spoiled a session.
---
# Round 8 — encumbrance rework, readiness, dice, playthrough
*8 Aug 2026. Brief: `reviews/BRIEF_r8_encumbrance_readiness.md`. Codex round 8 was
still running at the time of writing; findings will be folded in when it returns.*
Owner directives implemented, then tested by play rather than by inspection.
`R-23` **revised.** Encumbrance now has two readings of one condition, deliberately:
`floor(over/10)` whole steps drive MOV and skill penalties as before, while a
continuous `ceil(over)/10` drives Strike Rank. Any overage at all now costs part of
a rank and no single kilogramme costs a whole one. The 1 kg cliff is gone.
`R-69` **Initiative is no longer only about weapons.** The single skill term split
into two, each narrow: **T** training, the declared action's own skill, and **R**
readiness, the best of Tactics, Insight, Spot and Listen, floored by INT × 3 so a
clever but untrained character is not flat. New **Tactics** skill, Mental, base 5.
`R-70` **The bands were tuned to a distribution that does not exist.** After
narrowing to −1/0/+1 the thresholds sat at 80 and 30 — but measured across all 102
skill ratings in the world, weapon skills span **31–59** and top out at 59, so the
−1 band was literally unreachable and both T and R were flat +0 for every character.
Retuned to **55 / 35**, the observed quartiles. Skill now actually moves initiative.
*Found by measuring, not by reading.*
`R-63`, `R-64` the two-axis modifier system and the ±40% two-modifier cap.
`R-65`–`R-68` defects found by playing: new characters started wounded, "Automatic"
difficulty rolled normally, aiming is free for rapid weapons, packs need a restart.
All detailed in `RULES_COVERAGE_AUDIT.md`.
**Dice.** Three house sets registered with Dice So Nice — Void (flat), Shadow
Squares and The Arch — in the sheet palette with Michroma numerals, plus matching
in-chat tens/units chips that work whether or not 3D dice are enabled.
One real integration bug: `addTexture` returns a **Promise**, so colorsets
registered in the same tick silently lost their textures. Every call is now awaited.
**Scoreboard.** Round 8's most valuable finding came from neither AI reviewing code
nor from the specs, but from measuring the actual data (`R-70`) and from creating a
character and playing it (`R-65`, `R-66`, `R-67`).
# Round 9 follow-up — blockers closed, findings 5/6 and R-56 fixed
*8 Aug 2026. Codex returned FAIL on round 9 with two blockers. Both were correct.*
`R-71` **The setting reproduced Fringeworthy's premise.** Under-ice gateway, ancient
builders, alternate Earths, and a rare compatible minority named for the fringe —
Codex was right that the combination could not read as coincidence, and that
"fringe-capable" was "Fringeworthy expanded into an adjective". Replaced with
Codex's own suggestion, which is a better idea than the original: **anyone can
cross, crossing is an induced misidentification, and Coherence is the accumulating
error.** No door, no builders, no network, no catalogue, no gifted minority. The
mechanic is now the reason travel works rather than a consequence of it.
`R-72` **Both species were reskins.** Triune retained the entire Puppeteer design
and Ferren was still recognisably a Kzin. Replaced from the silhouette up: the
**Vesh** is bilaterally asymmetric with no head at all (senses on a dorsal ridge,
so no killing headshot and no face to read), and the **Cadence** is four to six
bodies that are one person, so the location roll picks which body is hit and
killing one reduces rather than kills.
`R-73` **Displacement was prose only** — zero clamped and sat there, and recovery
would walk a displaced character back to 1. Now an event: resets to full, archives
the drift log, whispers the GM to change one true thing.
`R-74` **"−X% everything" affected almost nothing** — Coherence was applied in the
skill-tab handler only, so attacks, characteristic rolls, Luck and case phases all
bypassed it. Moved to the single choke point in `skillRoll`.
`R-75` **Coherence was entirely manual.** Now charged by the system: a fumble while
across, and any use of transposed materiel, both fire automatically; `debrief()` is
one operation and refuses to run while the team is across. Verified: 8 → 7 on a
fumble, 7 → 6 on the lance, blocked across, 6 → 7 at home.
`R-76` **The case-file remap was naming only.** The phase resolver still chose a
"navigator" by Navigate; it now leads on **Tradecraft** with Navigate as fallback.
`RINGBRP.caseTracks` maps the stored fields to case vocabulary — supply→Resources,
power→Clearance, integrity→Containment — without breaking existing actors.
`R-77` **R-56 violation confirmed and closed.** "Cannot open a path home unaided"
was a gate whenever no second agent existed. The way home is now never closed:
aided 0%, alone −30%, and a failed return lands you *near* the target rather than
nowhere. Verified: `possible=true` in both cases.
# Playthrough — The Ilfracombe Crossing, 8 Aug 2026
*Ran a whole case in the live world: import, four phases, a firefight, the device,
the way home, debrief. Nine defects, every one found by playing rather than reading.*
`R-78` **The case file shipped with no team.** `members: []`, so the phase resolver
had nobody to lead and Debrief iterated an empty list. The published case was
unusable on import. Now built with its four agents.
`R-79` **Two consumers, two shapes.** The resolver read `members` as bare ids; the
Debrief handler I wrote read them as objects. Standardised on ids.
`R-80` **Bulk find/replace corrupted the language file.** The setting rename ran
`" leg" → " phase"` and turned hit locations into **"Right phase"**, `LocEffect.leg`
into "The phase gives way", and a chat title into "case team phase". Seven strings
repaired. The lesson is the one from the earlier case-sensitivity miss: a rename
sweep needs an audit pass with *different* patterns from the ones that made it.
`R-81` **Two of the three case tracks were inert.** Across a full four-phase case,
Clearance and Containment never moved once — only Resources was driven by anything.
Failure now costs Containment, a fumble costs Containment 2 and Clearance 1.
`R-82` **R-56 violated at case scale.** An exhausted track set `inCrisis` and the
sheet then *blocked* phase resolution — a case that ran out of Resources could not
continue at all. Now it degrades: phases go Difficult and cost Containment, and can
always be attempted. Verified with Resources 0 and Clearance 0: phase resolved,
`blocked=false`.
`R-83` **Forced encounters were data nobody read.** Both dramatic beats of the case
— the contact and the ambush — were declared in the phase data and referenced by
zero lines of code. Now posted to chat when the phase resolves.
`R-84` **The phase lead took the best of Tradecraft or Navigate** rather than
preferring Tradecraft, so Dunn's Navigate 67 beat the Tradecraft it was meant to
showcase. Now Tradecraft first, Navigate only as fallback.
`R-85` **The device gate was never enforced** — Codex flagged this in rounds 7 and
9 and it was still true. `requiredDiscoveryIds` was read by no code, so the locked
function at the centre of the case could be operated on turn one. Now enforced with
four distinct refusals: locked, damaged, unpowered, ready.
`R-86` **Presence could not mean acquired.** The discovery item shipped *on* the
case file, so "does the team have it" was true from the start. Discoveries now ship
`earned: false` and `earnDiscovery()` is the moment the case turns.
**Scoreboard.** Playing found nine defects in one session, including two that made
published content unusable (`R-78`, `R-85`) and one invariant violation (`R-82`)
that the two AI review rounds had both missed at case scale despite catching its
character-scale twin.
# Second playthrough — BRACKWATER, 8 Aug 2026
*A deliberately different case: a Hollow world, unexplored phases, the tables, first
contact, and the new species anatomies. Four more defects.*
`R-87` **First contact had no engine at all.** Standings, impression, taboos,
desires and `pidginEstablished` were all stored, the three-stage READ/SIGNAL/OFFER
procedure was written up in the slice — and **zero code** touched any of it. The
entire social pillar was prose over inert data.
Worse, it stranded `R-57`. That fix hides a group's secrets until the team has
*learned* them, reading from `knownTaboos` — and nothing in the system could ever
write to `knownTaboos`. Players would have seen "Nothing learned about this group
yet" for the whole campaign, permanently. A privacy fix and a missing feature that
were individually invisible and together made the content unusable.
Now implemented: three stages with their own skill pools, READ writing learned
taboos and desires back to the standing, SIGNAL establishing pidgin, OFFER the only
stage that moves standing, and `R-44` (one OFFER per encounter) enforced. Verified:
READ learned the taboo and the desire, impression reached 2, a second OFFER in the
same encounter was refused and allowed in the next — and the player view then
correctly showed exactly the one taboo and one desire the team had earned.
`R-88` **A re-attempted phase re-fires its forced encounter.** A fumbled phase does
not advance, so the same forced encounter posts again. Left as-is for now — noted
rather than fixed, because "the thing you failed to get past is still there" is
arguably correct.
`R-89` **Setting content survived in the group and slice prose.** The desire was
still "Salt. None in the canopy; the nearest seep is eleven days away", the site was
a "dead flyer", and one heading read "the The Quiet Neighbours" from an earlier
substitution. Four sweeps had passed because each used the patterns of the previous
rename rather than looking for what the *content* was still about.
`R-90` **Cadence anatomy verified in play**: 26 hit points derived into five bodies
of 6, all `unit` kind, no vital and no head, location rolls distributing evenly.
**Scoreboard.** Two playthroughs, thirteen defects. The pattern across both is the
same: the mechanics that were *reviewed* held up, and the ones that were only ever
*written down* did not exist. `R-85` and `R-87` are both cases where a document
described a system in detail and no code implemented any of it.
# Third playthrough — wounds, displacement, healing. 8 Aug 2026
`R-91` **Armour was skipped on one of the two damage paths.** `resolveHit` applied
armour; `hitLocationRoll` did not, so the same blow did more damage when it went
through the location roll — the path the sheet button uses. Now applies worn armour
plus any location armour, with the critical bypass preserved. Verified: 7 damage
against armour 4 gives 3 through on a success and 7 on a critical.
`R-92` **Healing existed nowhere and now exists.** Flagged in the coverage audit and
twice more in play without being fixed: a wounded agent could not recover by any
means the system offered. Implemented BRP-standard natural healing (1 per week),
First Aid (3, +20% with a kit, −1 on a fumble, once per wound), the field medic pack
(1 per 4 hours) and the trauma unit (1 per 2 hours). Location damage mends alongside
hit points. Characteristic loss is still not reversed, which is the standing rule.
Verified: Dunn at 5/23 → First Aid +3 → 14 days natural +2 → 12 hours trauma unit
+6 → 16/23.
`R-93` **CLOSED — location mending was silently dropping full heals.** In the same run,
a 2-point heal correctly mended `legR` from 3 to 1, but a subsequent 6-point heal
mended nothing at all despite 4 points of outstanding location damage. The mend loop
in `heal()` was correct; the write was not. **Foundry MERGES object updates**, so
`delete dmg[id]` on a cloned object leaves the stored value untouched — a location
mended to zero silently kept its damage, while a partial mend appeared to work
because it was a decrement rather than a deletion. My guess that
`prepareDerivedData` was rebuilding the object was wrong: that code only reads it.
Fixed by writing per-key paths and removing cleared locations with Foundry's `-=`
deletion form. Verified against the exact sequence that failed: 6 trauma-unit points
now clear `{legR:1, armL:3}` to `{}`, a full heal empties the object, and a targeted
mend clears the named location first.
Corrections to my own earlier reporting: `resolveLocationHit` *does* return
`majorWound`, which I initially read as a missing check — the flag was there and my
test harness simply did not print it.
**Scoreboard.** Three playthroughs, sixteen defects. Two subsystems described in
detail by the design documents turned out to have no implementation at all (the
device gate, first contact), and one core rule had never been written in either
place (healing).
# Fourth playthrough — the end-of-case loop. 8 Aug 2026
`R-94` **Experience was stored and never earned.** Every skill carried
`experienceTicked` and `experienceEligible`, the sheet drew the tick, and nothing in
the system ever set one — so the flags were decorative and no character could ever
improve. A successful roll now ticks its skill automatically, and `endOfCase()` runs
the BRP improvement roll on every ticked skill: roll over the current rating to gain
1D6, ticks clear either way. Verified in play — two skills ticked from actual rolls,
both rolled under and gained nothing, ticks cleared, and a second call correctly
does nothing.
This is the step that closes the episodic loop the owner asked for: a case ends, the
agents are a little better, and the ticks reset for the next one.
`R-95` **CLOSED — gear bonuses were prose.** The cover identity pack promised "+20% to Fast
Talk" and the medical kit "+20% to First Aid" in their descriptions, while
`skillBonus` shipped as `{skillFamilyId:"", value:0}` on every item and was read by
no code. Now populated at build time AND read: `gearBonusFor()` applies the best matching
carried item at the `skillRoll` choke point, so every path gets it. Only the single
best item applies — a pack full of medical kits does not make anyone a surgeon — and
dropped gear stops helping immediately.
`R-97` **The medical kit had two authorities for one number.** `firstAid()`
hardcoded +20% while the item itself carried `skillBonus: +20% first_aid`, so once
the reader existed the bonus would have applied twice. The hardcoded copy is gone
and the item is now the only source. Verified: First Aid 78% → 98%, with two kits
still 98%, and 78% with the kit dropped.
`R-96` **CLOSED — NPC targeting was prose only.** "Fires at the nearest visible
agent; ties go to the lowest current hit points" was written into three statblocks
and lived nowhere else, so every GM arbitrated it by hand and inconsistently.
`npcTarget()` now resolves it in the published order — nearest visible, then lowest
current hit points, then lowest DEX, then the GM chooses from what remains — and a
DOWN character is not a legal target while anything is still standing, because
these enemies are being paid rather than fanatical and shooting the unconscious
wastes a round. `declareNpcTarget()` whispers the choice and the reason to the GM,
so the table can see the rule was followed rather than fudged.
`npcShouldWithdraw()` implements the morale line from the same statblocks — "when
two of a team of six are down, the rest withdraw" — as a fraction, so it holds for
any group size.
Verified across all eight branches: nearest wins; a range tie falls to hit points;
a hit-point tie falls to DEX; a full tie reports GM choice and returns the tied set;
downed targets are skipped while others stand; the downed become legal only when
nobody stands; hidden tokens are ignored; an empty field returns no legal target.
Morale fires at 2 of 6 and not at 1 of 6.
**Scoreboard.** Four playthroughs, nineteen defects. Every one of the last three
sessions found at least one subsystem that was fully described in the design
documents and entirely absent from the code.
# R-98 — the three "unloadable" fields, root cause. 8 Aug 2026
Not a Foundry bug, not a LevelDB bug, not a data-model bug. **Foundry was never
being stopped.**
Every restart this session used `pgrep -f "main.js --dataPath=..."` to find and
kill the server. That pattern matched the *first* instance, which ran as a bare
`node main.js`. After the app was relaunched with `open -a`, it ran as the bundled
Electron binary with entirely different process arguments — so from that point on
`pgrep` matched **nothing**, `kill` was a no-op, and Foundry kept running.
The check for "did it stop" used the same pattern, so an empty result was read as
"stopped" when it actually meant "not found". A verification that can only confirm
what it is looking for is not a verification.
Everything else follows: Foundry held its original in-memory compendium for hours,
never re-read the rebuilt packs, and served a `MARKER` value that survived deleting
the entire packs directory — because the process that held it never died. The disk
was correct the whole time. So were the build script, the data model and the packs.
Two false diagnoses were published along the way and are recorded here so they are
not re-guessed: that `prepareDerivedData` was rebuilding `locationDamage` (it only
reads it), and that concurrent read-write LevelDB opens were corrupting the packs
(a genuine hazard, but not this).
Correct way to stop Foundry, whatever it was launched as — find it by the port it
is actually serving:
PID=$(lsof -nP -iTCP:30000 -sTCP:LISTEN -t | head -1)
kill -TERM $PID
# then confirm the PORT is free, not that a name pattern is absent
Verified after a genuine stop, clean rebuild and fresh start: marker gone, gear
bonuses present, case file members 4, discovery `earned: false`, and the full path
from compendium to actor to roll works — First Aid 78% → 98% with the kit.
# Fifth playthrough — the import path, now that packs actually load. 8 Aug 2026
`R-99` **The shipped case file resolved to nobody.** `R-78` stored `members` as raw
actor ids taken from the compendium. Foundry assigns fresh ids on import, so after
a normal import the case file reported four members and resolved **none** of them —
the phase resolver had no lead and Debrief iterated an empty list. The count looked
right, which is why it survived the earlier playthrough: I checked `members.length`
and never checked that the ids pointed at anything.
This was only findable once the pack-loading problem (`R-98`) was fixed, because
until then no import reflected the built content at all.
Fixed by storing **stable keys** rather than ids: every pregen is stamped
`flags.ringbrp.key`, the case file stores those keys, and `caseMembers()` resolves
key → flag → id → name, tolerating all three shapes `members` has had over the
project's life.
A second defect inside the fix: the flag was initially written before `...BASE()`,
which spreads an empty `flags: {}` and silently overwrote it. Order matters in
object spread and the symptom was an undefined flag with no error.
Verified from a clean import: members resolve to all four agents, four phases run
with all three tracks moving (a phase-4 fumble took Containment 2 and Clearance 1),
contact resolves, the device gate correctly reports locked, and debrief recovers
Coherence for the whole team.
**Scoreboard.** Five playthroughs, twenty defects. `R-99` is the third case of a
fix that made the *symptom* look correct while leaving the behaviour broken —
alongside `R-95` (data populated, no reader) and `R-57`/`R-87` (secrets hidden, no
way to learn them). Checking the number rather than the behaviour is now the
single most reliable way I have produced a false pass.
# Sixth playthrough — the sheets as UI. 8 Aug 2026
*Everything to date had been driven through the API. This drove the actual DOM.*
`R-100` **The Resolve Phase button was permanently disabled.** The case-file sheet
computed its own phase lead — resolving `members` by raw id and picking on Navigate
— duplicating logic that `resolveLegDialog` had already been fixed to do via
`caseMembers()` and Tradecraft. So the API path worked perfectly while the button a
GM would actually click was dead, and the sheet reported `navigator: NONE`,
`blocked: true`.
Two copies of one lookup, one of them fixed. The sheet now calls the shared
resolver. Verified by clicking: `Ceyla Marspont 55% (tradecraft)`, rollTarget 27,
button enabled, phase 0 → 1, two chat cards, tracks moved.
Fixing it exposed two dangling references to the removed local (`ctx.blocked` and
`ctx.rollTarget`), both of which threw at render — caught immediately because the
sheet failed to open, which is the useful kind of breakage.
## What the UI pass confirmed working
Character, NPC, case-file, weapon, gear and skill sheets all render with no console
errors. 45 rollable elements on the character sheet. Clicking a characteristic
opens the roll dialog and posts to chat. The Coherence buttons move the track
(8 → 7). Tab switching works across all six tabs. On the case file: Across/Home
toggles, the six track-adjust buttons work, and Resolve Phase now runs.
## Two false alarms, both mine
I reported tab switching as broken; it was fine — I had several sheets open and was
reading `.active` from the wrong one. I reported the NPC sheet as failing to render;
it renders fine, my harness mishandled the element reference.
**Scoreboard.** Six playthroughs, twenty-one defects. `R-100` is the fourth
duplicated-logic defect (`R-97` medical kit, `R-84` phase lead, `R-76` case tracks)
— every one the same shape: a rule implemented twice, fixed once. Worth a standing
rule of its own: when fixing a lookup, grep for every other place that does it.
# Seventh playthrough — the journal and the tables. 9 Aug 2026
`R-101` **Old-setting vocabulary survived in the journal page titles.** "The Journey
— four legs" and "Combat — the defile" — plus body text — came through every
previous sweep because those sweeps searched for *proper nouns* (Kelnok, cziltang,
scrith) and the surviving words were ordinary ones the conversion had quietly
retired: "legs" for phases, "defile" for the terrain. Renamed to "The Approach —
four phases" and "Combat — the cliff path".
A capitalised-variant miss inside the same fix: the first pass caught `defile` and
`four legs` but not `Defile` and `Four legs` at the start of a heading. That is the
same case-sensitivity error made earlier in the project, in the same file.
284 documents now clean against a pattern including the ordinary-word retirees.
`R-102` **OPEN — table effects are prose.** Drawing from the hazard table returns
its text and changes nothing: no track is debited, no roll is prompted, and there
is no table-effect function anywhere in the API. Confirmed by drawing a hazard and
watching Resources/Clearance/Containment sit unchanged at 6/6/9. Same shape as the
device gate and first contact before them — a mechanic fully specified in content
and implemented nowhere. **Not attempted; no code written.**
## Duplicated-lookup audit
Acting on the standing rule from `R-100`, I grepped for every remaining duplicate of
the lookups that have caused four defects. All clean: `system.members` is now only
read through `caseMembers()`, and coherence and gear bonuses are each applied
exactly once, at the `skillRoll` choke point.
**Scoreboard.** Seven playthroughs, twenty-two defects. The pattern holds: reviewing
finds errors in code that exists; playing finds code that does not exist at all.
# Round 10 — first playable build, and Codex as an adversarial GM. 9 Aug 2026
Built: 18 talents in four categories with a Talents tab; equipment to 27 gear, 18
weapons, 10 armour; **76 custom flat SVG icons** on every item and actor, replacing
all stock Foundry art; icons wired through the skills, combat, gear and talents
lists. Verified in the running world: 27/27 items on a pregen carry custom art.
Codex reviewed it as a cooperative-but-adversarial GM and the verdict is harsh and
largely correct.
`R-103` **My "284 documents clean" claim in `R-101` was false.** The scenario's
opening was still, almost verbatim, the Ringworld case: "two falans", "four hundred
thousand kilometres to antispinward", "starboard rim wall", "spaceport ledge",
"Architect freeway". Every sweep I ran passed because every sweep searched a word
list built from the *previous* sweep — and none of those words was ever on it. The
README still read "Basic Roleplaying on the Great Arch" and so did the engine's own
first line.
This is the fourth time a rename sweep of mine has passed its own check and failed
someone else's, and the first time I published a false all-clear on the strength of
it. **A sweep that greps for the terms you already thought of cannot tell you
whether you thought of enough terms.**
Fixed: the case opening is now an actual Custodians brief — a Custodian team missing
eleven days, a beacon still transmitting, something holding a crossing open that
should not be able to. 329 documents now clean against a term list that includes
the megastructure vocabulary I had never checked for.
`R-104` **Kit and training bonuses were silently colliding.** `gearBonusFor()`
iterated *every* embedded item, so talents were swept into the gear pool and the
"best single item applies" guard then discarded one of them — a medical kit and
Triage collapsed into a single +20 rather than +40. Now two separate pools that
stack, with best-of-one *within* each. Verified: Tomas 78% → 118%.
## Codex's findings not yet fixed — all confirmed by inspection
| | |
|---|---|
| **The talents are cosmetic.** No runtime reads `grantsAction`, `usesPerCase`, `usedThisCase` or talent `coherenceCost`. There is no Use control. Only the numeric `skillBonus` does anything, and only by the accident fixed above. | Large build |
| **First contact has no UI.** `resolveContact()` is API-only; the case sheet has no READ / SIGNAL / OFFER buttons, so the scenario's forced contact stalls the session dead. | High |
| **The case cannot be completed through the UI.** No resolve-step control for research projects, no Operate/Diagnose/Repair buttons, no way to earn the Discovery except from the console. | High |
| **Group rewards are inert arrays** — impression moves, nothing it promises happens. | High |
| **The journal contradicts the engine** — it says Navigate governs phases; the engine prefers Tradecraft. | Medium |
| **Ceyla has Cover Work but no Fast Talk skill**, so her signature talent cannot fire. | Medium |
| Research as written is ~44 days plus 34 hours of repair — not one session without a montage procedure that does not exist. | Design |
**Scoreboard.** Ten rounds, 104 findings. The pattern of this round: everything
*visible* was built and works — sheets, icons, talents on the page — and almost
nothing behind the newest content is wired to a button. Codex's single most useful
sentence: *"the exact first-session stall is 'The Quiet Neighbours intercept you.
Now what button do I press?' There is none."*
# Round 10b — contact and device controls wired. 9 Aug 2026
*Answering Codex's two highest-severity findings: the first-session stall and the
uncompletable case.*
`R-105` **First contact now has controls.** The case-file contacts tab carries
READ / SIGNAL / OFFER / New encounter per group, with live impression, standing,
pidgin and offer-spent state. Choosing who talks opens a picker **ordered by who is
actually best at that stage, with the rating shown**, so the obvious choice is the
default and the interesting choice is still one click away. Verified end to end:
picker listed Tomas 58% (insight) above Ceyla 46% above Dunn 25%, and READ resolved
to chat. OFFER greys out once spent and New encounter restores it.
`R-106` **Device functions now have buttons.** Operate / Diagnose / Repair appear
under any device on the gear tab, each showing its own live blocker rather than a
generic disabled state — "Locked — the discovery that explains this has not been
made", "Too damaged. Needs Integrity 3; it has 0", "Not enough Clearance. Needs 4;
the case has 2". `useDeviceFunction()` resolves the roll, applies familiarity after
the Discovery, charges Clearance and Coherence, moves Integrity on repair, and
posts the function's own printed outcome text.
Verified as a chain: Integrity 3 alone stays locked → earning the Discovery unlocks
it → Operate succeeds, charges **4 Clearance and 1 Coherence**, and dropping
Clearance to 2 re-blocks it with the reason stated.
`R-107` **A cost of 4 was being charged as 0.** `powerCostAfterDiscovery` is absent
from the source data but the data model fills absent keys with **null**, so the
guard `f.powerCostAfterDiscovery !== undefined` passed and `Number(null)` gave 0 —
Operate ran free. Now `!= null`. Caught only because I checked the track moved
rather than that the button worked, which is the rule from `R-99` doing its job.
Still open from round 10: the talents remain mechanically inert (no Use control, no
reader for `grantsAction` / `usesPerCase` / talent `coherenceCost`), group rewards
are still inert arrays, research projects have no resolve-step control, and the
journal still says Navigate where the engine prefers Tradecraft.
# Round 10c — talents made mechanical. 9 Aug 2026
*Codex round 10 finding 4: "a cosmetic talent release, not a mechanical one."*
`R-108` **Talents now do things.** The split that makes this work is ACTIVE versus
PASSIVE, decided by the data rather than by hand: a talent is active if it costs
Coherence, is limited per case, or bends an action — those get a Use button.
Everything else is passive, has no button, and is read at the point it applies.
That prevents the failure mode where a sheet is covered in buttons that mostly say
"yes, you still have this talent".
Active, verified: **Echo Sense** charges 1 Coherence (10 → 9). **Clean Scene** is
1/case, arms, refuses a second use, and its flag clears when spent. **Requisition**
trades 1 Clearance for 2 Resources or the reverse, refusing when neither track can
pay.
Passive, verified: **Chain of Custody** absorbs a transposed cost entirely (a
1-point crossing key now costs Rakhi nothing). **Triage** adds a point to First Aid
(Ceyla 5 → 9 where a plain success gives 3). **Read the Room** learns a second fact
on a successful READ (Tomas learned 2 in one roll). **Debriefer** gives another
agent an extra point at debrief. **First Through** and **Out of Step** bend Strike
Rank to 1 and by −1 respectively.
`R-109` **`endOfCase` reset talents after its early return**, so a character who
ticked no skills kept every talent spent — permanently, for the rest of a campaign.
Moved above the return: end of case is end of case whether or not anything was
learned. Verified: clean_scene 1/1 → 0/1 with zero ticked skills.
This also answers Codex's `Chain of Custody` concern in the affirmative — it *does*
reduce a 1-cost item to free, which removes the central tension for that one agent.
Flagged for the owner rather than silently nerfed; the talent is doing exactly what
it says, and whether what it says is right is a design call.
Still open: group rewards remain inert arrays, research projects have no
resolve-step control, and the journal still says Navigate where the engine prefers
Tradecraft.
# Round 11 — generators, and Codex's verdict on them. 9 Aug 2026
*Owner: "press a button and a character is created", with full customisation
still available, plus NPCs, monsters and things in between.*
Built `generateCharacter()` and `generateNPC()`, both callable with **no arguments
at all**; a dialog that opens pre-randomised so the fastest path is open → Create;
buttons on the Actors sidebar; 5 roles, 4 experience tiers, 6 NPC threat tiers,
3 species, and a count field for 1-12 at once.
Codex reviewed as an adversarial GM. Verdict: *"Do not ship this as fully formed
character generation. It is a competent statblock seeder."* It was right.
`R-110` **Most combat NPCs could not use their own weapons.** The threat tier chose
the weapon while an unrelated random PC role chose the skills, so the two never had
to agree. Codex's measured numbers: Trained received no weapon at all, Armed 44%,
Dangerous 20%, **Apex 0%** — eight Apexes in ten had no usable attack. Replaced
`weapons: [...]` with `attacks: [{weapon, skill}]`, granted as one invariant.
Verified 10 of each tier: **60/60 weapons now have their skill**.
`R-111` **"Make four" produced four copies of one character.** The dialog resolved
"random" ONCE and passed the same role to every actor in the loop, with species and
tier hard-defaulted. Now every choice resolves per actor, "Random (rolled
separately for each)" is an explicit option, and party mode guarantees role
coverage when several PCs are made with the role left random. Verified: a party of
four came out with three distinct top skills instead of four identical sheets.
`R-112` **The generator was rolling characteristics that do not exist.** It rolled
`app` and `mas`; the data model has `str siz con int pow dex cha edu`. The Vesh
`app: -2` shift was therefore modifying a dead field — species generation was
mechanically lying. Also wrote `origin.homeWorld` where the sheet reads
`origin.world`, so every generated character had a blank home world.
`R-113` **Two shipped weapons had no skill to roll.** The Collapsible baton needs
`melee_weapon:club` and the Error cutter needs `transposed_weapon`; neither existed
in the 50-skill catalogue. This was a **content bug affecting players**, not just
the generator — anyone handed a baton could not attack with it. Catalogue now 52.
`R-114` **The Cover Work class of bug, reproduced and then closed.** A generated
talent whose `skillBonus` names a skill the character was never granted is dead on
arrival. The generator now grants that skill. Verified across all five roles: zero
dead talents, zero unusable weapons. Also added: everyone carries a **personal
anchor** (the character hook and the only Coherence recovery route) and an agency
handset, reserve ammunition is stocked at twice capacity, and the Liaison's
duplicated Trade Cant is deduplicated.
Still open from Codex round 11: Containment starts encumbered by its own issued
kit; the bonus-point spray can produce one absurd skill; blank biography, fixed age
30 and identical portraits; and the gap between Armed and Anomalous where most
Custodians cases actually live.
# Round 11b — encumbrance, biography and portraits. 9 Aug 2026
`R-115` **Everyone is issued a full kit; nobody carries all of it.** Containment
began every case at +0.78 SR for owning its own breaching charges. `stowOverload()`
now marks the heaviest items `carried: false` after kitting, so the kit is *issued*
and the player decides what comes off the vehicle. Containment is now +0.00 to
+0.34 depending on the STR roll, and every other role is exactly 0.
Two flaws in the first version of that logic, both found by reading the output
rather than trusting it:
- It stowed **everything** when it could never get under, because heavy armour is
never stowable — including shedding a 0.1 kg keepsake to fix a 4 kg overload.
It now only stows an item when that item is big enough to matter, and gives up
gracefully instead of stripping the character.
- The **personal anchor was being stowed**. It is the character hook and the only
Coherence recovery route, and it weighs 0.1 kg. It is now never stowable, and a
secondary melee weapon is, because an entry bar can be slung.
The role's key characteristic is now the best of three rolls rather than two, so a
Containment specialist is actually strong enough to carry what Containment is
issued. The residual load on a low-STR Containment agent is the 11 kg breaching
suit, which *should* slow you down and is left alone deliberately.
`R-116` **Biographies and portraits.** Every generated character gets a short
service record — role and tier, an origin drawn from their home gravity, how they
were recruited, a role-specific observation, and an **anchor**, which ties the
character hook to the Coherence rule rather than leaving it as colour. Ages are
26-54 instead of a fixed 30. 25 new portrait icons: ten baseline, five Vesh (mask),
five Cadence (three-body), five threat. Verified 4/4 distinct across a party, and
species-appropriate.
A cluster of three Physicians in one sample of four looked like a distribution bug.
It was not — over 24 characters all five roles appeared 2-7 times and all six
anchors 2-6 times. Checked rather than assumed, and left alone.
# Round 13 — Reaction Lanes replaces Strike Rank. 9 Aug 2026
*Owner rejected Strike Rank as "too complex for Foundry"; then added that the game
must also run at a physical table with paper sheets, and that the random element
must be no more than 30-35% of the outcome.*
`R-117` **Strike Rank removed entirely and replaced by Reaction Lanes.**
Reflex = ceil(DEX / 3)
Awareness = ceil(readiness / 10)
Reaction Base = 2 x max(Reflex, Awareness)
Initiative = Current Base + d4 HIGHEST acts first
Quick thought fully substitutes for quick hands — `max()` rather than a sum is what
removes the combat-training bias. Verified live: Ceyla reaches base 12 through
awareness, Dunn reaches the same 12 through reflexes, Tomas through awareness,
Rakhi 10.
**The randomness ceiling drove the die size.** Measured as
`die range / (die range + printed-base range)` over a realistic envelope
(DEX 9-18, readiness 30-60):
| | share |
|---|---:|
| `3 x Lane + d6` as first proposed | 35.7% — over |
| Dual-Axis `Reflex + Awareness + d6` | 45.5% — well over |
| **`2 x Lane + d4`** | **33.3%** |
A d6 could never have worked: its range of 5 is too close to the entire printed
spread. Probabilities that follow: trained-and-advantaged versus
untrained-and-dragged is **100%** (the gap is 6, the largest opposed d4 swing is 3),
one band apart is **87.5%**, same band is a genuine **50%**.
`R-118` **The paper sheet drove the design.** The base is calculated once at
character creation and never again. Beneath it sits a seven-box **modifier rail**
(−3 to +3) filled in once; a clip marks the box in force. Aim, Edge, Drag and Load
move the clip — they never trigger a recalculation. At the table a round is: move
the clip if anything changed, roll one d4, add it to the marked number. **One die,
one addition, no lookup.** The digital sheet renders the same rail.
`R-119` **Coherence Flux without breaching the ceiling.** An additive ±1/±2 Flux
would have pushed the random share to 45-54%, so Flux **consumes the die instead of
adding to it**: at Coherence 3-5 the outer faces misfile (1 reads as 4, 4 as 1); at
0-2 every face inverts. The distribution stays uniform 1-4, so the ceiling and the
probability tables are untouched — but the die visibly lies, and a misfiled agent
acts at the wrong moment. Verified: Rakhi at Coherence 1 rolled a 3 and it was read
as a 2.
**Four self-inflicted breakages during the excision**, all caught by reading the
console rather than assuming: removing the Strike Rank block also deleted the
module-level `ceil` helper, `readinessRating()`, and `percentileFaces()`, each of
which other subsystems depended on; and the `game.ringbrp` API object silently
failed to attach because it referenced one of them. A single undefined name in an
object literal takes the whole init hook down with no error attributable to the
system. Restored, and the API block is now verified by script for undefined
identifiers.
# Round 13b — the invisible die, and the d6. 9 Aug 2026
`R-120` **The initiative die was never shown.** `rollReaction()` evaluated a `Roll`
and read `.total`, then posted a plain `ChatMessage` with rendered HTML. Foundry
only hands dice to Dice So Nice when a message actually **carries** the Roll, so
nothing animated and the roll was invisible — the owner reasonably concluded there
was no random element at all.
Fixed by pooling every combatant's die into one `Roll` via `PoolTerm.fromRolls()`
and attaching it to the chat card with `rolls: [pool]`. Verified: the card now
reports `isRoll: true` with **4 dice objects**, and `showForRoll` fires **once** for
the whole group.
A first attempt awaited `game.dice3d.showForRoll()` per combatant inside
`rollReaction`. That worked but serialised one full animation per character and
made rolling for a party hang long enough to time out. Removed — the pooled roll on
the message animates them all together.
`R-121` **d4 → d6 at the owner's request.** Random share rises from **33.3% to
45.5%** of the printed-base spread. That is above the 30-35% ceiling he set
earlier, and he accepted the trade knowingly. Observed spread across the party went
from 12-17 to 14-17 with more movement between rounds.
`R-122` **OPEN — three of the four agents share Reaction Base 12, and it is not a
bug.** Readiness 51-60 all round to Awareness 6, and Ceyla 55, Dunn 55 and Tomas 58
are genuinely within three points of one another. The banding is faithfully
reporting that the characters are similar. Options measured:
| awareness divisor | distinct bases | spread | cost |
|---|---:|---:|---|
| /10 (current) | 2/4 | 2 | — |
| /8 | 3/4 | 4 | awareness begins to dominate DEX |
| /7 | 3/4 | 6 | Dunn's DEX 18 stops mattering |
No change made: separating them means making awareness beat reflexes more often,
which is a design decision for the owner rather than a defect to fix.
# Round 14 — pregens differentiated, all content as Items, combat playtested. 9 Aug 2026
`R-123` **The pregens now pull the two initiative axes apart.** Three of four shared
Reaction Base 12 because their readiness was 55/55/58 — the mechanic was faithfully
reporting that the characters were nearly identical. Rebuilt so each sits clearly
on one axis:
| | DEX | reflex | readiness | aware | base | led by |
|---|---:|---:|---:|---:|---:|---|
| Ceyla | 10 | 4 | 71 | 8 | **16** | awareness |
| Dunn | 18 | 6 | 34 | 4 | **12** | reflex |
| Tomas | 14 | 5 | 58 | 6 | **12** | awareness |
| Rakhi | 11 | 4 | 47 | 5 | **10** | awareness |
Three distinct bases, spread 6. Ceyla is now the slowest on her feet and reliably
the first to act, which is the design working: she has usually worked out what is
happening a beat before it happens. Dunn and Tomas tie at 12 by *different routes* —
reflexes and awareness — which is the point of `max()` rather than a sum. Bios were
rewritten to match the numbers.
`R-124` **All content installed as Items.** `installContent()` now covers every pack
— skills, talents, weapons, armour, gear and devices — **125 items** browsable and
draggable in the Items directory, not just the 70 skills and talents.
## Combat playtest — three defects found and fixed
`R-125` **HP was calibrated for weapons that no longer exist.** `hitPointsFor` was
`CON + SIZ`, double standard BRP, set deliberately when the weapon ladder was the
1984 one (laser rifle 1d10+30). The setting rewrite replaced those with modern
agency issue averaging 3.5-9 damage, and HP was never brought down. Measured
result: **ten rounds of melee with nobody dropped and armour absorbing 51% of all
damage**. Now `(CON + SIZ) / 2` — Dunn 23 → 12, Rakhi 29 → 15.
Note for the owner: the Major Wound threshold is unchanged at 9, because the floor
was already doing all the work at both scales. But 9 is now 60-75% of a character's
pool rather than 35%, so a Major Wound is a much bigger event than it was. That
follows from your "floors at 9" ruling meeting the new HP scale — worth a look.
`R-126` **Human NPCs had natural armour.** Cleaners carried `naturalArmour: 2` on
top of a stab vest, an alien-hide leftover from the old setting, making ordinary
contractors better protected (4 AP) than the agents shooting at them (2-3 AP). Set
to 0 for all human NPCs; the Revenant keeps its 4 because it is not a person.
`R-127` **The compact pistol could not hurt anything.** 1d6 against 4 AP did
**nothing 66% of the time**, average 0.51 through — and it is the sidearm issued to
the Physician and Liaison roles, so those characters simply could not take part in
a firefight. Raised to 1d6+2, still the weakest firearm but functional.
Also: **Dunn was issued a baton but trained in knife**, so he was swinging at his
untrained 5% club skill. Reissued a utility knife.
## Result, 10 runs of each
| | before | after |
|---|---|---|
| Ranged, 2 PCs vs 2 Cleaners | **PC 0/10**, avg 8.4 rounds | **PC 7/10**, avg 8 rounds |
| Melee, 2 PCs vs 2 Keepers | PC 10/10, avg 6.5 | PC 10/10, avg 6 |
| Ranged, full team vs 3 Cleaners | not run | **PC 10/10**, avg 4.4 |
Armour now absorbs 28-39% rather than 40-51%. A fair fight is genuinely in doubt;
a full team against a smaller force wins in about four rounds. Melee remains
strongly PC-favoured, which is Rakhi: an entry bar with a +1d6 damage modifier
against unarmoured scavengers is meant to be decisive.
# Round 14b — Major Wound at half hit points. 9 Aug 2026
`R-128` **Major Wound is now `ceil(HP / 2)`**, replacing `max(9, HP/3)`.
The floor of 9 had been doing all the work at both the old and new HP scales, so
once HP halved it became 60-75% of a character's whole pool and a Major Wound
almost never fired. Half HP restores it to what it is meant to be: a blow that
takes you out of the fight, not one that nearly kills you outright.
Dunn HP 12 → MW 6 Rakhi HP 15 → MW 8
Tomas HP 13 → MW 7 Ceyla HP 13 → MW 7
A duplicate copy of the formula in `tools/content.mjs` still carried the old
`max(9, hp/3)` and was silently disagreeing with the engine — the packs were built
with one rule and played with another. Aligned.
Dunn's biography also still explained the retired floor ("his threshold would be 8
by the arithmetic; the floor raises it to 9") and carried a mangled phrase left by
an earlier blanket rename. Rewritten.
## Measured effect, 10 runs each
| | result | rounds | majors |
|---|---|---:|---|
| Ranged 2 v 2 | PC 8/10 | 6.9 | 23% of hits |
| Melee 2 v 2 | PC 10/10 | 4.9 | 19% of hits |
| Ranged 4 v 3 | PC 10/10 | 4.2 | 50% of hits |
Roughly one hit in five now causes a Major Wound in an even fight, and the ranged
2 v 2 improved from 7/10 to 8/10 with a round shaved off. The 50% figure in the
4 v 3 is the team's heavy weapons — Rakhi's marksman rifle and Ceyla's Error lance
— against 2 AP contractors, which is what those weapons are for.
# Round 14c — wound effects exercised and made real. 9 Aug 2026
*The hit-location path had never been run inside a fight; every previous playtest
used `resolveHit`, which skips locations entirely.*
`R-129` **Location sizes had not been rescaled with HP.** Fractions were set
against the old 26-point pool, so halving HP left a leg at 4 hit points against a
sidearm doing 4-6 through armour. Raised and, more importantly, set **explicitly by
location kind** rather than by chained string replacement — an earlier attempt at
this corrupted the values into `0.280` and `0.425` because each replacement ran over
the output of the last.
leg .42 · body .42 · vital .50 · arm .35 · head .40 · unit .28
Measured over 150 clean hits each, against armour 3:
| weapon | disables the limb | major wound |
|---|---:|---:|
| Compact pistol | 5% | 0% |
| Service pistol | 29% | 23% |
| Marksman rifle | 46% | 43% |
| Entry bar +1d6 | 56% | 53% |
A sidearm rarely cripples, a rifle often does, a heavy melee weapon usually does.
`R-130` **My own measurement was wrong first, and it was the R-93 trap again.**
The initial reading was "every hit disables a location, 6 of 6" and then "97-100%".
Both were artefacts: my harness reset with `{"system.locationDamage": {}}`, and
**Foundry merges object updates**, so nothing was ever cleared. Accumulated `taken`
values reached 382 and 434, which of course disabled everything. The fix is the
`-=` deletion form, exactly as recorded in `R-93` — and I did not apply my own
earlier lesson. The system was closer to correct than my test said.
`R-131` **Wound effects were printed and never applied.** The chat cards were
right — "The arm is useless. Two-handed actions are impossible; −30% Manipulation",
"A vital hit. Knocked down, and a Stamina roll or unconscious" — but nothing read
`disabledLocations`, and a *destroyed* leg left MOV at 10. Now derived in
`prepareDerivedData` and applied at the `skillRoll` choke point alongside
encumbrance and Coherence:
| state | MOV | effect |
|---|---:|---|
| healthy | 10 | shot at 59% |
| arm destroyed | 10 | shot at **29%** (−30 Manipulation) |
| leg disabled | **5** | −30 Physical |
| leg destroyed | **1** | prone |
Clean six-round firefight afterwards: 9 hits, 4 crippled limbs, 3 major wounds,
two shots stopped dead by armour, PCs won with Dunn at 7/12 and a battered leg.
# Round 14d — combat with wound effects live. 9 Aug 2026
The penalty is visibly load-bearing. From a narrated firefight, round 5:
FOE 45%-30= 15% roll 6 success
— a Cleaner whose arm had been crippled two rounds earlier, now shooting at 15%
instead of 45%. Dunn finished the fight on 1 hit point with MOV 5 and a ruined leg.
Across 12 runs of each engagement:
| | result | rounds | majors |
|---|---|---:|---:|
| Ranged 2 v 2 | PC 8/12 | 6.4 | 36 |
| Melee 2 v 2 | PC 12/12 | 5.5 | 22 |
| Ranged 4 v 3 | PC 12/12 | 4.4 | 57 |
**No death spiral.** Only 8-14 shots per engagement were fired by an already-wounded
combatant, because fights resolve before the penalty compounds. The wound matters
in the moment it lands without dragging the loser through a hopeless tail.
`R-132` **`destroyed` was tested against a single blow while `disabled` was
cumulative.** `resolveLocationHit` used `d >= max * 2` — this hit's damage — so a
limb could never be destroyed by repeated wounds, only by one enormous strike. The
actor's own derived data meanwhile computed `destroyed` from *cumulative* damage, so
the resolver and the sheet disagreed about the same limb: a location carrying 100
points of damage against a maximum of 6 reported `destroyed: false` from the
resolver and `destroyed: true` on the sheet. Now cumulative in both places.
That is the fifth duplicated-rule defect in this project (`R-97`, `R-100`, `R-104`,
`R-128`, and now this) — a rule implemented twice and changed in one place.
**Caveat on an earlier figure.** The "47% of hits crippled" in the trials counts
repeat hits on a limb that was *already* crippled, because `disabled` correctly
stays true once crossed. The honest per-hit rate is the controlled measurement:
5% for a sidearm, 29% service pistol, 46% marksman rifle, 56% entry bar.
# Round 15 — the duplicated-rule class, fixed at the root. 9 Aug 2026
Five separate defects in this project came from one shape: a rule implemented in
two places and changed in one — `R-97` the medical kit's bonus, `R-100` the phase
lead, `R-104` kit versus training bonuses, `R-128` Major Wound, `R-132` limb
destruction. Fixing them individually was not working, so this round fixes the
cause.
`R-133` **`tools/content.mjs` carried its own copies of the engine's rules**, under
a comment that said so out loud: *"rule functions mirrored from ringbrp.mjs"*. They
had drifted twice. Major Wound was `max(9, hp/3)` in the builder while the engine
used `hp/2`; and — found live during this audit — **hit points were still
`CON + SIZ` in the builder after the engine had halved them**, so the shipped packs
carried Ceyla at 26 HP and Rakhi at 29 while the game computed 13 and 15. Derived
data masked it on load, which is why playtesting never caught it.
**Fixed structurally.** New `rules.mjs` is the single authority for every pure rule
formula — hit points, Major Wound, damage modifier, marksmanship, location size,
the two Reaction axes and the base, encumbrance. It imports nothing from the
runtime and touches no Foundry globals, so both sides can use it: `ringbrp.mjs`
imports it in the browser, `tools/content.mjs` imports it in Node. Every local copy
deleted; verified zero re-definitions remain in either file.
`R-134` **A guard, so the class cannot come back.** `tools/check-rules.mjs`:
1. fails if anything outside `rules.mjs` re-defines a canonical rule,
2. asserts the builder's aliases *are* the canonical functions rather than copies
with the same name,
3. spot-checks eight formulas against known values.
`build-packs.mjs` now runs it first and **refuses to build** on failure. Proven by
re-introducing the exact `hitPoints = con + siz` regression: the check failed with
`content.hitPoints is not the same function as rules.hitPointsFor` and exit code 1.
Verified after rebuild: packs store 13/15/12/13 HP with Major Wounds 7/8/6/7, and
sheet and engine agree on every pregen for the first time.
`R-135` **Codex found the one the guard missed: constants are rules too.** The first
version of `check-rules.mjs` policed rule *functions* only. `ringbrp.mjs` still held
`RINGBRP.reaction = { dexDivisor: 3, readinessDivisor: 10, laneMultiplier: 2, die: 6,
modCap: 3 }` — a second literal copy of `rules.REACTION`, with its own copy of the
divisors — and two places recomputed `laneMultiplier * max(reflex, aware)` inline
rather than calling `reactionBaseFrom()`. Exactly the defect being fixed, surviving
inside the fix. Now `RINGBRP.reaction = REACTION` by assignment, both call sites go
through the canonical function, and the guard fails on any object literal carrying
two or more keys of a canonical constant set.
`R-136` **Discovery made the guard weaker, and the regression test caught it.**
Deriving the alias list by scanning for `export const X = Y;` meant that replacing an
alias with the drifted arrow function `(con, siz) => con + siz` stopped matching the
discovery regex — so it was never checked and the guard reported OK. The original bug
passed. Fixed by making a hand-held `REQUIRED` map authoritative, with discovery only
able to ADD: a missing alias now fails as loudly as a wrong one.
`R-137` **The same drift in prose.** The Reaction comment still described a d4 and
quoted d4 probabilities after the die changed to d6; `README.md` still advertised
`CON + MAS`, `max(9, ceil(HP/3))` and gravity-based marksmanship. Both corrected, and
the code comment no longer restates any formula — it points at `rules.mjs` and carries
only measured behaviour.
**Guard now fails on four escape routes, each verified by deliberate regression:**
an alias swapped for a drifted copy; an alias deleted; a rule defined in a brand-new
file; a rule added to `rules.mjs` with no spot-check. Plus the constant-literal case.
**Measured, and worth the owner knowing:** on the shipped pregens the d6 puts the
random share at **45.5%**, above the 30–35% ceiling that was set. One band apart wins
72.2% (81.3% excluding ties). The d6 was chosen deliberately for table feel over the
d4 that hit 33.3%. `REACTION.laneMultiplier: 2 -> 3` returns the share to 35.7%
without touching the die — a one-line change, left undone because it was not asked for.
`R-138` **laneMultiplier 2 -> 3, at the owner's instruction.** The random share is
now **35.7%**, inside the 30–35% ceiling. Verified live: bases 24 / 18 / 18 / 15.
Measured on the shipped party over 200,000 rounds, both settings, load applied:
| | share | Ceyla | Dunn | Tomas | Rakhi |
|---|---:|---:|---:|---:|---:|
| laneMultiplier 2 | 45.5% | 73.1% | 14.8% | 12.1% | 0.0% |
| **laneMultiplier 3** | **35.7%** | **94.8%** | **2.8%** | **2.4%** | **0.0%** |
The ceiling was bought with order. Cutting the die's share does not spread the lead
around — it concentrates it. Ceyla goes from a strong favourite to a near-certainty,
and the two mid agents lose most of their share of the lead. Same band is still a
50/50 (16.7% ties, broken by DEX) and one band apart is 83.3%, but **two bands apart
is now 100%**: a six-point gap cannot be closed by a d6 at all.
Rakhi never leads at either setting, so that is not caused by this change. It is her
−2 Load from carrying 38 kg against a free carry of 18. Dropping roughly 20 kg returns
her to 15 and puts her level with Dunn and Tomas — a real decision on the sheet rather
than a fixed disadvantage, which is the intent.
`R-139` **A third duplicate, found while measuring.** `prepareDerivedData` recomputed
encumbrance inline — its own `Math.floor(over / 10)`, `steps * 5` and `over / 10` —
instead of calling `encumbranceFrom()`. It also wrote the over-weight under the key
`overKg` while `rules.mjs` returned `over`, so simply routing the call through the
canonical function would have silently zeroed every encumbrance penalty, because
`loadBand()` reads `overKg`. Fixed by returning both names from one computation and
saying in the source why both exist. The inline block is gone.
**Still open.** `srLoad` — the exact fractional cost per kilogramme, written for the
old Strike Rank so no weight was ever free — is computed and displayed but no longer
used: Reaction Lanes reads `loadBand()`, a three-value integer band (0 / −1 / −2).
The granular rule the owner asked for survives in the data and not in play.
`R-140` **srLoad wired into the rail — the granular rule is in play at last.** The
exact per-kilogramme cost, computed since R-23 and consumed by nothing, now reaches
initiative. The three-value band (`0 / −1 / −2`) is gone.
**Load is not a rail position.** It is a property of what the agent is carrying,
known before the fight starts, so it is subtracted into the number printed on the
sheet rather than occupying a marker slot. The rail is now situation only — Aim,
Edge, Drag — which is what it should always have meant, and load can no longer be
double-counted by moving the clip.
| | old band | now |
|---|---:|---:|
| 0.1 kg over | −1 | −0.1 |
| 1 kg | −1 | −0.1 |
| 5 kg | −1 | −0.5 |
| 10 kg | −1 | −1 |
| 20 kg | −2 | −2 |
| 30 kg | −2 | −3 |
Gentler where it was absurd — the old band charged a full point for the first gram
over and then nothing more for the next 10 kg — and heavier where it was too kind,
since the cap at −2 meant nothing above 20 kg cost anything at all.
**Rounded up, not to nearest.** To nearest made the first half-kilogramme free, which
verified live: a 1 kg weight added to Dunn moved his printed value not at all. `ceil`
to one decimal makes 100 g over cost 0.1 and nothing cost nothing, which is the rule
as stated. One decimal is the smallest unit worth writing on a paper sheet.
**Floored at 1** — R-56 applied to encumbrance. No amount of kit reduces an agent to
acting never; it reduces them to acting last. A 300 kg overload prints 1, not −15.
`R-141` **Ordering bug caught before it shipped.** `prepareDerivedData` derived the
reaction block *before* encumbrance, so the printed value read an unset
`sys.encumbrance` and would have silently printed a load of zero for every actor —
the same silent-wrong class as the double hit points. The block is moved below
encumbrance with the dependency stated in the source.
Verified live: Ceyla 15.4 kg against a free carry of 12 prints **23.6** (base 24 less
0.4); Rakhi at 38 against 18 prints **13**. Adding 1 kg to Dunn moves him 18 → 17.9
and removing it restores 18. Turn order sorted correctly on decimals, and
`CONFIG.Combat.initiative.decimals` was already 1.
# Round 16 — combat played under the new load rule. 9 Aug 2026
Two fights, four agents against four Cleaners, opening at 25 m and closing to
contact. Driven through the real engine — `resolveBands`, `gradeRoll`,
`rangeBandFor`, `armourAgainst`, `damageAfterArmour`, `hitLocationRoll`,
`majorWoundRoll`, `woundPenaltyFor` — not a hand simulation.
**Fight one: total party kill, three Cleaners still standing.** Tomas and Ceyla
both dropped to single critical hits, which ignore armour entirely. Rakhi survived
six rounds of concentrated pistol fire behind a breaching suit and then lost to a
critical. Losing two agents in the first two rounds against equal numbers is not
recoverable.
**Fight two: agents win in five rounds, three standing, all four Cleaners down.**
The difference was Rakhi dropping her pack.
`R-142` **The load rule works, and it produced a real decision.** Rakhi begins at
38 kg against a free carry of 18 — Load −2, printing 13, the slowest lane in the
team. In round one she acted **last of eight**, behind all four Cleaners. She spent
round two dropping her pack: 17.4 kg, Load 0, printing 15. From round three she
acted third or better every round and killed a Cleaner with the prybar.
Measured over 100,000 rounds against a Cleaner printing 12:
| Rakhi | acts first | tie |
|---|---:|---:|
| carrying 38 kg (prints 13) | 58.6% | 13.9% |
| pack dropped (prints 15) | 83.2% | 8.3% |
A 24.6 point swing for one action. That is the trade the rule exists to create, and
it is legible at the table: the number on the sheet changes when the pack comes off.
`R-143` **Armour is the dominant variable in Major Wounds, far more than hit points.**
Frequency of Major Wounds among service-pistol hits that beat armour, 200,000 trials:
| | armour | MW threshold | hits through | of those, Major Wounds |
|---|---:|---:|---:|---:|
| Ceyla | 6 | 7 | 50% | **0.0%** |
| Rakhi | 7 | 8 | 38% | **0.0%** |
| Dunn | 3 | 6 | 88% | **28.6%** |
| Tomas | 2 | 7 | 100% | **24.9%** |
A pistol maxes at 10, so 6+ armour makes a Major Wound arithmetically impossible on
a normal success, while 2–3 armour takes one every fourth hit. The pressure valve is
the critical, which ignores armour — and in both fights every agent who went down
went down to a critical. That is a defensible design, but the cliff between armour 3
and armour 6 is much sharper than the two-point difference on the sheet suggests.
`R-144` **Major Wounds change no roll.** The most serious finding. `majorWoundRoll`
rolls 1d10, resolves the table, posts a chat card with the full effect text, records
the entry in `system.wounds`, and shows it on the sheet — and stops there.
`woundPenaltyFor` reads `system.wounded`, which is derived **only** from
`locationDamage`, so:
- a `manipulation` Major Wound, whose own text reads "−30% Manipulation", applies 0
- a `mobility` Major Wound, "MOV halved", leaves MOV at 10
- `concussed`, "−20% to all rolls", applies 0
Verified directly: with `manipulation` and `mobility` both recorded in
`system.wounds`, every `woundPenaltyFor` category returns 0 and MOV is unchanged.
Only `crippling` and `catastrophic` do anything at all, via the `isDown` check.
Disabled *locations* do work correctly — a destroyed leg gives `physical −30`,
`movFactor 0.1`, `prone true`, and a destroyed arm gives `manipulation −30`. It is
the Major Wound table specifically that is prose. Same class as `R-102`.
Observed live in fight two: Ceyla took a manipulation Major Wound in round two and
shot at exactly the same 53% in round three.
**Not a bug found this round:** I first reported `woundPenaltyFor` as broken because
it returned 0 for `"physical"` and `"manipulation"`. Those are `system.wounded`
field names, not category ids. The ids are `phys`, `manip`, `melee`, `ranged`,
`comm`, `mental`, `percep`, and the function handles all seven correctly.
`R-145` **Major Wound effects wired into the penalty pipeline — R-144 closed.** The
table is no longer prose. `woundEffectsFor(wounds)` in `rules.mjs` turns each row
into numbers, `prepareDerivedData` folds them into `system.wounded` on top of the
location-derived state, and `woundPenaltyFor` reads the result at the existing
`skillRoll` choke point. No new choke point was added: everything that already
respected a shattered arm now respects a concussion.
| Wound | Applied |
|---|---|
| concussed | −20% to **every** category, via a new `all` term |
| winded | −20% Physical, `lostAction` flag |
| bleeding / deepBleeding | 1 and 2 hp per round, exposed as `wounded.bleed` |
| manipulation | −30% manipulation, melee and ranged |
| mobility | −30% Physical, MOV halved |
| sensory (sight) | −40% Perception, −40% manipulation/melee/ranged |
| sensory (hearing) | −40% Perception, −40% Communication |
| broken | as manipulation or mobility, by variant |
| crippling | defence only, MOV 1, counts as down |
| catastrophic | unconscious, removed from the fight |
Three decisions worth recording:
**They stack, and they stack with locations.** Two concussions are −40%; a
concussion plus a destroyed leg is −50% Physical with MOV 1 and prone. Both facts
are true about the same agent, so the numbers add rather than one overwriting the
other. Verified live.
**The 1D2 variants are rolled and stored.** `sensory` and `broken` each split two
ways. That roll used to be left to the GM because nothing consumed it; now the
penalty depends on it, so `majorWoundRoll` rolls it, stores it on the wound entry
and prints it on the chat card. **A missing variant falls back to the harsher
reading**, so an old wound saved before this change can never quietly cost nothing.
**`isDown` now reads the derived flag** rather than re-testing the wound keys, which
was a second place that knew which wounds end a fight.
Verified through the real `skillRoll`, not a reimplementation:
```
healthy Firearm (Pistol) 59% → 59% ≤3 ≤12 ≤59 ≥98
concussed (−20 all) 59% -20% → 39% Wounded: -20% ≤2 ≤8 ≤39 ≥97
concussed + manipulation 59% -50% → 9% Wounded: -50% ≤1 ≤2 ≤9 ≥96
cleared 59% → 59% ≤3 ≤12 ≤59 ≥98
```
Critical, special and fumble bands all move with the penalty, and the chat card
names the cause. In a live fight Tomas took a catastrophic wound at 1 hp and was
correctly removed from the turn order for the rest of the encounter.
Guard extended to 13 rules and 35 formulas, including the missing-variant fallback.
**Two harness errors of mine, recorded so they are not re-found as engine bugs:**
`woundPenaltyFor` takes category ids (`phys`, `manip`), not `system.wounded` field
names; and `skillRoll` takes `rating` and `categoryId`, not a `skill` object —
passing one silently rolls against 0%.
# Round 17 — combat with wounds live, and a correction. 9 Aug 2026
`R-146` **Correction: every fight in rounds 15 and 16 ran at double damage.**
`hitLocationRoll` applies the hit points itself (`ringbrp.mjs:929`) as well as
recording the location. My harness subtracted the same damage a second time. The
engine was never wrong; the playtests were.
What this invalidates: the fight narratives in R-142 and R-144 — the total party
kill, "every agent who went down went down to a critical", and the impression that
Major Wounds rarely fire. What it does **not** touch: the load measurements in R-142
(initiative does not depend on damage) or the armour arithmetic in R-143 (computed
directly, not from a fight).
`R-147` **Wounds re-tested at correct damage, and they carry the fight.** Four agents
against two marksmen and two contractors at 30 m, twelve rounds. Eight Major Wounds
fired and every one of them changed what happened next:
| | |
|---|---|
| Dunn | **catastrophic**, round 1 — unconscious, removed from the order, never acted |
| Tomas | **manipulation** — pistol 43% → 13%, missed everything after and died at 3 hp |
| Marksman | **manipulation** — rifle 55% → 25% |
| Marksman | **broken (manipulation)** — 55% → 25%, MOV 5 |
| Rakhi | **deepBleeding** — 2/round from 10 hp, dead in round 6 |
| Marksman, Cleaner, Cleaner | **bleeding / deepBleeding** — all three bled out |
**Bleeding decides fights.** Four of the six casualties died of blood loss rather
than the blow that caused it, including Rakhi, who was on 10 hit points when she took
it. Two per round against 12–15 hit points is roughly a five-round death sentence and
there is currently no First Aid step in the loop to stop it — the rule assumes
someone spends an action, and nothing in play forces that choice to be visible. This
is the wound effect with the largest influence on outcomes and it deserves a check.
**The -30% wounds are decisive rather than flavour.** Tomas at 13% and the marksmen
at 25% were all still standing and effectively disarmed. That is the intent of the
table text, now actually true.
`R-148` **Stalemate at the tail.** Rounds 9 to 11 produced nothing: Ceyla at 53%
against a marksman at 55%, armour 6 against 1d10+2, both missing or being stopped.
Two roughly equal combatants with good armour and mid skills can circle indefinitely.
Worth a pressure mechanic — a closing clock, or escalating penalties for a stalled
exchange — rather than leaving it to the dice.
Wound keys not seen this fight — `concussed`, `winded`, `mobility`, `sensory`,
`crippling` — were verified separately against every category and MOV in round 16.
`R-149` **Bleeding fixed — the rule was missing both halves, failing in opposite
directions.** The table printed a rate and a cure and the game implemented neither:
- **Nothing ticked the loss.** `wounded.bleed` was derived and displayed, and no
code ever subtracted it. At an actual Foundry table bleeding did *nothing*.
- **Nothing stopped it.** `firstAid` healed hit points and never cleared the wound,
so once my harness applied the loss it ran until the target was dead.
That is why R-147 read as a death sentence: my simulation supplied the half the
engine was missing and none of the half that ends it.
**Both halves now implemented.** `BLEED` in `rules.mjs` is the single authority for
the rate and for what stops it; `woundEffectsFor` reads it rather than carrying its
own copies of 1 and 2.
| | per round | stopped by |
|---|---:|---|
| bleeding | 1 | First Aid, or Medicine |
| deepBleeding | 2 | **Difficult** First Aid, or any successful Medicine |
`applyBleeding()` runs from the `updateCombat` hook; `stopBleeding()` is called by
`firstAid` on success and by the new `medicalCare()`, both routed through
`bleedStoppedBy` so there is one authority on what qualifies.
**One deviation from the printed text, deliberately.** The table stops plain
bleeding with First Aid only. Medicine now also stops it, because the rule as
written left a surgeon unable to close the lesser wound they could treat.
**A free tick, caught in testing.** The hook fired on entering round 1, charging a
bleeding agent before any round had elapsed — 15 hp became 11 by round 2 instead of
13. Now only round 2 and later charge.
**No rebalancing was needed, and I checked before assuming.** Survival with a medic
retrying each round, 50,000 trials:
| from | Difficult First Aid (23%) | Medicine-grade (46%) |
|---:|---:|---:|
| 15 hp | 87.6% | 99.3% |
| 12 hp | 78.8% | 97.5% |
| 10 hp | 72.9% | 95.5% |
| 5 hp | 54.5% | 84.3% |
Bleeding was never too harsh — it was unstoppable. Treatable, it costs the medic an
action every round and still usually works, which is the decision the rule wants.
A single unlucky live run took seven attempts; the distribution says four.
Verified live: the clock runs on round change, an Average First Aid does **not**
close a deep bleed, a Difficult one does, Athletics does not, plain bleeding closes
on an Average First Aid, and the loss stops at 0 rather than grinding negative.
Guard now covers 15 rules, 2 constant sets and 44 formulas.
# Round 18 — bleeding in play, and a defect that outranks it. 9 Aug 2026
Fights run on a real `Combat` document this time, so initiative came from
`rollReaction` and blood loss from the engine's own `updateCombat` hook rather than
anything in the harness.
`R-150` **Bleeding works in play.** Two bleeds fired in the reported fight and both
became attrition clocks rather than announcements:
```
R3 Tomas pistol 43% → Marksman 2 critical — 8 dmg (hp 6)
** BLEEDING ** bleeds 1/round
[engine bleed] Marksman -1 → 5
R4 [engine bleed] Marksman -1 → 3 ← dead to a critical the next round
R5 Dunn knife 34% → Cleaner 2 critical — 6 dmg (hp 8)
** DEEPBLEEDING ** bleeds 2/round
[engine bleed] Cleaner -2 → 6
R6 [engine bleed] Cleaner -2 → 4
```
Both wounded hostiles died at least partly to the bleed. The tick fires once per
round, from the engine, with no harness involvement. The First Aid branch did not
trigger only because both bleeds landed on hostiles; staunching was verified
directly in R-149.
Wound penalties were visible throughout: Tomas's pistol fell 43% → 23% and Rakhi's
rifle 47% → 27% from concussions, for the whole rest of the fight.
`R-151` **Initiative is rolled once and frozen for the entire combat.** The larger
find. Foundry keeps `combatant.initiative` across rounds and nothing re-rolls it, so
the d6 is drawn once per fight, not once per round. Verified over four rounds — the
same four numbers, unchanged:
```
round 1: Ceyla 25.6 · Dunn 22 · Tomas 19 · Rakhi 15
round 2: Ceyla 25.6 · Dunn 22 · Tomas 19 · Rakhi 15
round 3: Ceyla 25.6 · Dunn 22 · Tomas 19 · Rakhi 15
round 4: Ceyla 25.6 · Dunn 22 · Tomas 19 · Rakhi 15
```
Three reasons this is wrong rather than merely a choice:
1. **The Aim rail is inert.** "Aim one round, fire the next" for +1/+2 cannot do
anything if the order never recomputes. The entire seven-box rail — Aim, Edge,
Drag — only means something between rounds. It currently means nothing after
round 1.
2. **The measured random share is per round.** 35.7% was tuned as round-to-round
variance. Drawn once, the variance is 0% for the rest of the fight and one draw
decides the whole engagement.
3. **The engine's own description says otherwise** — "current printed base + one
die" per round, and load, wounds and coherence all change mid-fight without ever
being re-read.
Seen in the fight above: the order was identical for all seven rounds, and the
marksman that Ceyla out-ran in round 1 stayed out-run until it died.
Not yet fixed — it changes every fight and is the owner's call.
`R-152` **Not a bug: the Major Wound d10 is uniform.** Three catastrophic results in
one fight looked rigged. 4,000 draws give every row 9.3–11.0%. Variance, not a
weighted table.
`R-153` **Initiative re-rolls every round — R-151 closed.** `RingBRPCombat.nextRound()`
now resets and re-rolls after `super.nextRound()`. Six consecutive rounds gave six
distinct orders; before the change, four rounds gave the same four numbers.
```
r1: Ceyla 28.6 Dunn 21 Tomas 20 Rakhi 16
r2: Ceyla 25.6 Dunn 22 Tomas 19 Rakhi 19
r3: Ceyla 25.6 Dunn 23 Tomas 21 Rakhi 16
r4: Ceyla 27.6 Tomas 24 Dunn 22 Rakhi 19
r5: Ceyla 27.6 Dunn 22 Tomas 19 Rakhi 18
r6: Ceyla 28.6 Tomas 24 Dunn 21 Rakhi 15
```
Three consequences deliberately handled rather than left to fall out:
**Aim is spent, not permanent.** Aim is read by the roll it paid for and then
cleared, so "aim one round, fire the next" costs a round and buys one round. Left
alone it would have granted +2 for the rest of the fight the moment re-rolling made
it matter. Verified: mod 2 applied on the next round, flag 0 after, mod 0 the round
following. Edge and Drag persist — they describe equipment and conditions, which do
not evaporate at a round boundary.
**The downed are not re-rolled.** Only combatants above 0 hit points and not
unconscious draw a die; the rest keep a null initiative, which the existing
comparator already sorts to the bottom. Before this, a corpse drew a d6 every round
and could sort above the living. Verified: Rakhi bleeds to 0 in round 3 and holds
"—" from then on.
**The re-roll runs after blood loss**, so an agent who bleeds out in the round
transition does not receive a fresh initiative first.
**Mid-fight changes now reach the order at last.** Rakhi dropping her pack moved her
printed value 13 → 15 and her very next roll used 15. Under frozen initiative that
decision — the one the whole load rule exists to create — could not affect the fight
it was made in.
Played through: order churned every round (the marksman ran 24 → 22 → 24 → 19,
Dunn and Tomas swapping repeatedly), while Ceyla still led all five rounds. That is
`R-138` visible in play, not a fault in the re-roll: at laneMultiplier 3 she leads
94.8% of rounds. Re-rolling changes the *middle* of the order every round and leaves
the top almost fixed.
Also seen: Dunn's manipulation Major Wound took his knife from 34% to **4%**, and it
stayed there for the rest of the fight.
# Round 19 — Strike Rank residue, and the missing damage roll. 9 Aug 2026
`R-154` **Strike Rank survived in three talents and four dead lang keys.** The
mechanic was removed in round 12; its vocabulary was not. Found by sweeping code,
templates, language, packs and the live world:
| | was | now |
|---|---|---|
| First Through | "declare an action at Strike Rank 1 once per fight" | "act first in the round no matter what anyone rolled" |
| Out of Step | "act as though your Strike Rank were 1 lower" | "+3 Reaction for that round — a full lane" |
| Quorum | "one additional non-combat action, at Strike Rank +2" | "resolved 2 points lower on the Reaction order" |
**Four language keys were referenced by live code and did not exist.**
`RINGBRP.SR.ReadinessSkill` and `RINGBRP.SR.ReadinessInt` are read every time a
character sheet renders, so the sheet was printing the raw key string. Renamed to
`RINGBRP.React.*` and supplied ("from training" / "from wits").
`RINGBRP.SR.DeclareHint` and `RINGBRP.SR.Action` belonged to
`templates/dialog-declare.hbs`, the old declare-your-action dialog, which nothing
has opened since Strike Rank was removed. Template deleted.
Two mentions remain deliberately: the comment above `rollReaction` and a README
row, both of which say Reaction Lanes *replaced* Strike Rank. Those are history,
not rules.
**A note on the search.** My first pack scan reported zero hits because
`ClassicLevel` could not open the databases while Foundry held the lock, and the
`try/catch` swallowed it — the same shape as `R-98`, a check that reports success
when it has not run. Re-run against the live world, it found eight.
`R-155` **A successful attack did not offer its damage.** The roll ended at
"Success" and left the player to find the weapon on the sheet and click it
separately — and that path did not know what had been rolled, so **a critical did
ordinary damage**.
Fixed at the cause, which was again duplication: `_onRollDamage` existed **twice**,
once on the character sheet and once on the item sheet, near-identical and neither
aware of the success level. Both now delegate to one `rollWeaponDamage({ actor,
weapon, level, band })`, and successful attack cards carry a **Roll damage** button
bound to it.
Success levels now reach the dice (spec §4.4):
```
critical → 6, 6, 6, 6, 6, 6 maximum weapon damage, armour ignored
success → 6, 4, 5, 3, 3, 5 normal 1d4+2, full armour
```
The damage modifier is still *rolled* on a critical rather than maximised, per the
rule. The card carries the note — "maximum damage, armour ignored" for a critical,
"armour counts half" for a special — so the level is not lost between the two rolls.
The button disables itself once used, so one hit buys one damage roll, and it is
bound on render rather than inline so it still works after a reload.
Verified live: button present on critical, special and success; absent on failure
and fumble; clicking produces "Utility knife — damage — armour counts half", total 6.
`R-156` **Kit dropped onto a character was not readied.** The catalogue packs ship
armour and weapons with `equipped: false` — correct for a compendium entry, wrong
the moment one lands on an actor. A vest dragged onto a sheet contributed **0
armour** and a pistol never reached the READIED strip. Nothing errored: the item was
there, the number simply did not move, which is the hardest kind of defect to notice.
Fixed with a `preCreateItem` hook. **Only the first of each kind is readied** —
a spare vest or a second pistol stays stowed, because silently stacking armour points
or crowding the strip is the opposite failure. Anything arriving explicitly equipped
(pregens, generated characters) is untouched.
| dropped | result |
|---|---|
| 1st armour | worn, armourPoints 6 |
| 2nd armour | stowed, armourPoints stays 6 |
| 1st weapon | readied |
| 2nd weapon | stowed |
`R-157` **"ARMOUR 0" now says why.** Carrying armour you have not put on displayed
as a bare zero beside a list containing a vest, which reads as a broken sheet rather
than a state of the fiction. The box now turns red and prints **NOT WORN**, with a
tooltip pointing at the shield toggle on the Gear tab. Verified rendering as
"ARMOUR 0 NOT WORN".
This was the reported symptom. The underlying cause was `R-156`; the display change
is so that if it ever happens again — a player unequips deliberately — the sheet
says so instead of looking wrong.
`R-158` **`generateCharacter` threw on an unknown role.** `ROLES[role]` came back
undefined and the next line read `R.stat`, so a typo in a macro produced a stack
trace instead of a character. It now falls back to a random valid role, the way
`tier` already did. Found while testing the armour paths, not by design.
Existing actors were checked and are unaffected: Ceyla 6, Dunn 3, Rakhi 7, Tomas 2.
# Round 20 — legibility. 9 Aug 2026
`R-159` **Installed items now file themselves into folders.** `installContent` put
125 items into one flat alphabetical column, where a Service pistol sits between
Science (Physics) and Sleight of Hand and nothing can be found. One folder per pack:
Skills 52 · Talents 18 · Weapons 18 · Armour 10 · Gear & Devices 27, unfiled 0.
Folders are matched by name on re-install so running it twice does not produce
"Weapons (2)", and items already installed flat are swept into place, so an existing
world tidies itself rather than keeping two conventions at once.
`R-160` **The Reaction number now shows its working.** "23.6" on its own is
unreadable — it does not say whether the agent is quick or merely alert, nor what
the kit is costing. A collapsible breakdown sits under the rail:
```
Reflex DEX 10 ÷ 3, rounded up 4
Awareness Readiness 71 ÷ 10, rounded up 8
Readiness comes from training: your best of Tradecraft,
Insight, Spot or Listen (71) — wits would only give 51
Lane the better of the two × 3 24
Load 15.4 / 12 kg — 3.4 kg over −0.4
Reaction on the sheet — then add d6 23.6
```
The Readiness line is the part that was genuinely invisible: it is the best of
**Tradecraft, Insight, Spot or Listen**, floored at **INT × 3** so a clever but
untrained agent is never flat. Ceyla is an analyst — DEX 10 gives her Reflex 4, but
Tradecraft 71 gives her Awareness 8, and the better of the two sets her lane. That
is the design working as intended: she is first in the order because she is alert,
not because she is fast.
Put in the reaction area rather than the character's notes, deliberately — notes are
free text and would go stale the moment a skill or a pack changed, which is the
duplication defect this project keeps re-learning.
`R-161` **"Error lance" renamed.** The name came from the setting premise — a
crossing is an induced filing error — but it reads as a bug rather than a weapon.
Renamed with its sibling, since "Error cutter" had the same problem and leaving one
would have broken the pair:
| was | now |
|---|---|
| Error lance | **Redaction lance** |
| Error cutter | **Excision cutter** |
Both keep the bureaucratic-horror register the setting asks for — the Custodians
correct the record — without naming a weapon after a fault condition. The journal
text was updated with them.
# Round 21 — the sheet as a thing you read. 9 Aug 2026
`R-162` **The species drop-down is gone from Anatomy.** Every character sheet
offered a choice of all three anatomies, which is not a decision anyone makes while
playing — a Baseline is not one option among three, it is what the character is. The
tab now names the species and shows only its locations. The selector moved to Bio,
where identity lives, with a note that changing it changes the anatomy.
`R-163` **A figure, drawn per species, on the Combat tab.** `RINGBRP.bodyArt` gives
each species profile a shape per location, keyed by the same ids as
`RINGBRP.locationTables` — so the drawing and the table are the same data and a
location cannot appear in one without the other. A species with no art simply draws
nothing rather than drawing a human by mistake.
The anatomy is honest rather than generic: **Baseline** is a seven-part humanoid;
**Vesh** is asymmetric with one long grasping limb, one short bracing limb, a leaning
trunk and a sensory ridge where a head is not; **Cadence** is five separate bodies,
equal and unranked, which is the point of them.
Five states, readable at sheet size without reading a number:
whole · grazed (any damage) · hurt (half) · disabled · destroyed (dashed outline).
Hovering a limb highlights its table row and the reverse, so the picture and the
numbers are visibly the same thing.
`R-164` **The location table was ragged because every column sized itself.** Now
`table-layout: fixed` with a flexible name column, fixed 3.6em numeric columns,
right alignment and `font-variant-numeric: tabular-nums`, so digits line up down the
column instead of drifting with content width.
`R-165` **Wounds moved onto the Combat tab**, beside the figure, with the full
effect text and the clear button. They remain on their own tab as well — nothing was
taken away, the information was simply put where it is used.
`R-166` **A wounded agent is now visible on the first tab.** Major Wounds modify
every roll on the Skills tab and were legible only two tabs away. A red bar sits
above the skill list naming each wound and the arithmetic it is applying:
```
WOUNDED [Concussed] [Bleeding] all rolls -20% · Manipulation -30% · bleeding 1/round
```
It only appears when there is something to say.
Verified live across all three species: 7 / 6 / 5 parts drawn, states resolving
correctly against real location damage, drop-down absent from Anatomy and present on
Bio, wound bar rendering with the right numbers.
# Round 22 — the rules go in the box. 9–10 Aug 2026
`R-167` **A rules journal, generated from the rules.** `packs/rules` ships
"The Custodians — Rules", seven pages, built by `tools/rules-text.mjs` from
`rules.mjs` and `lang/en.json`. **No number in the book is typed by hand.** Hit
points, Major Wound thresholds, the damage ladder, the reaction formulas, the wound
table and its consequences, coherence bands and costs, the bleed rates, the range
ladder and the encumbrance table are all computed at build time; every wound and band
name is read from the language file, so the book and the interface cannot disagree.
That defect has landed five times in this project — a hand-written rulebook would
have been the sixth.
Shaped to be learned and then abandoned:
| Page | |
|---|---|
| 1 · The whole game | the d100 core, the three modifiers, where the numbers come from |
| 2 · Fighting | reaction, the turn, range, what a hit does |
| 3 · Getting hurt | locations, the Major Wound table, bleeding |
| 4 · Coherence | the setting mechanic |
| 5 · A case | the session structure |
| 6 · Reference | tables you look up, never read |
| 7 · A round, worked through | Ceyla's real sheet numbers, start to finish |
Page 1 says explicitly that it is enough to play, and page 7 walks one round using
figures a reader can check against the shipped pregen.
`R-168` **Four more rule tables moved into `rules.mjs`** so the journal could be
generated from them at all: the circumstance ladder, the range ladder, the Major
Wound table and the coherence bands, costs and recovery. `ringbrp.mjs` now assigns
rather than re-declares them, `rangeBandFor` and `coherenceBand` are aliases of the
shared functions, and the guard covers all of it — 17 rules, 8 constant sets, 54
formulas.
`R-169` **The printed chance was not the real chance.** `resolveBands` returned a raw
`effective`, so a skill beaten below zero printed **0%** on the chat card. The floor
was genuinely there — `gradeRoll` tests the critical band first, so a 1 still
crits — but it arrived sideways, and the card contradicted the rule. `effective` is
now the clamped figure. Verified: rating 1 at Difficult prints 1% and succeeds 1% of
the time; every band from 1 to 100 now prints what it actually does.
`R-170` **The 96–00 rule was in the engine and in no document.** `gradeRoll` has
always failed 96–99 regardless of skill and fumbled on 00 — correct BRP, and the
reason a 100% skill really succeeds 95% of the time. It appears nowhere in the design
notes and would have appeared nowhere in the book. Now on page 1 and in the reference.
`R-171` **Correction: my combat harnesses double-counted armour.** `hitLocationRoll`
applies armour itself and takes an `armourApplied` flag for callers that have already
done it. My harnesses deducted armour and did not pass the flag, so every playtest
from round 16 onward understated damage. The engine was right throughout.
Re-run with armour counted once, the same fight resolves in **4 rounds instead of
7–12**, and Tomas goes down in round one. Combat is materially more decisive than my
earlier reports suggested — worth knowing before any further balance work.
**Sweeps run this round.** Generators across every role, tier and species (85 checks);
subsystems — wounds, bleeding, healing, coherence, skill rolls, encumbrance edges,
sheets, tables, every pack (25 checks); talents and progression (6 checks). Everything
that failed was my test's assumption rather than the system, except `R-169`, which was
real. `check-rules` is green and gates the build.
# Round 23 — the table, and a species that does not exist. 10 Aug 2026
`R-172` **The hit-location table was unreadable, and `table-layout: fixed` was why.**
The header read `MELEERANGETAKENARMO`, ranges wrapped onto two lines, rows were
different heights and the numbers sat a hand's width from the names they described.
Three separate causes, all mine:
1. **Fixed layout sizes every column from the first row** — the header row, where
the font is 0.62rem. `width: 3.6em` therefore resolved to about 39px, too narrow
for "RANGED", so the headers overflowed into each other and the ranges wrapped.
2. **`.rb-loc-cell` was `display: flex` on a `<td>`**, which takes the cell out of
table layout altogether — it had been there since before these columns existed.
3. **The name column was given `width: 99%`**, so on a wide sheet it absorbed 352px
of slack and threw the numbers to the far edge of the panel.
Now: auto layout, `white-space: nowrap`, content-sized columns, a fixed 22px gutter
after the name, and `width: auto` so the table is only as wide as it needs to be.
Measured in isolation at four container widths — 1400, 900, 640 and 420px — the
table is **297px every time**, every row **20px**, the last column right-aligned to
within a pixel, across Baseline, Vesh, Cadence and a wounded sheet.
Also: the roll button carried a whole sentence and wrapped to three lines — short
label, sentence moved to the tooltip — and "No active wounds." was centred in a wide
empty box instead of sitting under its heading.
**A note on verification.** Foundry reports its window as 0×0 in this browser, so
layout cannot be measured in place. The check was done by lifting the real markup
and the real stylesheet into a shadow root at controlled widths — which measures the
shipped CSS rather than a mock-up of it.
`R-173` **Every packed actor was a species the game does not define.** Ten of them —
all four pregens and all six NPCs — shipped with `speciesProfile: "hominid"`, a name
left from the Ringworld setting. It is not a key in `locationTables`, so the sheet
fell back to Baseline and nobody noticed: the hit locations were right by accident,
and would have been silently wrong the moment a Vesh or a Cadence was built from a
pack.
Found by accident while restoring test actors, which is not a system I want to rely
on. Fixed in three places:
- `template.json`'s default is `baseline`, not `hominid`;
- `buildActor` now **writes the profile explicitly** from the content spec rather
than leaning on the template default, so a packed actor always carries a real one;
- `check-rules` fails the build if any `speciesProfile` in `template.json` is not a
key of `locationTables`. Verified by reintroducing "hominid": the build stops with
`speciesProfile "hominid" is not a species (have: baseline, vesh, cadence)`.
The actor packs were deleted and rebuilt from clean, since the stale value was baked
into the stored documents rather than coming from anything still in the source.
Verified: ten actors, zero invalid profiles.
# Round 24 — a fight, and what the sheet knew about it. 10 Aug 2026
Four agents against a marksman and two contractors, 25 m closing to contact. Real
`Combat` document, armour counted once, initiative re-rolled each round. Agents took
it in four rounds, three standing; Dunn went down in round three.
The sheet was then read against the fight log, and it agreed on everything it
covered — Ceyla's left leg at 4/6 showing `hurt` on the figure, Dunn's head at 13/5
showing `destroyed`, his bleed on the wound bar, armour, reaction, MOV. What it
covered was the problem.
`R-174` **Only legs and arms ever did anything.** The location-effect table is
localised prose for nine anatomical kinds and the derived state implemented two of
them. Three of the remaining seven were live rules doing nothing:
| Kind | The text promises | It applied |
|---|---|---|
| `head` | "Unconscious immediately" | prone, and nothing else — **the agent stayed up** |
| `ridge` (Vesh) | "−30% to all Perception" | **nothing** |
| `unit` (Cadence) | "each disabled body costs the whole person −10% to everything" | **nothing at all** |
The Cadence one is the serious one: losing bodies is that species' entire mechanical
identity, and it was costing them nothing. A Cadence was strictly better than a
Baseline — same hit points spread over five bodies with no vital location to hit and
no penalty for losing four of them.
Found by shooting Dunn in the head and noticing he was still standing.
**Fixed the same way the Major Wounds were.** `locationEffectsFor(locations)` in
`rules.mjs` turns each kind into numbers; `prepareDerivedData` folds it together with
`woundEffectsFor` rather than computing anything inline. The two stack, because a
shattered leg and a concussion are separate facts about the same body.
Verified live:
```
leg destroyed phys -30 MOV 1
arm destroyed manip -30
HEAD destroyed unconscious=true down=true
vesh RIDGE opened percep -30
cadence 1 body lost all -10
cadence 3 bodies lost all -30 → a 60% skill rolls at 30%
leg gone + concussed all -20, phys -30, MOV 1 ← both tables stacking
```
Guard extended to 18 rules and 63 formulas, including every location kind the three
species tables use, so a kind cannot be added to a species without a consequence.
**Still prose, deliberately:** `vital` says "knocked down, and a Stamina roll or
unconscious" — the knockdown applies, the Stamina roll is left to the GM, which is
the right place for it.
# Round 25 — a mixed team, and the banner that was only half looking. 10 Aug 2026
Ceyla (Baseline), a generated Vesh and a generated Cadence against three Cleaners,
25 m closing to contact. Chosen so the location effects wired in round 24 would be
exercised by real dice rather than by me setting fields.
They were. The Cadence took a body and the sheet reported
`all-10 · 1 body lost`; a Cleaner's arm was disabled and his next shot went out at
**5%** — 45 less 30 for the arm, less 10 for range. A marksman's leg went and he
finished the fight at MOV 5 with `phys -30`. Team won in three rounds without a
casualty.
`R-175` **The Skills-tab banner was only watching one of the two sources.** It keyed
off `wounds.length`, which counts Major Wounds. Lost locations now produce identical
penalties and were invisible on that tab:
- a Cadence down two bodies sat at **−20% to everything** and the skills page said
nothing;
- a Vesh with an opened ridge and a dead grasp limb was at **−30% Manipulation and
−30% Perception**, likewise silent.
This is precisely the defect the banner was built to fix, for the other half of the
system. It now reads from a single `impaired` context built from the derived state,
so it shows whatever is actually modifying rolls, whatever produced it:
```
Cadence, 2 bodies lost
WOUNDED [First body ✕] [Fifth body ✕] all rolls -20%
Vesh, ridge destroyed and grasp limb gone
WOUNDED [Grasp limb] [Sensory ridge ✕] Manipulation -30% · Perception -30%
Cadence, bodies lost AND concussed and bleeding
WOUNDED [Concussed] [Bleeding] [First body ✕] [Fifth body ✕]
all rolls -40% · bleeding 1/round
Ceyla, head destroyed
UNCONSCIOUS [Head ✕]
Ceyla, unhurt
(no banner)
```
The headline changes with the state — **Wounded**, **Out of the fight**, or
**Unconscious** — so the worst news is the first word rather than something to be
inferred from a list of chips.
# Round 26 — Coherence in a fight, and a rule that had never once applied. 10 Aug 2026
Four agents against a Revenant, a marksman and a contractor, opening at 40 m so the
range ladder had work to do. Built around the two things dice had never exercised:
Ceyla firing the Redaction lance, which costs Coherence a shot, and Rakhi spending a
round aiming.
Both worked. Aim paid out — `Rakhi 21(+2)` in round two, one round after she spent
her action. Coherence ran **10 → 5** across five shots and left Ceyla **Loose** at
the end of the fight, which is the design's whole argument: the tool that solves the
case is the tool that costs you. And the stacking held up — the marksman took a
disabled arm and then a manipulation Major Wound and finished the fight shooting at
**1%**: 55, less 10 for range, less 60 for two −30s.
`R-176` **Encumbrance's skill penalty had never been applied to anything.**
`encumbranceFrom` has returned a `skillPenalty` since R-23. `prepareDerivedData`
stored it, the sheet displayed it, the rules journal tabulated it, and no roll ever
read it. Rakhi has been 20 kg over her free carry all session, carrying a printed
−10%, and rolling at full.
The comment above the modifier chain in `skillRoll` says gear bonuses "sit outside
the ±40% circumstance cap **alongside encumbrance and coherence**" — so it was
always meant to be there. Coherence was wired; encumbrance was not.
Now applied at the same choke point. **Scoped to physical, melee, ranged and
manipulation** — a heavy pack does not make you worse at Knowledge, and a rule that
says otherwise is one nobody will use at the table. That is a decision rather than a
fix, and the rules journal now states the scope explicitly so book and code agree.
Verified: Rakhi's rifle 47% → **37%** with the card reading `Overloaded: -10%`, and
her Navigate untouched at 31%.
`R-177` **The banner was watching two of four sources.** Rounds 24 and 25 taught this
lesson twice; here it was a third time. Four things modify rolls through
`skillRoll` — Major Wounds, lost locations, Coherence and load — and the banner knew
about the first two. Ceyla at Coherence 5 was losing 5% Communication with nothing on
the Skills tab; Rakhi was overloaded and silent.
All four now feed one `impaired` context, and **totals sum per category** rather than
listing each source, so a concussion and an Unmoored band read as one figure:
```
Rakhi, overloaded
WOUNDED [Overloaded] Physical -10% · MOV 8
Ceyla, Unmoored + concussed + arm destroyed
WOUNDED [Concussed] [Right arm ✕] [Unmoored]
all rolls -30% · Manipulation -30% · Communication -20%
```
Before the fix that last line read `all rolls -20% · all rolls -10%`.
**The pattern, stated plainly.** Four rounds running, the defect has not been a wrong
rule but an incomplete reader of one: Major Wounds computed and unapplied, locations
applied for two kinds of nine, the banner watching one source then two, encumbrance
computed and never read. The guard now covers 19 rules and 69 formulas, but it checks
*formulas*, not whether anything consumes them. **A rule that is computed and never
read is the failure mode this project actually has**, and it is worth a dedicated
check: every value `prepareDerivedData` produces should have at least one reader.
# Round 27 — the callout boxes, the range picker, and what a strong person can lift. 10 Aug 2026
`R-178` **Dialogs looked like a different application.** Every callout — attack
options, skill rolls, the reaction rail, the generators — rendered in Foundry's
default parchment with a serif face, sitting on top of a dark blue sheet in
Michroma and Helvetica. `rbDialogClass()` now returns a themed subclass that tags
every system dialog with `ringbrp-window`, and that class was added to the selector
that defines the design tokens, so dialogs inherit the sheet's palette rather than
carrying a copy of it.
Verified by computed style, not by eye:
```
window bg rgb(16, 26, 110) — identical to the sheet's --rb-deep
header rgb(12, 18, 91), yellow text, Michroma
content Helvetica Neue, --rb-white on --rb-deep
select --rb-panel ground, --rb-line border
Roll button --rb-yellow on --rb-void (yellow means the thing that commits)
labels uppercase 10px in --rb-muted
```
`R-179` **The range field asked for arithmetic the weapon had already done.** The
dialog printed "Bands: 20 / 60 / 180 m" and then a number box, so the player read
the bands, decided which one they were in, and typed a distance for the system to
convert back into a band. It is now a dropdown of the bands themselves:
```
Short 1–20 m +0%
Medium 21–60 m -10%
Long 61–180 m -30%
Extreme 181–360 m -50%
Beyond range over 360 m out of range
```
Each option carries a distance inside its band rather than a modifier, so
`rangeBandFrom` remains the single authority on what a range costs — the dropdown
chooses a distance, it does not duplicate the ladder.
`R-180` **Free carry ignored how big you are.** It was STR in kilogrammes. Rakhi at
STR 18 and MAS 19 — a large, powerful woman — had a free carry of **18 kg** and was
twenty kilos over while carrying 38 kg, which is an ordinary soldier's fighting
load. A rule that treats a realistic pack as an emergency is one the table quietly
drops.
Now **STR + MAS**, the same pair that sets the damage modifier, so a single physical
axis decides both how hard you hit and how much you can shoulder.
| | STR + MAS | free | carrying | over, before → after |
|---|---:|---:|---:|---|
| Ceyla | 12 + 13 | 25 | 15.4 | 3.4 → **0** |
| Dunn | 9 + 10 | 19 | 8.2 | 0 → 0 |
| Rakhi | 18 + 19 | **37** | 38 | 20.0 → **1.0** |
| Tomas | 13 + 12 | 25 | 7.2 | 0 → 0 |
Rakhi keeps a cost for her load — 1 kg over is −0.1 Reaction, so she prints 14.9
rather than 15 — which is the granular rule doing exactly what it was written for:
a heavy pack is felt, it is not a catastrophe. Rejected `(STR+MAS)×1.5`, which gave
her 56 kg and would have made encumbrance unreachable, and `STR×2`, which ignores
mass — the thing the owner specifically asked to count.
The rules journal states the new formula, and the guard covers it.
# Round 28 — the subsystem that was only ever bookkeeping. 10 Aug 2026
`R-181` **Defending did not exist.** `system.combat.defencesThisRound`,
`defenceTypeLock` and `secondAttackUsed` are declared in `template.json`, written
into every packed actor by `buildActor`, and reset every round by the combat hook.
Nothing ever incremented them, nothing rolled a defence, and no control existed
anywhere in the interface. The rules journal — which I generated — promised "one
offensive action, plus **as many defensive reactions as you like at an escalating
penalty**".
So the escalation was the only part of the rule that had ever been built, and it was
being applied to a counter nothing could raise. This is the same failure as the last
four rounds at its largest scale yet: a whole combat subsystem present as
bookkeeping for an event that could not happen.
**Now implemented.** `defencePenaltyFor` and `defenceTypeAllowed` in `rules.mjs`;
`defend(actor, {type, weapon})` rolls through the same `skillRoll` choke point, so a
defence is subject to wounds, coherence and load like everything else; a Dodge /
Parry control on the Combat tab that shows what the next one will cost.
| Defence this round | Modifier |
|---|---:|
| 1st | free |
| 2nd | −30% |
| 3rd | −60% |
| 4th | −90% |
Verified end to end — four attacks on Dunn in one round:
```
defence 1: no penalty → 49% failure
defence 2: -30% → 19% failure
defence 3: -60% → 1% failure (the 1% floor holding)
defence 4: -90% → 1% failure
asked to parry after dodging → "dodge" (the type lock)
round tick → 0 defences, lock cleared
sheet: "DEFEND DODGE PARRY first is free"
→ "DEFEND DODGE PARRY 1 this round · next at -30%"
```
**Played.** Four agents against three contractors at 10 m closing to contact, with
every agent defending and NPCs taking only their free defence. Agents won in seven
rounds **without a casualty** — considerably softer than the same fight without
defences, which is the point: a defence is free the first time and the rule makes
standing in front of three people possible and stupid.
The escalation never fired in that fight, because with seven combatants nobody was
attacked twice in a round. It needed a forced test to exercise, which is worth
remembering — **a rule that only bites in a corner will not be exercised by ordinary
play, and that is exactly how the last five defects survived.**
The journal now documents defending, generated from `defencePenaltyFor` so the table
in the book cannot drift from the ladder in the code. Guard at 22 rules and 77
formulas.
# Round 29 — weight on the kit, and a dice set that reports the drift. 10 Aug 2026
`R-182` **Weapons and armour did not show their weight.** Gear did; the two
categories that account for most of a load did not — so a player deciding what to
drop could see that a notebook weighs 0.3 kg and not that the rifle weighs 3.4.
Weapons gained a WEIGHT column, armour gained the figure beside its points, both
right-aligned with tabular figures so a kit list scans:
```
WEAPON | DAMAGE | RANGE | AMMO | WEIGHT
Marksman rifle | 1d10+2 | 20/60/180 m | 8/10 | 3.4 kg
Entry bar | 1d10 | — | — | 3 kg
Breaching suit 7 pts 11 kg
```
That matters more now that free carry is STR + MAS and the encumbrance penalty
actually applies: Rakhi is 1 kg over 37, and the sheet finally shows her which
kilogramme to put down.
`R-183` **A dice set for Dice So Nice, and it is the setting rather than a skin.**
The agency keeps a filing system and the game is about that filing system being
wrong. So the dice carry a ruled registry grid — and **the colourset is chosen per
roll from the roller's Coherence**, so the dice report the one number the players
are trying not to look at:
| Coherence | Set | What the die looks like |
|---|---|---|
| 10–8 | Anchored | true rule, lemon numerals on ultramarine, metal |
| 7–5 | Loose | the same grid, dimmer, plastic |
| 4–3 | Adrift | the rule has begun to wander and grain shows through |
| 2–1 | Unmoored | torn registry, coral numerals, glass |
| 0 | Displaced | no grid at all — fragments of rule going nowhere |
Plus an **Agency issue** set in brushed plate for anything that is not a person.
Four textures, generated rather than drawn, by `tools/make-dice-textures.mjs` —
raw RGBA encoded to PNG through `node:zlib` with a small built-in encoder, so the
repo gains no image dependency and a rebuild is byte-identical. All four tile
cleanly, which matters on a d20.
Verified positively rather than by absence of error, since Dice So Nice falls back
silently on an unknown colourset: `dice3d.exports.TEXTURELIST` holds all four
textures, `dice3d.exports.COLORSETS` all six sets, and
`getLoadedDiceSystems()` reports "The Custodians, 7 presets" — d100 and d10 labelled
as the percentile pair, plus d4/d6/d8/d12/d20. Every roll the system makes is
dressed: skills, the reaction die, damage, hit location and the Major Wound d10.
`R-184` **A deprecation warning of my own making, removed.** The damage-button
handler registered both `renderChatMessageHTML` and `renderChatMessage` for
compatibility, and v13+ warns on every single chat message when the old name is
bound. It now picks the new name when the v13 chat class is present.
# Round 30 — art, a catalogue that spans centuries, and the Quiet Arts. 10 Aug 2026
`R-185` **Icons rebuilt with depth.** A glyph was one silhouette; it is now up to
three layers — mass in near-white, **detail** in a darker tone (the grip and sight on
a pistol, the fuller on a blade, the lens housing on a scanner), and an accent in the
rim colour. The detail layer is the revision: it is what makes a flintlock read as a
flintlock rather than a pistol-shaped blob at 32 px.
**Era is carried by the tile**, so a glance at an inventory dates a thing before the
name is read: a notched corner and muted ground for antique, a cut corner and a
scanline for recovered, and **a broken rim for anomalous** — the tile does not close
on things that should not exist. 141 icons: 99 modern, 23 antique, 10 recovered,
9 anomalous.
`R-186` **The catalogue spans time.** Weapons 18 → 30, armour 10 → 13, gear 26 → 45,
filed into era sub-folders on install rather than one alphabetical column:
```
Weapons Armour Gear & Devices
└ Antique 6 └ Antique 2 └ Antique 8
└ Agency issue 21 └ Agency 10 └ Agency issue 29
└ Recovered 3 └ Recovered 1 └ Recovered 6
└ Anomalous 2
```
Flintlock, musket, crossbow, duelling sword, boarding axe, brigandine, oil lantern,
tallow candles, brass compass, pocket watch, chirurgeon's roll, wax tablet. Gauss
sidearm and lance-rifle, monofilament blade, survey drone, optic visor, data wafer,
stasis cannister, sealed hardsuit. And two that should not be in a kit list at all:
a **threshold key** that opens a crossing a hand's width, and **the quiet ledger**,
which writes down what happened — sometimes before it has.
`R-187` **THE QUIET ARTS.** The setting already contained the answer, so the powers
are not an addition to it. Crossing is an *induced filing error* — you persuade the
record you are somewhere else. **An Art is the same trick performed small and on
purpose: a misfiling committed deliberately.** There are therefore no fireballs;
every Art is a sentence about the record.
| Art | It says | Ordinary | Pushed |
|---|---|---:|---:|
| **Thread** | what was together is still together | −1 | −2 |
| **Echo** | *when* is loosely filed | −1 | −2 |
| **Hollow** | this is not filed here | −1 | −3 |
| **Witness** | the record agrees you belong | −1 | −2 |
| **Unmake** | this was not present | −2 | −4 |
| **Ledger** | read the record — and amend it | −2 | −4 |
Three design decisions worth recording:
**No new resource.** The cost is Coherence, because using an Art *is* doing something
that only makes sense somewhere else — precisely what transposed materiel already
charges for. It is levied whether or not the roll succeeds, since the misfiling is
committed the moment you try.
**Coherence penalises the Arts.** They run through the same `skillRoll` choke point
as everything else, so the further an agent has drifted the worse they are at the
thing that made them drift. Verified: Echo at 30% rolls 30 / 25 / 20 at Coherence
10 / 4 / 1, and at 0 the attempt is refused — there is nothing left to spend.
**A fumble is not a failure.** The misfiling *sticks*: something small and true about
the world is now different and the GM never says which. A story hook with a
mechanical anchor.
`R-188` **They only ever arrive from crossings**, per the owner's decision. There is
no path in but `grantPower()`: not taught, not bought, not chosen at creation, not
offered by the generator. Verified — a freshly generated character has none, and a
second grant of the same Art is refused. Each carries an `origin` line naming the
crossing that left it, printed on the sheet:
```
Echo 30% −1 [USE] [PUSH −2]
When is loosely filed.
Left by: Ilfracombe, the second crossing. She came back eleven minutes before she left.
```
New Item type `power`, its own compendium, an Arts tab, and a rules-journal page
generated from `ARTS` and `powerCostFor` so the book cannot drift. Guard at 24 rules
and 85 formulas — it caught both new rules arriving without coverage and stopped the
build until they had it.
`R-189` **The return is now a roll, and it is the only door the Arts come through.**
`returnCrossing()` described the modifiers and nothing ever resolved them — the GM
rolled it themselves, so "a crossing that goes wrong" was not an event the system
could observe. `crossBack(actor, {aimedByAnother, aimedBy})` resolves it through the
usual choke point.
| Return | What happens | Coherence | An Art? |
|---|---|---:|---|
| Success or better | home, where and when you meant | — | no |
| **Failure** | you arrive near your target, not at it | −1 | only if already Unmoored |
| **Fumble** | you come back, and not the same | −2 | **always** |
**R-56 is enforced in code rather than by convention.** There is no branch that
leaves an agent across: `setAcross(false)` runs on every outcome. Verified over
twenty returns by an Unmoored agent rolling alone — never stranded once.
**You do not choose the Art, and the pool darkens as you come apart:**
| Coherence when it went wrong | What can arrive |
|---|---|
| 8+ Anchored or Loose | Thread, Echo, Witness |
| 4–3 Adrift | Hollow, Thread, Echo |
| 2 and below Unmoored | **Unmake, Ledger, Hollow** |
Which is the trap the setting has been laying since the Coherence track was written:
**the agents most likely to come back with an Art are the ones least able to afford
using it.** Verified live — an anchored fumble produced Witness at 30%; an Unmoored
failure produced Unmake.
`R-190` **Three of the four pregens could not perform the job's defining act.**
Transposition sat at its printed base of 1% for Ceyla, Rakhi and Tomas; only Dunn had
it at 68. Every return would have failed, making "you arrive near your target" the
normal experience of being a Custodian and quietly reducing the crossing rules to
scenery.
Defensible as a design — one specialist aims for the team, and `aimedByAnother`
exists precisely for that — but not at 1%: these are people who cross for a living.
Ceyla 32, Rakhi 28, Tomas 35, Dunn unchanged at 68 as the field lead. The team can
now get itself home, and is markedly better off doing it through Dunn.
Found only because `crossBack` made the roll real. A skill nothing rolls is a skill
nobody notices is wrong — the same shape as R-176 and its four predecessors.
`R-191` **A crossing panel on the case file.** The sheet had a single `toggle-across`
flag-flip, which is right for going out — the brief names a place and a time and the
margin is the case's first problem — and wrong for coming back, because coming back
is a roll.
The panel now shows the whole decision at a glance while the team is across:
```
CROSSING [ BRING THEM HOME ]
AGENT | SKILL | COH | ALONE |
Ceyla | 32% | 10 | 32% | [RETURN] [AIMED]
Dunn | 68% | 10 | 68% | [RETURN] [AIMED]
Rakhi | 28% | 2 | 1% | [RETURN] [AIMED]
Tomas | 35% | 10 | 35% | [RETURN] [AIMED]
```
The **ALONE** column is the argument the panel exists to make: Rakhi, Unmoored at
Coherence 2, aims her own way home at **1%** — 28 less the −30 the band imposes,
floored. AIMED routes her through the best crosser at no penalty. Nobody has to know
the rule to see the cost.
`R-192` **`crossBack` no longer ends the case.** It cleared `across` on the case file,
so the first agent through the door marked the whole team home while the rest were
still standing in it. It now returns only the agent it was called for, and the caller
decides when the last one is back — which is what "Bring them home" does, looping
every member through the best aimer and then clearing the flag.
Verified end to end: Dunn aimed for the team, three came home at a cost of 1
Coherence apiece for rough returns, and **Rakhi failed hers while Unmoored and came
back with Hollow** — the dark pool, arriving exactly as designed, on the agent least
able to afford it.
# Round 31 — a full case, played end to end. 10 Aug 2026
Played as the team's **second** crossing, because Arts only arrive from crossings
that go wrong: Rakhi carried the Hollow she came back with from the first Ilfracombe
return. Brief → cross → four phases → first contact → an Art used → a fight →
return → debrief → end of case.
**What the case did.** The destination is *rumoured*, so every phase is Difficult:
Tradecraft 71 halves to 35 before the terrain modifier. Three of four phases failed
and the team reached the cove on **Containment 7, Resources 2**. Rakhi hollowed the
regulator case to get it past a watched approach — Hollow 26%, success, −1 Coherence.
Two contractors at the cove went down in two rounds. Dunn aimed everyone home at 68%
and took the only rough return himself. Debrief restored the team to Anchored.
That is the loop working: the pressure came from the tracks and the Coherence, not
from the dice being unkind.
`R-193` **First contact could not be made with a group nobody had met.**
`standingFor` returned `null` when a group had no standing entry, and
`resolveContact` bailed on null — so the *first* contact with a new group was the one
thing first contact could not do. Every stage silently returned nothing. It had gone
unnoticed because the shipped case file ships with a standing already on it; the bug
only appears with a group the team encounters for the first time, which is the normal
case in play and never once in testing.
`ensureStanding()` creates the entry on demand. Verified against a wiped standings
list: signal → success, read → failure, offer → success, standing 2 and impression 2
recorded.
`R-194` **The skills the job most uses could never improve.** The experience tick in
`skillRoll` fires only when a `skillItem` is passed. Every internal caller — case
phases, First Aid, Medicine, defences, and the crossing itself — passes a
`skillFamilyId` and no item. So Transposition, Tradecraft, Dodge and First Aid were
structurally incapable of earning a tick, and `endOfCase` reported "0 skills ticked"
for the entire team after a full case.
`skillRoll` now resolves the item from the family when the caller has not handed one
over. Verified: Ceyla ticked Dodge from a defence, Dunn ticked Transposition from a
return, and at end of case **Transposition improved 68 → 69**.
Both defects are the same shape as the run since R-176 — a rule that exists but that
nothing reaches — and both were found by *playing the case* rather than by testing a
subsystem. Two harness errors of mine on the way, recorded so they are not re-found
as bugs: the contact stages are `signal` / `read` / `offer`, not approach/pidgin; and
combat rolls driven through `resolveBands` directly bypass `skillRoll`, so they
legitimately do not tick.
**Still open from this case.** Nobody on the team has Fast Talk above its printed
base of 5, so the Cover Identity pack's +20% lands on a 25% skill. Tomas is the
talker on Persuade 44 and Language (Sign) 42, which works — but the Cover Work
talent, which exists, remains close to dead on the pregens.
`R-195` **Cover Work moved onto Persuade.** The talent granted +20% Fast Talk, and
nobody in the cast has Fast Talk above its printed base of 5 — so an agency training
talent held by the Case Officer was worth +20% on a 25% skill and effectively did
nothing. This is the surviving half of the "Cover Work / Fast Talk" problem the
generator already guards against for generated characters: the generator grants the
skill a talent modifies, but the hand-written pregens were never revisited.
It now grants **+20% Persuade**, which Ceyla has at 41 — and which feeds two of the
three contact stages, `signal` and `offer`, so the talent now bears on the work it
is named for.
Verified live: Persuade 41% rolls at **61%** with the chat card reading
`Cover Work: +20%`.
**Left alone deliberately:** the Cover identity pack (gear) still grants +20% Fast
Talk and has the same dead-bonus problem. It is a separate item and the instruction
was specific to the talent.
`R-196` **The Cover identity pack moved onto Persuade too.** Same dead bonus as the
talent: +20% to a Fast Talk nobody trains. Nothing in the content now hangs a bonus
on `fast_talk` at all.
Both work, and — worth recording — **the shipped party does not stack them.** The
talent sits on Ceyla, the Case Officer; the pack is in Tomas's kit, and Tomas is the
talker. As issued:
```
Ceyla · Cover Work talent 41% → 61% "Cover Work: +20%"
Tomas · Cover identity pack 44% → 64% "Cover identity pack: +20%"
```
Stacking is possible and the engine handles it cleanly, labelling both sources:
```
Ceyla · talent AND pack 41% → 81% "Cover identity pack + Cover Work: +40%"
```
**Flagged rather than changed:** +40% is a large bonus and gear bonuses sit outside
the ±40% circumstance cap, so an agent with a full legend rolls at 81% on a base of
41. That is arguably correct — trained cover officer plus a complete documentary
identity should be very good at being believed — but it is the largest single-skill
swing in the game and it is available by moving one item between two members of the
same team. If it wants trimming, the pack at +10 is the lever; the talent is the part
that ought to be worth the most.
`R-197` **Cover identity pack trimmed to +10.** The stacked total was the largest
single-skill swing in the game — 41 → 81 — and reachable by handing one item to the
agent who has the training. At +10 the ceiling is 71, and the ordering now says the
right thing: **the training is worth twice the paperwork.**
```
Ceyla · Cover Work talent 41% → 61% +20%
Tomas · Cover identity pack 44% → 54% +10%
Ceyla · talent AND pack 41% → 71% +30%
```
The item description carries the reasoning so a GM reading the sheet knows why it is
the smaller number: *"Paper helps; it is the training that sells it."*
# Round 32 — case three, and the Arts in anger. 10 Aug 2026
Played as the team's **third** crossing, with two Arts already carried: Rakhi's
Hollow from Ilfracombe and a Thread that followed Dunn back from Steepholm. They
started drifted — Coherence 9 / 8 / 7 / 6 — because they have been out twice.
**What happened.** Dunn threaded the missing team's beacon before setting out and
**failed** — and paid the Coherence anyway, dropping to 5. Two of four phases came
good; the cliff path did not; they reached the cove on Containment 9, Resources 3.
Rakhi tried to hollow the boathouse door and **pushed it** to cover herself as well,
failed, and paid all three: Coherence 7 → 4, **Adrift**, standing in the open. The
fight took four rounds and nobody was hurt. Dunn aimed everyone home; two rough
returns; the debrief pulled them back to 10 / 7 / 7 / 9.
Nothing followed anybody home this time, which is correct — no return was fumbled,
and the two failures were rolled by Anchored agents. **The Arts are meant to be
rare, and the gate held.**
`R-198` **An Art could never improve.** The `power` item carries an
`experienceTicked` field. `usePower` never set it, and `endOfCase` filters on
`i.type === "skill"`, so it was never read either — a field with no writer and no
reader, which is this project's signature defect in its purest form.
The consequence was worse than cosmetic. An Art arrives at roughly POW × 2 — **22%
for Dunn, 26% for Rakhi** — and charges Coherence whether or not it works. Fixed
forever at that rating, it is not a temptation, it is a trap: both Arts used in this
case failed and cost 4 Coherence between them for nothing.
Now a successful use ticks it and `endOfCase` improves it on the same footing as a
skill. Verified: Hollow ticked on its second attempt and improved **26% → 29%**.
Modelled over ten cases with one success each, a Hollow that arrived at 26 reaches
roughly **59%** — so the Art you keep using is the one that becomes reliable, and
the risk pays back across a campaign instead of never.
That also repairs the fiction. "The crossing left this in you" now has a second half:
you learn what to do with it.
# Round 33 — a mission framework, and two agents taking it apart. 10 Aug 2026
Built `tools/mission-tables.mjs` (content) and `tools/mission.mjs` (composer),
generating a full case: brief, truth, a named GAP between them, opposition, cast,
phases, clock, Coherence hooks, timeline, debrief question and failure state, as
five journal pages plus a playable case-file Actor. Wired into Foundry as
`generateMissionCase()` and a GM dialog.
Then handed it to two subagents: one running it as a GM deciding whether it is
runnable on Thursday, one auditing whether every field the generator emits actually
connects to the engine. Both came back with more than I expected.
## What the GM agent found
**89.3% of missions contained a defect a GM would have to patch before running.**
The cause was structural: **the composer treated every table as independent when
roughly half of them depend on the anomaly.**
`R-199` **The brief's objective and the truth's "how it closes" agreed 24.9% of the
time.** Drawn from unrelated tables. A case about a duplicated person ordered the
team to *"Recover the object"*; 12.8% ordered them to find a first team that does not
exist. Worse than wrong: the mission's own `gap` field names a *different*
contradiction, so a GM could not tell the defect from the design.
**Fixed by making the anomaly the parent key.** Every anomaly now carries
`fits: { resolutions, gaps, adversaries, classes, placeKinds }` and the composer
filters before picking. That single change also killed the Hollow-world cases whose
containment note read *"the town has noticed"* (there is no town — that is the
classification), the `not_hostile` gap applied to an eleven-minute time loop
(*"It runs from them at the first opportunity"* — a loop cannot run), and
`already_contained` on the inheritance anomaly, whose truth is that the family have
held it competently for four generations.
`R-200` **No phase in which they did the thing they were sent to do.** The spine was
approach → search → extraction, and extraction's verb is *"getting out with it"* —
presuming the *it* had been obtained offscreen. A **Containment** phase is now
compulsory in every mission. The GM's note was exact: this is the difference between
a three-hour session and a ninety-minute one.
`R-201` **Half of all missions were arithmetically unfinishable.** Phases cost
1+2+2+2 = 7 against a starting Resources of 6 for every non-Near class. Resources are
now derived from the phases that were actually generated, plus slack scaled to the
classification. Verified over 500 seeds: **0 unfinishable budgets.**
`R-202` **String plumbing.** "has been open for since the spring" (8.8%); a timeline
row reading "since a date nobody will confirm ago" (28.9%); briefs starting
lowercase (21.1%); a hard-coded *"The second phase makes the true scale unmissable"*
printed under heading 3; the same anomaly sign used in three of four phases (100%);
duplicate cast names in a game about duplicates; and "eleven" appearing up to eight
times in one case, which turns the register into a running gag the bible forbids.
`DURATIONS` is split into spans and vague dates, the timeline is relative, signs
cycle, names dedupe. Verified over 500 seeds: **all five string defects at zero.**
Also cut: a human spokesman for a revenant or a duplicate — entities whose whole
design is that they are singular and do not negotiate — who then lied *"about how
many of them there are"* against a hard-coded count of 1.
## What the integration agent found
`R-203` **The journal ownership was inverted, and it leaked the plot.** In Foundry
`2` is OBSERVER, `0` is NONE and `-1` is INHERIT. I had written
`i === 0 ? 0 : -1` — setting the **brief** to NONE and the four secret pages to
INHERIT. A GM opening the entry so the table could read the brief would have
published the truth, the opposition, the phases and the pressure. Now `2` for the
brief and `0` for the rest, verified page by page.
`R-204` **No case file was ever the active one.** `activeCaseFile()` looks for a flag
that **nothing in the system ever set**, so it fell through to the first expedition
in the collection — the shipped ILFRACOMBE case. Every `setAcross`, `isAcross`,
`debrief` and Clean Scene on a generated case was operating on a different actor, and
the sheet's Across toggle read one case and wrote to another. Generated cases now set
the flag and clear it from any other.
`R-205` **The whole first-contact pillar was unreachable from generated content.**
`resolveContact` needs a `group` item on the case file; `generateMissionCase` created
the actor with no items at all. Three stages, standings, learned taboos, desires,
correct offer form and the reward effects the phase resolver already applies — all
dead on anything the generator produced. Four GROUPS added, each with a taboo, a
desire, an offer form and standing rewards, and the case is built with one embedded.
Verified live: signal → read → offer, standing 2.
`R-206` **`leg.difficulty` was written and never read.** The generator sets a
deliberate ramp and explains it in a comment; `resolveLegDialog` derived difficulty
from `destinationKnowledge` instead, so the brief said "phase 1 is Average" and the
phase table disagreed on the same document set. The resolver now honours an explicit
per-leg difficulty when one is given.
**Still open, and worth doing:** `crossingModifier` is printed in the brief as a
mechanical promise and never applied by `crossBack`; `salvageEra` is computed and
referenced nowhere at all; `leg.explored` is read by the resolver and written by
nothing; `journey.elapsedDays` never accumulates; `tools/generator.mjs` is orphaned
dead code duplicating the live THREATS table. Four more instances of the pattern.
## What the GM agent said was good, and worth protecting
The `ANOMALIES` table — *"each row is internally airtight, and each truth is a
genuine inversion of its brief rather than an escalation"* — and the `GAPS` names,
*"ten different session shapes, not ten flavours of one"*. The failure was never the
content. It was a composer that rolled dependent tables independently.
`R-207` **The four dangling values, cleared.** Each was the project's signature
defect — a value computed and never read — and each is now either wired or gone.
**`crossingModifier` was a promise the engine did not keep.** The generated brief
printed "return modifier for the classification: −20%" and `returnCrossing` computed
the penalty purely from the agent's Coherence band, so a **Wrong** world was no
harder to leave than a Near one. The case file now carries it and the return charges
it. Verified on a Wrong case:
```
Anchored, alone −20% world −20, coherence 0
Unmoored, alone −50% world −20, coherence −30
Unmoored, aimed −20% aiming removes the coherence part, not the world
```
That last line is the one worth having: another agent can steady *you*, but nobody
can make the place less strange.
**`salvageEra` was the cleanest dead field in the codebase** — computed by the
generator, referenced nowhere, not even rendered. It is now a mechanic:
`salvageFrom(caseFile)` draws from the catalogue by era, which items have carried
since the era rework. A Wrong world yields the Threshold key and the quiet ledger; a
Hollow one yields lanterns and brigandine. That is what crossing to other times is
*for*, and it was one line from existing.
**`leg.explored` was read and never written.** The resolver branches on it to choose
between "you already know this ground" and "you find a site", so every critical
reported a site. Ground the team has crossed is now marked explored: 0/5 → 2/5 after
two phases.
**`journey.elapsedDays` never accumulated.** Recorded per leg, displayed on the
sheet, never summed. Now 0 → 2 after two phases.
**`tools/generator.mjs` deleted.** Orphaned duplicate of the live THREATS table,
imported by nothing, and stale — its entries used the retired `weapons: [...]` shape
against the live `attacks: [{weapon, skill}]`. Checked before removing and again
after: all six threat tiers still generate, and all six mission adversary tiers and
NPC names still resolve.
## Play-through: SOFT TENANT (seed 2718)
Generated a case from the mission framework and ran it end to end — crossing, five
phases, first contact, a fight, the return, debrief, close. Four findings, all of
them the same species as everything before them.
**R-208 · `generateNPC` guarded one lookup out of three.** An unknown `species` fell
back to baseline; an unknown `threat` or `role` reached `TH.armour`/`R.label` and
threw. Callers got a TypeError instead of an NPC. All three now fall back, and the
substitution is announced rather than silent.
**R-209 · `hitLocationRoll` treated bad damage as zero damage.** `Number(damage) || 0`
meant a caller passing `undefined` got a hit that rolled a location, printed a chat
card, and took nothing off. A six-round fight ran with fourteen connecting blows and
no casualties, and nothing anywhere said a word — the fight *looked* right in the
log. Damage must now be finite or the hit is refused loudly. `majorWoundRoll` got the
same treatment: called positionally by mistake it rolled and applied a Major Wound to
nobody.
**R-210 · `endOfCase` was exported and called by nothing.** Documented in its own
comment as "the step that closes the episodic loop", wired to no button and no hook.
Every ticked skill and Art sat on the sheet forever: no character could improve
through play, and the Arts — which already had `experienceTicked` fixed once for
exactly this reason — still could not get better. Now a **Close the case** control on
the case file, guarded against being pressed while the team is still across. First
use: Rakhi's Hollow 26% → 31%.
**Salvage returned "Punch / kick".** The unarmed pseudo-weapon every character
carries was in the catalogue draw, so it turned up as loot lying in a filter house.
Salvage now requires an item with mass.
Not defects, recorded because play made them look like defects:
`crossBack` deliberately leaves `system.across` set — one agent returning does not
end a crossing the rest of the team is standing in, and "Bring them home" is what
clears it. Deep bleeding correctly refused an average First Aid and yielded to a
difficult one. Both behaved exactly as written.
## The establishment: 20 more postings
Five roles were never the whole agency — they were the five a field team fields.
Twenty more are now on the books, each with its own characteristic, core and
support skills, talent leaning, and a loadout containing at least one thing no
other posting is issued. Two are deliberately archaic (the Antiquities Officer
draws a duelling sword and a flintlock; the Night Warden carries an oil lantern
and a ward plate), because an agency this old has never got round to abolishing
anything.
Adding them exposed three faults.
**R-211 · the generator matched kit by guessing at its name.** `keyMatches` was
fuzzy substring matching against a hand-maintained alias table, and anything the
table had not heard of was dropped without a word. The catalogue names diverge
from the keys almost everywhere — `binocs` is "Field glasses", `rope` is "Line and
harness", `geiger` is "Radiation counter", `sealed_cannister` is "Stasis
cannister" — so a Stores Officer issued five things received one, and an Overwatch
Officer never got the rifle the posting is named for. Worse, `climbing_kit` was
mapped to "breaching charges" outright, so the Rigging Officer was handed
explosives instead of a harness and nothing looked wrong.
Items now carry their catalogue key in `flags.ringbrp.key`, stamped at pack build,
and the match is exact. The name pass survives only as a fallback for documents
built before the flag existed, and an unresolved kit key is now reported instead
of lost. All 25 postings receive their full loadout.
**R-212 · two weapons named a skill that does not exist.** The boarding axe and the
entrenching tool both declare `melee_weapon:axe`, and the catalogue had no such
skill, so neither could ever be rated above the 5% base — the generator would hand
a Rigging Officer an axe they were permanently incompetent with. Skill added; every
weapon in the catalogue now maps to a real skill, checked.
**`tools/check-kits.mjs`**, gated into the pack build alongside `check-rules`. Every
kit key must name a real catalogue item, every skill and talent reference must
resolve, no two postings may share a loadout, and it warns when a posting has
nothing issued to it alone.
## Sheet
**Characteristics were smaller than the skill ratings below them.** The eight
numbers the entire system derives from read as a caption under the ×5 chips. They
are now the dominant band on the sheet, ruled top and bottom.
**The marksmanship dropdown offered a choice with no stated consequence** — two
words, Reflex or Deliberate, and nothing anywhere on the sheet saying what either
buys. Both styles are now printed under the control with the chosen one picked
out, assembled from `RINGBRP.marksmanship` itself so the sheet cannot describe a
style differently from the way it rolls.
## One agent per posting, sheets checked
Generated a character in each of the twenty new postings and audited every sheet —
rendered DOM, not just the data. All twenty come out clean: eight non-zero
characteristics, both marksmanship rows, 53 skills, 2 talents, 8 hit locations and
a figure on the Combat tab, full loadout issued, no `undefined`, `NaN` or unrendered
Handlebars anywhere. Empty states read properly ("No Arts. They are not learned —
they arrive.").
Two faults found.
**R-213 · nobody had hands.** "Punch / kick" is in the weapon catalogue and was
issued to no character, pregen or NPC — every agent carries Brawl and had no item
to roll damage with, so an unarmed attack was unrollable for every character in the
game. It went unnoticed because every playtest so far happened to arm everyone.
Worse, the comment written into the salvage fix a few hours earlier asserted it was
"the unarmed pseudo-weapon every character has", which was simply untrue. Both the
generator and the pack builder now issue it to everyone.
**Salvaged crossing plate was standard issue for two desk postings.** At 10 AP it is
the strongest armour in the game — better than plate (8) and the sealed hardsuit (9)
— and the Materials Officer and Rigging Officer were both wearing it as routine kit,
making two support specialists the best-armoured people in the agency. Materials now
draws a stab vest, Rigging an impact vest. Salvaged plate is no longer anybody's
standard issue, which is as it should be: it is salvage.
Checked and not a fault: the Disposal Officer at 9 AP is in a sealed hardsuit, which
is the posting, and pays 12kg for it.
## A fight with four of the new postings
Overwatch, Rigging, Antiquities and Stores against three Dangerous adversaries —
chosen to exercise the anti-materiel rifle, the axe skill added yesterday, a
single-shot flintlock, and an agent whose only weapon is the unarmed attack. Twelve
rounds; the team lost, all four down, two adversaries still standing. That is a fair
result for a rifle, an axe, an archaic swordsman and an unarmed storekeeper against
three armed professionals.
Three faults, all found by playing rather than by reading.
**R-214 · every generated agent was an unlinked token.** `generateCharacter` never
set `prototypeToken.actorLink`, and Foundry defaults it to false. So an agent put on
the map took damage, wounds and Major Wounds on the token's private copy while the
player's character sheet stayed pristine — the sheet and the body were two different
people. The pack builder has always linked characters (`actorLink: type ===
"character"`); the runtime generator, written later, did not. Agents are now linked;
NPCs stay deliberately unlinked, because one statblock stands up four bodies and each
needs its own hit points. Verified at the end of the fight: every agent's token and
sheet agree, and the adversary's token reads 0 while its statblock still reads 12,
ready for the next body.
**R-215 · Reaction was re-rolled every round except the first.** `nextRound()` re-rolls,
which is the whole design — but Foundry does not roll initiative when a combat
starts, it waits for the GM to press Roll All. Nothing did. So round one ran with
every initiative null and the comparator falling through to its DEX tiebreak: the
opening round of every fight, the one where the shooting starts, was the single round
the Reaction system did not order. `startCombat()` now rolls it.
**Ten of the twenty new postings carried a weapon they had no skill for.** The
Antiquities Officer drew a flintlock and rolled it at 5%, which is what exposed it;
the Registry, Interview, Pathology, Recording, Access, Disposal, Translation,
Naturalist, Pursuit and Settlements Officers all had the same fault. Every posting's
support list now trains what it is issued, and `check-kits` fails the build if a role
is ever handed a weapon it cannot use. The flintlock is now 40%.
**Flagged, not changed — a critical attack is cancelled by an ordinary success.**
`defend()` never learns what it is defending against: `turnedAside` is true for any
critical, special or success, so a bare success parry stops a critical entirely. The
rules text as written supports this — a defence either works or it does not — so the
code is not wrong, but it sits badly against everything else the system says about
criticals (maximum damage, armour ignored entirely). In play a boarding-axe critical
was turned aside by a special parry and cost nothing. Grading defences by level
(a defence stops an attack of equal or lower level; a lesser defence only reduces it)
would make criticals matter, but it changes combat maths materially and is a design
decision, not a defect fix.
## Defences graded by level
The binary rule is gone. A defence now stops a blow of its own quality or worse
outright; against something better it still helps, but only by as much as it was
outclassed by.
landing rank = failure + (attack rank − defence rank)
vs critical vs special vs success vs failed
critical turned aside success special critical
special turned aside turned aside success special
success turned aside turned aside turned aside success
So only a critical stops a critical, a success parry shaves a critical to a special
rather than cancelling it, and a failed defence does nothing at all. The reduced
level is the result for everything downstream — a critical cut to a special no
longer ignores armour, it merely halves it.
`defenceOutcomeFor(attackLevel, defenceLevel)` and `levelRank()` live in `rules.mjs`
with the rest, carry ten spot-checks in `check-rules`, and the rules journal builds
its table by calling the function, so the book cannot describe it differently from
the way it resolves. 29 rules, 104 formulas.
**The standing blow.** A defence has to know what it is answering and the sheet's
Dodge and Parry buttons had no way of knowing — they were pressed in isolation.
Every attack roll now records what it scored; the defender picks it up; the defence
amends it to whatever survived. It is kept on the active Combat so it reaches every
client rather than only the machine that rolled, falls back to a module-level copy
when nobody started a combat, expires after two minutes, and is never offered to the
person who threw it.
**R-216 · the damage button ignored the attack entirely.** `_onRollDamage` called
`rollWeaponDamage` with no level at all, so it always rolled ordinary damage: a
critical got neither maximum damage nor its armour bypass, and the whole critical
rule existed only in the wording of the chat card. This is why grading defences was
not enough on its own. Damage now rolls at the level the blow actually landed at,
and a blow that was turned aside refuses to roll damage at all.
Played it: four senior agents against three Dangerous adversaries, twelve rounds,
the agents won. Every cell of the table came up in play, including the one that
prompted the change — Ganger's critical against Marek's success parry, cut to a
special and landing for 6 where it would previously have cost nothing — and its
mirror, a critical parry turning aside a special.
## Balance check on the graded defence
Played a standard field team — Field Lead, Containment, Technical, Physician —
against four Armed, and then ran the maths properly: 3000–4000 simulated fights per
configuration, driven by the shipped functions (`resolveBands`, `gradeRoll`,
`defencePenaltyFor`, `defenceOutcomeFor`, `armourAgainst`, `damageAfterArmour`)
rather than a reimplementation.
**Grading did not make combat more lethal.** My warning when I shipped it was wrong
and is withdrawn. Both sides lose defensive value equally, so it nets out:
4 trained agents vs binary graded Δ win
bystander x4 100.0% / 5.8r 100.0% / 5.7r +0.0
trained x4 100.0% / 7.8r 100.0% / 7.5r +0.0
armed x4 79.9% / 13.2r 80.7% / 12.2r +0.7
dangerous x3 35.0% / 12.8r 37.8% / 11.8r +2.8
anomalous x2 18.6% / 16.4r 20.6% / 15.2r +2.0
Agent casualties are unchanged (1.76 → 1.78 at Armed, 3.14 → 3.09 at Dangerous).
Fights are consistently about one round shorter. `DEFENCE_STEP` does not need
touching.
**R-217 · Dodge was 0% for most of the agency.** Dodge is the only skill whose base
is written as a formula — `dex*2` — and nothing in the system ever evaluated a
formula base. `base.value` stayed 0, `skillRating()` read `system.value` at face
value, and so any posting that did not separately train Dodge had **Dodge 0%**: the
game's universal defence, unavailable to ten of the twenty-five postings, and
missing its DEX×2 floor even for the fifteen that had it. This is what the balance
check was actually for, and it is worth far more than the grading change:
dodge 0% — as it actually shipped win 52.0% 2.77 of 4 agents lost
dodge 26% — DEX x 2 floor (fixed) win 66.5% 2.34 of 4 agents lost
dodge 47% — a posting that trains it win 79.7% 1.82 of 4 agents lost
`skillBaseFrom(formula, characteristics)` now lives in `rules.mjs` with five
spot-checks, is applied as a floor rather than a replacement in the skill item's
derived data, and deliberately parses rather than evals. Every posting now shows a
real Dodge: the untrained sit exactly at DEX×2, the trained above it, none at zero.
30 rules, 109 formulas.
**R-213, third time.** Generated NPCs still had no unarmed attack. The pack builder
and `generateCharacter` were both fixed; `generateNPC` builds its items on a
separate path and was missed, so four adversaries armed only with pistols stood
through an entire close-quarters fight doing nothing. Fixed there too, with the
Brawl skill to go with it.
**Two observations, not defects.** Threat tiers are steep — Dangerous x3 is winnable
about 38% of the time and costs three of four agents; Anomalous x2 is around 20%.
And an unarmed attack is 1d3 against armour that is routinely 2, so fists are close
to useless against anyone equipped: the tail of the played fight was four people
punching each other for five rounds to no effect. Both are arguably correct, but a
GM should know before pricing an encounter.
## Two more fights with the Dodge floor in
Standard field teams including postings that rely on the new DEX x 2 floor, against
Armed adversaries. First fight: a clean win, all three adversaries down, nobody
lost. Second: ten rounds, three of four adversaries down, two agents lost.
Neither fight happened to land a floor-rated dodge, which is too thin to conclude
anything from, so the effect was measured per incoming blow instead — 200,000 trials
at each rating, attacker at 48%, first defence of the round:
dodge 0% stopped 1.0% reduced 0.0% through in full 99.0%
dodge 18% stopped 14.7% reduced 3.1% through in full 82.2%
dodge 24% stopped 19.9% reduced 4.0% through in full 76.1%
dodge 30% stopped 24.8% reduced 5.2% through in full 70.0%
dodge 47% stopped 38.9% reduced 8.1% through in full 52.9%
dodge 58% stopped 47.8% reduced 10.1% through in full 42.0%
0% is what shipped; 18–30% is the floor; 47–58% is a posting that trains it. A
support agent went from taking 99% of blows at full effect to taking 70–82%. The
1.0% at dodge 0% is the 1% floor in `resolveBands` doing its job — a roll of 1 is
still a critical dodge.
The "reduced" column is the graded rule earning its keep: 3–10% of blows now land
degraded rather than either landing whole or vanishing.
Two harness faults, mine not the system's, recorded so the next play-through does
not repeat them. Picking a weapon by highest skill rating made adversaries punch
rather than shoot, because `generateNPC` gives Brawl a rating from the same range as
the weapon skills — a fight ran entirely at 1d3 against armour 2. Choosing by
expected damage after the target's armour fixes it. And once Punch / kick is always
available, a weapon-selector that only reloads when it finds nothing to swing will
never reload at all.
## Brawl is a fallback
`generateNPC` rated Brawl from the same range as the weapon skills, so an Armed
adversary was exactly as good with its fists as with the pistol on its hip — and any
sensible target-picker chose the fists, which then bounced off armour 2 for 1d3. A
whole fight ran that way.
`brawlFallbackFor(trained)` in `rules.mjs`: sixty per cent of the trained rating,
never below the skill's printed base of 25. Four spot-checks. 31 rules, 113 formulas.
tier weapon avg brawl avg
bystander — 25%
trained 39% 25%
armed 44% 28%
dangerous 61% 35%
anomalous 67% 67% <- attacks WITH Brawl
apex 79% 75% <- attacks WITH Brawl
**The trap in this one.** The block that issues the unarmed attack ran BEFORE the
attack packages, and the package loop will not overwrite a skill that already
exists. Lowering Brawl in place would therefore have crippled exactly the creatures
whose attack IS Brawl — the anomalous grasp and the apex — reducing a 67% attack to
40% and quietly gutting the top two threat tiers. The block now runs after the
packages and only fills Brawl in when nothing else has, so a grasp keeps its full
rating and only an adversary genuinely falling back on its fists takes the
reduction. The three roles that legitimately train Brawl — Containment, Night
Warden, Rigging — are likewise untouched.
Played it: four field agents against three Dangerous, ten rounds, three agents down
and two of three adversaries down before it came to a last two standing. **35 weapon
attacks, no fist attacks.** The fight opened on the graded rule doing precisely what
it was built for: Whitloe's rifle critical against Mirren's successful dodge, cut to
a special, still 10 to the chest, still fatal. Under the old binary rule that dodge
cancelled it outright.
## Stores, and saying what a posting is
**The catalogue is roughly twice the size.** Gear went from 45 entries to 93 — 137
items across gear, weapons and armour. The new stock is the small equipment an
agency this old actually issues: warrant cards, field dressings, marking chalk,
numbered seals, carbon duplicate books, a date stamp that occasionally stamps the
wrong date, cordon tape citing an Act that does not exist, and a vacuum flask
described in the file as the most requisitioned item in the department's history.
Spread across all four eras — tinderboxes and quill and ink horns, wound foam and
cold light strips, and five more transposed objects priced in Coherence including
the coin that comes back and the attendance register, which is right, and is not
always a list you want. 189 icons regenerated; every catalogue item has one.
**An agent now leaves creation properly equipped**, 16 to 21 items rather than 8:
- `STANDING_ISSUE` — anchor, handset, warrant card, dressings, lamp, chalk,
whistle, notebook. Every officer, every posting.
- The posting's own kit, expanded from five items to eight.
- `TIER_ISSUE` — seniority is issued more and trusted with more, from nothing at
Probationary up to seals, master keys, a duplicate book and a trauma unit for a
Case Officer.
- `PERSONAL_EFFECTS` — one thing that is theirs rather than the department's, which
is the reason the anchor works: an agent carrying nothing of their own has nothing
to be filed against.
Encumbrance holds: a Containment officer comes out at 26.1 kg against a 29 kg free
carry, and `stowOverload` already sheds the rest for anyone heavier.
**The role dropdown said nothing about the roles.** Twenty-five job titles, no
indication of what any of them did. Each posting now carries a `blurb`, and the
creation dialog paints a card as the posting is selected: what the job is, its key
characteristic, what it trains, and what it draws from stores. The names are read
from the compendium rather than derived from the ids, so the dialog cannot print
"Quill Ink" where the sheet says "Quill and ink horn" — which is exactly what the
first cut of it did.
## Transport, and the forward stores
**A `vehicle` item type.** A lorry is not a piece of kit and modelling it as gear
would have meant lying about it in the encumbrance sum, so vehicles are their own
Item type with their own schema: crew, seats, cargo, hull (its own hit points),
armour points (which protect the occupants), range, terrain, and the skill that
works it. Speed is a BAND — walking pace through to no travel time at all — because
the game measures distance in bands everywhere else and a figure in km/h would have
been the only number on the sheet nobody used. Vehicles carry `carried: false`, so a
van in the boot of the character sheet does not weigh the agent down.
Fourteen of them in their own compendium: the pool car, the Custodian van with the
cage bulkhead, a long-wheelbase utility, a containment lorry, a dispatch motorcycle,
a river launch (most of the department's older sites are drains), a light
helicopter, and — still on the inventory and never struck off — a hand cart and a
pony and trap. Forward of us: the flycycle, a cargo lifter, and a sealed crawler
with eleven days of its own air. And two that came back with us: the stepping frame,
and the carriage that is already parked outside wherever you were going to go.
**Twenty-five more items in stores.** Transport stores — tow line, jack and sand
plates, jerry can, spares roll, the vehicle log book that is the most audited
document in the department. Then the forward stores, which Registry will not issue
to a probationary officer: stepping discs, a stasis box, a field autodoc, impact
liner, shadow cloth, a descent harness, a sonic probe, a pressure tent, translator
pins, trauma ampoules that bring an agent back from zero once. And three things
filed further out still — a sealed sphere nothing has opened from outside, the
contentment tap the department has destroyed six of, and a maintenance log for a
structure nobody has located, still being written.
The catalogue is now **175 items**: 118 gear, 14 vehicles, 30 weapons, 13 armour —
103 modern, 26 antique, 34 future, 12 anomalous. 228 icons; every one is drawn.
## Hacking: three options, and a debate with Codex
Not built. Three options put to the designer to choose from, after two rounds
against Codex (gpt-5.6). Codex opened with three designs and picked The Access
Dossier; I objected on two grounds it could not have known — that the system already
has a standings subsystem with taboos, desires and per-tier rewards that an
"Authority" maps onto almost exactly, and that the Dossier models the break-in but
not the thing that actually frightens people about modern intrusion, which is access
that persists and detection that arrives weeks later. Codex conceded both, discarded
its own first option as "the rejected run structure reskinned", and moved its pick
to Borrowed Authority with the Dossier demoted to an acquisition procedure rather
than a second subsystem.
Full transcripts in the session scratchpad. Options as put to the designer:
1. CORDON & TRACE — staged intrusion against a rising Trace clock. Both of us
rejected this: a live infiltration with an alert meter is the cyberpunk run in a
grey coat, whatever the fiction on top.
2. THE ACCESS DOSSIER — the team assembles a case file across several scenes and
different skills, then one decisive roll buys exactly the declared objective.
Fast, collective, sharply bounded; weak on persistence, and its Posture numbers
need tightening or three generic items beat any target.
3. BORROWED AUTHORITY — access is a named credential with a scope, a Trust value, an
expiry and a red flag. In scope, it simply works. Out of scope, roll. The horror
is that Coherence can widen a scope because reality now remembers the person held
that duty — and below zero Trust, the agent's own records begin merging with the
identity they borrowed.
## Borrowed Authority, built
Option 3 with the dossier as acquisition, as chosen. There is no hacking skill and
nothing to break into.
**The rules** are in `rules.mjs` with nineteen spot-checks: `SCOPE_TIERS`,
`scopeStanceFor`, `scrutinyFor`, `authorityOutcomeFor`, `trustAfter`,
`dossierTrustFor`, `dossierScopeFor`, `widenScopeCostFor`, `mergeStageFor`.
40 rules, 132 formulas.
- **Inside scope there is no roll.** A facilities contractor opening a plant room is
Tuesday. 1 Trust, and it works.
- **One tier beyond is a stretch** — roll, at −20 per red flag you break. The system
is not testing whether you are clever, it is testing whether you behave like the
person whose name is on the record.
- **Two tiers beyond is impossible at any skill.** That is the rule that stops one
agent quietly owning every system in the setting.
- Trust: critical +1, special free, success −1, failure −1, **fumble burns it** and
puts a real person in the department's way. At 0 the account is flagged until a
scene rehabilitates it.
- **The dossier** is acquisition, not a second subsystem: three items, each a
separate scene with a different skill, at most one at a keyboard. Items make the
credential better, never the roll easier — which is what stops three generic
successes beating any target.
- **AI** is a posture on the target, not an opponent. A *gatekeeper* raises what
counts as routine; an *analyst* writes a new red flag onto the credential every
time it is stretched; an *asset* stands in for a dossier item.
- **Coherence** widens a scope, because reality can be made to remember the person
always held that duty. First costs 1, then 2, then 3. Push past what Trust will
carry and the two files converge a step, and nothing spends anything to converge
them back.
Played it end to end: a Registry, Interview and Access officer built the credential
for M. Hughes, facilities contractor; the Signals officer opened a ticket and read a
door schedule with no roll at all; failed to alter the schedule at two in the morning
because that is one of the things Hughes never does; was told flatly that a
contractor could never issue accounts; spent Coherence to make the switchroom his
remit, after which the same action needed no roll; ran the credential to 0 Trust,
found it flagged and refused, and had it talked back into service by the Interview
Officer. Coherence 10 to 7, and a circular now arrives at the agent's desk addressed
to M. Hughes.
**R-218 · one localisation key un-localised the entire system.** Shipping both
`RINGBRP.Authority.Merge` (a string) and `RINGBRP.Authority.Merge.0` made Foundry try
to assign a property to a string while expanding dotted keys. It throws inside the
loader and the loader abandons the WHOLE FILE — every sheet in the system silently
began printing raw keys, with no error a user would ever connect to the cause. The
leaf is renamed, and `tools/check-lang.mjs` now fails the build on any key that is
both a value and a prefix of another, and on duplicate keys, which `JSON.parse`
swallows.
## The case generator gets a door
**R-219 · `missionDialog()` was exported, complete, and reachable from nothing.** The
entire case generator — anomalies, gaps, adversaries, phases, seeds — existed behind a
console call. The Actor Directory offered "New Agent" and "New NPC" and no way to make
a case. It now sits with the other two generators, because a case IS an actor and that
is where actors are made.
**R-220 · a new case conscripted every character in the world.** The roster fell back
to `game.actors.filter(a => a.type === "character")`, which is defensible in a world
holding one party and absurd in a world holding several — a freshly generated case
here listed thirty-six agents. The fallback exists for a real reason (a case with no
roster cannot resolve a phase, and `resolveLegDialog` bails silently), so it is now
ordered by how good the guess is: player-assigned characters first, then whatever the
GM has selected on the canvas, and the whole world only as a last resort — and it says
so when it does. Verified: with four tokens selected, the new case has four members.
## SOFT VERGE, played with four new postings
Registry, Access, Disposal and Settlements — no role from the original five. The case
generated a location filed twice with the two filings drifting apart, which is a
Registry Officer's whole job, and a police cordon the team could not show a warrant to
because the staff's taboo forbids it.
They tried Borrowed Authority on the cordon: Registry failed the roll that establishes
what PC Aldiss is actually FOR, so the credential stayed at "read their own records",
which made signing four people through a STRETCH rather than routine — and it was
refused. That is the subsystem working exactly as designed; the dossier item that
matters is the one nobody rolled well on.
The case was won socially, not tactically. The Settlements Officer read the staff and
made the offer, standing 0 → 2, and they handed over the log — the only continuous
record of a place filed twice. Containment and Extraction both failed at 8% and 3%,
Resources hit 0, and everybody came home. Two skills improved at close.
**Balance note, not a defect.** The phase ladder gets brutal at the tail: difficult
plus escalating modifiers plus destitution put the last two phases at 8% and 3%. A
team that loses Resources early is not going to pass another phase, and the case
becomes about what they can still do socially. That reads as intended given the tone,
but a GM should know that Resources is the track that actually decides a case.
## Documentation pass
Audited every exported rule in `rules.mjs` against what the rulebook actually says,
and closed the gaps the audit found:
- **Dodge starts at DEX × 2** — newly evaluated, and stated nowhere. Now on the
fighting page, worked with a real number.
- **Encumbrance** — free carry is STR + SIZ, and which skill categories it penalises,
as a table generated from `ENCUMBERED_CATEGORIES`.
- **Bleeding** — rates and what stops each kind, from `BLEED`.
- **The merge ladder** — the five stages of your file converging with a borrowed one,
as a table rather than prose.
- **A new page, "The postings"** — all 25, each with its key characteristic and what
the job is, generated from `ROLES` in `ringbrp.mjs` (read out of the source, since
that module cannot be imported outside Foundry).
The rulebook is 9 pages, every page generated from the code it documents, and no
unresolved localisation key leaks into it. All packs rebuilt: 53 skills, 18 talents,
30 weapons, 13 armour, 119 gear, 14 vehicles, 6 Arts, 4 pregens, 6 NPCs, 2 tables,
the rulebook and the starter case. 231 icons and the four dice textures regenerated.
## IRON SIGNAL, played with Anchor / Pathology / Overwatch / Night Warden
A family removed from the world and not from the paperwork, with something using the
gap; the adversaries were themselves misfiled here generations ago and this is their
last stable ground. Good case. The team lost it.
Approach succeeded, then everything after failed. Contact with the staff opened on a
special and then failed twice, so the log never came across. Resources reached zero at
phase four. The case ends unclosed — the record was not put back — which the generator
already has an answer for: it followed somebody home, and that is next season's case.
**R-221 · the phase-lead fallback could never fire.** `bestIn("tradecraft") ??
bestIn("navigate")` — and every character carries every skill, because
`grantFullSkillList` puts them there, so `skillRating()` returns the printed base
rather than null. `bestIn("tradecraft")` therefore always found somebody and Navigate
was unreachable. A team of postings that train Navigate and not Tradecraft — and ten
of the twenty-five train neither, while eight train only Navigate — was led by
whoever had the highest UNTRAINED Tradecraft, which is the printed 5%. Measured on a
Pathology / Overwatch / Warden / Materials team: **the lead was 5%, and phase three
resolved at 1%.** With TRAINED meaning "above the printed base", the same team leads
at 34%. Fixed.
**And a correction to my own diagnosis.** I first read IRON SIGNAL's collapse as that
bug. It was not. The Anchor Officer trains Tradecraft at 40%, which beats the team's
best Navigate of 34, so the fix changes nothing for this team and the case would have
gone the same way. The real cause is tuning, and it is worth stating plainly:
phase chance across the five-phase ramp (difficult, 0 to −20)
lead 40% 20% → 15% → 10% → 5% → 1%
lead 55% 27% → 22% → 17% → 12% → 7%
lead 70% 35% → 30% → 25% → 20% → 15%
lead 85% 42% → 37% → 32% → 27% → 22%
Tradecraft is a SUPPORT skill on most postings that have it at all, so a realistic
lead is 40–55%, and the back half of every case sits in single figures. Two cases in
a row have now been decided by Resources running out rather than by anything the
players did. This is a design decision rather than a defect — a case is meant to be
survived rather than won — but if phases are supposed to be passable, the ramp
(−5 per phase, on top of `difficult`) is the dial, not `DEFENCE_STEP` and not the
skill ratings.
## The case ramp, retuned
The old ramp made every phase after the first Difficult — which HALVES the lead's
skill — and then stacked a cumulative −5% on top of that. The two pressures were
never the same idea, and they are now separated in `phaseRampFor(index, kindId)` in
`rules.mjs`, with six spot-checks:
- **Difficulty is about the phase.** Only Containment is Difficult — the step the case
is actually about — and it does not also take the ramp. It is hard because it is
containment, not because you are tired.
- **The modifier is attrition.** −5 per phase, capped at −15, because a fifth phase is
not four times worse than a second.
```
was now
lead 40% 40 → 15 → 10 → 5 → 1 40 → 35 → 30 → 20 → 25
lead 55% 55 → 22 → 17 → 12 → 7 55 → 50 → 45 → 27 → 40
lead 70% 70 → 30 → 25 → 20 → 15 70 → 65 → 60 → 35 → 55
```
Simulated over 20,000 full five-phase cases per row, WITH the destitution rule
modelled (a spent track upgrades every remaining phase to Difficult):
```
lead 40% passed 0.72 → 1.37 of 5 hit destitution 87% → 63% of cases
lead 55% passed 1.14 → 2.06 of 5 hit destitution 72% → 40%
lead 70% passed 1.60 → 2.78 of 5 hit destitution 54% → 20%
```
Destitution almost always lands on the final phase rather than compounding, so it
stings rather than spirals — my first description of it as a death spiral was
overstated.
Played two cases on the new numbers. SHORT ORCHARD passed 2 of 5 and ended scraped
clean at Resources 0 but Containment 7. PALE VERGE passed 4 of 5 including two
specials, failed the Containment step, and came home at Containment 9, Resources 4.
Both read as cases rather than as arithmetic.
**R-222 · leading a phase earned no experience.** `resolveLegDialog` called
`skillRoll` with an actor and a rating but **no skill family**, so the choke point
could not resolve the skill item and the tick never fired. This is the same fault the
internal-callers fix addressed — that fix resolves the item FROM the family, and this
caller supplied none — so Tradecraft, the skill the job most uses, was still the one
that could never improve. Fixed and verified: PALE VERGE's lead ticked Tradecraft and
took it 62% → 66% at close.
## SHORT ORCHARD, on the retuned ramp
Containment, Physician, Materials, Pursuit. The case: a person at a farm in Angus is
in two places and both are attending work. The truth is that there is no original
left — both are copies, and one of them knows — and the case is deciding which one
goes into custody. The opposition is another desk of the same agency, tier dangerous,
whose stated leverage is Clearance.
The ramp read as intended: 41% → 36% → 31% → (Containment, Difficult) → and the team
passed the first two comfortably before fumbling the police cordon, which cost two
Containment and a Clearance. That fumble is what brought the rival desk.
They tried the leverage first, which is the right play — but the Containment Officer
fronted it on Status 15% with the Clearance bonus already spent by the fumble, and
failed. Eleven rounds on a farm road: two agents down, two Cleaners down, and the
Materials Officer — the worst shot present at 27% — ended it with a critical to the
head. Four blows were graded during the fight, including a Cleaner's critical cut to
a special by Kester's successful dodge, which still did 9 and a Major Wound. That is
exactly what grading was built to do.
The Physician then patched her own team, in the order she was taught.
**The wound pipeline reaches the case phases**, which is the observation worth
keeping: the last two phases resolved at 11% and 1%, and the chat card says why —
`Wounded: −20%`. The team failed those phases because they had been shot, not because
the ramp is broken. On a healthy team the same phases had been running at 31% and
40%. A GM reading those cards can see the cause, which is the whole point of putting
the modifier on the card.
Case closed at Containment 6, Resources 3, Clearance 5 — spent, but nothing like the
floor the old ramp produced.
**R-222 confirmed across the board.** Every agent came out of this case with ticks —
Dodge, First Aid, Firearm (Pistol), Firearm (Rifle), Navigate — where before the fix
a case produced none at all from phase work.
## Zero hit points now means something
**R-223 · nothing happened at zero.** An agent reduced to 0 hit points was marked down
and then lay there indefinitely. Bleeding deliberately stops at zero, no rule advanced
anything, and the only mention of death in the entire system was flavour text on a
destroyed head location. A game with hit locations, Major Wounds and bleeding had no
way for anyone to die.
Hit points are not taken below zero — every damage path clamps there and unpicking
that would touch everything. Zero starts a clock instead:
above 2 up
2 or below DOWN — out of the fight, still conscious, still yours
0 DYING — unconscious, no actions, no defences, the count begins
count met DEAD
The count is the agent's own **Major Wound threshold**, so the number that already
decides how much damage they take standing up also decides how long they have on the
floor, with a floor of two rounds. It ticks from the same end-of-round hook that
charges blood loss, so nobody has to remember it — which is exactly why bleeding
worked and dying did not exist.
**Stabilising** takes First Aid or Medicine and stops the count. It does not put
anyone back on their feet: still zero, still unconscious, until somebody heals them
above it, which clears the count automatically. A FUMBLED attempt over a dying agent
costs them a round they do not have. `heal()` now refuses the dead, which is the one
place that had to say so because every medical path routes through it.
**The last entry.** A Custodian's death is a filing question. How well filed they were
decides what the record says: at Coherence 7+ a death in service with a form and a
pension; at 4–6 two dates of death on file and Registry asking which is correct; at
1–3 a file that will not close, with correspondence still arriving and being answered;
at 0 they are not recorded as dead at all but filed as transferred, to a section the
department cannot produce an address for.
Four rules, ten spot-checks, on the sheet as a banner in all three states, and
documented in the rulebook. 44 rules, 148 formulas.
## The case generator, simplified and made to say the useful things
**Four phases, not five.** The arc drew TWO interchangeable middle phases, which read
as padding — two seeds in a row produced "The search" then "Inside" with nothing to
tell them apart. One middle phase: Approach, the thing in the way, the job, and out.
**The brief now answers the operational questions first.** It opened on a
classification note and never stated where the site was, how long the team had, what
was likely to be in the way, or how they were getting out — all of which the generator
already knew and threw away. `place` and `duration` were generated and never printed.
The brief now leads with a table of Where / When / Classification / Phases, then the
job, then what is reported, then a full section on the opposition — numbers, tier,
motive, tactics, and what might work instead of shooting — then **How you get back**:
the extraction ground, the Transposition roll, the classification modifier, and the
warning that a bad crossing is the only way an Art is ever acquired.
The same four facts are now on the CASE FILE ITSELF, so a GM does not have to open the
journal to answer "where are we and how long have we got".
Checked across 300 seeds for `[object Object]`, `undefined` and `NaN` in every
generated page: clean. One real leak found and fixed on the way — the group's taboo
and desire are objects with a `.text`, and the brief was printing the object.