Rulebook and compendium refresh

Audited every exported rule in rules.mjs against what the rulebook says and closed
the gaps:

- Dodge starts at DEX x 2 — newly evaluated, and stated nowhere until now
- Encumbrance: free carry, and which skill categories it actually penalises
- Bleeding rates and what stops each kind
- The merge ladder for Borrowed Authority, as a table
- A new page listing all 25 postings, generated from ROLES

The rulebook is now 9 pages and every one is generated from the code it documents.
All packs, 231 icons and the dice textures rebuilt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
slaguru666
2026-08-10 20:31:01 +01:00
co-authored by Claude Opus 5
parent 1734612c06
commit cc23fdb7bd
31 changed files with 168 additions and 79 deletions
+45
View File
@@ -3631,3 +3631,48 @@ roster cannot resolve a phase, and `resolveLegDialog` bails silently), so it is
ordered by how good the guess is: player-assigned characters first, then whatever the ordered by how good the guess is: player-assigned characters first, then whatever the
GM has selected on the canvas, and the whole world only as a last resort — and it says GM has selected on the canvas, and the whole world only as a last resort — and it says
so when it does. Verified: with four tokens selected, the new case has four members. so when it does. Verified: with four tokens selected, the new case has four members.
## SOFT VERGE, played with four new postings
Registry, Access, Disposal and Settlements — no role from the original five. The case
generated a location filed twice with the two filings drifting apart, which is a
Registry Officer's whole job, and a police cordon the team could not show a warrant to
because the staff's taboo forbids it.
They tried Borrowed Authority on the cordon: Registry failed the roll that establishes
what PC Aldiss is actually FOR, so the credential stayed at "read their own records",
which made signing four people through a STRETCH rather than routine — and it was
refused. That is the subsystem working exactly as designed; the dossier item that
matters is the one nobody rolled well on.
The case was won socially, not tactically. The Settlements Officer read the staff and
made the offer, standing 0 → 2, and they handed over the log — the only continuous
record of a place filed twice. Containment and Extraction both failed at 8% and 3%,
Resources hit 0, and everybody came home. Two skills improved at close.
**Balance note, not a defect.** The phase ladder gets brutal at the tail: difficult
plus escalating modifiers plus destitution put the last two phases at 8% and 3%. A
team that loses Resources early is not going to pass another phase, and the case
becomes about what they can still do socially. That reads as intended given the tone,
but a GM should know that Resources is the track that actually decides a case.
## Documentation pass
Audited every exported rule in `rules.mjs` against what the rulebook actually says,
and closed the gaps the audit found:
- **Dodge starts at DEX × 2** — newly evaluated, and stated nowhere. Now on the
fighting page, worked with a real number.
- **Encumbrance** — free carry is STR + SIZ, and which skill categories it penalises,
as a table generated from `ENCUMBERED_CATEGORIES`.
- **Bleeding** — rates and what stops each kind, from `BLEED`.
- **The merge ladder** — the five stages of your file converging with a borrowed one,
as a table rather than prose.
- **A new page, "The postings"** — all 25, each with its key characteristic and what
the job is, generated from `ROLES` in `ringbrp.mjs` (read out of the source, since
that module cannot be imported outside Foundry).
The rulebook is 9 pages, every page generated from the code it documents, and no
unresolved localisation key leaks into it. All packs rebuilt: 53 skills, 18 talents,
30 weapons, 13 armour, 119 gear, 14 vehicles, 6 Arts, 4 pregens, 6 NPCs, 2 tables,
the rulebook and the starter case. 231 icons and the four dice textures regenerated.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.733467 171e23000 Recovering log #3 2026/08/10-20:30:07.065707 1755c3000 Recovering log #3
2026/08/10-20:22:43.733493 171e23000 Level-0 table #5: started 2026/08/10-20:30:07.065728 1755c3000 Level-0 table #5: started
2026/08/10-20:22:43.733658 171e23000 Level-0 table #5: 1902 bytes OK 2026/08/10-20:30:07.065850 1755c3000 Level-0 table #5: 1902 bytes OK
2026/08/10-20:22:43.734023 171e23000 Delete type=0 #3 2026/08/10-20:30:07.066113 1755c3000 Delete type=0 #3
2026/08/10-20:22:43.734059 171e23000 Delete type=3 #2 2026/08/10-20:30:07.066156 1755c3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.453598 1723af000 Delete type=3 #1 2026/08/10-20:29:51.729566 1735fb000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.741301 17262f000 Recovering log #3 2026/08/10-20:30:07.073841 174db7000 Recovering log #3
2026/08/10-20:22:43.741323 17262f000 Level-0 table #5: started 2026/08/10-20:30:07.073861 174db7000 Level-0 table #5: started
2026/08/10-20:22:43.741456 17262f000 Level-0 table #5: 4129 bytes OK 2026/08/10-20:30:07.073980 174db7000 Level-0 table #5: 4129 bytes OK
2026/08/10-20:22:43.741707 17262f000 Delete type=0 #3 2026/08/10-20:30:07.074278 174db7000 Delete type=0 #3
2026/08/10-20:22:43.741744 17262f000 Delete type=3 #2 2026/08/10-20:30:07.074323 174db7000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.478012 1723af000 Delete type=3 #1 2026/08/10-20:29:51.752510 174613000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.734698 172e3b000 Recovering log #3 2026/08/10-20:30:07.066829 175dcf000 Recovering log #3
2026/08/10-20:22:43.734790 172e3b000 Level-0 table #5: started 2026/08/10-20:30:07.066918 175dcf000 Level-0 table #5: started
2026/08/10-20:22:43.735067 172e3b000 Level-0 table #5: 27954 bytes OK 2026/08/10-20:30:07.067237 175dcf000 Level-0 table #5: 27954 bytes OK
2026/08/10-20:22:43.735279 172e3b000 Delete type=0 #3 2026/08/10-20:30:07.067567 175dcf000 Delete type=0 #3
2026/08/10-20:22:43.735322 172e3b000 Delete type=3 #2 2026/08/10-20:30:07.067649 175dcf000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.456036 172bbb000 Delete type=3 #1 2026/08/10-20:29:51.731275 172def000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.726077 171e23000 Recovering log #3 2026/08/10-20:30:07.059142 1755c3000 Recovering log #3
2026/08/10-20:22:43.726308 171e23000 Level-0 table #5: started 2026/08/10-20:30:07.059383 1755c3000 Level-0 table #5: started
2026/08/10-20:22:43.726763 171e23000 Level-0 table #5: 60660 bytes OK 2026/08/10-20:30:07.059924 1755c3000 Level-0 table #5: 60660 bytes OK
2026/08/10-20:22:43.727036 171e23000 Delete type=0 #3 2026/08/10-20:30:07.060223 1755c3000 Delete type=0 #3
2026/08/10-20:22:43.727073 171e23000 Delete type=3 #2 2026/08/10-20:30:07.060258 1755c3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.466901 1723af000 Delete type=3 #1 2026/08/10-20:29:51.741242 172def000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.730937 171e23000 Recovering log #3 2026/08/10-20:30:07.063712 1755c3000 Recovering log #3
2026/08/10-20:22:43.730959 171e23000 Level-0 table #5: started 2026/08/10-20:30:07.063735 1755c3000 Level-0 table #5: started
2026/08/10-20:22:43.731083 171e23000 Level-0 table #5: 3295 bytes OK 2026/08/10-20:30:07.063831 1755c3000 Level-0 table #5: 3295 bytes OK
2026/08/10-20:22:43.731312 171e23000 Delete type=0 #3 2026/08/10-20:30:07.064030 1755c3000 Delete type=0 #3
2026/08/10-20:22:43.731345 171e23000 Delete type=3 #2 2026/08/10-20:30:07.064057 1755c3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.473073 1723af000 Delete type=3 #1 2026/08/10-20:29:51.746762 172def000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.723772 171e23000 Recovering log #3 2026/08/10-20:30:07.056785 1755c3000 Recovering log #3
2026/08/10-20:22:43.723973 171e23000 Level-0 table #5: started 2026/08/10-20:30:07.056996 1755c3000 Level-0 table #5: started
2026/08/10-20:22:43.724492 171e23000 Level-0 table #5: 54921 bytes OK 2026/08/10-20:30:07.057503 1755c3000 Level-0 table #5: 54921 bytes OK
2026/08/10-20:22:43.724852 171e23000 Delete type=0 #3 2026/08/10-20:30:07.057829 1755c3000 Delete type=0 #3
2026/08/10-20:22:43.724895 171e23000 Delete type=3 #2 2026/08/10-20:30:07.057871 1755c3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.462217 172bbb000 Delete type=3 #1 2026/08/10-20:29:51.736946 172def000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.737261 172e3b000 Recovering log #3 2026/08/10-20:30:07.070149 174db7000 Recovering log #3
2026/08/10-20:22:43.737309 172e3b000 Level-0 table #5: started 2026/08/10-20:30:07.070202 174db7000 Level-0 table #5: started
2026/08/10-20:22:43.737540 172e3b000 Level-0 table #5: 18581 bytes OK 2026/08/10-20:30:07.070462 174db7000 Level-0 table #5: 23316 bytes OK
2026/08/10-20:22:43.737891 172e3b000 Delete type=0 #3 2026/08/10-20:30:07.070725 174db7000 Delete type=0 #3
2026/08/10-20:22:43.737944 172e3b000 Delete type=3 #2 2026/08/10-20:30:07.070754 174db7000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.476400 173bd3000 Delete type=3 #1 2026/08/10-20:29:51.751001 1735fb000 Delete type=3 #1
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.728284 171e23000 Recovering log #3 2026/08/10-20:30:07.061396 1755c3000 Recovering log #3
2026/08/10-20:22:43.728327 171e23000 Level-0 table #5: started 2026/08/10-20:30:07.061441 1755c3000 Level-0 table #5: started
2026/08/10-20:22:43.728508 171e23000 Level-0 table #5: 6679 bytes OK 2026/08/10-20:30:07.061612 1755c3000 Level-0 table #5: 6679 bytes OK
2026/08/10-20:22:43.728819 171e23000 Delete type=0 #3 2026/08/10-20:30:07.061918 1755c3000 Delete type=0 #3
2026/08/10-20:22:43.728856 171e23000 Delete type=3 #2 2026/08/10-20:30:07.061984 1755c3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.439488 172bbb000 Delete type=3 #1 2026/08/10-20:29:51.723371 172def000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.738727 173647000 Recovering log #3 2026/08/10-20:30:07.071532 1745ab000 Recovering log #3
2026/08/10-20:22:43.738770 173647000 Level-0 table #5: started 2026/08/10-20:30:07.071606 1745ab000 Level-0 table #5: started
2026/08/10-20:22:43.739020 173647000 Level-0 table #5: 16892 bytes OK 2026/08/10-20:30:07.071826 1745ab000 Level-0 table #5: 16892 bytes OK
2026/08/10-20:22:43.739311 173647000 Delete type=0 #3 2026/08/10-20:30:07.072104 1745ab000 Delete type=0 #3
2026/08/10-20:22:43.739337 173647000 Delete type=3 #2 2026/08/10-20:30:07.072155 1745ab000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.471420 1733c7000 Delete type=3 #1 2026/08/10-20:29:51.745389 173e07000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.740093 171e23000 Recovering log #3 2026/08/10-20:30:07.072788 1755c3000 Recovering log #3
2026/08/10-20:22:43.740113 171e23000 Level-0 table #5: started 2026/08/10-20:30:07.072808 1755c3000 Level-0 table #5: started
2026/08/10-20:22:43.740221 171e23000 Level-0 table #5: 3739 bytes OK 2026/08/10-20:30:07.072910 1755c3000 Level-0 table #5: 3739 bytes OK
2026/08/10-20:22:43.740519 171e23000 Delete type=0 #3 2026/08/10-20:30:07.073132 1755c3000 Delete type=0 #3
2026/08/10-20:22:43.740563 171e23000 Delete type=3 #2 2026/08/10-20:30:07.073159 1755c3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.469485 1723af000 Delete type=3 #1 2026/08/10-20:29:51.743450 174613000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.729734 172e3b000 Recovering log #3 2026/08/10-20:30:07.062652 175dcf000 Recovering log #3
2026/08/10-20:22:43.729768 172e3b000 Level-0 table #5: started 2026/08/10-20:30:07.062680 175dcf000 Level-0 table #5: started
2026/08/10-20:22:43.729974 172e3b000 Level-0 table #5: 5105 bytes OK 2026/08/10-20:30:07.062844 175dcf000 Level-0 table #5: 5105 bytes OK
2026/08/10-20:22:43.730252 172e3b000 Delete type=0 #3 2026/08/10-20:30:07.063075 175dcf000 Delete type=0 #3
2026/08/10-20:22:43.730302 172e3b000 Delete type=3 #2 2026/08/10-20:30:07.063106 175dcf000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.445214 1733c7000 Delete type=3 #1 2026/08/10-20:29:51.726270 174613000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.736006 171e23000 Recovering log #3 2026/08/10-20:30:07.068780 1755c3000 Recovering log #3
2026/08/10-20:22:43.736029 171e23000 Level-0 table #5: started 2026/08/10-20:30:07.068812 1755c3000 Level-0 table #5: started
2026/08/10-20:22:43.736168 171e23000 Level-0 table #5: 5012 bytes OK 2026/08/10-20:30:07.068996 1755c3000 Level-0 table #5: 5012 bytes OK
2026/08/10-20:22:43.736419 171e23000 Delete type=0 #3 2026/08/10-20:30:07.069362 1755c3000 Delete type=0 #3
2026/08/10-20:22:43.736457 171e23000 Delete type=3 #2 2026/08/10-20:30:07.069417 1755c3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.458174 1733c7000 Delete type=3 #1 2026/08/10-20:29:51.733002 1735fb000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-20:22:43.732107 172e3b000 Recovering log #3 2026/08/10-20:30:07.064707 175dcf000 Recovering log #3
2026/08/10-20:22:43.732143 172e3b000 Level-0 table #5: started 2026/08/10-20:30:07.064741 175dcf000 Level-0 table #5: started
2026/08/10-20:22:43.732280 172e3b000 Level-0 table #5: 7240 bytes OK 2026/08/10-20:30:07.064869 175dcf000 Level-0 table #5: 7240 bytes OK
2026/08/10-20:22:43.732579 172e3b000 Delete type=0 #3 2026/08/10-20:30:07.065071 175dcf000 Delete type=0 #3
2026/08/10-20:22:43.732631 172e3b000 Delete type=3 #2 2026/08/10-20:30:07.065101 175dcf000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-20:22:35.450781 172bbb000 Delete type=3 #1 2026/08/10-20:29:51.728080 1735fb000 Delete type=3 #1
+45 -1
View File
@@ -17,7 +17,9 @@ import {
encumbranceFrom, woundEffectsFor, bleedRateFor, encumbranceFrom, woundEffectsFor, bleedRateFor,
defencePenaltyFor, defenceOutcomeFor, ARTS, powerCostFor, defencePenaltyFor, defenceOutcomeFor, ARTS, powerCostFor,
SCOPE_TIERS, scopeStanceFor, scrutinyFor, authorityOutcomeFor, SCOPE_TIERS, scopeStanceFor, scrutinyFor, authorityOutcomeFor,
dossierTrustFor, widenScopeCostFor, DOSSIER, dossierTrustFor, widenScopeCostFor, DOSSIER, MERGE_STAGES,
skillBaseFrom, encumbrancePenaltyFor, ENCUMBERED_CATEGORIES,
powerStartingRating, brawlFallbackFor, bleedStoppedBy,
crossingArtPoolFor, crossingGrantsArt, crossingCostFor, crossingArtPoolFor, crossingGrantsArt, crossingCostFor,
REACTION, BLEED, CIRCUMSTANCE, RANGE_LADDER, rangeBandFrom, REACTION, BLEED, CIRCUMSTANCE, RANGE_LADDER, rangeBandFrom,
MAJOR_WOUND_TABLE, COHERENCE_BANDS, COHERENCE_COSTS, COHERENCE_RECOVERY MAJOR_WOUND_TABLE, COHERENCE_BANDS, COHERENCE_COSTS, COHERENCE_RECOVERY
@@ -25,6 +27,12 @@ import {
const LANG = JSON.parse(await readFile(new URL("../lang/en.json", import.meta.url), "utf8")); const LANG = JSON.parse(await readFile(new URL("../lang/en.json", import.meta.url), "utf8"));
const L = key => LANG[key] ?? key; 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}%`; const pc = n => `${n > 0 ? "+" : ""}${n}%`;
/* ---------- small builders so the tables below stay readable ---------- */ /* ---------- small builders so the tables below stay readable ---------- */
@@ -181,6 +189,10 @@ they get harder, and you may not mix them: having chosen to dodge, you dodge.</p
${table(["Defence this round", "Modifier"], ${table(["Defence this round", "Modifier"],
[0,1,2,3].map(n => [`${n + 1}${["st","nd","rd","th"][Math.min(n,3)]}`, [0,1,2,3].map(n => [`${n + 1}${["st","nd","rd","th"][Math.min(n,3)]}`,
defencePenaltyFor(n) ? `${defencePenaltyFor(n)}%` : "no penalty"]))} defencePenaltyFor(n) ? `${defencePenaltyFor(n)}%` : "no penalty"]))}
<p><strong>Dodge starts at DEX × 2</strong> 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.</p>
<p><strong>Dodge</strong> uses the skill of that name and avoids the blow. <p><strong>Dodge</strong> uses the skill of that name and avoids the blow.
<strong>Parry</strong> uses the weapon in your hand and stops it. Standing in front <strong>Parry</strong> uses the weapon in your hand and stops it. Standing in front
of three people is therefore possible, and stupid.</p> of three people is therefore possible, and stupid.</p>
@@ -258,6 +270,9 @@ a circular arrives addressed to them; your pension record shows service you did
colleagues are certain you chaired a meeting you did not attend; and at the end, 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.</p> Registry holds one file where there were two, and yours is the copy.</p>
${table(["Times you pushed it past its Trust", "What has happened to your file"],
MERGE_STAGES.map((k, i) => [String(i), L(k)]))}
<h2>Shooting and hitting</h2> <h2>Shooting and hitting</h2>
${table(["Range", "Modifier"], RANGE_LADDER.map(r => ${table(["Range", "Modifier"], RANGE_LADDER.map(r =>
[L(`RINGBRP.Range.${r.id}`) === `RINGBRP.Range.${r.id}` ? r.id : L(`RINGBRP.Range.${r.id}`), [L(`RINGBRP.Range.${r.id}`) === `RINGBRP.Range.${r.id}` ? r.id : L(`RINGBRP.Range.${r.id}`),
@@ -401,6 +416,19 @@ ${table(["Coherence at the time", "What can arrive"], [
<p>Which is the trap the setting has been building the whole time: the agents most <p>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.</p>` }, likely to come back with an Art are the ones least able to afford using it.</p>` },
{ name: "5b · The postings", content: `
<h2>The postings</h2>
<p>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.</p>
${table(["Posting", "Key", "What it is"],
Object.values(ROLES).map(r => [`<strong>${r.label}</strong>`, r.stat.toUpperCase(), r.blurb ?? ""]))}
<p>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.</p>
` },
{ name: "6 · A case", content: ` { name: "6 · A case", content: `
<p>One case is one session. It has three tracks, and they are the pressure.</p> <p>One case is one session. It has three tracks, and they are the pressure.</p>
<ul> <ul>
@@ -433,6 +461,22 @@ is the four coherence tracks going down.</p>` },
<p>STR + SIZ, added to melee and thrown damage.</p> <p>STR + SIZ, added to melee and thrown damage.</p>
${table(["STR + SIZ", "Modifier"], damageLadder)} ${table(["STR + SIZ", "Modifier"], damageLadder)}
<h2>Carrying it all</h2>
<p>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.</p>
${table(["Category", "Encumbered?"],
["phys", "melee", "ranged", "manip", "mental", "social"].map(c =>
[c, ENCUMBERED_CATEGORIES.includes(c) ? `yes, ${pc(encumbrancePenaltyFor(1, c))} a step` : "no"]))}
<h2>Bleeding</h2>
<p>A wound that bleeds takes it off you every round until somebody stops it, and what
stops it depends on how bad it is.</p>
${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(", ")]))}
<h2>Circumstance</h2> <h2>Circumstance</h2>
${table(["Situation", "Modifier"], CIRCUMSTANCE.map(c => [L(c.label).replace(/\s*\([^)]*\)/, ""), pc(c.mod)]))} ${table(["Situation", "Modifier"], CIRCUMSTANCE.map(c => [L(c.label).replace(/\s*\([^)]*\)/, ""), pc(c.mod)]))}
<p>Capped at &plusmn;40% no matter how many apply.</p> <p>Capped at &plusmn;40% no matter how many apply.</p>