Files
RingBRP/tools/primer-text.mjs
slaguru666andClaude Opus 5 24b1aaa682 Development: skills improve from what beat you, not what worked
Reversed experience. A skill is marked when it FAILS - failure or fumble - and
never when it works, and the improvement roll is the same reversal: roll OVER
your rating and the skill goes up 1d6. Nobody learns anything from the lock that
opened first time.

The two halves pull against each other on purpose, so ratings converge instead of
running away. Simulated over 2000 campaigns: a 10% and an 85% skill start 75
points apart and are 11 apart after 60 cases.

- rules.mjs owns it: DEVELOPMENT, developmentMarksOn, developmentImproves,
  developmentCeiling, developmentGainFor
- trainingCap has been on the skill schema and the item sheet since it was
  written and nothing has ever read it. Development reads it now: a skill stops
  at its own cap, and everything else stops at DEVELOPMENT.ceiling, because a
  d6 gain could otherwise push a 98% skill past 100
- GM opens the phase from a GM-only toggle in the token controls; it posts a
  card naming every agent and their marked count, and puts a Development button
  on each sheet. Players press their own
- the toggle re-reads the setting on change, since getSceneControlButtons only
  fires at load and the button would otherwise lie if the phase were opened by
  the API or a second GM
- rules journal and player primer rewritten from the tables; the old text
  described the pre-reversal rule on both halves
- 7 spot-checks, 4 behaviour tests; verified end to end in a running Foundry

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

576 lines
36 KiB
JavaScript
Raw Permalink 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 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,
PANIC_BANDS, PANIC_TRIGGERS, PANIC_RELIEF, PANIC_MAX, PANIC_AFTER_BREAK, PANIC_BREAK,
DEVELOPMENT
} 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: "8b · Panic — the other track", content:
P("Coherence is slow. It measures what <em>knowing</em> has cost you, and it is counted "
+ "in weeks. <strong>Panic is fast.</strong> It measures what is happening to you right "
+ "now, and it is counted in minutes.")
+ P("<span class=\"rb-big-number\">Panic runs 0 to ${PANIC_MAX}. It goes UP.</span>"
+ "You take a point when something happens to you. <strong>There is no roll to take "
+ "one</strong> — nobody has to roll to find out whether they are frightened.")
+ TABLE(["Panic", "You are", "It costs you"],
[...PANIC_BANDS].reverse().map(b =>
[String(b.min), `<strong>${L(b.label)}</strong>`,
b.all ? `${pc(b.all)} to everything` : "nothing"]))
+ P("<strong>The first point is free.</strong> At 1 you are rattled and you roll at no "
+ "penalty at all. It is only from 2 that it starts costing you.")
+ H(2, "What puts a point on")
+ TABLE(["This happens to you", "Panic"],
Object.values(PANIC_TRIGGERS).map(t => [L(t.label), `+${t.cost}`]))
+ H(2, "What takes one off")
+ P("<strong>Roll it.</strong> A success takes one point off, a special or better takes "
+ "two — and a <strong>fumble puts one on</strong>, because trying to steady somebody "
+ "and making it worse is the most familiar thing on this list.")
+ TABLE(["How", "Who it helps", "How often"],
Object.values(PANIC_RELIEF).map(r => [L(r.label), r.reach === "self" ? "you"
: r.reach === "other" ? "somebody else" : "everyone who hears it", r.once]))
+ P("<strong>Psychology</strong> is the only one that steadies somebody who is not you. "
+ "<strong>Occult</strong> is the only one that reaches a whole room — naming a thing "
+ "correctly is the job. <strong>Religion</strong> steadies you alone, and does not "
+ "care whether you are right.")
+ H(2, "Breaking")
+ P("At ${PANIC_MAX} you break. Choose one — <strong>${PANIC_BREAK.join(', ')}</strong> — take "
+ "it, and come back at <strong>${PANIC_AFTER_BREAK}</strong>. Not at nought. Breaking is not a rest.")
+ P("<strong>Panic clears when the scene does.</strong> Somewhere safe, door shut, "
+ "somebody has made tea: you are back to nought. Coherence does not clear.") },
{ name: "8c · Getting better at things", content:
P("<span class=\"rb-big-number\">You improve at what BEATS you.</span>"
+ "Not at what you are already good at. If you have played this kind of game before, "
+ "this is the opposite way round, and it is deliberate.")
+ H(2, "During the case")
+ P("<strong>Every time a skill fails you, it gets marked.</strong> You do not have to "
+ "do anything &mdash; the sheet marks it. A success marks nothing. Nobody learns "
+ "anything from the lock that opened first time.")
+ H(2, "At the end")
+ P("The GM opens the <strong>development phase</strong> and a button appears on your "
+ "sheet. Press it once. For every marked skill you roll 1D100, and:")
+ TABLE(["The roll", "What happens"], [
["<strong>Over</strong> your current rating &mdash; another failure",
"<strong>the skill goes up ${DEVELOPMENT.gain}</strong>"],
["Equal to or under your rating &mdash; you made it",
"nothing. You knew that already."]
])
+ P("Marks clear either way, and the next case starts clean.")
+ P("<strong>Why it works like that.</strong> A skill you are bad at fails all the "
+ "time, so it gets marked, and then it fails the development roll too, so it climbs "
+ "fast. A skill you are already good at hardly ever fails, and when it does it "
+ "usually passes the development roll and gains nothing. Everybody's weak spots "
+ "close and nobody runs away with a 95%.") },
{ 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 two tracks")
+ TABLE(["", "Range", "Direction", "Clears"], [
["<strong>Panic</strong> — what is happening now",
`0–${PANIC_MAX}`, "up", "when the scene ends"],
["<strong>Coherence</strong> — what knowing has cost",
"10–0", "down", "a debrief, or a week at home"]
])
+ P("Panic: the first point is free, then "
+ [...PANIC_BANDS].reverse().filter(b => b.all)
.map(b => `<strong>${b.min}</strong> is ${pc(b.all)}`).join(", ")
+ ". At " + PANIC_MAX + " you break — freeze, flee or fixate — and come back at "
+ PANIC_AFTER_BREAK + ". A relief roll takes one off, a special two, "
+ "<strong>a fumble puts one on</strong>.")
+ 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.")
}
];