# 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 `