/** * THE RULES, generated from the rules themselves. * * Every number in this journal is computed from rules.mjs at build time and every * name is read from lang/en.json, so the book cannot say one thing while the engine * does another. That failure has happened five times in this project already; a * rulebook written by hand would have been the sixth. * * Written to be learned in order and abandoned early: pages 1 to 3 are the whole * playable game, 4 to 6 are what happens when it goes wrong, and the rest is * reference you look up rather than read. */ import { readFile } from "node:fs/promises"; import { freeCarryFor, hitPointsFor, majorWoundFor, damageModifierFor, locationMaxHp, reflexRating, awarenessRating, reactionBaseFrom, reactionLoadFrom, reactionPrintedFrom, encumbranceFrom, woundEffectsFor, bleedRateFor, defencePenaltyFor, defenceOutcomeFor, ARTS, powerCostFor, SCOPE_TIERS, scopeStanceFor, scrutinyFor, authorityOutcomeFor, dossierTrustFor, widenScopeCostFor, DOSSIER, MERGE_STAGES, skillBaseFrom, encumbrancePenaltyFor, ENCUMBERED_CATEGORIES, DYING, dyingLimitFor, LAST_ENTRY, powerStartingRating, brawlFallbackFor, bleedStoppedBy, crossingArtPoolFor, crossingGrantsArt, crossingCostFor, REACTION, BLEED, CIRCUMSTANCE, RANGE_LADDER, rangeBandFrom, MAJOR_WOUND_TABLE, COHERENCE_BANDS, COHERENCE_COSTS, COHERENCE_RECOVERY } from "../rules.mjs"; const LANG = JSON.parse(await readFile(new URL("../lang/en.json", import.meta.url), "utf8")); const L = key => LANG[key] ?? key; // ROLES lives in ringbrp.mjs, which cannot be imported outside Foundry — it reaches // for globals on load. Read the literal out of the source, the way check-kits does, // so the postings page is generated from the same table the game uses. const ROLES = eval("(" + (await readFile(new URL("../ringbrp.mjs", import.meta.url), "utf8")) .match(/export const ROLES = (\{[\s\S]*?\n\});/)[1] + ")"); const pc = n => `${n > 0 ? "+" : ""}${n}%`; /* ---------- small builders so the tables below stay readable ---------- */ const table = (head, rows) => `${head.map(h => ``).join("")}` + rows.map(r => `${r.map((c, i) => `${c}`).join("")}`).join("") + `
${h}
`; /* ---------- derived figures, computed not typed ---------- */ const D = REACTION.die; const band = 2; // two agents one lane apart, for the odds quoted below const oddsOneLane = (() => { let w = 0, t = 0, n = D * D; for (let a = 1; a <= D; a++) for (let b = 1; b <= D; b++) { const x = REACTION.laneMultiplier + a; if (x > b) w++; else if (x === b) t++; } return { win: (100 * w / n).toFixed(0), tie: (100 * t / n).toFixed(0) }; })(); const damageLadder = [6, 12, 20, 28, 36, 45, 55, 65, 75] .map(t => [`${t}`, damageModifierFor(t)]); const woundRows = MAJOR_WOUND_TABLE.map(r => { const eff = woundEffectsFor([{ key: r.key }]); const bits = []; if (eff.all) bits.push(`${pc(eff.all)} to everything`); if (eff.manipulation) bits.push(`${pc(eff.manipulation)} hands, melee and ranged`); if (eff.physical) bits.push(`${pc(eff.physical)} physical`); if (eff.perception) bits.push(`${pc(eff.perception)} perception`); if (eff.communication) bits.push(`${pc(eff.communication)} communication`); if (eff.movFactor !== 1 && eff.movFactor > 0) bits.push(`MOV x${eff.movFactor}`); if (eff.bleed) bits.push(`${eff.bleed} hp every round`); if (eff.lostAction) bits.push("lose your next action"); if (eff.unconscious) bits.push("unconscious"); else if (eff.defenceOnly) bits.push("out of the fight, MOV 1"); return [`${r.min}`, L(`RINGBRP.Wound.${r.key}.title`), bits.join(" · ") || "—"]; }); const cohRows = COHERENCE_BANDS.map((b, i) => { const hi = i === 0 ? 10 : COHERENCE_BANDS[i - 1].min - 1; const rng = b.min === hi ? `${b.min}` : `${hi}–${b.min}`; const eff = [b.all ? `${pc(b.all)} everything` : "", b.comm ? `${pc(b.comm)} communication` : ""] .filter(Boolean).join(", ") || "none"; return [rng, L(b.label), eff]; }); /* Ceyla, from the shipped pregens — real numbers, so the worked example is checkable */ const CEYLA = { dex: 10, readiness: 71, con: 12, siz: 13, carried: 15.4, free: 12 }; const cReflex = reflexRating(CEYLA.dex); const cAware = awarenessRating(CEYLA.readiness); const cBase = reactionBaseFrom(CEYLA.dex, CEYLA.readiness); const cEnc = encumbranceFrom(CEYLA.carried, CEYLA.free); const cLoad = reactionLoadFrom(cEnc.srLoad); const cPrinted = reactionPrintedFrom(cBase, cEnc.srLoad); const cHp = hitPointsFor(CEYLA.con, CEYLA.siz); const cMw = majorWoundFor(cHp); export const RULES_PAGES = [ { name: "1 · The whole game", content: `

