Nothing validated creature specs. check-kits validates postings and check-scenarios validates prose; buildActor throws on an unknown skill or item key and is silent about everything else, so species, marksmanship style, characteristic blocks, species-locked talents and misspelt field names could all be wrong and still build, pack and play. Every packed actor once shipped as species "hominid" and the whole cast was hit-located as something the rules do not contain. tools/creature-schema.mjs derives every allowed value from the catalogues rather than retyping them: skills from SKILL_CATALOGUE, kit from the item catalogues, species from SPECIES, stats from CHARACTERISTIC_DICE, and the two marksmanship styles out of the language file. It reports every problem with a spec rather than dying on the first, and names the nearest real key. It also reports fields buildActor never reads, because "armours:" is not an error, it is an actor that silently equips nothing. On its first run it found six baseline humans carrying vesh biology. "unblinking" is cat: species, species: vesh, and its own rule text reads "A Vesh has no face to read". Out of Step is reclassified as anomalous with a Coherence cost, which is what every talent in that category has and what the Anchor who is three years older than his birthday says always wanted to be; the other five are swapped to agency and field talents that fit them. Not fixed here: the root cause is upstream in postings.mjs, where twelve posting talent pools still offer unblinking, distributed or quorum to any agent. Picking replacements across those is a design decision, not a mechanical one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
318 lines
19 KiB
JavaScript
318 lines
19 KiB
JavaScript
/**
|
|
* THE DUTY ROSTER — ten ready-to-play agents for the compendium.
|
|
*
|
|
* Written compactly: a posting or a trade, a rank, eight characteristics and a face.
|
|
* Skills and kit are expanded from the register at build time by the same helper the
|
|
* starter scenario uses, so a roster agent cannot drift from the generator. Change
|
|
* TRADE_BANDS and every doctor on this list changes with it.
|
|
*
|
|
* Postings and trades in equal numbers on purpose. A group picking from this sheet
|
|
* sees both halves of character creation without anybody having to explain them, and
|
|
* no two entries share a posting or a trade.
|
|
*
|
|
* Portraits are generated art, one per agent, in art/portraits.
|
|
*/
|
|
|
|
export const ROSTER = [
|
|
|
|
/* ---------------- postings: career department ---------------- */
|
|
|
|
{ key: "pc_holloway", name: "Marta Holloway", role: "lead", rank: "Senior Field Officer",
|
|
portrait: "pc_holloway", chosenStyle: "reflex", species: "baseline",
|
|
age: { chronological: 44, physiological: 44 },
|
|
ch: { str: 11, siz: 12, con: 13, int: 15, pow: 14, dex: 14, cha: 14, edu: 13 },
|
|
talents: ["first_through", "read_room", "clean_scene"],
|
|
bio: "<p>Nineteen years, four of them on secondment to an office that no longer "
|
|
+ "appears in the directory. Signs for everything and reads it first.</p>"
|
|
+ "<p><em>Anchor:</em> a wedding ring she does not wear, kept in a coin envelope "
|
|
+ "in her jacket.</p>" },
|
|
|
|
{ key: "pc_okonkwo", name: "Ben Okonkwo", role: "heavy", rank: "Field Officer",
|
|
portrait: "pc_okonkwo", chosenStyle: "reflex", species: "baseline",
|
|
age: { chronological: 33, physiological: 33 },
|
|
ch: { str: 17, siz: 16, con: 16, int: 11, pow: 11, dex: 13, cha: 11, edu: 10 },
|
|
talents: ["hard_landing", "first_through"],
|
|
bio: "<p>Goes through the door and holds the corridor while everyone else does the "
|
|
+ "clever part. Has never once complained about being the one who is hit.</p>"
|
|
+ "<p><em>Anchor:</em> his sister's voice note, saved and never deleted.</p>" },
|
|
|
|
{ key: "pc_sandoval", name: "Rey Sandoval", role: "overwatch", rank: "Field Officer",
|
|
portrait: "pc_sandoval", chosenStyle: "deliberate", species: "baseline",
|
|
age: { chronological: 37, physiological: 37 },
|
|
ch: { str: 12, siz: 12, con: 13, int: 14, pow: 13, dex: 17, cha: 10, edu: 12 },
|
|
talents: ["first_through", "read_room"],
|
|
bio: "<p>Takes the long lane and the long view. Keeps a private list of the shots "
|
|
+ "she chose not to take, and will not discuss it.</p>"
|
|
+ "<p><em>Anchor:</em> a paperback with somebody else's name inside the cover.</p>" },
|
|
|
|
{ key: "pc_bhattacharya", name: "Arun Bhattacharya", role: "registry",
|
|
rank: "Senior Field Officer", portrait: "pc_bhattacharya",
|
|
chosenStyle: "deliberate", species: "baseline",
|
|
age: { chronological: 56, physiological: 56 },
|
|
ch: { str: 10, siz: 11, con: 11, int: 17, pow: 15, dex: 11, cha: 12, edu: 17 },
|
|
talents: ["chain_custody", "debriefer", "requisition"],
|
|
bio: "<p>Thirty years in the registry. Has read files that were withdrawn "
|
|
+ "before he finished them and remembers what was in them anyway.</p>"
|
|
+ "<p><em>Anchor:</em> a brass slide rule that belonged to his predecessor.</p>" },
|
|
|
|
{ key: "pc_rahimi", name: "Soraya Rahimi", role: "settlements", rank: "Field Officer",
|
|
portrait: "pc_rahimi", chosenStyle: "deliberate", species: "baseline",
|
|
age: { chronological: 41, physiological: 41 },
|
|
ch: { str: 10, siz: 11, con: 12, int: 15, pow: 15, dex: 11, cha: 17, edu: 15 },
|
|
talents: ["read_room", "debriefer"],
|
|
bio: "<p>Writes the arrangement that everyone can live with, then holds both sides "
|
|
+ "to it. Has never raised her voice on an operation.</p>"
|
|
+ "<p><em>Anchor:</em> her mother's tea glass, chipped, carried in a sock.</p>" },
|
|
|
|
/* ---------------- trades: recruited from an ordinary job ---------------- */
|
|
|
|
{ key: "pc_nkemdirim", name: "Dr Chidi Nkemdirim", trade: "doctor", rank: "Probationary",
|
|
portrait: "pc_nkemdirim", chosenStyle: "deliberate", species: "baseline",
|
|
age: { chronological: 32, physiological: 32 },
|
|
ch: { str: 10, siz: 11, con: 12, int: 16, pow: 13, dex: 13, cha: 13, edu: 18 },
|
|
talents: ["triage", "read_room"],
|
|
bio: "<p>Six years of emergency medicine and eleven months of this. Still the "
|
|
+ "calmest person in the room, and still startled by what the room contains.</p>"
|
|
+ "<p><em>Anchor:</em> a pager that has not worked since 2019.</p>" },
|
|
|
|
{ key: "pc_kowalczyk", name: "Marek Kowalczyk", trade: "engineer", rank: "Probationary",
|
|
portrait: "pc_kowalczyk", chosenStyle: "reflex", species: "baseline",
|
|
age: { chronological: 30, physiological: 30 },
|
|
ch: { str: 13, siz: 13, con: 13, int: 17, pow: 11, dex: 14, cha: 11, edu: 15 },
|
|
talents: ["hard_landing", "requisition"],
|
|
bio: "<p>Chartered, unimpressed, and the only person who asks what a thing is made "
|
|
+ "of before asking what it wants.</p>"
|
|
+ "<p><em>Anchor:</em> the first tool he ever bought, resharpened forty times.</p>" },
|
|
|
|
{ key: "pc_finch", name: "Wren Finch", trade: "criminal", rank: "Probationary",
|
|
portrait: "pc_finch", chosenStyle: "reflex", species: "baseline",
|
|
age: { chronological: 26, physiological: 26 },
|
|
ch: { str: 10, siz: 10, con: 12, int: 14, pow: 12, dex: 18, cha: 14, edu: 9 },
|
|
talents: ["cover_work", "clean_scene"],
|
|
bio: "<p>Opens things without leaving a mark, which is the part she is proud of. "
|
|
+ "Signed the Act in an interview room, which is the part she is not.</p>"
|
|
+ "<p><em>Anchor:</em> a door key to a flat that was demolished.</p>" },
|
|
|
|
{ key: "pc_agyeman", name: "Joseph Agyeman", trade: "police", rank: "Field Officer",
|
|
portrait: "pc_agyeman", chosenStyle: "reflex", species: "baseline",
|
|
age: { chronological: 52, physiological: 52 },
|
|
ch: { str: 14, siz: 14, con: 14, int: 13, pow: 13, dex: 12, cha: 14, edu: 12 },
|
|
talents: ["read_room", "chain_custody", "first_through"],
|
|
bio: "<p>Twenty-six years on division before somebody handed him a file with no "
|
|
+ "crime number. Knows how a statement is taken and how a scene is lost.</p>"
|
|
+ "<p><em>Anchor:</em> his old warrant number, written inside his watch strap.</p>" },
|
|
|
|
{ key: "pc_lindqvist", name: "Professor Åke Lindqvist", trade: "professor",
|
|
rank: "Field Officer", portrait: "pc_lindqvist",
|
|
chosenStyle: "deliberate", species: "baseline",
|
|
age: { chronological: 63, physiological: 63 },
|
|
ch: { str: 9, siz: 11, con: 10, int: 17, pow: 16, dex: 10, cha: 14, edu: 19 },
|
|
talents: ["debriefer", "wrong_memory", "read_room"],
|
|
bio: "<p>A chair in comparative folklore and a reading list nobody else has "
|
|
+ "finished. Was consulted once and never quite sent home.</p>"
|
|
+ "<p><em>Anchor:</em> a photograph of a lecture theatre, taken from the back.</p>" },
|
|
|
|
/* ---------------- second intake ---------------- */
|
|
/* Six more, written for the group that actually plays. Three postings and three
|
|
trades again, and none of the six repeats a posting or a trade already on the
|
|
sheet above, so the roster covers sixteen different jobs and no two players
|
|
pick the same character by accident. */
|
|
|
|
{ key: "pc_pollard", name: "Gareth Pollard", role: "warden", rank: "Field Officer",
|
|
portrait: "pc_pollard", chosenStyle: "reflex", species: "baseline",
|
|
age: { chronological: 47, physiological: 47 },
|
|
ch: { str: 14, siz: 15, con: 16, int: 12, pow: 13, dex: 12, cha: 11, edu: 10 },
|
|
talents: ["chain_custody", "first_through"],
|
|
bio: "<p>Sits with a site all night so that nobody else has to, and writes down the "
|
|
+ "times. Has never once been found asleep and never once complained about it.</p>"
|
|
+ "<p><em>Anchor:</em> a wind-up alarm clock that has not kept time since 2003.</p>" },
|
|
|
|
{ key: "pc_ashcroft", name: "Dominic Ashcroft", role: "anchor", rank: "Senior Field Officer",
|
|
portrait: "pc_ashcroft", chosenStyle: "deliberate", species: "baseline",
|
|
age: { chronological: 38, physiological: 41 },
|
|
ch: { str: 11, siz: 12, con: 13, int: 15, pow: 17, dex: 13, cha: 12, edu: 14 },
|
|
talents: ["out_of_step", "borrowed_hour", "not_quite_here"],
|
|
bio: "<p>Holds the door open while everyone else goes through it, and stays filed "
|
|
+ "correctly for as long as that takes. Three years older than his birthday says "
|
|
+ "and does not discuss the difference.</p>"
|
|
+ "<p><em>Anchor:</em> his own school tie, cut in half; the department has the "
|
|
+ "other half.</p>" },
|
|
|
|
{ key: "pc_braithwaite", name: "Neil Braithwaite", role: "pathology", rank: "Field Officer",
|
|
portrait: "pc_braithwaite", chosenStyle: "deliberate", species: "baseline",
|
|
age: { chronological: 51, physiological: 51 },
|
|
ch: { str: 11, siz: 12, con: 12, int: 17, pow: 12, dex: 13, cha: 10, edu: 17 },
|
|
talents: ["chain_custody", "clean_scene", "triage"],
|
|
bio: "<p>Establishes what a thing was, which is usually the question the case turns "
|
|
+ "on. Talks to the table, not to the room, and is right often enough that "
|
|
+ "nobody minds.</p>"
|
|
+ "<p><em>Anchor:</em> a fountain pen his father used to sign death "
|
|
+ "certificates.</p>" },
|
|
|
|
{ key: "pc_renshaw", name: "Kate Renshaw", role: "interview", rank: "Senior Field Officer",
|
|
portrait: "pc_renshaw", chosenStyle: "deliberate", species: "baseline",
|
|
age: { chronological: 44, physiological: 44 },
|
|
ch: { str: 10, siz: 11, con: 12, int: 15, pow: 14, dex: 12, cha: 17, edu: 15 },
|
|
talents: ["debriefer", "read_room", "wrong_memory"],
|
|
bio: "<p>Sits with the witness afterwards, for as long as it takes, and is very hard "
|
|
+ "to lie to twice. Gets more out of an hour and a pot of tea than the rest of "
|
|
+ "the department gets out of a warrant.</p>"
|
|
+ "<p><em>Anchor:</em> a hair clip that belonged to a woman whose statement she "
|
|
+ "never finished taking.</p>" },
|
|
|
|
{ key: "pc_sharrock", name: "Danny Sharrock", trade: "builder", rank: "Probationary",
|
|
portrait: "pc_sharrock", chosenStyle: "reflex", species: "baseline",
|
|
age: { chronological: 42, physiological: 42 },
|
|
ch: { str: 16, siz: 15, con: 15, int: 12, pow: 10, dex: 14, cha: 12, edu: 9 },
|
|
talents: ["hard_landing", "first_through"],
|
|
bio: "<p>Twenty years of sites and other people's mistakes. Knows what a building is "
|
|
+ "about to do a good few seconds before it does it, and says so, loudly.</p>"
|
|
+ "<p><em>Anchor:</em> a bent nail from the first roof he ever stripped, kept in "
|
|
+ "a tobacco tin.</p>" },
|
|
|
|
{ key: "pc_ferriby", name: "Colin Ferriby", trade: "it", rank: "Probationary",
|
|
portrait: "pc_ferriby", chosenStyle: "deliberate", species: "baseline",
|
|
age: { chronological: 29, physiological: 29 },
|
|
ch: { str: 10, siz: 12, con: 11, int: 17, pow: 12, dex: 13, cha: 10, edu: 14 },
|
|
talents: ["echo_sense", "chain_custody"],
|
|
bio: "<p>Second line, then infrastructure. Has read the logs nobody else reads and "
|
|
+ "believes them over people, which is either the reason he is here or the "
|
|
+ "reason he was let go.</p>"
|
|
+ "<p><em>Anchor:</em> a dead hard drive with his own handwriting on the "
|
|
+ "label.</p>" }
|
|
];
|
|
|
|
/**
|
|
* THE SAME TEN, BEFORE THE DEPARTMENT FOUND THEM.
|
|
*
|
|
* Same person, same characteristics, same face — a civilian who has not been inducted
|
|
* and has never been issued anything. Skills and tools come from the trade alone, so a
|
|
* player can hold the two sheets side by side and see exactly what the job did to them.
|
|
* (The linguist is the one who already speaks trade cant before she arrives. It is a
|
|
* language, and she is a linguist.)
|
|
*
|
|
* Five of the ten went into the department straight into a posting rather than out of a
|
|
* trade, so this is where they came from. Between them the ten cover all ten trades
|
|
* exactly once, which means this set also shows a table the whole trade half of
|
|
* character creation without anybody having to read the rules for it.
|
|
*
|
|
* `job` replaces the rank line on the sheet. The second paragraph of each biography is
|
|
* the seam: what they did that got them noticed. It is the hook a GM pulls on.
|
|
*/
|
|
export const BEFORE = {
|
|
|
|
pc_holloway: { trade: "linguist", job: "Court Interpreter", age: 25,
|
|
bio: "<p>Interpreting at magistrates' courts and immigration tribunals. Ten hours a "
|
|
+ "day saying other people's worst sentences in the first person, and going home "
|
|
+ "able to repeat every one of them.</p>"
|
|
+ "<p><em>How they found her:</em> she interpreted a statement nobody had asked "
|
|
+ "her to hear, and then wrote down what the room had done while it was said.</p>" },
|
|
|
|
pc_okonkwo: { trade: "builder", job: "Steel Erector", age: 28,
|
|
bio: "<p>Ten storeys up on a frame with no floor in it yet, in weather that sends "
|
|
+ "everyone else down the ladder. Never late, never sick, and never the one who "
|
|
+ "says the job cannot be done.</p>"
|
|
+ "<p><em>How they found him:</em> he carried two men off a site that had gone "
|
|
+ "wrong in a way the report could not describe, and he did not ask a single "
|
|
+ "question about it.</p>" },
|
|
|
|
pc_sandoval: { trade: "it", job: "Network Field Engineer", age: 31,
|
|
bio: "<p>Alone at the top of a mast in the dark with a laptop and a flask, fixing "
|
|
+ "links for towns that only notice the network when it stops. Long climbs, long "
|
|
+ "views, long silences.</p>"
|
|
+ "<p><em>How they found her:</em> a relay logged forty minutes of traffic from a "
|
|
+ "site decommissioned in 1994, and she kept the file instead of clearing it.</p>" },
|
|
|
|
pc_bhattacharya: { trade: "scientist", job: "Research Chemist", age: 26,
|
|
bio: "<p>An industrial laboratory, a bench, and a notebook filled in the same hand "
|
|
+ "every day for four years. Slow, exact, and constitutionally unable to leave a "
|
|
+ "discrepancy alone.</p>"
|
|
+ "<p><em>How they found him:</em> he noticed a batch record amended by somebody "
|
|
+ "who had never worked there, and he raised it in writing. Twice.</p>" },
|
|
|
|
pc_rahimi: { trade: "entertainer", job: "Local Radio Presenter", age: 33,
|
|
bio: "<p>Mid-morning phone-in on a station nobody outside the county has heard of. "
|
|
+ "Six years of listening to people who rang up angry and hung up calmer.</p>"
|
|
+ "<p><em>How they found her:</em> she talked a caller down from something on air "
|
|
+ "for eleven minutes, and the recording of those minutes does not play back the "
|
|
+ "same way twice.</p>" },
|
|
|
|
pc_nkemdirim: { trade: "doctor", job: "Registrar, Emergency Medicine", age: 31,
|
|
bio: "<p>Six years of A&E and the flat calm that comes with it. The one the "
|
|
+ "department turns to when nobody else will say the decision out loud.</p>"
|
|
+ "<p><em>How they found them:</em> a patient came in with an injury that had "
|
|
+ "healed in the wrong direction, and the notes they wrote were too good to leave "
|
|
+ "in the system.</p>" },
|
|
|
|
pc_kowalczyk: { trade: "engineer", job: "Structural Engineer", age: 29,
|
|
bio: "<p>Chartered, practical, and happiest with a survey and a set of drawings that "
|
|
+ "disagree. Asks what a thing is made of before he asks what it is for.</p>"
|
|
+ "<p><em>How they found him:</em> he condemned a footbridge on load figures that "
|
|
+ "were correct, for a span he had measured three times and got three "
|
|
+ "answers.</p>" },
|
|
|
|
pc_finch: { trade: "criminal", job: "Between Things", age: 25,
|
|
bio: "<p>Not organised, and not caught either — doors, locks and other people's "
|
|
+ "assumptions, worked quietly and left tidy. Rents by the week and pays cash.</p>"
|
|
+ "<p><em>How they found her:</em> she was inside a building she had no business "
|
|
+ "in on the afternoon it stopped being on the street it was on, and she was the "
|
|
+ "only witness who could describe the inside.</p>" },
|
|
|
|
pc_agyeman: { trade: "police", job: "Detective Sergeant", age: 50,
|
|
bio: "<p>Twenty-six years on division. Takes a statement properly, keeps a scene, "
|
|
+ "and has watched three generations of the same families come through the same "
|
|
+ "door.</p>"
|
|
+ "<p><em>How they found him:</em> a file with no crime number landed on his desk "
|
|
+ "and he worked it anyway, on his own time, and got further than he was meant "
|
|
+ "to.</p>" },
|
|
|
|
pc_lindqvist: { trade: "professor", job: "Professor of Comparative Folklore", age: 59,
|
|
bio: "<p>A chair, a reading list, and forty years of stories about places that take "
|
|
+ "people and give them back wrong. Treats them as evidence rather than as "
|
|
+ "literature.</p>"
|
|
+ "<p><em>How they found him:</em> he was consulted once, as an academic "
|
|
+ "courtesy, and gave an answer so specific that he was never quite sent "
|
|
+ "home.</p>" },
|
|
|
|
pc_pollard: { trade: "police", job: "Custody Sergeant", age: 39,
|
|
bio: "<p>Nights, on the custody desk, for eleven years. Signs people in, signs them "
|
|
+ "out, and writes down the times, which is the entire job and most of the "
|
|
+ "reason it is done properly.</p>"
|
|
+ "<p><em>How they found him:</em> he logged a detainee in and out on the same "
|
|
+ "sheet, in his own hand, forty minutes apart, and there is no detainee.</p>" },
|
|
|
|
pc_ashcroft: { trade: "scientist", job: "Research Physicist", age: 31,
|
|
bio: "<p>A university group, a beamline, and six years of measuring one thing very "
|
|
+ "carefully. Comfortable with results that are correct and unwelcome.</p>"
|
|
+ "<p><em>How they found him:</em> he published a null result nobody wanted, twice, "
|
|
+ "and the second time he included the run his supervisor had asked him to "
|
|
+ "drop.</p>" },
|
|
|
|
pc_braithwaite: { trade: "doctor", job: "Consultant Histopathologist", age: 41,
|
|
bio: "<p>Twenty years of establishing what a thing was. Talks to the specimen rather "
|
|
+ "than the room, and is right often enough that nobody minds.</p>"
|
|
+ "<p><em>How they found him:</em> he reported a sample as being from a tissue "
|
|
+ "type he could not name, in writing, under his own signature, and refused to "
|
|
+ "amend it.</p>" },
|
|
|
|
pc_renshaw: { trade: "entertainer", job: "Actor", age: 37,
|
|
bio: "<p>Eleven years of rep, tour and two soaps, and a decade before that of front "
|
|
+ "of house. Learned to read a room from the wrong side of the lights and never "
|
|
+ "stopped doing it.</p>"
|
|
+ "<p><em>How they found her:</em> she took a witness statement off a stranger in "
|
|
+ "a foyer, unasked, in longhand, and it is the best one anybody took.</p>" },
|
|
|
|
pc_sharrock: { trade: "builder", job: "Site Foreman", age: 39,
|
|
bio: "<p>Twenty years of sites, scaffold and other people's mistakes, and a voice "
|
|
+ "that carries across one. Knows what a building is about to do.</p>"
|
|
+ "<p><em>How they found him:</em> he stopped a pour because a wall was in the "
|
|
+ "wrong place, and he was right, and the drawings agreed with him afterwards "
|
|
+ "and had not before.</p>" },
|
|
|
|
pc_ferriby: { trade: "it", job: "Infrastructure Engineer", age: 28,
|
|
bio: "<p>Second line, then infrastructure. Has read the logs nobody reads and "
|
|
+ "believes them over people, which has cost him two jobs.</p>"
|
|
+ "<p><em>How they found him:</em> he kept a packet capture his employer told him "
|
|
+ "twice to delete, because the timestamps in it do not run in order.</p>" }
|
|
};
|