Add the player primer and cheat sheet, and brand every journal
Ten pages a player reads before session one, in their own compendium at OBSERVER ownership: nine of setting, one cheat sheet. The draft called the organisation "the Bureau"; everything else in the game calls it the department or the agency, so that is what it is called. Thin areas filled rather than padded: the seven character principles as a list, the real Coherence band table instead of a paragraph describing one, and "what continuity is not" pulled out of the middle of a page because it is the most important sentence for a new player. The cheat sheet's numbers are computed from rules.mjs and lang/en.json at build time. A hand-typed one would have been the sixth time this project shipped a number that disagreed with the code, and the one the players were holding. R-250: the design tokens were never on :root. Journals rendered as default parchment, and four things were wrong in sequence — v14's hook is renderJournalEntrySheet; its `element` is the node, so `[0]` returns the first CHILD and put the class on a header button; there is no .journal-page-content in v14; and then the real one: with the selector matching, font-size, line-height and padding all applied while every colour did not, because the palette is declared on .ringbrp-sheet/.ringbrp-chat/.ringbrp-window and never on :root. A journal inherited no tokens, so every var() silently became nothing. A rule that HALF applies is not a specificity problem — I spent three attempts treating it as one, including a version bump on a cache theory that was wrong. Branded: primer, rulebook, starter GM text and handouts. Scoped by flag, so other modules' journals are untouched. Version 0.9.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d9bce12c27
commit
7f8355f0d0
@@ -180,7 +180,7 @@ The build runs all three guards before it writes anything:
|
||||
|
||||
<!-- guards:start -->
|
||||
```
|
||||
check-rules: OK — 87 rules, 16 files scanned, 3 aliases + 41 constant sets checked, 275 formulas verified
|
||||
check-rules: OK — 87 rules, 17 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 — 38 behavioural tests
|
||||
|
||||
@@ -5070,3 +5070,63 @@ species bonus."
|
||||
Checked afterwards that every localisation key still referenced by the engine or a
|
||||
template exists; the six apparent misses are Handlebars concatenation prefixes rather
|
||||
than lookups.
|
||||
|
||||
## The player primer, and the journals finally look like the game
|
||||
|
||||
Ten pages a player reads before session one, in their own compendium at OBSERVER
|
||||
ownership: nine of setting and one cheat sheet. Plus the branding that all four
|
||||
journals in the system had been going without.
|
||||
|
||||
**The voice was the first thing to fix.** The draft called the organisation "the
|
||||
Bureau" throughout. Everything else in the game — twelve rules pages, the starter, 927
|
||||
localisation strings — calls it *the department* or *the agency*. One name, and it is
|
||||
not Bureau.
|
||||
|
||||
**Where the draft was thin, it got filled rather than padded.** The character section
|
||||
now carries the seven principles as an ordered list a player can actually work from;
|
||||
"what it costs" gets the real Coherence band table rather than a paragraph describing
|
||||
one, and says what the anchor in the kit is for; and the recruitment page separates
|
||||
*what continuity means in practice* from *what it is not*, because "it is not a
|
||||
superpower" is the single most important sentence for a new player and it was buried.
|
||||
|
||||
**The cheat sheet's numbers are computed, not typed.** Hit points, Major Wound, damage
|
||||
modifier, the defence penalties, the range ladder and the Coherence bands all come out
|
||||
of `rules.mjs` and `lang/en.json` at build time. A hand-typed cheat sheet would have
|
||||
been the sixth time this project shipped a number that disagreed with the code — and it
|
||||
would have been the one the players were holding. It closes on six questions new
|
||||
players actually ask, including "am I going to die?" answered honestly.
|
||||
|
||||
### R-250. The design tokens were never on `:root`
|
||||
|
||||
The journals rendered as Foundry's default parchment, and chasing why took four wrong
|
||||
guesses that are worth recording, because each one looked right.
|
||||
|
||||
1. **The hook name.** v14's journal sheet is an ApplicationV2: the hook is
|
||||
`renderJournalEntrySheet`, not `renderJournalSheet`.
|
||||
2. **`element` is the node.** On AppV2 it is the root element, not a jQuery wrapper.
|
||||
Reaching for `[0]` silently returns the first *child* — which is how the first
|
||||
version appeared to work, put the class on a header button, and changed nothing.
|
||||
3. **The page container.** There is no `.journal-page-content` in v14. The prose sits
|
||||
directly inside `article.journal-entry-page`.
|
||||
4. **And then the real one.** With the class on the right element and the selector
|
||||
matching, `font-size`, `line-height` and `padding` from my rule were all applying —
|
||||
and every colour was not. `--rb-deep` resolved to nothing. The palette is declared
|
||||
on `.ringbrp-sheet, .ringbrp-chat, .ringbrp-window` and **never on `:root`**, so a
|
||||
journal carrying a new class inherited no tokens at all. Every `var()` silently
|
||||
became nothing, which CSS treats as "unset" rather than as an error.
|
||||
|
||||
The tell was that the *numeric* properties from the same rule worked while the coloured
|
||||
ones did not. A rule that half-applies is not a specificity problem, and I spent three
|
||||
attempts treating it as one — including bumping the system version on a cache theory
|
||||
that was wrong.
|
||||
|
||||
Adding `.ringbrp-journal` to the token block fixed all of it at once.
|
||||
|
||||
**Now branded:** the primer, the generated rulebook, the starter's GM text and its
|
||||
handouts. Scoped by a flag, so another module's journals in the same world are
|
||||
untouched. The contents rail is the yellow-on-navy of the sheet tabs, read-aloud gets a
|
||||
spot-colour margin rule, diagrams are monospaced in a bordered block, and the cheat
|
||||
sheet is a grid of cards so it can be skimmed under pressure rather than read.
|
||||
|
||||
Version bumped to 0.9.0 — which, incidentally, is what makes Foundry re-fetch the
|
||||
stylesheet for anyone updating.
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ringbrp",
|
||||
"version": "0.8.0",
|
||||
"description": "The Custodians \u2014 a Basic Roleplaying game of crossings, for Foundry VTT",
|
||||
"version": "0.9.0",
|
||||
"description": "The Custodians — a Basic Roleplaying game of crossings, for Foundry VTT",
|
||||
"main": "ringbrp.mjs",
|
||||
"scripts": {
|
||||
"build": "node tools/build-packs.mjs && node tools/update-readme.mjs",
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.855265 1754e3000 Recovering log #3
|
||||
2026/08/12-00:06:16.855669 1754e3000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.855836 1754e3000 Level-0 table #5: 4591 bytes OK
|
||||
2026/08/12-00:06:16.856081 1754e3000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.856115 1754e3000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.925114 17200b000 Recovering log #3
|
||||
2026/08/12-10:15:09.925148 17200b000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.925283 17200b000 Level-0 table #5: 4591 bytes OK
|
||||
2026/08/12-10:15:09.925570 17200b000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.925615 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.690846 171b57000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.782384 173eef000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.873912 1754e3000 Recovering log #3
|
||||
2026/08/12-00:06:16.874024 1754e3000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.874152 1754e3000 Level-0 table #5: 4129 bytes OK
|
||||
2026/08/12-00:06:16.874423 1754e3000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.874466 1754e3000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.941304 173023000 Recovering log #3
|
||||
2026/08/12-10:15:09.941329 173023000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.941465 173023000 Level-0 table #5: 4129 bytes OK
|
||||
2026/08/12-10:15:09.941748 173023000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.941781 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.732633 17134b000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.827491 1726cb000 Delete type=3 #1
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.857059 174cd7000 Recovering log #3
|
||||
2026/08/12-00:06:16.857573 174cd7000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.857903 174cd7000 Level-0 table #5: 31614 bytes OK
|
||||
2026/08/12-00:06:16.858145 174cd7000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.858196 174cd7000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.926306 173023000 Recovering log #3
|
||||
2026/08/12-10:15:09.926421 173023000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.926814 173023000 Level-0 table #5: 31614 bytes OK
|
||||
2026/08/12-10:15:09.927163 173023000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.927207 173023000 Delete type=3 #2
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.692998 170b3f000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.784415 173eef000 Delete type=3 #1
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.840426 174cd7000 Recovering log #3
|
||||
2026/08/12-00:06:16.842114 174cd7000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.843749 174cd7000 Level-0 table #5: 225945 bytes OK
|
||||
2026/08/12-00:06:16.844107 174cd7000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.844201 174cd7000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.914246 173023000 Recovering log #3
|
||||
2026/08/12-10:15:09.915233 173023000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.916771 173023000 Level-0 table #5: 225945 bytes OK
|
||||
2026/08/12-10:15:09.917162 173023000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.917219 173023000 Delete type=3 #2
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.708940 170b3f000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.800859 1726cb000 Delete type=3 #1
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.850448 1754e3000 Recovering log #3
|
||||
2026/08/12-00:06:16.850937 1754e3000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.851096 1754e3000 Level-0 table #5: 3295 bytes OK
|
||||
2026/08/12-00:06:16.851406 1754e3000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.851447 1754e3000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.922245 17200b000 Recovering log #3
|
||||
2026/08/12-10:15:09.922265 17200b000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.922421 17200b000 Level-0 table #5: 3295 bytes OK
|
||||
2026/08/12-10:15:09.922681 17200b000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.922713 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.725493 171b57000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.821081 1726cb000 Delete type=3 #1
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.836787 174cd7000 Recovering log #3
|
||||
2026/08/12-00:06:16.837779 174cd7000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.838453 174cd7000 Level-0 table #5: 58792 bytes OK
|
||||
2026/08/12-00:06:16.838760 174cd7000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.838812 174cd7000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.911544 173023000 Recovering log #3
|
||||
2026/08/12-10:15:09.911759 173023000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.912426 173023000 Level-0 table #5: 58792 bytes OK
|
||||
2026/08/12-10:15:09.912801 173023000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.912846 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.699399 170b3f000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.791044 173eef000 Delete type=3 #1
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
MANIFEST-000004
|
||||
@@ -0,0 +1,5 @@
|
||||
2026/08/12-10:15:09.929600 17382f000 Recovering log #3
|
||||
2026/08/12-10:15:09.929643 17382f000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.929903 17382f000 Level-0 table #5: 21613 bytes OK
|
||||
2026/08/12-10:15:09.930174 17382f000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.930216 17382f000 Delete type=3 #2
|
||||
@@ -0,0 +1 @@
|
||||
2026/08/12-10:14:43.817678 1726cb000 Delete type=3 #1
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.861054 174cd7000 Recovering log #3
|
||||
2026/08/12-00:06:16.861635 174cd7000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.861956 174cd7000 Level-0 table #5: 33473 bytes OK
|
||||
2026/08/12-00:06:16.862206 174cd7000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.862237 174cd7000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.930884 17200b000 Recovering log #3
|
||||
2026/08/12-10:15:09.930957 17200b000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.931274 17200b000 Level-0 table #5: 33484 bytes OK
|
||||
2026/08/12-10:15:09.931485 17200b000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.931513 17200b000 Delete type=3 #2
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.729349 17134b000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.825718 1726cb000 Delete type=3 #1
|
||||
|
||||
Binary file not shown.
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.846928 1744cb000 Recovering log #3
|
||||
2026/08/12-00:06:16.847099 1744cb000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.847302 1744cb000 Level-0 table #5: 7590 bytes OK
|
||||
2026/08/12-00:06:16.847547 1744cb000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.847579 1744cb000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.920048 17200b000 Recovering log #3
|
||||
2026/08/12-10:15:09.920101 17200b000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.920292 17200b000 Level-0 table #5: 7590 bytes OK
|
||||
2026/08/12-10:15:09.920533 17200b000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.920569 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.685076 170b3f000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.775951 172ed7000 Delete type=3 #1
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.870148 1744cb000 Recovering log #3
|
||||
2026/08/12-00:06:16.870587 1744cb000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.870864 1744cb000 Level-0 table #5: 16892 bytes OK
|
||||
2026/08/12-00:06:16.871211 1744cb000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.871266 1744cb000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.938481 172817000 Recovering log #3
|
||||
2026/08/12-10:15:09.938536 172817000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.938839 172817000 Level-0 table #5: 16902 bytes OK
|
||||
2026/08/12-10:15:09.939182 172817000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.939235 172817000 Delete type=3 #2
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.723650 170b3f000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.819552 1736e3000 Delete type=3 #1
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.864877 1744cb000 Recovering log #3
|
||||
2026/08/12-00:06:16.865947 1744cb000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.866787 1744cb000 Level-0 table #5: 82274 bytes OK
|
||||
2026/08/12-00:06:16.867102 1744cb000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.867176 1744cb000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.933901 172817000 Recovering log #3
|
||||
2026/08/12-10:15:09.934300 172817000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.935025 172817000 Level-0 table #5: 82299 bytes OK
|
||||
2026/08/12-10:15:09.935279 172817000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.935312 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.720012 170333000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.811578 1736e3000 Delete type=3 #1
|
||||
|
||||
Binary file not shown.
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.872191 173cbf000 Recovering log #3
|
||||
2026/08/12-00:06:16.872466 173cbf000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.872601 173cbf000 Level-0 table #5: 3739 bytes OK
|
||||
2026/08/12-00:06:16.872838 173cbf000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.872870 173cbf000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.940141 17200b000 Recovering log #3
|
||||
2026/08/12-10:15:09.940161 17200b000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.940281 17200b000 Level-0 table #5: 3739 bytes OK
|
||||
2026/08/12-10:15:09.940565 17200b000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.940598 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.713770 17134b000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.805210 1726cb000 Delete type=3 #1
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.848619 173cbf000 Recovering log #3
|
||||
2026/08/12-00:06:16.848765 173cbf000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.848948 173cbf000 Level-0 table #5: 5770 bytes OK
|
||||
2026/08/12-00:06:16.849247 173cbf000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.849307 173cbf000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.921227 173023000 Recovering log #3
|
||||
2026/08/12-10:15:09.921256 173023000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.921405 173023000 Level-0 table #5: 5770 bytes OK
|
||||
2026/08/12-10:15:09.921672 173023000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.921715 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.687537 17134b000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.779038 173eef000 Delete type=3 #1
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.859394 1754e3000 Recovering log #3
|
||||
2026/08/12-00:06:16.859688 1754e3000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.859874 1754e3000 Level-0 table #5: 6939 bytes OK
|
||||
2026/08/12-00:06:16.860159 1754e3000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.860190 1754e3000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.928207 172817000 Recovering log #3
|
||||
2026/08/12-10:15:09.928248 172817000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.928429 172817000 Level-0 table #5: 6939 bytes OK
|
||||
2026/08/12-10:15:09.928761 172817000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.928810 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.694993 17134b000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.786318 173eef000 Delete type=3 #1
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
2026/08/12-00:06:16.852833 173cbf000 Recovering log #3
|
||||
2026/08/12-00:06:16.853536 173cbf000 Level-0 table #5: started
|
||||
2026/08/12-00:06:16.853822 173cbf000 Level-0 table #5: 14767 bytes OK
|
||||
2026/08/12-00:06:16.854096 173cbf000 Delete type=0 #3
|
||||
2026/08/12-00:06:16.854134 173cbf000 Delete type=3 #2
|
||||
2026/08/12-10:15:09.923574 17382f000 Recovering log #3
|
||||
2026/08/12-10:15:09.923739 17382f000 Level-0 table #5: started
|
||||
2026/08/12-10:15:09.924049 17382f000 Level-0 table #5: 14767 bytes OK
|
||||
2026/08/12-10:15:09.924348 17382f000 Delete type=0 #3
|
||||
2026/08/12-10:15:09.924394 17382f000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-00:05:23.689273 17134b000 Delete type=3 #1
|
||||
2026/08/12-10:14:43.780736 1726cb000 Delete type=3 #1
|
||||
|
||||
+32
@@ -6259,6 +6259,38 @@ async function migrateFittedGear() {
|
||||
return fixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dress this system's journals in the system's own paper.
|
||||
*
|
||||
* Twelve pages of generated rulebook, a player primer and a starter scenario were all
|
||||
* rendering as Foundry's default parchment — the only part of the game that did not
|
||||
* look like the game. Scoped by a flag rather than applied to every JournalSheet, so a
|
||||
* module's journals in the same world are left exactly as they were.
|
||||
*/
|
||||
/**
|
||||
* Dress this system's journals in the system's own paper.
|
||||
*
|
||||
* Twelve pages of generated rulebook, a ten-page player primer and a starter scenario
|
||||
* were all rendering as Foundry's default parchment — the only part of the game that
|
||||
* did not look like the game. Scoped by a flag rather than applied to every journal, so
|
||||
* a module's entries in the same world are left exactly as they were.
|
||||
*
|
||||
* v14 note: the sheet is an ApplicationV2, so the hook is `renderJournalEntrySheet` and
|
||||
* `element` is the root node itself rather than a jQuery wrapper. Reaching for `[0]` on
|
||||
* it silently returns the first CHILD — which is how the first version of this appeared
|
||||
* to work, added the class to a header button, and changed nothing.
|
||||
*/
|
||||
const rbBrandJournal = (doc, el) => {
|
||||
const mine = doc?.flags?.ringbrp?.brand
|
||||
|| (typeof doc?.pack === "string" && doc.pack.startsWith("ringbrp."))
|
||||
|| doc?.compendium?.metadata?.packageName === "ringbrp";
|
||||
const node = el instanceof HTMLElement ? el : (el?.[0] ?? null);
|
||||
if (mine && node?.classList) node.classList.add("ringbrp-journal");
|
||||
};
|
||||
Hooks.on("renderJournalEntrySheet", (app, el) => rbBrandJournal(app?.document, el));
|
||||
Hooks.on("renderJournalEntryPageSheet", (app, el) =>
|
||||
rbBrandJournal(app?.document?.parent, el));
|
||||
|
||||
Hooks.once("ready", async () => {
|
||||
console.log("RingBRP | ready");
|
||||
if (!game.user.isGM) return;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
.ringbrp-sheet,
|
||||
.ringbrp-chat,
|
||||
.ringbrp-journal,
|
||||
.ringbrp-window {
|
||||
/* sampled from the 1984 Explorer Book cover */
|
||||
--rb-void: #0c125b;
|
||||
@@ -423,6 +424,154 @@
|
||||
.ringbrp-sheet .rb-equip:hover { color: var(--rb-white); }
|
||||
.ringbrp-sheet .rb-equip.on { color: var(--rb-yellow); }
|
||||
|
||||
/* ============================================================
|
||||
JOURNALS — the department's own paper
|
||||
============================================================
|
||||
Twelve pages of rulebook and a player primer were rendering as Foundry's default
|
||||
parchment, which is the one place in the system that did not look like the system.
|
||||
Scoped to .ringbrp-journal, applied by a hook to this system's entries only, so
|
||||
other modules' journals are left alone. */
|
||||
|
||||
.ringbrp-journal .window-content,
|
||||
.ringbrp-journal .journal-entry-content,
|
||||
.ringbrp-journal .journal-entry-pages { background: var(--rb-deep); }
|
||||
|
||||
/* The contents rail: this is how a player moves through ten pages. */
|
||||
.ringbrp-journal .journal-sidebar {
|
||||
background: var(--rb-void); border-right: 1px solid var(--rb-line);
|
||||
}
|
||||
.ringbrp-journal .journal-sidebar .toc li.page {
|
||||
font-family: var(--rb-display); font-size: 10px; letter-spacing: .05em;
|
||||
text-transform: uppercase; color: var(--rb-muted);
|
||||
border-bottom: 1px solid var(--rb-hairline);
|
||||
}
|
||||
.ringbrp-journal .journal-sidebar .toc li.page:hover { color: var(--rb-white); }
|
||||
.ringbrp-journal .journal-sidebar .toc li.page.active,
|
||||
.ringbrp-journal .journal-sidebar .toc li.page.readied {
|
||||
color: var(--rb-void); background: var(--rb-yellow);
|
||||
}
|
||||
.ringbrp-journal .journal-header .title {
|
||||
font-family: var(--rb-display); color: var(--rb-yellow);
|
||||
background: transparent; border: none;
|
||||
}
|
||||
|
||||
/* A page. v14 puts the prose straight inside article.journal-entry-page — there is no
|
||||
.journal-page-content wrapper, which is why the first version of this styled nothing. */
|
||||
.ringbrp-journal .journal-entry-page {
|
||||
background: var(--rb-deep);
|
||||
color: var(--rb-white);
|
||||
font-family: var(--rb-body);
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
padding: 4px 18px 28px;
|
||||
}
|
||||
|
||||
.ringbrp-journal .journal-entry-page h1 {
|
||||
font-family: var(--rb-display); font-size: 19px; letter-spacing: .06em;
|
||||
text-transform: uppercase; color: var(--rb-yellow);
|
||||
border: none; border-bottom: 2px solid var(--rb-yellow); padding-bottom: 8px;
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.ringbrp-journal .journal-entry-page h2 {
|
||||
font-family: var(--rb-display); font-size: 13px; letter-spacing: .1em;
|
||||
text-transform: uppercase; color: var(--rb-yellow);
|
||||
border: none; border-bottom: 1px solid var(--rb-hairline); padding-bottom: 5px;
|
||||
margin: 26px 0 12px;
|
||||
}
|
||||
.ringbrp-journal .journal-entry-page h3 {
|
||||
font-family: var(--rb-display); font-size: 11px; letter-spacing: .09em;
|
||||
text-transform: uppercase; color: var(--rb-white); border: none; margin: 20px 0 8px;
|
||||
}
|
||||
.ringbrp-journal .journal-entry-page p { margin: 0 0 12px; }
|
||||
.ringbrp-journal .journal-entry-page strong { color: var(--rb-yellow); font-weight: 700; }
|
||||
.ringbrp-journal .journal-entry-page em { color: var(--rb-muted); }
|
||||
.ringbrp-journal .journal-entry-page a { color: var(--rb-yellow); }
|
||||
|
||||
.ringbrp-journal .journal-entry-page ul,
|
||||
.ringbrp-journal .journal-entry-page ol { margin: 0 0 14px; padding-left: 22px; }
|
||||
.ringbrp-journal .journal-entry-page li { margin-bottom: 5px; }
|
||||
.ringbrp-journal .journal-entry-page ul { list-style: none; padding-left: 16px; }
|
||||
.ringbrp-journal .journal-entry-page ul > li:before {
|
||||
content: "\25aa"; color: var(--rb-yellow); margin-left: -14px; margin-right: 8px;
|
||||
}
|
||||
.ringbrp-journal .journal-entry-page ol > li::marker { color: var(--rb-yellow); font-weight: 700; }
|
||||
|
||||
.ringbrp-journal .journal-entry-page table {
|
||||
width: 100%; border-collapse: collapse; margin: 0 0 16px;
|
||||
background: var(--rb-void); border: 1px solid var(--rb-line);
|
||||
}
|
||||
.ringbrp-journal .journal-entry-page th {
|
||||
font-family: var(--rb-display); font-size: 10px; letter-spacing: .08em;
|
||||
text-transform: uppercase; text-align: left; color: var(--rb-void);
|
||||
background: var(--rb-yellow); padding: 6px 9px; border: none;
|
||||
}
|
||||
.ringbrp-journal .journal-entry-page td {
|
||||
padding: 6px 9px; border-top: 1px solid var(--rb-hairline); vertical-align: top;
|
||||
}
|
||||
.ringbrp-journal .journal-entry-page tbody tr:nth-child(even) { background: var(--rb-panel); }
|
||||
|
||||
/* Read-aloud: the one thing a GM says in somebody else's voice. */
|
||||
.ringbrp-journal .rb-readaloud {
|
||||
margin: 0 0 18px; padding: 12px 16px;
|
||||
background: var(--rb-void);
|
||||
border: none; border-left: 3px solid var(--rb-yellow);
|
||||
color: var(--rb-white); font-size: 15px; line-height: 1.6;
|
||||
}
|
||||
.ringbrp-journal .rb-readaloud strong,
|
||||
.ringbrp-journal .rb-readaloud em { color: var(--rb-yellow); }
|
||||
|
||||
/* Maps and diagrams are typed, not drawn. */
|
||||
.ringbrp-journal .rb-map {
|
||||
background: var(--rb-void); border: 1px solid var(--rb-line);
|
||||
color: var(--rb-muted); font-family: var(--rb-mono); font-size: 11px;
|
||||
line-height: 1.45; padding: 12px 14px; margin: 0 0 16px;
|
||||
overflow-x: auto; white-space: pre;
|
||||
}
|
||||
|
||||
/* A departmental form: the stamped block a primer page opens with. */
|
||||
.ringbrp-journal .rb-form {
|
||||
background: var(--rb-void); border: 1px solid var(--rb-line);
|
||||
border-top: 3px solid var(--rb-yellow);
|
||||
padding: 12px 14px; margin: 0 0 20px;
|
||||
font-family: var(--rb-mono); font-size: 11px; line-height: 1.7;
|
||||
color: var(--rb-muted); text-transform: uppercase; letter-spacing: .04em;
|
||||
}
|
||||
.ringbrp-journal .rb-form b { color: var(--rb-white); font-weight: 700; }
|
||||
.ringbrp-journal .rb-stamp {
|
||||
display: inline-block; border: 2px solid var(--rb-danger); color: var(--rb-danger);
|
||||
font-family: var(--rb-display); font-size: 11px; letter-spacing: .14em;
|
||||
text-transform: uppercase; padding: 4px 10px; transform: rotate(-2deg);
|
||||
margin: 4px 0 14px;
|
||||
}
|
||||
/* The line a page lands on. */
|
||||
.ringbrp-journal .rb-closer {
|
||||
font-family: var(--rb-display); font-size: 13px; letter-spacing: .05em;
|
||||
color: var(--rb-yellow); text-align: center;
|
||||
border-top: 1px solid var(--rb-hairline); padding-top: 16px; margin-top: 22px;
|
||||
}
|
||||
.ringbrp-journal .rb-two { columns: 260px 2; column-gap: 26px; }
|
||||
.ringbrp-journal .rb-two > * { break-inside: avoid; }
|
||||
|
||||
/* The cheat sheet: a card per rule, so it can be skimmed under pressure. */
|
||||
.ringbrp-journal .rb-cards {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 12px; margin: 0 0 18px;
|
||||
}
|
||||
.ringbrp-journal .rb-card {
|
||||
background: var(--rb-void); border: 1px solid var(--rb-line);
|
||||
border-left: 3px solid var(--rb-yellow); padding: 10px 12px;
|
||||
}
|
||||
.ringbrp-journal .rb-card h4 {
|
||||
font-family: var(--rb-display); font-size: 10px; letter-spacing: .09em;
|
||||
text-transform: uppercase; color: var(--rb-yellow); margin: 0 0 6px; border: none;
|
||||
}
|
||||
.ringbrp-journal .rb-card p { margin: 0 0 6px; font-size: 13px; }
|
||||
.ringbrp-journal .rb-card p:last-child { margin-bottom: 0; }
|
||||
.ringbrp-journal .rb-big-number {
|
||||
font-family: var(--rb-display); font-size: 22px; color: var(--rb-white);
|
||||
line-height: 1.15; display: block; margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Shields — armour with a switch, so the switch has to be visible
|
||||
============================================================ */
|
||||
|
||||
+8
-1
@@ -2,7 +2,7 @@
|
||||
"id": "ringbrp",
|
||||
"title": "The Custodians",
|
||||
"description": "A Basic Roleplaying game of agents who cross the crossing to contain what should not be there — and who come back a little less from here each time.",
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.0",
|
||||
"compatibility": {
|
||||
"minimum": 13,
|
||||
"verified": "14.364"
|
||||
@@ -113,6 +113,13 @@
|
||||
"type": "Item",
|
||||
"system": "ringbrp"
|
||||
},
|
||||
{
|
||||
"name": "primer",
|
||||
"label": "The Custodians · Read this first (players)",
|
||||
"path": "packs/primer",
|
||||
"type": "JournalEntry",
|
||||
"system": "ringbrp"
|
||||
},
|
||||
{
|
||||
"name": "rules",
|
||||
"label": "The Custodians · Rules",
|
||||
|
||||
@@ -622,6 +622,7 @@ await writePack("armour", ARMOURS.map(a => {
|
||||
// Handouts are for handing out. A GM who has to change ownership on seven pages
|
||||
// before the session starts will simply read them aloud instead.
|
||||
handouts.doc.ownership = { default: 2 };
|
||||
handouts.doc.flags = { ringbrp: { brand: true } };
|
||||
for (const p of handouts.pages) p.ownership = { default: 2 };
|
||||
|
||||
const advId = idFor("adventure:black_platform");
|
||||
@@ -656,10 +657,25 @@ await writePack("armour", ARMOURS.map(a => {
|
||||
+ `${S.STARTER_PAGES.length} GM pages, ${S.STARTER_HANDOUTS.length} handouts`);
|
||||
}
|
||||
|
||||
// The player primer. Its own pack, OBSERVER by default, because it is the one thing
|
||||
// in the system written to be handed to players before anybody has read a rule.
|
||||
{
|
||||
const { PRIMER_PAGES } = await import("./primer-text.mjs");
|
||||
const j = journalDoc("The Custodians — Read this first", "primer", PRIMER_PAGES);
|
||||
j.doc.ownership = { default: 2 };
|
||||
j.doc.flags = { ringbrp: { brand: true } };
|
||||
for (const pg of j.pages) pg.ownership = { default: 2 };
|
||||
const entries = [[`!journal!${j.id}`, j.doc]];
|
||||
for (const pg of j.pages) entries.push([`!journal.pages!${j.id}.${pg._id}`, pg]);
|
||||
await writePack("primer", entries);
|
||||
console.log(` primer ${PRIMER_PAGES.length} pages, player-visible`);
|
||||
}
|
||||
|
||||
// the slice journal
|
||||
{
|
||||
const { SLICE_PAGES } = await import("./slice-text.mjs");
|
||||
const j = journalDoc("The Ilfracombe Crossing", "slice", SLICE_PAGES);
|
||||
j.doc.flags = { ringbrp: { brand: true } };
|
||||
const entries = [[`!journal!${j.id}`, j.doc]];
|
||||
for (const p of j.pages) entries.push([`!journal.pages!${j.id}.${p._id}`, p]);
|
||||
await writePack("slice", entries);
|
||||
@@ -693,6 +709,7 @@ await writePack("armour", ARMOURS.map(a => {
|
||||
{
|
||||
const { RULES_PAGES } = await import("./rules-text.mjs");
|
||||
const j = journalDoc("The Custodians — Rules", "rules", RULES_PAGES);
|
||||
j.doc.flags = { ringbrp: { brand: true } };
|
||||
const entries = [[`!journal!${j.id}`, j.doc]];
|
||||
for (const p of j.pages) entries.push([`!journal.pages!${j.id}.${p._id}`, p]);
|
||||
await writePack("rules", entries);
|
||||
|
||||
@@ -0,0 +1,502 @@
|
||||
/**
|
||||
* THE PLAYER PRIMER — what a new player reads before session one.
|
||||
*
|
||||
* Written to be handed out, not studied. Eight pages of setting and one cheat sheet,
|
||||
* and the cheat sheet's numbers are computed from rules.mjs at build time so the card
|
||||
* a player holds cannot say one thing while the engine does another. That failure has
|
||||
* happened five times in this project; a hand-typed cheat sheet would have been the
|
||||
* sixth, and it would have been the one the players were looking at.
|
||||
*
|
||||
* On the voice: the source draft called the organisation "the Bureau". Everything else
|
||||
* in the game — twelve rules pages, the starter, every localisation string — calls it
|
||||
* "the department" or "the agency", so that is what it is called here. One name.
|
||||
*/
|
||||
// TRADE_BANDS and INDUCTION are register data, not rules — they live in postings.mjs.
|
||||
import { TRADE_BANDS, INDUCTION } from "../postings.mjs";
|
||||
import {
|
||||
hitPointsFor, majorWoundFor, damageModifierFor, defencePenaltyFor,
|
||||
COHERENCE_BANDS, RANGE_LADDER, CIRCUMSTANCE
|
||||
} from "../rules.mjs";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
// Labels come from the language file, the same way the rulebook generator reads them,
|
||||
// so the primer prints what the sheet prints rather than a prettified lang key.
|
||||
const LANG = JSON.parse(await readFile(new URL("../lang/en.json", import.meta.url), "utf8"));
|
||||
const L = key => LANG[key] ?? key;
|
||||
|
||||
const P = s => `<p>${s}</p>`;
|
||||
const H = (n, s) => `<h${n}>${s}</h${n}>`;
|
||||
const UL = xs => `<ul>${xs.map(x => `<li>${x}</li>`).join("")}</ul>`;
|
||||
const OL = xs => `<ol>${xs.map(x => `<li>${x}</li>`).join("")}</ol>`;
|
||||
const TABLE = (head, rows) =>
|
||||
`<table><thead><tr>${head.map(h => `<th>${h}</th>`).join("")}</tr></thead><tbody>`
|
||||
+ rows.map(r => `<tr>${r.map(c => `<td>${c}</td>`).join("")}</tr>`).join("")
|
||||
+ `</tbody></table>`;
|
||||
const READ = s => `<blockquote class="rb-readaloud">${s}</blockquote>`;
|
||||
const FORM = s => `<div class="rb-form">${s}</div>`;
|
||||
const STAMP = s => `<div class="rb-stamp">${s}</div>`;
|
||||
const CLOSER = s => `<p class="rb-closer">${s}</p>`;
|
||||
const CARDS = xs => `<div class="rb-cards">${xs.map(([t, b]) =>
|
||||
`<div class="rb-card"><h4>${t}</h4>${b}</div>`).join("")}</div>`;
|
||||
const TWO = s => `<div class="rb-two">${s}</div>`;
|
||||
const pc = n => `${n > 0 ? "+" : ""}${n}%`;
|
||||
|
||||
/* Worked from the same functions the sheet uses, so the primer cannot drift. */
|
||||
const EG = { con: 12, siz: 13, str: 12 };
|
||||
const egHp = hitPointsFor(EG.con, EG.siz);
|
||||
const egMw = majorWoundFor(egHp);
|
||||
const egDm = damageModifierFor(EG.str + EG.siz);
|
||||
|
||||
export const PRIMER_PAGES = [
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
{ name: "1 · You live in the 2020s", content:
|
||||
FORM("Departmental Custody Service · <b>Induction pack</b><br>"
|
||||
+ "Issued to: <b>new entrant</b> · Retain for the duration of service<br>"
|
||||
+ "Classification: <b>restricted</b> — not for disclosure outside the department")
|
||||
+ P("You have a phone, an email address, a National Insurance number and opinions "
|
||||
+ "about the quickest way through a self-service checkout. You have had jobs, "
|
||||
+ "relationships, debts, disappointments and plans. You know the world is troubled "
|
||||
+ "and frequently absurd, but you have never had reason to believe it is "
|
||||
+ "<em>unreal</em>.")
|
||||
+ P("<strong>You are not a wizard, a superhero or a chosen anything.</strong> You "
|
||||
+ "cannot see ghosts merely because they are ghosts. Bullets hurt you. A locked door "
|
||||
+ "stays locked until somebody opens it. If something with too many teeth reaches "
|
||||
+ "you, your advantages are whatever you brought, whoever is standing beside you, "
|
||||
+ "and the chance that somebody made a plan.")
|
||||
+ P("Until recently you thought that was all there was.")
|
||||
+ CLOSER("Then the department found you.")
|
||||
},
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
{ name: "2 · The problem", content:
|
||||
P("Reality is not as single or as steady as people assume.")
|
||||
+ P("There are other versions of the world — places where a different decision was "
|
||||
+ "taken, where history bent another way, where life developed under other "
|
||||
+ "conditions, or where the rules themselves settled into a shape impossible here. "
|
||||
+ "Most stay separate. Occasionally two of them touch.")
|
||||
+ P("The department calls that contact a <strong>crossing</strong>.")
|
||||
+ P("A crossing might be a door that opens into the wrong building, a road that "
|
||||
+ "arrives in a town on no map, or a train running between stations in different "
|
||||
+ "histories. It might be no wider than the gap under a filing cabinet. It might "
|
||||
+ "last three seconds or stand open for generations.")
|
||||
+ H(2, "Crossings do not merely join places. They create contradictions.")
|
||||
+ TWO(UL([
|
||||
"Two people hold the same birth certificate.",
|
||||
"A house stands on ground where its foundations were never laid.",
|
||||
"A dead soldier comes back to a family that remembers burying him.",
|
||||
"Rain obeying somebody else's physics falls on a field in Yorkshire.",
|
||||
"A person goes on living while banks, government systems and their own relatives stop being able to remember them."
|
||||
]))
|
||||
+ H(2, "The record")
|
||||
+ P("The department's oldest surviving explanation is that "
|
||||
+ "<strong>reality keeps a record of what belongs where</strong>.")
|
||||
+ P("It is not a literal ledger held by a god or a machine. It is the closest language "
|
||||
+ "anyone has found for how the world behaves. People, places and events have an "
|
||||
+ "identity: they are recognised by other people, by physical history, and by the "
|
||||
+ "thousand systems around them.")
|
||||
+ P("<strong>A crossing introduces something the record cannot file.</strong> Sometimes "
|
||||
+ "reality rejects the intrusion. Sometimes it alters the intruder. Sometimes it "
|
||||
+ "alters everything around the intruder until the contradiction goes away. None of "
|
||||
+ "those is reliably safe.")
|
||||
+ P("The department uses the theory of crossings because it is useful, not because it "
|
||||
+ "has been proved. A door into another history is plainly a crossing. A dead woman "
|
||||
+ "appearing in mirrors, a family curse that outlives the family, or one dream shared "
|
||||
+ "by six strangers is less easily explained.")
|
||||
+ P("The old hands avoid the argument. Whatever a thing <em>is</em>, the questions are "
|
||||
+ "the same:")
|
||||
+ READ("What does it do? What makes it worse? What is holding it here? And how do we "
|
||||
+ "stop it reaching anybody else?")
|
||||
},
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
{ name: "3 · Why the department exists", content:
|
||||
P("Because crossings have always happened.")
|
||||
+ P("Its offices today are fluorescent-lit government rooms with grey carpet, "
|
||||
+ "computers and secure cabinets. Its predecessors worked out of royal "
|
||||
+ "observatories, monasteries, counting houses, naval offices and rooms behind "
|
||||
+ "temples. Some job titles are modern. Others have lasted centuries because nobody "
|
||||
+ "has ever been confident enough to say the old work has stopped being necessary.")
|
||||
+ P("Governments have renamed it, split it, hidden it inside other departments and "
|
||||
+ "occasionally tried to abolish it. The work carried on under every arrangement. "
|
||||
+ "<strong>An organisation can be closed. The next impossible doorway still has to be "
|
||||
+ "dealt with.</strong>")
|
||||
+ P("Nobody knows when it began. The archive holds Roman inventories of objects that "
|
||||
+ "cast shadows the wrong way, medieval payments to wardens of roads that did not "
|
||||
+ "always reach the same county, and eighteenth-century orders about a lighthouse "
|
||||
+ "visible only to ships already lost. The oldest documents refer to earlier "
|
||||
+ "custodians as though <em>they</em> were ancient.")
|
||||
+ P("So the department has reached an institutional conclusion: <strong>the job did not "
|
||||
+ "begin with us, and it will not end with us.</strong>")
|
||||
+ H(2, "What it cannot do")
|
||||
+ UL(["Permanently repair reality.",
|
||||
"Find every crossing before somebody walks through one.",
|
||||
"Destroy everything that arrives — many arrivals are people, and destruction can make a contradiction worse.",
|
||||
"Tell the public, which would create panic, exploitation and, worst of all, millions of new records confirming that the anomaly exists."])
|
||||
+ H(2, "What it does instead")
|
||||
+ UL(["Finds crossings and charts where they go.",
|
||||
"Contains things that should not be here.",
|
||||
"Recovers people who have gone somewhere else.",
|
||||
"Negotiates with whoever lives on the other side.",
|
||||
"Closes routes that have become dangerous.",
|
||||
"Leaves alone the arrangements that are safer undisturbed.",
|
||||
"Alters, suppresses or completes records until reality has one answer it can accept.",
|
||||
"Comes back tomorrow, when yesterday's solution starts to fail."])
|
||||
+ CLOSER("This is not a war that can be won. It is maintenance, on an impossible scale.")
|
||||
},
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
{ name: "4 · What is out there", content:
|
||||
P("The things the department deals with are not one species and share no single "
|
||||
+ "origin. Some are hostile. Some are animals behaving normally somewhere humans are "
|
||||
+ "unprepared. Some are people defending their homes. Some are processes with no "
|
||||
+ "intention at all. A few are mistakes left by the department's own earlier attempts "
|
||||
+ "at containment.")
|
||||
+ P("<em>Agents use rough, unofficial categories. None of them is in the manual.</em>")
|
||||
+ H(2, "Things that crossed")
|
||||
+ P("A predator following prey through a temporary opening. A growth spreading through "
|
||||
+ "concrete because concrete does not exist where it evolved. A person who crossed by "
|
||||
+ "accident and is dangerous only because our world is poison to them — or because "
|
||||
+ "their being here is erasing somebody else.")
|
||||
+ H(2, "Echoes, duplicates and revenants")
|
||||
+ P("A crossing sometimes repeats a person instead of moving them. Two adults with the "
|
||||
+ "same childhood, the same fingerprints and twenty years of different memories. A "
|
||||
+ "dead porter who walks the same platform every night. A rescue beacon transmitted "
|
||||
+ "by the teammate they watched die.")
|
||||
+ P("<strong>The department does not assume a duplicate is false or a revenant "
|
||||
+ "mindless.</strong> Working out what something is — and whether it can be talked "
|
||||
+ "to — is the job.")
|
||||
+ H(2, "Wrong places")
|
||||
+ P("A government office with a thirteenth floor reachable only during fire alarms. A "
|
||||
+ "village on two maps in different counties that physically moves each winter. A "
|
||||
+ "service tunnel that gets longer every time somebody is sent to measure it.")
|
||||
+ P("<strong>Wrong places are the dangerous ones, because people relax indoors.</strong> "
|
||||
+ "A room with a kettle, a desk and a fire-exit sign goes on feeling ordinary long "
|
||||
+ "after it has stopped obeying the rules.")
|
||||
+ H(2, "Objects and procedures")
|
||||
+ P("A brass date stamp that makes a document true from the date applied. A field "
|
||||
+ "medical unit that heals a wound by moving it to the nearest blood relative. A road "
|
||||
+ "sign that creates the place printed on it.")
|
||||
+ P("Much of this is <strong>transposed materiel</strong>. Agents are trained not to "
|
||||
+ "operate unfamiliar equipment merely because it has a switch.")
|
||||
+ H(2, "People with an interest")
|
||||
+ P("The department is not the only one that knows. Collectors buy things that should "
|
||||
+ "never be catalogued. Contractors called <strong>Cleaners</strong> remove evidence "
|
||||
+ "for employers whose names rarely survive an investigation. Rival offices bury "
|
||||
+ "incidents to protect budgets and careers. People living beside a stable crossing "
|
||||
+ "may depend on it for food, medicine or faith.")
|
||||
+ P("They use guns, vehicles, surveillance, lawyers, political weight and correct "
|
||||
+ "paperwork. <strong>They are usually more immediately dangerous than the "
|
||||
+ "anomaly.</strong>")
|
||||
+ H(2, "The dead who do not leave")
|
||||
+ P("The department does not use <em>ghost</em> as a classification, because the word "
|
||||
+ "assumes an answer. A haunting might be a surviving person, an impression left on a "
|
||||
+ "place, somebody displaced from a history where they did not die, or a pattern that "
|
||||
+ "has learned to do what the dead are expected to do.")
|
||||
+ P("<strong>The distinction matters.</strong> A surviving person can be reasoned with. "
|
||||
+ "A recording can only repeat. A predatory pattern may imitate grief because grief "
|
||||
+ "brings people close enough.")
|
||||
+ H(2, "Curses and inherited conditions")
|
||||
+ P("A curse is rarely a beam of hostile energy. It is usually <strong>a rule the world "
|
||||
+ "has started enforcing</strong>. The eldest child stays in the house. Every owner of "
|
||||
+ "a certain ring dies before their spouse. Nobody appointed keeper of a particular "
|
||||
+ "bridge leaves the county alive.")
|
||||
+ P("These last centuries because generations of observance have made them part of the "
|
||||
+ "record. Breaking the ritual may end it, release whatever it was holding down, or "
|
||||
+ "move the obligation onto whoever interfered.")
|
||||
+ P("<em>This is where well-meaning agents do the most damage. An old practice can look "
|
||||
+ "cruel or absurd and still be the only thing standing under something worse.</em>")
|
||||
+ H(2, "Ideas that travel")
|
||||
+ P("Not everything needs a body. A dream can spread between sleepers. A proof can "
|
||||
+ "describe a geometry that starts appearing around anyone who understands it. An "
|
||||
+ "image can gain detail with every copy until it shows the room the copy is being "
|
||||
+ "viewed in.")
|
||||
+ P("The department files these as <strong>cognitive anomalies</strong>, which does "
|
||||
+ "nothing whatever to make them safer. It is also why publicity is feared: attention "
|
||||
+ "does not only expose an anomaly, it can feed one, define one, or hold one steady.")
|
||||
+ H(2, "The things behind the stories")
|
||||
+ P("There is no official category for gods, because an official category implies "
|
||||
+ "somebody knows what is being categorised. Agents say <strong>mythos case</strong> "
|
||||
+ "for incidents that repeat the same shapes across cultures that never met: the "
|
||||
+ "sleeper under the water, the king whose face must not be seen, the black star, the "
|
||||
+ "messenger who arrives before the disaster.")
|
||||
+ P("Some intelligences appear to work on a scale that makes individual people hard to "
|
||||
+ "notice. Their smallest movements can produce religions, extinctions or changes in "
|
||||
+ "physical law.")
|
||||
+ P("<strong>They are not necessarily evil.</strong> A storm is not evil. That is little "
|
||||
+ "comfort to anyone underneath one.")
|
||||
+ P("These are not solved by learning a creature's statistics and shooting it. Agents "
|
||||
+ "break the ritual, clear the site, spoil the alignment, close the route, deal with "
|
||||
+ "an intermediary, or stop the thing becoming present enough to act. Sometimes the "
|
||||
+ "most that can be done is to reinforce an old containment and leave instructions "
|
||||
+ "for the next generation.")
|
||||
+ H(2, "UNRESOLVED")
|
||||
+ STAMP("Unresolved")
|
||||
+ P("Some files defeat every model. There are rooms that cannot be entered twice by the "
|
||||
+ "same route, photographs of agents who never existed, and one recurring Wednesday. "
|
||||
+ "There are procedures whose purpose has been forgotten, because every attempt to "
|
||||
+ "write the purpose down removes another person from the archive.")
|
||||
+ P("<strong>Unresolved does not mean harmless.</strong> It means somebody found a "
|
||||
+ "procedure that limits the damage without understanding why it works. A Night "
|
||||
+ "Warden checks the seal. A Registry Officer confirms the names. Somebody replaces "
|
||||
+ "the bowl of milk, winds the silent clock, reads the number over a secure line, or "
|
||||
+ "leaves one chair empty.")
|
||||
+ CLOSER("Some of these duties are older than the explanation for them.")
|
||||
},
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
{ name: "5 · Why you were recruited", content:
|
||||
P("Most people cannot work near crossings for long.")
|
||||
+ P("They look past contradictions. Their memories quietly update to match the latest "
|
||||
+ "version of events. They are violently sick, or they dissociate, or they accept an "
|
||||
+ "impossibility as though it had always been ordinary. This is usually protective. "
|
||||
+ "Human minds are very good at continuing to function.")
|
||||
+ P("<strong>Yours does something slightly different.</strong>")
|
||||
+ P("The department calls it <strong>continuity</strong>, and does not know what it is.")
|
||||
+ P("It runs in families without following any clean pattern of inheritance. Certain "
|
||||
+ "genetic markers turn up more often among agents and none of them predicts it. "
|
||||
+ "Older texts describe an unusually <em>well-seated</em> soul. A twentieth-century "
|
||||
+ "programme argued that consciousness leaves a persistent informational pattern. "
|
||||
+ "Another department maintains that continuity is simply social: people who are "
|
||||
+ "strongly remembered resist being rewritten.")
|
||||
+ P("All of those have been tested. None of them explains everybody.")
|
||||
+ H(2, "What it means in practice")
|
||||
+ UL(["When reality changes its account, <strong>you are more likely to remember the previous one</strong>.",
|
||||
"You can go through a controlled crossing and come back recognisably yourself.",
|
||||
"You can look at two mutually exclusive facts and know the contradiction is real."])
|
||||
+ H(2, "What it is not")
|
||||
+ P("<strong>It is not a superpower.</strong> You are not immune to crossings. You can "
|
||||
+ "still be hurt, altered, duplicated, forgotten or killed. Continuity does not make "
|
||||
+ "you stronger, faster or braver, and it does not tell you what an anomaly is or how "
|
||||
+ "to stop it.")
|
||||
+ P("It means that when the impossible happens you have a better chance of noticing — "
|
||||
+ "and of carrying the memory home.")
|
||||
+ CLOSER("For the department, that is enough to build a career on.")
|
||||
},
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
{ name: "6 · Who your character is", content:
|
||||
P("A modern adult whose life was recognisable before the department turned up in it.")
|
||||
+ P("They might have been a doctor, an engineer, an academic, a police officer, a "
|
||||
+ "construction worker, an IT specialist, a scientist, a linguist, an entertainer or "
|
||||
+ "a criminal. They might already have served inside the department in one of its "
|
||||
+ "field postings. Either way, <strong>what they are good at comes from work, "
|
||||
+ "education and hard practice — not from destiny.</strong>")
|
||||
+ P("A doctor is valuable because they are a doctor. A burglar understands locks "
|
||||
+ "because they spent years opening them. A builder can tell when a building is "
|
||||
+ "carrying weight wrongly. A police officer can control a dangerous scene. An "
|
||||
+ "entertainer can hold a room that does not want to listen.")
|
||||
+ H(2, "The department adds six weeks")
|
||||
+ P("Induction: how to recognise a crossing, set a cordon, handle evidence, fire the "
|
||||
+ "issued weapon, survive first contact, and complete the forms that will very likely "
|
||||
+ "matter more than the gun.")
|
||||
+ P("<strong>Six weeks does not make anybody an expert.</strong> A surgeon recruited "
|
||||
+ "last year is still a far better surgeon than they are a Custodian. Your prior "
|
||||
+ "career sits at <strong>"
|
||||
+ `${TRADE_BANDS.core[0]}–${TRADE_BANDS.core[1]}%</strong>; what the department taught `
|
||||
+ `you starts at <strong>${INDUCTION.bandByTier.probationary[0]}–`
|
||||
+ `${INDUCTION.bandByTier.probationary[1]}%</strong> and climbs with service.`)
|
||||
+ P("<em>That gap is deliberate, and it is often frightening.</em>")
|
||||
+ H(2, "Why you stayed")
|
||||
+ P("You may have come willingly. You may have survived an incident and learned too "
|
||||
+ "much to be let go. You may have been offered a choice whose other option was not "
|
||||
+ "really a choice. The department might have lost a conviction, saved a relative, "
|
||||
+ "bought out a contract, or simply been the first organisation to believe you.")
|
||||
+ P("<strong>You were not picked for being fearless.</strong> You were picked because "
|
||||
+ "you can remember, because you have a useful human skill, and because there are "
|
||||
+ "never enough people for the work waiting tomorrow morning.")
|
||||
+ H(2, "Seven principles for building one")
|
||||
+ OL(["<strong>You are from the 2020s.</strong> Your references, assumptions, work and relationships are contemporary.",
|
||||
"<strong>You had a life before this.</strong> Decide what you did well enough that the department needed it.",
|
||||
"<strong>You are not superhuman.</strong> Training, teamwork, equipment and preparation keep you alive.",
|
||||
"<strong>Continuity is not immunity.</strong> You notice changes better than most people. The changes can still happen to you.",
|
||||
"<strong>You have a reason to stay.</strong> Duty, debt, curiosity, guilt, protection, belonging — or knowing nobody else will do it.",
|
||||
"<strong>You need the team.</strong> No posting or trade holds every answer. The lowest-ranked person in the room is often the only real expert in it.",
|
||||
"<strong>Decide what anchors you.</strong> Name a person, an object, a place or a habit that reminds you who you are when the records disagree."])
|
||||
+ CLOSER("The department does not need heroes. It needs people who will turn up, look "
|
||||
+ "straight at the impossible, and still remember what they came to do.")
|
||||
},
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
{ name: "7 · What the job is actually like", content:
|
||||
P("An operation usually starts with an incomplete briefing.")
|
||||
+ P("Something has happened. A person has vanished without leaving. An impossible "
|
||||
+ "object has been recovered. An old containment has failed. Somebody has opened a "
|
||||
+ "route that was supposed to stay shut. You get an objective, equipment, a cover "
|
||||
+ "story, and as much authority as the department is willing to admit it sent.")
|
||||
+ P("<strong>Then you find out what the briefing got wrong.</strong>")
|
||||
+ P("You may go through records, talk to witnesses, cross into another version of the "
|
||||
+ "world, walk unfamiliar ground, make contact with whoever lives there, recover "
|
||||
+ "people or evidence, and contain what is immediately dangerous. Sometimes that is a "
|
||||
+ "negotiation. Sometimes it is engineering. Sometimes it is holding a corridor with a "
|
||||
+ "rifle and a shield while somebody else repairs the only way home.")
|
||||
+ P("Fighting is dangerous and often unavoidable, and <strong>killing is rarely the "
|
||||
+ "whole answer</strong>. Shoot the creature and the crossing that produced it is "
|
||||
+ "still open. Beat the Cleaner and the evidence they came for is still lying there. "
|
||||
+ "Cases close when there is an answer reality can live with.")
|
||||
+ H(2, "Then you come home")
|
||||
+ P("You are debriefed. Reports are compared. Equipment goes back, except what was "
|
||||
+ "lost, broken, or has started behaving differently. The department decides what "
|
||||
+ "officially happened. You get rest, if the schedule allows it.")
|
||||
+ CLOSER("Sooner or later, another file arrives.")
|
||||
},
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
{ name: "8 · What it costs", content:
|
||||
P("Crossing leaves a mark.")
|
||||
+ P("The department measures your connection to your own reality as "
|
||||
+ "<strong>Coherence</strong>. It is not sanity and it is not morality. "
|
||||
+ "<strong>It is how firmly the world still recognises that you belong here.</strong>")
|
||||
+ P("Watching a paradox, using transposed equipment, breaking the physics of another "
|
||||
+ "place, meeting another version of yourself, or coming back badly all cost you some.")
|
||||
+ H(2, "How it goes")
|
||||
+ TABLE(["Coherence", "Where you are", "What it costs you"],
|
||||
COHERENCE_BANDS.map((b, i) => {
|
||||
const hi = i === 0 ? 10 : COHERENCE_BANDS[i - 1].min - 1;
|
||||
const eff = [b.all ? `${pc(b.all)} to everything` : "",
|
||||
b.comm ? `${pc(b.comm)} to talking to people` : ""]
|
||||
.filter(Boolean).join(", ") || "nothing yet";
|
||||
return [b.min === hi ? `${b.min}` : `${hi}–${b.min}`,
|
||||
`<strong>${L(b.label)}</strong>`, eff];
|
||||
}))
|
||||
+ P("Early on it is small. People mishear your name. Automatic doors hesitate. You look "
|
||||
+ "very slightly misplaced in photographs. Later, records disagree about where you "
|
||||
+ "work, people you love remember conversations differently, and the way home gets "
|
||||
+ "harder to aim.")
|
||||
+ H(2, "Getting it back")
|
||||
+ P("Debriefing, time at home, and contact with people or things that remember you "
|
||||
+ "correctly. <strong>Recovery is slow, and some of it never entirely comes back.</strong>")
|
||||
+ P("<em>This is what the anchor in your kit is for. It is one object that remembers "
|
||||
+ "you as you were, and it is the cheapest medicine the department issues.</em>")
|
||||
+ H(2, "The Quiet Arts")
|
||||
+ P("Very occasionally a crossing leaves somebody able to commit a small impossibility "
|
||||
+ "on purpose. The department calls these the <strong>Quiet Arts</strong>.")
|
||||
+ P("They are not gifts. They cannot be chosen at recruitment. They always cost more of "
|
||||
+ "your connection to the world. Most Custodians never get one.")
|
||||
+ P("<strong>Those who do are not superheroes. They are injured people who have learned "
|
||||
+ "how to press on the injury.</strong>")
|
||||
},
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
{ name: "9 · The whole game, in one page", content:
|
||||
P("You play ordinary adults in the modern world, pulled into an old and quiet "
|
||||
+ "government department because you stay yourselves when reality goes wrong.")
|
||||
+ P("You investigate crossings, recover the lost, face hostile people and impossible "
|
||||
+ "things, negotiate with strangers, contain what can be contained, and decide what "
|
||||
+ "the official record will say happened. You carry modern tools, professional "
|
||||
+ "experience, an issued weapon and incomplete information.")
|
||||
+ P("You can fight. Violence alone rarely closes a case. You can come home, and every "
|
||||
+ "journey makes home a little less certain.")
|
||||
+ P("The department has done this for longer than any living government, and expects to "
|
||||
+ "carry on long after you.")
|
||||
+ READ("There will always be another file.<br>"
|
||||
+ "There will always be something here that should not be.<br>"
|
||||
+ "<strong>Your job is to go there and sort it out.</strong>")
|
||||
},
|
||||
|
||||
/* ---------------------------------------------------------------- *
|
||||
* The cheat sheet. Numbers computed, not typed.
|
||||
* ---------------------------------------------------------------- */
|
||||
{ name: "10 · Cheat sheet — everything you need at the table", content:
|
||||
FORM("Departmental Custody Service · <b>Field aide-memoire</b><br>"
|
||||
+ "One page. Keep it beside you. <b>You do not need to read anything else to play.</b>")
|
||||
|
||||
+ H(2, "The only rule you must know")
|
||||
+ P("<span class=\"rb-big-number\">Roll 1d100. Get equal to or under your skill.</span>"
|
||||
+ "That is the whole engine. Everything else changes that number, or is a consequence "
|
||||
+ "of the result.")
|
||||
+ TABLE(["Roll", "Result", "What it means"], [
|
||||
["01 to a twentieth of your skill", "<strong>Critical</strong>", "the best version of what you wanted"],
|
||||
["up to a fifth of your skill", "<strong>Special</strong>", "notably better than you asked for"],
|
||||
["up to your skill", "<strong>Success</strong>", "you do it"],
|
||||
["over your skill", "Failure", "you do not"],
|
||||
["the top 5% of the range", "<strong>Fumble</strong>", "you do it wrong, and it costs"]
|
||||
])
|
||||
+ P("<strong>The sheet prints those four numbers next to every skill</strong>, so you "
|
||||
+ "never work them out. A skill of 60 criticals on 01–03, specials to 12, succeeds to "
|
||||
+ "60 and fumbles on 98–00.")
|
||||
+ P("Two rules beat everything: <strong>96–99 never succeed</strong>, however good you "
|
||||
+ "are, and <strong>00 is always a fumble</strong>. No skill is ever certain — and "
|
||||
+ "none is ever hopeless either, because the floor is 1%.")
|
||||
|
||||
+ H(2, "The three things that change your number")
|
||||
+ CARDS([
|
||||
["1 · Difficulty",
|
||||
P("Set by the GM <em>before</em> you roll.")
|
||||
+ P("<strong>Easy</strong> doubles your skill. <strong>Average</strong> leaves it "
|
||||
+ "alone. <strong>Difficult</strong> halves it.")],
|
||||
["2 · Circumstance",
|
||||
P("The situation, up to " + pc(Math.max(...CIRCUMSTANCE.map(c => c.mod)))
|
||||
+ " in your favour or " + pc(Math.min(...CIRCUMSTANCE.map(c => c.mod))) + " against.")
|
||||
+ P("At most <strong>two</strong> apply — the best good one and the worst bad one — "
|
||||
+ "and they cap at ±40%.")],
|
||||
["3 · Your condition",
|
||||
P("Wounds, what you are carrying and how far you have drifted.")
|
||||
+ P("<strong>All three are already on your sheet and applied for you.</strong>")]
|
||||
])
|
||||
+ P("<em>Difficulty first, then everything else. A skill can never drop below 1%.</em>")
|
||||
|
||||
+ H(2, "Fighting")
|
||||
+ CARDS([
|
||||
["Who goes first",
|
||||
P("<strong>Reaction</strong>, re-rolled every round including the first. Your "
|
||||
+ "printed number plus a d6. Highest acts first.")],
|
||||
["Attacking",
|
||||
P("Roll the weapon's skill. The dialog asks you the questions — range, cover, "
|
||||
+ "light, how you are firing — and does the arithmetic.")],
|
||||
["Defending",
|
||||
P("<strong>The first defence each round is free.</strong> Each one after that is "
|
||||
+ pc(defencePenaltyFor(1)) + ", then " + pc(defencePenaltyFor(2)) + ".")],
|
||||
["Defences are graded",
|
||||
P("A defence stops a blow of its own quality or worse. Against something better it "
|
||||
+ "only drags it down a step.")
|
||||
+ P("<strong>Only a critical stops a critical.</strong>")]
|
||||
])
|
||||
+ TABLE(["Range", "To hit"],
|
||||
RANGE_LADDER.map(r => [L(`RINGBRP.Range.${r.id}`),
|
||||
r.mod === null ? "<em>cannot be hit at all</em>" : pc(r.mod)]))
|
||||
|
||||
+ H(2, "Getting hurt")
|
||||
+ CARDS([
|
||||
["Hit points",
|
||||
P("<span class=\"rb-big-number\">" + egHp + "</span>"
|
||||
+ "(CON + SIZ) ÷ 2, rounded up. The example agent has CON " + EG.con + " and SIZ "
|
||||
+ EG.siz + ".")],
|
||||
["Major Wound",
|
||||
P("<span class=\"rb-big-number\">" + egMw + "</span>"
|
||||
+ "Half your hit points in <strong>one blow</strong>. Roll on the table and "
|
||||
+ "something specific and lasting happens.")],
|
||||
["Damage modifier",
|
||||
P("<span class=\"rb-big-number\">" + egDm + "</span>"
|
||||
+ "STR + SIZ on a ladder. Added to what you hit with.")],
|
||||
["Zero hit points",
|
||||
P("<strong>You are dying, not dead.</strong> A clock starts, equal to your Major "
|
||||
+ "Wound number, and somebody can still reach you.")]
|
||||
])
|
||||
+ P("Damage comes off your total <strong>and</strong> the part of you it hit. Armour "
|
||||
+ "only counts where it actually covers — a helmet does nothing for your legs.")
|
||||
|
||||
+ H(2, "Coherence")
|
||||
+ P("Ten down to zero. How firmly the world still agrees you belong here. It is not "
|
||||
+ "sanity. Spend it, lose it to paradoxes, get it back slowly at home.")
|
||||
+ P("<strong>Watch the band, not the number</strong> — the sheet tells you which one you "
|
||||
+ "are in and what it is costing you.")
|
||||
|
||||
+ H(2, "Six things new players ask")
|
||||
+ TABLE(["", ""], [
|
||||
["<strong>Can I just shoot it?</strong>",
|
||||
"Often, yes. It rarely closes the case — the crossing that produced it is still open."],
|
||||
["<strong>What do I roll?</strong>",
|
||||
"Say what you are doing. The GM names the skill. It is on your sheet with its four numbers beside it."],
|
||||
["<strong>I failed. Now what?</strong>",
|
||||
"The story continues. A failure costs time, resources, position or clarity — it does not stop the case."],
|
||||
["<strong>Can I help somebody?</strong>",
|
||||
"Yes, and you should. Assisting is a real action, and most specialist scenes want a second pair of hands."],
|
||||
["<strong>My skill is 5%. Why?</strong>",
|
||||
"You are untrained at it. That is honest, not a mistake. Ask whoever <em>is</em> trained."],
|
||||
["<strong>Am I going to die?</strong>",
|
||||
"You can. You are much more likely to come home changed, out of pocket, and holding a form you do not want to sign."]
|
||||
])
|
||||
+ CLOSER("Roll under. Ask questions. Take the shield.")
|
||||
}
|
||||
];
|
||||
Reference in New Issue
Block a user