Issue riot shields to Containment and the Night Warden

Both postings draw a riot shield, and both are trained to use it — issuing one without
the skill would have been R-212 again with a different item, the Antiquities Officer
who drew a flintlock and rolled 5% with it.

melee_weapon:shield is SWAPPED into each support list rather than added, so five core
and five support are preserved and the shield arrives without a free power bump.
Containment drops stealth (a posting whose job is being the thing in the doorway was
always the least stealthy in the department); the Night Warden drops navigate (they sit
with one site all night).

check-kits now requires the Shield skill for an issued shield, the same way it has
always required training for an issued weapon. Verified by removing the skill and
watching the build refuse.

Four generated agents: shield skill at 25-30%, nobody encumbered, though a Containment
officer lands at 32.9 of 33 kg. The shield ships slung — raising it is the choice, and
for the Warden it is the difference between 2 points of armour and 5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
slaguru666
2026-08-11 21:37:46 +01:00
co-authored by Claude Opus 5
parent 0c2636d543
commit 53eb72c100
16 changed files with 66 additions and 17 deletions
+36
View File
@@ -4617,3 +4617,39 @@ two defects found while building it.
Worth noting for anyone issuing shields: a riot shield is `torsoArms`, and in melee
**legs and head are 10 of 20**. A shield is not a suit of armour and this fight is what
that sentence costs.
## Riot shields for Containment and the Night Warden
Both postings now draw a **riot shield** from stores, and both are trained to use it.
Issuing a shield without the skill would have been R-212 again with a different item —
the Antiquities Officer who drew a flintlock and rolled 5% with it — so
`melee_weapon:shield` goes into each posting's support list. It is **swapped in, not
added**: keeping five core and five support means the shield arrives without a free
power bump, which is not what was asked for.
* **Containment** drops `stealth`. A posting whose blurb is "goes through the door,
holds the corridor" was always the least stealthy thing in the department.
* **Night Warden** drops `navigate`. They sit with one site all night; they are not
going anywhere.
**check-kits now covers shields.** The guard has always required a posting to be
trained in the weapons it carries, and a shield is armour you defend *with*, so it
needs its skill for exactly the same reason. Verified by removing the skill from the
Warden and watching the build refuse:
check-kits: FAILED
warden (Night Warden): issued Riot shield, trains no melee_weapon:shield
**What it does to the two postings.** Four generated agents, shield skill landing at
25–30% (the support tier), and none of them encumbered — though a Containment officer
came out at 32.9 of 33 kg, which is as close to the line as this kit should get.
| | slung | raised |
|---|---|---|
| Containment (breaching suit, `all` 7) | 7 everywhere | torso and arms **10**, legs and head 7 |
| Night Warden (stab vest, `torso` 2) | torso 2, all else 0 | torso **5**, arms **3**, legs and head 0 |
The shield ships **slung**. Raising it is a choice made at the table, which is the whole
design — and for the Warden it is the difference between two points of armour and a
real one.
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.919810 17373b000 Delete type=3 #1
2026/08/11-21:36:45.974918 171407000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.954955 173f47000 Delete type=3 #1
2026/08/11-21:36:46.010315 1703ef000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.921916 172723000 Delete type=3 #1
2026/08/11-21:36:45.977183 171407000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.938436 172723000 Delete type=3 #1
2026/08/11-21:36:45.995509 171c13000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.946701 172723000 Delete type=3 #1
2026/08/11-21:36:46.003934 1703ef000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.928648 172723000 Delete type=3 #1
2026/08/11-21:36:45.985591 171c13000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.951456 172f2f000 Delete type=3 #1
2026/08/11-21:36:46.008640 171c13000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.912459 172f2f000 Delete type=3 #1
2026/08/11-21:36:45.965730 171c13000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.945043 17373b000 Delete type=3 #1
2026/08/11-21:36:46.002345 1703ef000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.943087 17373b000 Delete type=3 #1
2026/08/11-21:36:46.000570 170bfb000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.915613 173f47000 Delete type=3 #1
2026/08/11-21:36:45.970370 1703ef000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.924112 172723000 Delete type=3 #1
2026/08/11-21:36:45.980477 171c13000 Delete type=3 #1
+1 -1
View File
@@ -1 +1 @@
2026/08/11-18:22:31.917625 172f2f000 Delete type=3 #1
2026/08/11-21:36:45.972739 171c13000 Delete type=3 #1
+4 -4
View File
@@ -3774,9 +3774,9 @@ export const ROLES = {
heavy: { label: "Containment",
blurb: "Containment. Goes through the door, holds the corridor, and puts things in boxes that do not want to be in boxes.", stat: "str",
core: ["firearm:rifle","brawl","melee_weapon:prybar","athletics","dodge"],
support: ["demolition","first_aid","repair:mechanical","spot","stealth"],
support: ["demolition","first_aid","repair:mechanical","spot","melee_weapon:shield"],
talents: ["hard_landing","chain_custody","first_through","clean_scene"],
kit: ["rifle_med","breach_suit","prybar","restraints","climbing_kit", "bolt_croppers", "cordon_kit", "ear_defenders"] },
kit: ["rifle_med","breach_suit","prybar","riot_shield","restraints","climbing_kit", "bolt_croppers", "cordon_kit", "ear_defenders"] },
/* ---------------------------------------------------------------
The rest of the establishment.
@@ -3820,9 +3820,9 @@ export const ROLES = {
warden: { label: "Night Warden",
blurb: "Sits with a site overnight so that nobody else has to. Listens, watches, and writes down the times.", stat: "con",
core: ["listen","spot","stealth","dodge","brawl"],
support: ["first_aid","navigate","athletics","melee_weapon:club","anomaly_lore"],
support: ["first_aid","melee_weapon:shield","athletics","melee_weapon:club","anomaly_lore"],
talents: ["unblinking","out_of_step","first_through","clean_scene"],
kit: ["oil_lantern","ward","cudgel","stab_vest","torch", "vacuum_flask", "stopwatch", "blanket"] },
kit: ["oil_lantern","ward","cudgel","riot_shield","stab_vest","torch", "vacuum_flask", "stopwatch", "blanket"] },
stores: { label: "Stores Officer",
blurb: "Requisition, custody and the paperwork that moves materiel. Can get you a thing the department says it does not have.", stat: "edu",
+13
View File
@@ -10,8 +10,10 @@
*/
import { readFile } from "node:fs/promises";
import { SKILL_CATALOGUE, TALENTS, GEAR, WEAPONS, ARMOURS, DEVICES } from "./content.mjs";
import { SHIELD } from "../rules.mjs";
const WEAPON_BY_KEY = new Map(WEAPONS.map(w => [w.key, w]));
const ARMOUR_BY_KEY = new Map(ARMOURS.map(a => [a.key, a]));
const src = await readFile(new URL("../ringbrp.mjs", import.meta.url), "utf8");
const ROLES = eval("(" + src.match(/export const ROLES = (\{[\s\S]*?\n\});/)[1] + ")");
@@ -44,6 +46,17 @@ for (const [id, r] of Object.entries(ROLES)) {
const need = w.spec ? `${w.fam}:${w.spec}` : w.fam;
if (!trained.has(need)) problems.push(`${where}: issued ${w.name}, trains no ${need}`);
}
// A shield is armour you defend WITH, so it needs its skill for exactly the same
// reason a weapon does — a posting issued one and untrained in it parries at the
// 5% base and would be better off with both hands free.
const shieldSkill = `${SHIELD.parryFamily}:${SHIELD.parrySpec}`;
for (const k of r.kit) {
const a = ARMOUR_BY_KEY.get(k);
if (!a?.shield) continue;
if (!trained.has(shieldSkill)) {
problems.push(`${where}: issued ${a.name}, trains no ${shieldSkill}`);
}
}
const sig = JSON.stringify([...r.kit].sort());
if (kitSets.has(sig)) problems.push(`${where}: identical loadout to ${kitSets.get(sig)}`);
kitSets.set(sig, id);