You are a Custodian. Something is here that should not be. Go there, sort it out, come back. It is the coming back that gets harder.

The only rule you must know

Everything you attempt has a percentage. Roll 1D100 and get equal to or under it. That is the whole engine. Everything else is a modifier to that number or a consequence of the result.

${table(["Roll", "Result", "What it means"], [ ["01 to 1⁄20 of your skill", "Critical", "the best version of what you wanted"], ["up to 1⁄5 of your skill", "Special", "notably better than asked for"], ["up to your skill", "Success", "you do it"], ["over your skill", "Failure", "you do not"], ["the top 5% of the range", "Fumble", "you do it wrong, and it costs"] ])}

A skill of 60 criticals on 01–03, specials on 04–12, succeeds to 60, and fumbles on 98–00. The sheet prints those four numbers next to every skill so you never work them out.

Two rules override everything. 96 to 99 never succeed, however good you are, and 00 is always a fumble. So no skill is ever a certainty: a 100% expert still comes unstuck one roll in twenty. And no skill is ever hopeless either — the floor is 1%, so there is always the chance of a critical.

The three things that change the number

  1. Difficulty — set by the GM before you roll. Average leaves it alone, Difficult halves it, Easy doubles it.
  2. Circumstance — the situation, from ${pc(CIRCUMSTANCE[0].mod)} to ${pc(CIRCUMSTANCE.at(-1).mod)}, capped at ±40% however many apply.
  3. Your condition — wounds, what you are carrying, and how far you have drifted from your own reality. All three are printed on the sheet and applied automatically.

Difficulty first, then everything else. A skill can never be reduced below 1%.

Where the numbers come from

${table(["Thing", "How", "Ceyla, for example"], [ ["Hit points", "(CON + SIZ) ÷ 2, rounded up", `${CEYLA.con} + ${CEYLA.siz} → ${cHp}`], ["Major Wound", "half your hit points, rounded up", `${cMw} in one blow`], ["Damage modifier", "STR + SIZ on a ladder", "see the reference page"], ["Reaction", "your best route to acting first", `${cPrinted}`] ])}

That is enough to play

Page 2 is fighting and page 3 is being hurt. Pages 4 and 5 are the setting doing its work. The last page is a table you look things up in and never read.

` }, { name: "2 · Fighting", content: `

Who goes first

Your sheet prints one number: Reaction. Add 1D${D} to it. Highest acts first; a tie goes to the higher DEX. That is it — no per-weapon arithmetic, and the die is rolled fresh every round.

Reaction is the better of two routes, because being quick and being alert are both ways of acting first:

