Files
RingBRP/README.md
T
slaguru666andClaude Opus 5 02ec47a351 Fix every NPC sheet, which has never opened
npc-sheet.hbs line 43 read

    {{#if (lookup ../skillsByCategory key).length}}

and Handlebars cannot read a property off a subexpression. That is a PARSE
error, so the template never compiled, so every NPC sheet in the system threw
on click — Natives & Threats, the starter's enemy roster, and the whole LAST
ADMISSION cast. #if already treats an empty array as falsy, so the .length was
never doing anything in the first place.

It was invisible to all four existing guards, and the reason is worth writing
down: check-rules reads .mjs, check-behaviour says in its own header that it
cannot reach sheets, and every play-through in this project has driven the
system through its API rather than by clicking. Nobody had ever opened an NPC
sheet, including me, in any of the sessions that "played through" scenarios
full of NPCs.

So: check-templates.mjs, a fifth guard, compiling every .hbs in about a tenth
of a second, wired into the build ahead of check-behaviour. It also names the
specific shape — a property read off a subexpression — because the raw parse
error says only where. Verified it bites by reintroducing the bug. Handlebars
is a devDependency now.

Opening the sheet then showed a second thing nobody had seen: the NPC skill
rows carry four children and were using the character sheet's seven-column
grid, so the name landed in a 13px column and the whole list overlapped
itself. The NPC row now has its own four-column template.

Verified by opening all six LAST ADMISSION cast sheets with an error hook
attached: six rendered, no errors, and Old Marrow reads HP 20/20, major wound
10, armour 2, Brawl 60%, Melee Weapon (Polearm) 60%.

Version 1.0.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 18:42:05 +01:00

189 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# THE CUSTODIANS
*A Basic Roleplaying game of crossings, as a Foundry VTT system.*
A drab British government agency deals with **crossings**. Reality keeps a record of
what belongs where, and it can be made to get it wrong. Agents are civil servants:
they carry warrant cards and cordon tape, they file in triplicate, and the thing that
makes them dangerous is that they know how the record works.
The tone is bureaucratic horror. Most cases close with an arrangement rather than a
fight, and the supernatural is a filing error somebody committed on purpose.
---
## Installing
**In Foundry:** *Game Systems → Install System → Manifest URL*
```
https://gitea.timevans.uk/slaguru666/RingBRP/raw/branch/main/system.json
```
**Or by hand:** clone this repository into your Foundry data directory as
`Data/systems/ringbrp`, then restart Foundry.
```bash
git clone https://gitea.timevans.uk/slaguru666/RingBRP.git ringbrp
```
Requires **Foundry v13+** (verified against v14.364). No modules are required. Dice So
Nice is supported but optional — the system registers four of its own dice textures.
### Deploying to a remote Foundry server
The built compendia in `packs/` **are committed on purpose**, so a remote server needs
nothing but the files:
```bash
cd /path/to/foundrydata/Data/systems
git clone https://gitea.timevans.uk/slaguru666/RingBRP.git ringbrp
# restart Foundry
```
`node_modules/` is *not* committed. You only need it if you intend to rebuild the
packs or regenerate art on that machine:
```bash
npm install # pulls classic-level, used to write the LevelDB packs
npm run build # rebuilds packs/ from tools/content.mjs
npm run check # the three guards; the build refuses to run if they fail
```
To update a deployed server: `git pull` and restart Foundry. If the pull touches
`packs/`, Foundry must be **stopped** first — LevelDB will not tolerate its files
changing underneath a running server.
---
## What is in the box
<!-- box:start -->
| | |
|---|---|
| **Postings** | 25, each with a key characteristic, trained skills, talents and its own kit |
| **Trades** | 10 ordinary jobs the department recruits from, plus induction |
| **Catalogue** | 236 items — 123 gear, 62 weapons, 32 armour, 19 vehicles |
| **Skills / talents** | 59 skills, 19 talents |
| **Bestiary** | 21 statblocks across folklore, horror and the far side |
| **Eras** | modern, antique, future, anomalous — the agency is old and never abolishes anything |
| **Icons** | 292, all generated from `tools/icons.mjs`; no external art |
| **Starter scenario** | **BLACK PLATFORM**, a six-player action starter, importable as a single Adventure |
| **Compendia** | pregens, NPCs, skills, weapons, armour, gear, vehicles, powers, tables, rules, the starter |
| **Rules journal** | 12 pages, generated from `rules.mjs` so the book cannot drift from the code |
<!-- box:end -->
---
## The rules, in brief
**d100 roll-under**, banding into critical / special / success / failure / fumble.
96–99 always fail, 00 always fumbles, and every skill has a 1% floor.
**Reaction Lanes** replace Strike Rank. Initiative is
`3 × max(⌈DEX/3⌉, ⌈readiness/10⌉)` less carried load, plus a d6, **re-rolled every
round** — including the first, so the opening round is ordered like every other.
**Coherence** runs 10 → 0 and measures how well-filed an agent is in reality. It bands
into Anchored / Loose / Adrift / Unmoored / Displaced, and spending it is how the
supernatural works.
**The Quiet Arts** are deliberate misfilings, not spells. They cost Coherence, and
they **only ever arrive from a crossing that goes wrong**.
**Defences are graded.** A defence stops a blow of its own quality or worse outright;
against something better it only reduces it by as many steps as it was outclassed by.
Only a critical stops a critical.
**Access is a record.** There is no hacking skill and nothing to break into — see
below.
**A weapon's fire mode is a decision.** Slow weapons shoot better and then make you
work; fast ones offer a second shot at the cost of both; rapid ones fire bursts; beams
can be held on the target for damage instead of hits. A weapon that cannot fire a
burst cannot be made to.
**Sights and lamps apply themselves.** A sight is graded by range band — a telescopic
sight is a penalty inside ten metres — and a carried lamp buys the dark back to dim,
never to daylight, and tells everyone where you are. Neither is a thing the player
selects: both are facts about what is in the pack.
---
## Borrowed Authority — the access subsystem
Deliberately not cyberpunk. Nobody jacks into anything.
What an agent has is 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 **red flags** describing how the real owner behaves.
- **Inside its scope there is no roll.** A facilities contractor opening a plant room
is not intrusion, it is Tuesday. It costs 1 Trust and it works.
- **One tier beyond is a stretch** — roll, at −20 for each red flag you break. 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.
- **Two tiers beyond is impossible at any skill.** No percentage on any sheet gets a
contractor issuing accounts.
- A **fumble burns** the credential, and somebody real is going to be asked about it.
A credential is assembled, not found: up to **three dossier items, each a separate
scene using a different skill, at most one of them at a keyboard**. Items make the
credential better, never the roll easier.
**AI** is a posture on the target rather than an opponent — a *gatekeeper* raises what
counts as routine, an *analyst* writes a new red flag every time you stretch, an
*asset* stands in for a dossier item.
And you may spend **Coherence** to widen a scope, because reality can be made to
remember that the person always held that duty. 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.
---
## Repository layout
```
ringbrp.mjs the system: sheets, engine, hooks (~5,500 lines)
rules.mjs THE SINGLE AUTHORITY for every rule formula
system.json Foundry manifest
template.json data model for every Actor and Item type
templates/ Handlebars sheets
styles/ one stylesheet
lang/en.json all user-facing text
icons/ dice/ fonts/ generated art
packs/ built LevelDB compendia (committed — see Deploying)
tools/
content.mjs the catalogue: skills, weapons, armour, gear, vehicles, NPCs
build-packs.mjs builds packs/ — runs the three guards first and refuses on failure
make-icons.mjs draws all 231 icons
make-dice-textures.mjs
rules-text.mjs generates the rules journal FROM rules.mjs
mission.mjs the case generator
mission-tables.mjs its content tables
check-rules.mjs guard: no rule may be defined twice
check-kits.mjs guard: every posting's kit must exist and be usable
check-lang.mjs guard: no localisation key may be both a value and a branch
docs/REVIEW_LOG.md the design and defect log, R-1 onward
```
### The one rule this codebase enforces on itself
**No rule formula may exist in more than one place.** Everything lives in `rules.mjs`,
and `tools/check-rules.mjs` fails the build if a formula is restated, if an alias
stops being identity-equal to what it aliases, if a constant is re-declared as a
literal, or if an exported rule has no spot-check. The packs used to be built under
one set of numbers and played under another; this makes that impossible to ship.
The build runs all three guards before it writes anything:
<!-- guards:start -->
```
check-rules: OK — 87 rules, 21 files scanned, 3 aliases + 41 constant sets checked, 275 formulas verified
check-kits: OK — 25 roles, 10 trades, 218 catalogue items, every kit key resolves, every posting can use what it carries, every loadout distinct
check-lang: OK — en.json, 927 keys, no leaf/branch collisions
check-behaviour: OK — 44 behavioural tests
```
<!-- guards:end -->