Asking for the type of armour per location turned out not to be a display request. Every equipped piece added its points to EVERY location, so a Brodie helmet protected your shins and a cuirass protected your skull — with a catalogue containing sallets, lobster-tail helmets and three cuirasses, that could not be printed without the sheet saying something visibly false. Armour now declares coverage as a named set rather than a list of locations, because a piece has to fit whatever body wears it: all, torso, torsoArms, torsoLimbs, head. The sets expand to location KINDS, so a cuirass covers a baseline torso and a vesh trunk and ridge without either knowing the other exists. Unstated coverage is `all`, so anything that does not declare behaves as everything did before. Eighteen of the twenty-five catalogue pieces are not whole-body and now say so. The trap was double counting: hitLocationRoll subtracted the global worn total PLUS the body's own armour at that location, which was right while armour was global and becomes a double count the moment a location knows its own pieces. It reads the location's total and nothing else, verified by firing fourteen hits of 10 damage across every location and asserting damage applied == 10 - that location's armour. A single ARMOUR number no longer exists, so the header shows the torso with "0-8 by location" beside it when the rest of you is not that. This is a real difficulty change: limbs are frequently bare and limbs are 12 of 20 on the melee table. Coherence moves out of the derived strip into the vitals at hit-point size, with its band under it and the box reddening as it falls. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4472 lines
255 KiB
Markdown
4472 lines
255 KiB
Markdown
# 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.
|
||
|
||
## LOW VERGE — played until it killed somebody
|
||
|
||
Field Lead, Physician, Recording Officer, Translation Officer at a boarding school out
|
||
of term. The brief did its new job: Where, When, the job, what to expect, and the way
|
||
back, all before the prose. Four phases.
|
||
|
||
The rival desk never became a fight — the Field Lead rolled a natural 1 on Status 15%
|
||
and they stood down, which is the leverage the Opposition page names. The danger was
|
||
never them. The Pressure page had already declared the price: *"There is a body that
|
||
has to be carried, and it is one of ours."*
|
||
|
||
Two phases failed, the Containment phase failed, and the other side's weather came
|
||
through a gap at the true scale. Three of the four went to zero in a single exposure
|
||
and the Physician — 48% First Aid, on four hit points herself — stabilised all three,
|
||
one after another, and then did it again after the next wave.
|
||
|
||
Then the extraction failed on a tidal causeway with the window already past. Marek
|
||
Achterberg and Tomas Vance died in the water, both at Coherence 10, both **recorded
|
||
properly — a death in service, a form, a pension, a name that stays where it was put.**
|
||
Nkechi Quist came home stable at zero. Ambaro Vance walked off the causeway on four
|
||
hit points having kept three people alive for six rounds and lost two of them anyway.
|
||
|
||
**R-224 · a body at zero absorbed damage for free.** Found by this play-through and it
|
||
is what killed them. Hit points clamp at zero, so further damage to somebody already
|
||
there changed nothing — and a STABILISED agent absorbed it forever, because the count
|
||
was held and there was nothing else left to move. You could empty a magazine into
|
||
someone bleeding out on the floor and the system would not notice. Damage at zero now
|
||
breaks the stabilisation and costs a round of the count, and `hitLocationRoll` calls
|
||
`died()` directly when that meets the limit. That is precisely how the causeway killed
|
||
two people who were medically stable four minutes earlier.
|
||
|
||
**Worth noting for the Last Entry table:** both died at Coherence 10 and so got the
|
||
dullest of the four entries. Coherence is normally spent on the way home, and these
|
||
two never got there — an agent who dies mid-case is likely to be *well* filed. The
|
||
interesting entries belong to people who die after a bad crossing, which is the right
|
||
way round but means the table's bottom half will be rarer than its top.
|
||
|
||
## IRON MARGIN — nobody died, and that is the more useful result
|
||
|
||
Containment, Overwatch, Access, Stores at a market town with a working parish office.
|
||
The job as briefed: *make an arrangement, file it, and never speak of the terms again.*
|
||
|
||
Nobody drew a weapon in the entire case. The Approach came up SPECIAL, the cordon
|
||
FUMBLED (−2 Containment, −1 Clearance), Containment succeeded, and the gap surfaced —
|
||
the first helpful local had been steering them all day. Then the Stores Officer opened
|
||
on the quiet neighbours: special, fumble, success, standing 0 → 2, and there was an
|
||
arrangement to be had. That IS the win condition on this case.
|
||
|
||
Extraction failed on the causeway, which cost tracks and nothing else. Everyone
|
||
crossed home. **No casualties at all — not a single hit point lost across four
|
||
phases.** Final tracks Containment 7, Resources 4, Clearance 5.
|
||
|
||
The interesting cost was the crossing. Anneke Oyelaran-Vost fumbled Transposition on
|
||
99, took 3 Coherence, and came back with **Witness** at 28% — *"A return that went
|
||
wrong. You come back. You do not come back the same."* Which is the rule working
|
||
exactly as designed: an Art has no other way in.
|
||
|
||
Taken with LOW VERGE, the two play-throughs bracket the system honestly. That case
|
||
killed two agents because a Containment failure put the whole team at zero in lethal
|
||
conditions and the extraction then failed on a tidal causeway. This one closed
|
||
socially, in four phases, and cost one agent three Coherence and a power she did not
|
||
ask for. The death rules are not a meat grinder; they bite when the fiction has
|
||
already gone badly, which is where they should.
|
||
|
||
## The extraction phase, tuned
|
||
|
||
Two play-throughs in a row were decided at the extraction, so I went looking. What I
|
||
found first was a correction to my own play: **a FAILED extraction has always read
|
||
"Phase complete, but at an extra Resources and half again the time."** The team gets
|
||
out. Only a fumble fails to complete. In LOW VERGE I treated a failure as *stranded on
|
||
the causeway* and invented a tide that killed two agents. That was my adjudication,
|
||
not the system, and I was wrong about the rule I was adjudicating.
|
||
|
||
But the reason I improvised is a real defect.
|
||
|
||
**R-225 · the window clock was generated and read by nothing.** Every case carries a
|
||
clock, and one of the five is *"The window closes"*, whose stated cost is **the way
|
||
home** and whose three steps are "the margin narrows", "the return is Difficult", "the
|
||
return is aimed at −30%". No code has ever touched it. So the phase whose entire job
|
||
is *getting out with it* had no consequence of its own, and whoever ran it had to
|
||
invent one — and improvising at that moment is exactly how a phase that should cost
|
||
you the trip home ends up costing lives.
|
||
|
||
The extraction now spends that clock instead of hurting anybody:
|
||
|
||
critical out clean, and the window opens back up (clock −1)
|
||
special out clean (clock 0)
|
||
success out (clock 0)
|
||
failure out, but not cleanly (clock +1)
|
||
fumble you do not get out this attempt (clock +1)
|
||
|
||
0 steps → 0%
|
||
1 step → −10% on every Transposition
|
||
2 steps → −20%, and the return is Difficult
|
||
3 steps → −30%, and the return is Difficult
|
||
|
||
`returnCrossing()` reads it alongside the classification modifier and the Coherence
|
||
band penalty it already applied, so the cost lands where this setting says the cost of
|
||
a case belongs — on the crossing home — and the players can see it coming, printed on
|
||
the phase card the moment it happens.
|
||
|
||
Three rules, ten spot-checks. 47 rules, 158 formulas.
|
||
|
||
**Also fixed while in there:** the leg carried no `kind`, so the extraction check
|
||
matched on the terrain PROSE and worked only because the string happens to begin
|
||
"Extraction —". Legs now store their kind, with the prose match kept as a fallback for
|
||
case files generated before today.
|
||
|
||
Verified in play on PLAIN HARBOUR: extraction failed at 47%, the card read *"You are
|
||
out, but not cleanly. The window has narrowed... The way home: −10% on every
|
||
Transposition roll"*, the clock went to 1, and all four crossings took −10%. Nobody
|
||
lost a hit point.
|
||
|
||
## A briefing, and a millennium of ironmongery
|
||
|
||
**The players now get a briefing, not a memo.** The generator was already writing the
|
||
timeline, the cast and two lines of texture that are the best prose in the whole
|
||
document — and filing every word of it behind the GM screen. A new PLAYERS page:
|
||
|
||
- a read-aloud opening built from the classification, the place and the window;
|
||
- **what it is like there** — the texture, with the note that none of it is in the
|
||
file, because the file was written by somebody who was not there;
|
||
- **what is known so far** — the timeline as a table;
|
||
- **who is there** — the cast by name, role, and what they want from you, but never
|
||
what they lie about, which stays on the GM page where it belongs;
|
||
- and a short "before you go" that tells a new player the three things this game
|
||
expects of them: sign for your kit, bring somebody who can make an arrangement,
|
||
keep your anchor on you.
|
||
|
||
Ownership is now **declared by the page** rather than guessed from its index — the
|
||
old `i === 0 ? 2 : 0` would have silently hidden any second player page. Two player
|
||
pages, four GM pages, 300 seeds checked clean.
|
||
|
||
Two bugs in my own prose, caught by reading the output: `classification.texture` is a
|
||
LIST and was being printed comma-joined into a sentence, and the "since then..."
|
||
line was reaching for the last timeline entry, which is *the team crosses* — so every
|
||
briefing ended "since then it has not stopped, and the team crosses."
|
||
|
||
**The historical range.** The department has been running since before it had a name
|
||
and has never thrown anything away.
|
||
|
||
- **38 historical weapons** (from 6): arming sword, longsword, falchion, war hammer,
|
||
mace, military flail, poleaxe, halberd, billhook, pike, quarterstaff, dirk, longbow,
|
||
arbalest, sling, javelin, rapier, main gauche, matchlock, wheellock, blunderbuss,
|
||
boarding pike, cavalry sabre, smallsword, cutlass, Brown Bess, Baker rifle, socket
|
||
bayonet, Martini-Henry, Webley, Lee-Enfield, trench club — each with its period
|
||
stated, because a GM handing out a matchlock should know it is a matchlock.
|
||
- **14 historical armours** (from 2): gambeson, maille shirt, coat of plates, cuirass,
|
||
full harness, buff coat, sallet and bevor, lobster-tail helmet, jack of plate,
|
||
cuirassier armour, Brodie helmet, flak jacket. Weight is the point — full harness is
|
||
9 AP and 25 kg, and the free-carry rule does the rest.
|
||
- **Five new skills** to hang it on: Melee Weapon (Polearm), Melee Weapon (Flail),
|
||
Missile Weapon (Sling), Missile Weapon (Thrown), and **Firearm (Muzzle-loader)** —
|
||
because a muzzle-loader is not a rifle and loading it is the skill.
|
||
- **275 icons**, all generated; every weapon maps to a real skill and every item has
|
||
art, both checked.
|
||
|
||
Catalogue is now **206 items**: 118 gear, 62 weapons, 25 armour, 14 vehicles — 145
|
||
modern, 77 antique, 34 future, 19 anomalous.
|
||
|
||
## GLASS LANTERN — the historical gear in play
|
||
|
||
A village with two war memorials, Hollow class, salvage era **antique**. Antiquities,
|
||
Containment, Rigging, Physician.
|
||
|
||
Salvage from a Hollow world did exactly what the era tag promises: a **poleaxe, a
|
||
chirurgeon's roll, a lodestone and a pocket watch**. They armed themselves out of the
|
||
village — poleaxe to the rigger, a Brown Bess and a maille shirt to the Antiquities
|
||
Officer, a halberd to Containment, a sling to the Physician.
|
||
|
||
Three things the play-through proved, all of them the right behaviour:
|
||
|
||
**Salvaged historical weapons are not automatically usable.** The poleaxe, halberd and
|
||
sling all came up at **5%**, because Polearm and Sling are skills nobody on the team
|
||
has. The Rigging Officer knows her boarding axe at 68% and a poleaxe at 5% — the same
|
||
shape of weapon, a different skill, and she sensibly kept the axe. Salvage is a
|
||
promise of usefulness, not a free upgrade.
|
||
|
||
**Historical armour makes a knife pointless.** The Antiquities Officer in maille over
|
||
brigandine came out at AR 8, and the village hit her four times with utility knives
|
||
for **0, 0, 0 and 0 damage**. 1d4+2 against eight points of armour is nothing, and
|
||
that is exactly the historical fact the numbers should produce. She was also
|
||
**overloaded at 28 kg against a 20 kg free carry**, which is the other half of the
|
||
same fact.
|
||
|
||
**Muzzle-loaders behave.** Flintlock and Brown Bess both hold one shot with a
|
||
two-round reload. She fired the flintlock in round one, and thereafter fought with the
|
||
duelling sword rather than spend two rounds reloading in a melee — which is the
|
||
decision the rule is supposed to produce.
|
||
|
||
The fight ran seven rounds. The village lost one dead and two dying; the team took
|
||
almost nothing, because they were wearing four hundred years of armour development and
|
||
the village had knives.
|
||
|
||
**R-226 · "Tradecraft first" was too literal.** My own R-221 fix overshot. A
|
||
Containment Officer whose Tradecraft had caught a stray bonus point sat at 12% against
|
||
a printed base of 5 — technically trained — and led every phase ahead of a Rigging
|
||
Officer on **53% Navigate**. The preference is meant to mean "whoever reads a
|
||
developing situation best", not "anybody with a point in the right column". Tradecraft
|
||
still wins ties and wins when it is genuinely better; it no longer wins when it is
|
||
plainly worse. The Containment phase went from 6% to 26% on the same team.
|
||
|
||
## An overnight build: hazards, automatic fire, two weapons, a bestiary, and space
|
||
|
||
### Stress test first
|
||
150 characters — every one of the 25 postings, at Probationary and Case Officer, in
|
||
all three species — checked against fourteen invariants each. Nothing broken. Then the
|
||
edges: characteristics at 1, 3 and 21; every location destroyed at once; twelve pikes
|
||
carried; hit points set to −99. All degraded gracefully except one.
|
||
|
||
**R-227 · a destroyed head was a nap.** The printed effect has always read "For
|
||
anything that keeps its brain there, this is death" and the code set `unconscious` and
|
||
stopped. An agent with every location on their body destroyed, head included, reported
|
||
as DOWN. `destructionOutcomeFor()` now says what destruction means: a head is death, a
|
||
vital or sensory location drops you to zero, a limb is ruinous and survivable.
|
||
|
||
### The ways the world kills you
|
||
- **Falling** — a die per 3 m, random location, **armour does not apply** because the
|
||
ground is not a weapon. Soft landing halves the distance; a controlled descent
|
||
halves it again. Verified: 12 m killed an agent outright; the same fall controlled
|
||
left them on 2 hit points.
|
||
- **Drowning** — CON rounds with warning, half without, then a point a round that does
|
||
not stop. Runs straight past the Major Wound threshold to zero and into the dying
|
||
count. Verified over 24 rounds.
|
||
- **Fire** — a condition with a size, not a hit. Worn armour helps on the round you
|
||
CATCH and never after, because by then the fire is inside it.
|
||
- **Explosives** — one roll at the centre, shared by band, and **cover beats armour**:
|
||
armour counts at contact only and only half. Verified on four people at 1 m, 4 m,
|
||
10 m and 40 m: 17, 3, 0, and outside it entirely.
|
||
|
||
### Automatic fire, and two weapons
|
||
One roll for a burst, and the **success level** says how much of it arrived — the same
|
||
band machinery as everything else rather than four rolls and a stalled table. **The
|
||
rounds are spent either way.** Verified: a failed full-auto burst spent ten rounds for
|
||
nothing, which is the honest part of the rule.
|
||
|
||
Two weapons costs −20/−40 untrained and 0/−20 with the new **Ambidextrous** talent.
|
||
Nothing removes the off-hand penalty, and the off hand takes the lighter weapon at no
|
||
more than 2 kg — the rule that refuses two poleaxes, tested and refused.
|
||
|
||
### The bestiary
|
||
Twenty-one entries in three families, each with characteristics, natural armour, a
|
||
tactics line and a POWER written as a rule with a cost rather than a special ability.
|
||
FOLKLORE: barghest, kelpie, redcap, boggart, church grim. HORROR: hollow man, the
|
||
tenant, the choir, the carrion file, the understudy. FORWARD: surveyor, quarantine
|
||
unit, cuckoo array, long walker, the auditor. Every skill and weapon reference checked
|
||
against the catalogue.
|
||
|
||
### Space, and everybody in it
|
||
Eight STATIONS, each using a skill somebody already has, so the Physician and the
|
||
Translation Officer have a chair. Range is a band; the helm moves one a round, in or
|
||
out. **Heat is the pressure** and it is the whole design.
|
||
|
||
Two rounds of tuning, both from playtest and both necessary:
|
||
- First cut had every station heating and one engineer venting. Heat reached the
|
||
ceiling by round four and locked the helm — a countdown, not a decision. Now only
|
||
Gunnery is charged for trying (the shot left either way, exactly as the burst rule
|
||
reasons), looking is free, and venting sheds 3.
|
||
- Second cut was too cold — heat sat at 0 and nobody cared. Settled between.
|
||
- **The helm only ever closed.** Every fight ended docked with no way to break off
|
||
short of the anchor. The helm now chooses in or out.
|
||
|
||
Final playtest: heat 2 → 5 → 3 → 5 → 7 → 10, and at the ceiling the helm locked out
|
||
because the crew kept shooting instead of cooling. That is the decision the subsystem
|
||
is for.
|
||
|
||
64 rules, 198 spot-checked formulas, a 12-page rulebook in reading order.
|
||
|
||
### The Codex pass, and what it cost me
|
||
|
||
Put the night's five subsystems to Codex with the instruction to be blunt. It found
|
||
three things that were genuinely wrong and I have fixed all three.
|
||
|
||
**R-228 · Ambidextrous was a compulsory build.** At 0/−20 the talent gave a
|
||
full-strength main hand AND a cheap second attack — near-free action economy, and
|
||
therefore the only sane pick for anybody who could hold two things. The 2 kg limit
|
||
stops poleaxes, not optimisation with a pistol and a knife. Ambidextrous now buys
|
||
CONSISTENCY rather than a free attack: **−20 in both hands**, against −20/−40
|
||
untrained. Still plainly worth having; no longer mandatory.
|
||
|
||
**R-229 · full auto was a firing squad.** Ten independent hits off one critical, each
|
||
with its own damage roll, hit location and Major Wound check. Against a hard target it
|
||
was the correct answer to everything. Hits are now **capped at 3/5/7** rather than
|
||
proportional to the burst, and however many rounds connect, **a burst is one wounding
|
||
event** and rolls the Major Wound table at most once.
|
||
|
||
**R-230 · Engines deleted the pressure it was supposed to create.** Guns +2 and Helm
|
||
+1 against a vent of 3 made a standard fighting round heat-NEUTRAL — the craft cooled
|
||
while it fought, and Heat only existed when the engineer failed or was absent. This
|
||
is the same fault as my earlier over-correction, in the other direction, and my own
|
||
second playtest had shown it sitting at 0 for four rounds without my drawing the
|
||
conclusion. Venting is now 2/3/4, so a shooting round costs +1 net.
|
||
|
||
Two more taken as written:
|
||
- **Falling halving stacked.** Soft ground and a controlled descent both halved, so
|
||
together they quartered — the same mitigation counted twice. They no longer stack.
|
||
- **Blast ordering was undefined.** Flooring at each step meant the answer depended on
|
||
the order the functions were called in. One formula now, floored once: rolled x
|
||
range share x cover, rounded down, and only then armour.
|
||
|
||
**And the sharpest observation, which I acted on structurally:** "giving everyone a
|
||
roll is not the same as giving everyone a decision." Sensors, Damage Control and
|
||
Medical were seats that rolled dice while the gunner decided the fight. Each now has
|
||
mutually exclusive modes — Sensors marks, spoofs or reads; Damage Control patches or
|
||
BRACES so the next hit lands at half; Medical restores a crewman or STEADIES the ship
|
||
so the heat penalty does not apply that round. Bracing and steadying both bite in the
|
||
engine, not just on the card.
|
||
|
||
Not taken tonight, recorded as owed: station fumble tables, Signature as a live
|
||
mechanic, mark duration and stacking, hull and system criticals, extinguishing fire,
|
||
rescue from drowning, and an explicit interaction table for hazards against armour,
|
||
Major Wounds, bleeding, zero and dying. Codex is right that these are implied; they
|
||
are a session's work, not a paragraph's.
|
||
|
||
## SALT COUNTY — the bestiary in play
|
||
|
||
A county records office over a bank, and a member of the public behaving as somebody
|
||
who died eleven days ago. Field Lead, Physician, Night Warden, Antiquities Officer.
|
||
|
||
The generator set this up better than I could have. The brief names a **revenant** —
|
||
which is in the bestiary already — and the truth page says the cause is *elsewhere in
|
||
the same location and has not been noticed*. In a records office, that is the
|
||
**carrion file**.
|
||
|
||
**The revenant was resolved without a shot.** Its printed leverage is "tell it where
|
||
that person is, and it will go, and that becomes somebody's problem" — the Antiquities
|
||
Officer made the Knowledge roll off the parish register and it left. That is a
|
||
statblock's tactics line doing the work a fight would otherwise have done.
|
||
|
||
**Then the gap surfaced, and the file did what it is for.** AMENDMENT is written as a
|
||
rule with no saving roll, and that is deliberate: it removed the Antiquities Officer's
|
||
Language (Trade Cant) at 63%, the Night Warden's at 79%, and the Field Lead's Dead
|
||
Reckoning talent — permanently, with no roll offered and nothing to roll against. The
|
||
horror of that entry is entirely in the absence of a defence, and it reads correctly
|
||
at the table.
|
||
|
||
**Burning it exercised the new fire rules on a thing rather than a person.** Armour 6
|
||
counted on the round it caught and never again: 3, then 6, then 11, and the file was
|
||
at zero in three rounds. The team were standing in the same burning archive and took
|
||
the "clothes alight" band — the Antiquities Officer's armour 4 absorbed it completely,
|
||
the Night Warden's 2 did not.
|
||
|
||
The case closed at Containment 8, Resources 1, Clearance 5. A failed extraction
|
||
advanced the window clock to 1, and every crossing home took the resulting −10%, which
|
||
is the retuned extraction rule working end to end.
|
||
|
||
**One honest note on my own reporting:** a probe line read "Dead Reckoning: gone" for
|
||
all four agents. Only the Field Lead ever had it — the other three had never been
|
||
awarded it, and the probe could not tell the difference between a talent removed and a
|
||
talent never held. The amendments actually made were three, not four.
|
||
|
||
## Fire modes, fitted kit, and a rail instead of a strip
|
||
|
||
Four requests, and the first of them turned over the usual stone.
|
||
|
||
**R-231. Every weapon has carried a fire mode since the packs were first built, and
|
||
nothing has ever read it.** A musket could rip a ten-round burst; "rapid" bought a
|
||
weapon precisely nothing. `FIRE_MODE` now gates the burst rules and carries its own
|
||
attack modifier, so the mode is a decision rather than a label: **slow** +10% and then
|
||
you work for the next one, **single** the baseline, **fast** a second shot at −20% to
|
||
*both*, **rapid** bursts, **beam** +5% and it can be held. The gate is checked inside
|
||
`burstAttack` rather than only in the dialog, so no macro or harness can route round it.
|
||
|
||
**R-232. `fast` was not a fire mode at all.** Five weapons carried it — longbow, sling,
|
||
javelin, the Webley and the Lee-Enfield — and the system's list of modes did not
|
||
include it, so the item sheet's dropdown had no matching option and re-saving one of
|
||
those weapons silently blanked its mode. The list was hand-written next to the rule
|
||
instead of derived from it. It is derived now, and the guard that should have caught
|
||
this was the one I had to fix in R-236.
|
||
|
||
**R-233. `burstAttack` was reachable from nothing.** The automatic-fire rules shipped
|
||
complete and could not be used from any sheet. It is now the resolution path the fire
|
||
selector chooses, and it takes the dialog's whole modifier stack rather than rebuilding
|
||
its own — the first version charged the player twice for the same burst.
|
||
|
||
**R-234. Any weapon with a Coherence cost could not be attacked with at all.** The
|
||
handler read the weapon, awaited the Coherence charge, and then read the weapon
|
||
*again* — but `currentTarget` is only set while an event is being dispatched, so the
|
||
second read returned null and the attack died on a TypeError with nothing shown to the
|
||
player. The Redaction lance, the Unmaker and every piece of transposed materiel were
|
||
unusable, and the failure was completely silent. Resolve once, before anything is
|
||
awaited.
|
||
|
||
**R-235. The damage card was the last one wearing Foundry's stock dice styling.** It
|
||
posted a bare Roll and let the core template dress it — grey boxes on a white panel in
|
||
the middle of a navy sheet. It renders through the module's own card now, with the Roll
|
||
still riding on the message so the dice stay inspectable and Dice So Nice still
|
||
animates.
|
||
|
||
**R-236. The duplicate-rule guard reported an honest table as a copy.** `FIRE_MODE` has
|
||
`slow` and `fast`; so does the entirely unrelated `speedBands` vocabulary, and two
|
||
shared key names was enough to fail the build. Every real instance this guard has ever
|
||
caught duplicated the *whole* table, so it now wants a majority of the keys — and
|
||
prints partial overlaps rather than swallowing them.
|
||
|
||
**Sights and lamps.** A sight is not a flat bonus, which is why most systems end up
|
||
with everybody carrying the same scope. It is graded by range band, so **a telescopic
|
||
sight is a −10% liability inside ten metres** and worth +20% at extreme, and it only
|
||
counts if it *fits* — the scope and the thermal sight are rifle furniture. Because the
|
||
band is already known by the time the modifier is wanted, the sight applies itself:
|
||
there is nothing to select and nothing to forget. The dark now costs something, a
|
||
carried lamp buys it back to dim and never to daylight, and the card says plainly that
|
||
the lamp shows them exactly where you are.
|
||
|
||
**R-237. "Automatic" that needs a re-import is not automatic.** Agents created before
|
||
those fields existed carried a Field lamp whose `lightSource` was the template default
|
||
of false — the lamp sat in the pack doing nothing. A migration stamps fitted gear from
|
||
the compendium on load, matched on `flags.ringbrp.key` rather than on display name,
|
||
because the fuzzy-name match this project used before once mapped a climbing kit to
|
||
breaching charges.
|
||
|
||
**My own error, caught by playing it.** I first costed a sustained beam as
|
||
`multiplier × energyPerShot`, which made a locked beam cost 60 charge from a 20-charge
|
||
cell — unfirable, and the failure was a warning the player would read as a bug. The
|
||
magazine is spent one per shot everywhere else in the system, so the cost is in shots:
|
||
a locked beam burns six. Worth noting that `energyPerShot` itself is written by the
|
||
pack builder and **read by nothing** — the same defect again, left alone here because
|
||
wiring it would change every energy weapon's economy.
|
||
|
||
**The tabs.** Eight of them across the top of an 820px sheet compressed to 11px type
|
||
and were genuinely hard to read. They are a rail down the side now: one line each,
|
||
larger type, a solid block for the active tab, and counts as badges. The sheet is 152px
|
||
wider to pay for the rail so nothing else got tighter, and the skills list is driven by
|
||
the space actually available rather than by a window-width breakpoint that was
|
||
measuring the wrong box.
|
||
|
||
Also noted, not fixed: fourteen rules are imported into `ringbrp.mjs` and read nowhere
|
||
in it. They are live rules with real readers in the rulebook generator and the guards,
|
||
so this is dead *imports* rather than dead rules — but it is the same smell.
|
||
|
||
## THE GOODS YARD — the four fire modes, side by side
|
||
|
||
One agent per mode, all four trained to exactly 60%, so the fight reads the mode and
|
||
not the shooter. A barghest in a coal yard at 02:40, pitch dark, one lamp between
|
||
four of them. Four rounds. The dog left at 4 hit points of 16 and nobody caught it.
|
||
|
||
**The modes separated cleanly, and not in the way I expected.**
|
||
|
||
**Slow was the best weapon in the fight.** Dunn's Baker rifle hit on both the shots it
|
||
got, and the +10% is what bought them: round one at 35% he rolled 33, and at the 25%
|
||
he would have had without it that is a miss. The reload is a real cost — he was out of
|
||
the fight for rounds two and three and the dog reached him — but a mode that turns a
|
||
33 into a hit twice is not the weak option.
|
||
|
||
**Fast is not two chances to hit. It is a second defence at a penalty.** Rakhi's two
|
||
Webley shots in round two both landed at 20%, and the first was dodged clean. The
|
||
second one hurt precisely because the barghest had already spent its free defence and
|
||
had to dodge again at −30%. That is the mode's actual function and it is better than
|
||
the tooltip suggests.
|
||
|
||
**Rapid was the worst thing in the yard.** Ceyla fired 20 of 30 rounds across four
|
||
rounds of combat and hit nothing at all. A full magazine at −30%, in the dark at −30%,
|
||
is a 1% roll that still spends ten rounds — and the rules charge her honestly for
|
||
every one. This is not obviously wrong; automatic fire into a dark coal yard at
|
||
seventy metres *should* be futile. But it means **the burst options are close to
|
||
unselectable in any fight where the light is bad**, and the light is bad in most of
|
||
this game. Flagged rather than tuned: I would rather see it in a second fight before
|
||
touching the numbers.
|
||
|
||
**Beam did the damage.** The locked beam landed for 10 — 1d4+6 with its 2d6 riding on
|
||
the standing blow exactly as designed — and took the barghest from 11 to 4 through
|
||
armour 3. It cost 6 charge a shot and, because the lance is transposed materiel, **1
|
||
Coherence every time Tomas pulled the trigger**. He ended the fight Loose at 6, with 2
|
||
charge left. That is the most interesting economy in the fight and nothing needed
|
||
changing to produce it.
|
||
|
||
**The sight graded itself the way it was meant to.** Ceyla's telescopic sight was +5%
|
||
at seventy metres and **−10% once the thing was inside twenty-five**, and she felt it
|
||
both times. Nobody had to remember it.
|
||
|
||
**The lamp is the finding.** Tomas's Field lamp turned pitch dark into dim for him — a
|
||
40-point swing, the largest single modifier in the fight — and the card told him each
|
||
time that it showed the barghest exactly where he was. Which it did: the dog went for
|
||
the lamp in round two. But **a lamp only lights its carrier**. Three agents stood
|
||
inside the same pool of light at −50% while the fourth shot at −10%. That is wrong at
|
||
the table, and it is the one design change this fight actually argues for.
|
||
|
||
### Defects the fight surfaced
|
||
|
||
**R-238. A fast action fired twice and paid for one round.** The Webley put two shots
|
||
out and the cylinder went down by one. Ammunition was charged once before the shot
|
||
loop instead of once per shot — the exact dishonesty the burst rules were written to
|
||
avoid, and the mode that ought to cost the most was the only one getting a free shot.
|
||
Charged per shot now, and the loop stops when the weapon runs dry mid-action.
|
||
|
||
**R-239. `morale.downThreshold` is written by the pack builder and the template and
|
||
read by nothing.** Every NPC in the game carries a morale field and there is no morale
|
||
rule. Whether the barghest broke off was mine to decide with no support from the
|
||
system, which is fine as a GM call and not fine as a shipped field. Not fixed — it
|
||
needs a rule, not a patch.
|
||
|
||
**Two of my own, worth recording because the pattern keeps repeating.** I did
|
||
arithmetic on `system.coherence` (an object) instead of `.value` and reported a
|
||
Coherence loss that never happened; and I read `system.hitPoints`, which does not
|
||
exist — the field is `system.hp`. Both were caught inside a minute by printing the
|
||
result instead of trusting it, which is the only reason this log is accurate.
|
||
|
||
**A content wart:** the barghest bites with something called "Vesh grasp limb" — the
|
||
generic `grasp` weapon still carrying a Ringworld-era display name. Every creature in
|
||
the bestiary that uses `grasp` inherits it.
|
||
|
||
## R-240. A lamp lights the people standing in it
|
||
|
||
The goods yard fight made the case: three agents inside the same pool of light shot at
|
||
−50% while the fourth, four metres away, shot at −10%. Nobody would run that at a
|
||
table, and the rule was wrong rather than merely coarse.
|
||
|
||
A lamp now has a **reach**, and it is per item rather than global, because a weapon
|
||
lamp and a vehicle searchlight are not the same offer — 6 m, 10 m for the issue field
|
||
lamp, 4 m for cold light strips, 30 m for a roof searchlight. Anyone inside the reach
|
||
is in the light, and the card names whose lamp it is: *"MODE: Tomas's, 4 m away — you
|
||
are in it."* Both halves of the rule travel together, so the pool is one position and
|
||
the whole group is standing in it.
|
||
|
||
Measured from the tokens, because the canvas already knows who is standing where. The
|
||
same shot that started this — Rakhi, no lamp of her own, four metres from Tomas's, in
|
||
pitch dark — went from **20% to 60%**, and Ceyla twenty-five metres out still ate the
|
||
full −50%.
|
||
|
||
**The interesting part was the unknown.** `Number(null)` is 0, so an unmeasured
|
||
distance read as *standing on top of the lamp* and would have lit any scene with no
|
||
tokens placed. My own spot-check caught it before it ever ran: an unknown distance is
|
||
now explicitly not lit, and the dialog carries a tick that arrives pre-set when the
|
||
canvas could measure it and is the GM's answer when there is nothing to measure. That
|
||
keeps it automatic where it can be and honest where it cannot, rather than quietly
|
||
lighting the world.
|
||
|
||
Note for deployment: `template.json` is read when Foundry loads the system, so the new
|
||
`lightReachM` field did not exist on items until the server was restarted — the
|
||
migration then stamped it. A remote server needs the restart, not just a pull.
|
||
|
||
## The combat tab, reordered — and R-241, a weapon nobody could ready
|
||
|
||
**R-241. There was no way to ready a weapon.** The strip under the characteristics
|
||
reads `equipped`, and the only `toggle-equipped` control on the whole character sheet
|
||
was on the *armour* list. A weapon could only be readied by opening its item sheet and
|
||
finding the tickbox there — which is why the strip on most sheets showed whatever the
|
||
pack builder happened to have equipped and nothing else. The weapons table now carries
|
||
the same toggle, the row is tinted when it is readied, and a line above the table says
|
||
plainly what the control does.
|
||
|
||
**R-242. The off state did not look off.** `.ringbrp-sheet a` is more specific than a
|
||
bare `.rb-equip`, so an unequipped toggle was painted white and read as lit. That was
|
||
true of the armour toggle too, and had been since the toggle was added.
|
||
|
||
**The tab now runs in the order it is used**: weapons, armour, defend, reaction, and
|
||
the body last. Weapons lead with 40px art and a display-face name, because they are
|
||
what the tab is for and they were previously below two screens of anatomy and reaction
|
||
arithmetic.
|
||
|
||
**The figure is a partial now.** It is drawn in three places — the header thumbnail,
|
||
the combat tab and the anatomy tab — and it was previously twenty lines of inline
|
||
Handlebars that existed once. Three copies of that would have been the template-layer
|
||
version of the defect `check-rules` exists to prevent: a location present in one
|
||
drawing and missing from another. One partial, registered at init, three call sites.
|
||
|
||
The header now carries a **34px figure and every location with its damage and the
|
||
armour over it**, so the commonest question at the table — how much armour is on the
|
||
part that just got hit — is answered on the main page without changing tab.
|
||
|
||
Noted while working: the mini figure inherited `.rb-body-svg { max-width: 180px }`
|
||
from further down the stylesheet and blew the header out to 385px tall. Same-specificity
|
||
selectors, later one wins; the compound `.rb-body-svg.rb-body-mini` fixes it. Worth
|
||
remembering that this stylesheet is ordered by section rather than by specificity.
|
||
|
||
## R-243. A helmet was protecting your legs
|
||
|
||
Asking for "armour and the type of armour" on each location turned out not to be a
|
||
display request. Every equipped piece added its points to **every** location, so a
|
||
Brodie helmet protected your shins and a cuirass protected your skull. With a
|
||
catalogue that contains sallets, lobster-tail helmets and three separate cuirasses
|
||
that is not a simplification, it is wrong — and it could not be printed on the sheet
|
||
without the sheet saying something visibly false.
|
||
|
||
Armour now declares **coverage**, as a named set rather than a list of locations,
|
||
because a piece has to fit whatever body is wearing it: `all`, `torso`, `torsoArms`,
|
||
`torsoLimbs`, `head`. The sets expand to location *kinds*, so a cuirass covers the
|
||
torso of a baseline agent and the trunk and ridge of a vesh without either the item or
|
||
the wearer knowing the other exists. Unstated coverage is `all`, so anything that does
|
||
not declare behaves exactly as everything did before.
|
||
|
||
Eighteen of the twenty-five catalogue pieces are not whole-body and now say so.
|
||
|
||
**The trap was double counting.** `hitLocationRoll` subtracted `sys.armourPoints +
|
||
loc.armour` — the global worn total plus the body's own armour at that location.
|
||
That was correct while armour was global and becomes a double count the moment the
|
||
location knows its own pieces. It now reads the location's total and nothing else,
|
||
checked by firing fourteen hits of 10 damage at every location and asserting the
|
||
damage applied equals `10 − that location's armour` each time. All fourteen matched.
|
||
|
||
**A single ARMOUR number no longer exists**, so the header shows the torso — which is
|
||
what the question means, and where most hits land — with `0–8 by location` beside it
|
||
when the rest of you is not that. Worked example from the verification: stab vest,
|
||
cuirass and a Brodie helmet gives 8 on the torso, 3 on the head and **0 on both arms
|
||
and both legs**, where before it was 11 everywhere.
|
||
|
||
**This is a real difficulty change and it is meant to be.** Agents in partial armour
|
||
are markedly more fragile than they were an hour ago — limbs are frequently bare, and
|
||
limbs are 12 of 20 on the melee table. Anyone who wants the old behaviour sets every
|
||
piece to `all` on its item sheet.
|
||
|
||
## Coherence, promoted
|
||
|
||
Moved out of the derived strip and into the vitals at the top right, at the same size
|
||
as hit points, with its band under it and the box itself changing colour as it falls —
|
||
yellow at Adrift, red at Unmoored and Displaced. It is the number that measures whether
|
||
an agent is still filed as themselves, and it was set in 11px grey between MOV and
|
||
carried weight.
|