${table(["Route", "How", "Ceyla"], [ ["Reflex", `DEX ÷ ${REACTION.dexDivisor}, rounded up`, `${CEYLA.dex} → ${cReflex}`], ["Awareness", `Readiness ÷ ${REACTION.readinessDivisor}, rounded up`, `${CEYLA.readiness} → ${cAware}`] ])}

Readiness is the best of your Tradecraft, Insight, Spot or Listen, floored at INT × 3 so a clever but untrained agent is never flat. Take the better of the two routes, multiply by ${REACTION.laneMultiplier}, and subtract what you are carrying. Ceyla: the better of ${cReflex} and ${cAware} is ${cAware}, × ${REACTION.laneMultiplier} = ${cBase}, less ${cLoad} for ${cEnc.over} kg over her free carry → ${cPrinted}.

An analyst can be quicker than a shooter. That is deliberate.

Moving the marker

Only three things move you on the rail during a fight, and they cap at ±${REACTION.modCap}:

One lane apart wins about ${oddsOneLane.win}% of the time, ties ${oddsOneLane.tie}%. Two lanes apart cannot be caught by the die at all.

Your turn

One offensive action, plus as many defensive reactions as you like at an escalating penalty. Attacking, aiming, taking cover, reloading, giving First Aid and operating a device each use the action.

Movement is free — 15 m if you also act, 30 m if you do not. So you may move and take cover, but not take cover and shoot.

Defending

Defences are not actions and you may make as many as you like. The catch is that they get harder, and you may not mix them: having chosen to dodge, you dodge.

${table(["Defence this round", "Modifier"], [0,1,2,3].map(n => [`${n + 1}${["st","nd","rd","th"][Math.min(n,3)]}`, defencePenaltyFor(n) ? `${defencePenaltyFor(n)}%` : "no penalty"]))}

Dodge starts at DEX × 2 and training raises it from there — a DEX 13 agent who has never been taught to get out of the way still dodges on ${skillBaseFrom("dex*2", { dex: { value: 13 } })}%. Every posting can defend itself; some are simply much better at it.

Dodge uses the skill of that name and avoids the blow. Parry uses the weapon in your hand and stops it. Standing in front of three people is therefore possible, and stupid.

A defence is graded against the blow it answers. It stops a blow of its own quality or worse outright. Against something better it still helps, but only by as much as it was outclassed by — so an ordinary parry will not cancel a critical, it will only reduce it. Only a critical stops a critical.

${table(["Blow", "vs critical", "vs special", "vs success", "vs failed defence"], ["critical", "special", "success"].map(atk => [`${atk}`, ...["critical", "special", "success", "failure"].map(def => { const o = defenceOutcomeFor(atk, def); return o.turnedAside ? "turned aside" : `lands as ${o.landsAt}`; })]))}

Read the reduced blow as the result for everything that follows: a critical cut down to a special no longer ignores armour, it merely halves it.

Access

There is no hacking skill and nothing to break into. What an agent has is a borrowed authority: a named credential belonging to somebody real, with a scope of things that person could plausibly do, a Trust that erodes as it is used, and a red flag or two describing how the real owner behaves.

Inside its scope there is no roll. A facilities contractor opening a plant room is not an act of intrusion, it is Tuesday. It costs 1 Trust and it works. What costs you is asking the record to believe something one step beyond what the person was for.

${table(["What you are asking", "Against a credential for routine work"], SCOPE_TIERS.slice(1).map(t => { const stance = scopeStanceFor("routine", t); return [L(`RINGBRP.Scope.${t}`), stance === "in-scope" ? "no roll — 1 Trust" : stance === "stretch" ? "a stretch — roll for it" : "impossible, at any skill"]; }))}

Two tiers beyond the credential is not difficult, it is impossible. No percentage on any sheet gets a contractor issuing accounts. That is the rule that stops one agent quietly owning every system in the setting.

Behaving unlike them

Each red flag you break is ${pc(scrutinyFor(1))} on the roll. The system is not testing whether you are clever. It is testing whether you are behaving like the person whose name is on the record.

${table(["Result", "What happens to the credential"], [ ["Critical", "it works, and Trust goes UP by 1"], ["Special", "it works and costs nothing"], ["Success", "it works; Trust down 1"], ["Failure", "refused; Trust down 1"], ["Fumble", "burned — and somebody real is going to be asked about it"] ])}

At 0 Trust the account is flagged and will not pass again until a scene is spent rehabilitating it. Burned is permanent.

The dossier

A credential is not found, it is assembled. Up to ${DOSSIER.maxItems} dossier items, each a separate scene using a different skill, and at most ${DOSSIER.maxComputerUse} of them at a keyboard — so access is something the whole team builds and not one specialist's party trick. A dossier item never makes the later roll easier; it makes the credential better. Trust starts at ${dossierTrustFor(0)} and rises to ${dossierTrustFor(DOSSIER.maxItems)} with a full dossier, and the item that establishes what the person is actually for widens the scope.

Making it true

You may spend Coherence to widen a scope, because reality can be made to remember that the person always held that duty. The first costs ${widenScopeCostFor(0)} and each one after costs more — ${widenScopeCostFor(1)}, then ${widenScopeCostFor(2)}. It works. It always works.

Push a credential past what its Trust will carry and your file and theirs converge one step, and nothing in this game spends anything to converge them back: a circular arrives addressed to them; your pension record shows service you did not do; colleagues are certain you chaired a meeting you did not attend; and at the end, Registry holds one file where there were two, and yours is the copy.

${table(["Times you pushed it past its Trust", "What has happened to your file"], MERGE_STAGES.map((k, i) => [String(i), L(k)]))}

Shooting and hitting

${table(["Range", "Modifier"], RANGE_LADDER.map(r => [L(`RINGBRP.Range.${r.id}`) === `RINGBRP.Range.${r.id}` ? r.id : L(`RINGBRP.Range.${r.id}`), r.mod === null ? "not possible" : pc(r.mod)]))}

Cover penalises, it does not block: solid partial cover is ${pc(-30)} to the attacker and never removes line of sight.

What a hit does

${table(["Result", "Damage", "Armour"], [ ["Critical", "maximum weapon damage, plus a rolled damage modifier", "ignored entirely"], ["Special", "normal", "counts half, rounded down"], ["Success", "normal", "full"], ["Fumble", "none", "you drop or jam it; a firearm needs a round to clear"] ])}

The critical is the pressure valve. Good armour makes an ordinary hit survivable and does nothing at all about a critical.

` }, { name: "3 · Getting hurt", content: `

Hit points and locations

Damage comes off your total and the location it struck. Each location has its own capacity, a fraction of your total.

A disabled leg halves your movement and costs ${pc(-30)} physical; a disabled arm costs ${pc(-30)} to anything done with hands, which includes shooting. The figure on the Combat tab colours in as this happens.

Major Wounds

Take half your hit points or more in a single blow and roll 1D10 on this table. Ceyla's threshold is ${cMw}.

${table(["1D10", "Wound", "What it does to you"], woundRows)}

These stack, with each other and with a shattered limb, and they are applied to every roll you make. The Skills tab shows a red bar when you are carrying any.

Zero hit points

Hit points do not go below zero. Zero starts a clock instead.

${table(["Hit points", "You are"], [ ["above 2", "up — no restriction from injury alone"], ["2 or below", "down — out of the fight, still conscious, still yours"], ["0", "dying — unconscious, no actions, no defences, and the count begins"] ])}

Each round at zero adds ${DYING.perRound} to the count. When the count reaches your own Major Wound threshold you are dead — so the same number that decides how much damage you can take standing up also decides how long you have on the floor. A minimum of ${DYING.minCount} rounds applies however slight you are.

Being hurt again at zero breaks any stabilisation and costs a round of the count. Hit points cannot go below zero, so this is what further damage to a body on the floor actually does — and it is why leaving somebody stable in a place that is still dangerous is not the same as getting them out.

Stabilising takes ${DYING.stabiliseWith.join(" or ")} and stops the count. It does not put you back on your feet: you are still at zero and still unconscious until somebody heals you above it. Healing above zero clears the count. A fumbled attempt over a dying agent costs them a round they do not have.

The last entry

A Custodian's death is a filing question, because everything here is. What the record ends up saying depends on how well filed they were when it happened.

${table(["Coherence at death", "What the record says"], LAST_ENTRY.map((e, i) => { const upper = i === 0 ? "10" : String(LAST_ENTRY[i - 1].atLeast - 1); return [e.atLeast === 0 ? "0" : `${e.atLeast}–${upper}`, L(e.label)]; }))}

The badly filed are not recorded as dead at all. That is the department's problem first, and then somebody else's.

Bleeding

${table(["Wound", "Per round", "Stopped by"], Object.entries(BLEED).map(([k, v]) => [L(`RINGBRP.Wound.${k}.title`), `${v.perRound} hp`, v.stop.map(s => `${s.difficulty ? `${s.difficulty} ` : ""}${s.family.replace("_", " ")}`).join(", or ")]))}

Blood loss is charged at the end of every round and does not stop on its own. Someone has to spend an action. That is the decision the rule exists to create.

Healing

First Aid restores a few points and closes a bleed. Real recovery takes days at home, or hours in an autodoc. Nothing in the field puts a destroyed limb back.

` }, { name: "4 · Coherence", content: `

You are from here. Every time you do something that only makes sense somewhere else, you are a little less from here.

Coherence runs from 10 down to 0. Ten is anchored. Zero is displaced, and displacement is permanent.

${table(["Coherence", "Band", "Effect"], cohRows)}

Communication degrades first and worst. You can still do the job for a while, and the people you are doing it for stop recognising you long before you stop being useful.

What it costs

${table(["Occurrence", "Cost"], Object.values(COHERENCE_COSTS) .sort((a, b) => a.cost - b.cost) .map(c => [L(c.label), `−${c.cost}`]))}

Charged once per occurrence. The first two losses cost you nothing at all — and the third drops you out of Anchored, by which point you have stopped watching.

What brings it back

Only at home, and never faster than one step at a time: a debrief +${COHERENCE_RECOVERY.debrief}, a week of downtime +${COHERENCE_RECOVERY.downtimeWeek}, time with an anchor +${COHERENCE_RECOVERY.anchorObject}. An anchor is a person or object that remembers you as you were.

The way home is never closed

Being Unmoored makes the return dearer, never impossible. Another agent may always aim it for you at no penalty; alone you may always aim it at ${pc(COHERENCE_BANDS.find(b => b.id === "unmoored").returnPenalty)}; and a failed return does not strand you either — you arrive near your target rather than at it. There is no state in which an agent cannot get home. There are only states in which getting home costs more than the case was worth.

` }, { name: "5 · The Quiet Arts", content: `

Some agents come back with something. It is not a gift and it is not training — it is the crossing, left in a person.

What an Art is

A crossing is an induced filing error: you persuade the world you are somewhere else and it moves you to resolve the contradiction. An Art is the same trick, performed small and on purpose — a misfiling you commit deliberately.

So there are no fireballs. Every Art is a sentence about the record: what is filed here, what was, what is about to be, and what could be persuaded not to have been.

Using one

Roll under its rating like any skill. It is governed by POW, and your own Coherence penalises it — the further you have drifted, the worse you are at the thing that made you drift.

The cost is Coherence, and it is charged whether or not it works, because the misfiling is committed the moment you try. Push an Art past its ordinary reach and it costs more.

${table(["Art", "It says", "Ordinary", "Pushed"], Object.keys(ARTS).map(k => [ L(`RINGBRP.Art.${k}.name`), { thread: "what was together is still together", echo: "when is loosely filed", hollow: "this is not filed here", witness: "the record agrees you belong", unmake: "this was not present", ledger: "read the record; amend it" }[k], `−${powerCostFor(k)}`, `−${powerCostFor(k, { pushed: true })}` ]))}

Fumbling one

A fumble is not a failure. The misfiling sticks. Something small and true about the world is now different, and the GM never says which. That is the cost of doing on purpose the thing the agency spends its budget undoing.

Where they come from

Nobody learns an Art. They are not taught, bought, chosen at character creation or found in a manual. A character who wants one has exactly one route: go across, and have the way home go wrong.

Coming back

The return is a second crossing and it has to be aimed. Roll Transposition. Being Unmoored makes it dearer; another agent can always aim it for you at no penalty. There is no result that leaves you across.

${table(["Return", "What happens", "Coherence", "An Art?"], [ ["Success or better", "home, where and when you meant", "—", "no"], ["Failure", "you arrive near your target, not at it", `−${crossingCostFor("failure")}`, "only if you were already Unmoored"], ["Fumble", "you come back, and not the same", `−${crossingCostFor("fumble")}`, "always"] ])}

What follows you home

You do not choose the Art, and the pool is decided by how far you had already drifted when it went wrong. The worse the state you were in, the worse the thing that comes back with you.

${table(["Coherence at the time", "What can arrive"], [ ["8 and up — Anchored, or Loose", crossingArtPoolFor(10).map(a => L(`RINGBRP.Art.${a}.name`)).join(", ")], ["4 to 3 — Adrift", crossingArtPoolFor(4).map(a => L(`RINGBRP.Art.${a}.name`)).join(", ")], ["2 and below — Unmoored", crossingArtPoolFor(1).map(a => L(`RINGBRP.Art.${a}.name`)).join(", ")] ])}

Which is the trap the setting has been building the whole time: the agents most likely to come back with an Art are the ones least able to afford using it.

` }, { name: "5b · The postings", content: `

The postings

Twenty-five of them. An agency this old has never got round to abolishing anything, so the register still carries a Night Warden and an Antiquities Officer alongside the Signals and Disposal Officers. Each posting has a key characteristic, the skills it trains, the talents it leans on, and a loadout containing at least one thing issued to no other posting.

${table(["Posting", "Key", "What it is"], Object.values(ROLES).map(r => [`${r.label}`, r.stat.toUpperCase(), r.blurb ?? ""]))}

None of this is a class. A posting is what the department has you down as, and the skills follow from the job rather than the other way round.

` }, { name: "6 · A case", content: `

One case is one session. It has three tracks, and they are the pressure.

The shape of a session

Briefing → Approach → Investigation → Confrontation → Extraction. Each phase has a difficulty, a cost and the chance of a forced encounter. Agents are briefed on a place, a time and a thing to do. They cross. They find the brief was, as always, incomplete. They sort it out or they do not. They come back and are debriefed, and the debrief is where coherence is recovered and where the GM finds out what the players think they did.

Afterwards

Every skill you used successfully under pressure is ticked. At the end of the case each ticked skill rolls to improve: beat your current rating on 1D100 and it goes up 1D6. Ticks clear either way.

Cases do not need to connect. The running thread of a campaign is not a plot — it is the four coherence tracks going down.

` }, { name: "7 · Reference", content: `

Damage modifier

STR + SIZ, added to melee and thrown damage.

${table(["STR + SIZ", "Modifier"], damageLadder)}

Carrying it all

Free carry is STR + SIZ in kilograms. Past that you are encumbered, and it costs you ${pc(encumbrancePenaltyFor(1, "phys"))} per step on ${ENCUMBERED_CATEGORIES.length} skill categories — the physical ones — and nothing at all on the rest. Thinking is not heavier when your pack is.

${table(["Category", "Encumbered?"], ["phys", "melee", "ranged", "manip", "mental", "social"].map(c => [c, ENCUMBERED_CATEGORIES.includes(c) ? `yes, ${pc(encumbrancePenaltyFor(1, c))} a step` : "no"]))}

Bleeding

A wound that bleeds takes it off you every round until somebody stops it, and what stops it depends on how bad it is.

${table(["Wound", "Per round", "Stopped by"], Object.entries(BLEED).map(([k, v]) => [k, String(v.perRound), v.stop.map(x => x.difficulty ? `${x.family} (${x.difficulty})` : x.family).join(", ")]))}

Circumstance

${table(["Situation", "Modifier"], CIRCUMSTANCE.map(c => [L(c.label).replace(/\s*\([^)]*\)/, ""), pc(c.mod)]))}

Capped at ±40% no matter how many apply.

Encumbrance

Your free carry is STR + MAS in kilogrammes — the same pair that sets your damage modifier. Over that:

${table(["Over free carry", "Skills", "MOV", "Reaction"], [0, 1, 5, 10, 15, 20, 30].map(kg => { const e = encumbranceFrom(100 + kg, 100); return [`${kg} kg`, e.skillPenalty ? pc(-e.skillPenalty) : "—", e.movPenalty ? `−${e.movPenalty}` : "—", reactionLoadFrom(e.srLoad) ? `−${reactionLoadFrom(e.srLoad)}` : "—"]; }))}

Skills and MOV move in whole steps of 10 kg. Reaction is exact — every kilogramme costs, so no weight is ever free.

The skill penalty applies to physical, melee, ranged and manipulation only. A heavy pack does not make you worse at Knowledge.

Location capacity

A fraction of your total hit points, rounded up. For a ${cHp}-hit-point agent:

${table(["Location", "Share", "Capacity"], [ ["Head", "40%", `${locationMaxHp(cHp, 0.4)}`], ["Chest", "50%", `${locationMaxHp(cHp, 0.5)}`], ["Abdomen and legs", "42%", `${locationMaxHp(cHp, 0.42)}`], ["Arms", "35%", `${locationMaxHp(cHp, 0.35)}`] ])}

Rolls that ignore your skill

${table(["Roll", "Result"], [ ["96–99", "always fails, whatever your skill"], ["00", "always a fumble"], ["01", "always at least a critical chance — the floor is 1%"] ])}

The turn, in order

  1. Roll 1D${D}, add your printed Reaction, apply Aim, Edge or Drag.
  2. Highest first. Ties to higher DEX.
  3. On your turn: one action, free movement, unlimited defences at a rising penalty.
  4. To attack: skill, then difficulty, then range and circumstance, then roll.
  5. On a hit: damage, less armour by success level, to a rolled location.
  6. Half your hit points in one blow? Roll 1D10 for a Major Wound.
  7. End of round: anyone bleeding loses their hit points.
` }, { name: "8 · A round, worked through", content: `

Ceyla Marspont, agency analyst, at a boathouse door with two Cleaners inside. Every number here is the one her sheet actually prints.

Initiative

DEX ${CEYLA.dex} gives Reflex ${cReflex}. Her Tradecraft is ${CEYLA.readiness}, giving Awareness ${cAware} — she is not fast, she is ready. The better route is ${cAware}, × ${REACTION.laneMultiplier} = ${cBase}. She is carrying ${CEYLA.carried} kg against a free carry of ${CEYLA.free}, so ${cEnc.over} kg over costs her ${cLoad}. Her sheet prints ${cPrinted}.

She rolls 1D${D} and gets 4: ${Math.round((cPrinted + 4) * 10) / 10}. The Cleaners print 12 and roll 5 and 2. Ceyla acts first, comfortably.

The shot

Redaction lance, Energy Weapon (Pistol) at 53%. The Cleaner is 25 m away — medium range, ${pc(-10)}. Nothing else applies, so she needs 43 or under. Her bands: critical on 01–02, special to 08, success to 43, fumble on 97+.

She rolls 07. A special.

The damage

The lance does 1D4+6. She rolls 8. A special means the Cleaner's stab vest counts half — 2 becomes 1 — so 7 gets through. The location roll says the leg.

The Cleaner has 12 hit points, so a Major Wound needs 6. Seven is more than six: roll 1D10. It comes up 6 — ${L("RINGBRP.Wound.deepBleeding.title")}. He is now losing ${bleedRateFor([{ key: "deepBleeding" }])} hit points every round until someone spends an action on him, and his friend has to decide whether that someone is going to be him.

What it cost her

The lance is transposed materiel. Using it costs 1 Coherence, charged automatically. Ceyla drops from 10 to 9. Nothing happens yet — the first two losses are free — which is exactly how the third one arrives without anybody noticing.

` } ];