A Foundry VTT game system: Basic Roleplaying d100 in a drab British agency that deals with crossings, where reality keeps a record of what belongs where and can be made to get it wrong. Contents: - rules.mjs as the single authority for every rule formula, with 40 rules and 132 spot-checked values. Nothing may restate a formula it defines. - 25 postings, each with a key characteristic, trained skills, talents and its own kit; a 175-item catalogue across four eras; 231 generated icons. - Reaction Lanes initiative, re-rolled every round including the first. - Coherence, and the Quiet Arts, which only ever arrive from a crossing gone wrong. - Graded defences: only a critical stops a critical. - Borrowed Authority — access as a record rather than a hacking roll. - A case generator, and a rules journal generated from rules.mjs so the book cannot drift from the code. Built compendia are committed so a remote Foundry needs only the files. node_modules is not; `npm install` restores it for rebuilding content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
431 lines
32 KiB
JavaScript
431 lines
32 KiB
JavaScript
/**
|
||
* MISSION TABLES — the raw material.
|
||
*
|
||
* Separated from the composer because this file is content and that one is
|
||
* machinery, and they change for entirely different reasons: a table gains rows
|
||
* when the setting gains texture, the composer changes when the SHAPE of a case
|
||
* changes. Keeping them apart means adding twenty new anomalies never risks the
|
||
* logic that makes a brief contradict its truth.
|
||
*
|
||
* Everything here is data. No Foundry, no functions with side effects.
|
||
*/
|
||
|
||
/* ============================================================
|
||
* WHAT IS WRONG
|
||
* ============================================================ */
|
||
export const ANOMALIES = [
|
||
{ id: "held_open", name: "A crossing held open", threat: "structural",
|
||
brief: "A threshold at {place} has been open for {duration} and has not closed.",
|
||
truth: "It is being held open deliberately, from the other side, and whoever is doing it is getting better at it.",
|
||
signs: ["compasses lag by a degree", "the tide comes in twice", "radio carries a conversation nobody is having",
|
||
"the same gull, repeatedly", "batteries drain from full in an hour"],
|
||
evidence: ["a regulator plate, cracked, not agency issue", "scorch in a ring, no heat damage at the centre",
|
||
"eleven days of identical weather readings"],
|
||
containment: "Anyone who walks through it is a second incident.",
|
||
closes: "Find what is holding it and stop holding it. It will close on its own in under a minute.",
|
||
fits: { resolutions: ["close", "map", "arrange"], gaps: ["worse", "already_contained", "wrong_thing", "agency_knew", "local_stake", "clock_running", "the_brief_is_bait", "already_over"],
|
||
adversaries: ["cleaners", "rival_desk", "locals", "quiet_neighbours"], classes: ["near", "deep", "hollow", "wrong"],
|
||
placeKinds: ["remote", "structure", "settled"] } },
|
||
|
||
{ id: "duplicate", name: "A duplicate", threat: "personal",
|
||
brief: "A person at {place} is reported to be in two places, and both are attending work.",
|
||
truth: "There is no original left. Both are copies, and one of them knows.",
|
||
signs: ["two sets of the same fingerprints, hours apart", "a wedding ring that fits neither",
|
||
"a dog that will not go near one of them", "the same signature, improving"],
|
||
evidence: ["a duplicated payslip", "a photograph with one of them slightly out of focus",
|
||
"a dental record that matches twice"],
|
||
containment: "The town has noticed. It has been explained as twins for eleven days.",
|
||
closes: "One of them must be taken into custody. Deciding which is the case.",
|
||
fits: { resolutions: ["custody", "arrange", "close"], gaps: ["not_hostile", "wrong_thing", "one_of_them", "local_stake", "clock_running", "already_over", "the_brief_is_bait"],
|
||
adversaries: ["cleaners", "rival_desk", "locals", "duplicate"], classes: ["near"],
|
||
placeKinds: ["settled"] } },
|
||
|
||
{ id: "misfiled_object", name: "Something misfiled in", threat: "material",
|
||
brief: "An object recovered near {place} is not manufacturable and is not ours.",
|
||
truth: "It is not an object. It is the part of a larger thing that fitted through.",
|
||
signs: ["it is warm on the side away from the light", "instruments read it as absent",
|
||
"it has a serial number in our numerals", "it is heavier on Tuesdays"],
|
||
evidence: ["a second, smaller piece, buried", "tool marks made by nothing in the catalogue",
|
||
"a packing crate with a shipping label for a road that does not exist"],
|
||
containment: "It has been photographed. The photographs are already circulating.",
|
||
closes: "Recover it whole, or account for every piece. Half of it is worse than none.",
|
||
fits: { resolutions: ["recover", "close", "map"], gaps: ["worse", "already_contained", "wrong_thing", "agency_knew", "local_stake", "clock_running", "the_brief_is_bait"],
|
||
adversaries: ["cleaners", "rival_desk", "locals", "quiet_neighbours"], classes: ["near", "deep", "hollow", "wrong"],
|
||
placeKinds: ["remote", "structure", "settled"] } },
|
||
|
||
{ id: "revenant", name: "Something wearing a shape", threat: "personal",
|
||
brief: "A member of the public at {place} has been behaving as somebody who died {duration} ago.",
|
||
truth: "It came through without a body and took the nearest one that was not being used.",
|
||
signs: ["it uses the dead man's turn of phrase", "it does not eat", "it keeps going back to the same doorway",
|
||
"it will not be photographed twice"],
|
||
evidence: ["a grave that has been opened neatly from above", "a coat that fits nobody in the family",
|
||
"a diary continuing in the same hand, badly"],
|
||
containment: "The family have started telling people.",
|
||
closes: "It is looking for something. Give it that, or take the body away from it.",
|
||
fits: { resolutions: ["custody", "close", "arrange"], gaps: ["not_hostile", "wrong_thing", "one_of_them", "local_stake", "already_over", "the_brief_is_bait"],
|
||
adversaries: ["cleaners", "rival_desk", "locals", "revenant"], classes: ["near"],
|
||
placeKinds: ["settled"] } },
|
||
|
||
{ id: "thin_place", name: "A place wearing thin", threat: "structural",
|
||
brief: "Three separate people have walked into {place} and come out somewhere they should not have.",
|
||
truth: "The location is filed twice and the two filings are drifting apart.",
|
||
signs: ["the same building on two streets", "post that arrives before it is sent",
|
||
"a staircase with a landing nobody built", "two sets of church bells, half a beat apart"],
|
||
evidence: ["an Ordnance Survey sheet that disagrees with itself", "a key that opens the same door twice differently",
|
||
"a bus timetable for a route that runs one way only"],
|
||
containment: "It is on a public right of way.",
|
||
closes: "Decide which filing is the true one and make the other untenable.",
|
||
fits: { resolutions: ["close", "map", "handover"], gaps: ["worse", "already_contained", "wrong_thing", "agency_knew", "clock_running", "local_stake"],
|
||
adversaries: ["cleaners", "rival_desk", "locals", "quiet_neighbours"], classes: ["near", "deep", "hollow", "wrong"],
|
||
placeKinds: ["structure", "settled", "remote"] } },
|
||
|
||
{ id: "missing_team", name: "A team that did not come back", threat: "agency",
|
||
brief: "A Custodian team crossed to {place} on a routine survey {duration} ago and their beacon is still transmitting.",
|
||
truth: "Three of the four are still alive. The fourth is what the beacon is transmitting.",
|
||
signs: ["the beacon repeats a fourteen-second interval", "their kit is stacked, not scattered",
|
||
"somebody has been eating", "one set of boots, walking out and not back"],
|
||
evidence: ["a case log with the last four pages torn out", "a regulator with its cell removed and pocketed",
|
||
"four sleeping bags, three used"],
|
||
containment: "The agency will not say the word 'rescue' in writing.",
|
||
closes: "Bring back what is left, and the log. The agency wants the log.",
|
||
fits: { resolutions: ["first_team", "close", "map"], gaps: ["worse", "not_hostile", "wrong_thing", "one_of_them", "local_stake", "already_over", "the_brief_is_bait"],
|
||
adversaries: ["cleaners", "rival_desk", "quiet_neighbours", "duplicate", "revenant"], classes: ["near", "deep", "hollow", "wrong"],
|
||
placeKinds: ["remote", "structure", "settled"] } },
|
||
|
||
{ id: "recurrence", name: "A day that will not file", threat: "structural",
|
||
brief: "{place} has recorded the same eleven minutes four times in {duration}.",
|
||
truth: "Something is being rehearsed, and each attempt gets further before it stops.",
|
||
signs: ["a till roll printing the same sale", "birds landing in identical formation",
|
||
"one witness who remembers all four", "a clock that gains exactly eleven minutes"],
|
||
evidence: ["four identical CCTV files with different timestamps", "a shopping receipt for a future date",
|
||
"a witness statement written in the witness's hand before they gave it"],
|
||
containment: "A local paper has run it as a curiosity.",
|
||
closes: "Find out what the rehearsal is FOR. Stopping the loop without knowing is the mistake.",
|
||
fits: { resolutions: ["close", "map", "arrange"], gaps: ["worse", "already_contained", "wrong_thing", "agency_knew", "clock_running", "already_over", "the_brief_is_bait"],
|
||
adversaries: ["cleaners", "rival_desk", "locals", "quiet_neighbours"], classes: ["near", "deep", "wrong"],
|
||
placeKinds: ["settled", "structure"] } },
|
||
|
||
{ id: "quiet_ledger", name: "A record that disagrees", threat: "agency",
|
||
brief: "The parish records for {place} list a family that no living person remembers.",
|
||
truth: "They were removed from the world and not from the paperwork. Something is using the gap.",
|
||
signs: ["a headstone with no plot", "a house number between two others",
|
||
"a school photograph with a gap in the row", "an electoral roll nobody will amend"],
|
||
evidence: ["a deed of sale signed by a person who does not exist", "a ration book, unused, correctly issued",
|
||
"a christening record in fresh ink on old paper"],
|
||
containment: "An amateur historian has published.",
|
||
closes: "Restore the record or complete the removal. The agency has a preference and will not state it.",
|
||
fits: { resolutions: ["restore", "arrange", "map"], gaps: ["worse", "agency_knew", "local_stake", "one_of_them", "already_over", "the_brief_is_bait", "wrong_thing"],
|
||
adversaries: ["cleaners", "rival_desk", "locals", "quiet_neighbours"], classes: ["near"],
|
||
placeKinds: ["records"] } },
|
||
|
||
{ id: "inheritance", name: "Something left to somebody", threat: "material",
|
||
brief: "A property at {place} has passed to an heir along with a clause the solicitors cannot explain.",
|
||
truth: "The clause is a containment protocol. The family have been Custodians without the word for four generations.",
|
||
signs: ["a room that is cleaned but not used", "a standing order to a bank that closed",
|
||
"instructions in three hands, the earliest in Latin", "a key held by the solicitor, not the family"],
|
||
evidence: ["a household ledger with one line per decade", "a photograph of a previous agency team, uncaptioned",
|
||
"a will that names the agency without naming it"],
|
||
containment: "The heir intends to sell.",
|
||
closes: "Take up the protocol, or persuade the heir to. There is no third answer that holds.",
|
||
fits: { resolutions: ["handover", "arrange", "custody"], gaps: ["not_hostile", "wrong_thing", "one_of_them", "local_stake", "clock_running", "the_brief_is_bait"],
|
||
adversaries: ["cleaners", "rival_desk", "locals", "quiet_neighbours"], classes: ["near"],
|
||
placeKinds: ["settled", "structure"] } },
|
||
|
||
{ id: "wrong_weather", name: "Weather that is not from here", threat: "structural",
|
||
brief: "{place} has had {duration} of conditions that do not appear in any regional record.",
|
||
truth: "It is not weather. It is the other side's weather, arriving through a gap too small to walk through.",
|
||
signs: ["snow that does not melt on skin", "rain that falls upward at the edges",
|
||
"a wind with a direction but no source", "pollen from nothing that grows here"],
|
||
evidence: ["a sample that is not water", "a barometer reading below the survivable",
|
||
"livestock refusing one specific field"],
|
||
containment: "The Met Office has asked a question in writing.",
|
||
closes: "Find the gap. It is smaller than a letterbox and it is somewhere absurd.",
|
||
fits: { resolutions: ["close", "map", "recover"], gaps: ["worse", "already_contained", "wrong_thing", "agency_knew", "clock_running", "the_brief_is_bait"],
|
||
adversaries: ["cleaners", "rival_desk", "locals", "quiet_neighbours"], classes: ["near", "deep", "hollow", "wrong"],
|
||
placeKinds: ["remote", "structure", "settled"] } }
|
||
];
|
||
|
||
/* ============================================================
|
||
* WHERE AND WHEN
|
||
* ============================================================ */
|
||
export const CLASSES = [
|
||
{ id: "near", name: "Near", weight: 5, modifier: 0, era: "modern",
|
||
note: "Almost this. Recognisable, survivable, dangerous because you relax.",
|
||
texture: ["the shops are the wrong chain", "the coins are right and the queen is not",
|
||
"everyone is very slightly better dressed", "a war ended a year late"] },
|
||
{ id: "deep", name: "Deep", weight: 2, modifier: -10, era: "future",
|
||
note: "The error resolved a long way out. The biology went another way, or the physics did.",
|
||
texture: ["the trees are wrong at the root", "there are no birds and something else does that job",
|
||
"the light arrives a moment after the sun moves", "the buildings are grown"] },
|
||
{ id: "hollow", name: "Hollow", weight: 2, modifier: -10, era: "antique",
|
||
note: "Nobody home. Something used to be.",
|
||
texture: ["the tables are laid", "a fire that has been out for a long time and is warm",
|
||
"no bodies anywhere", "every door open, every gate shut"] },
|
||
{ id: "wrong", name: "Wrong", weight: 1, modifier: -20, era: "anomalous",
|
||
note: "It does not behave, and the technique does not work cleanly there. Not given to probationers.",
|
||
texture: ["distances do not agree between two people", "the dead speak but only in the plural",
|
||
"your own handwriting is unfamiliar", "there is a sound that instruments do not record"] }
|
||
];
|
||
|
||
/**
|
||
* Places, tagged. `settled` means there are people here to talk to; `records` means
|
||
* there is an institution with paperwork; `structure` means there is a building to
|
||
* be inside. An anomaly that needs a parish register cannot happen in a forestry
|
||
* plantation, and one about a man attending work cannot happen on a canal bank.
|
||
*/
|
||
export const PLACES = [
|
||
{ text: "a stretch of coast outside Ilfracombe", kinds: ["remote"] },
|
||
{ text: "a flooded quarry above Nenthead", kinds: ["remote"] },
|
||
{ text: "the back of a working reservoir in Powys", kinds: ["remote", "structure"] },
|
||
{ text: "a decommissioned filter house on the Lea", kinds: ["structure"] },
|
||
{ text: "a farm at the end of a private road in Angus", kinds: ["settled", "structure"] },
|
||
{ text: "a pier that closed in 1974", kinds: ["structure"] },
|
||
{ text: "sixty acres of managed forestry near Kielder", kinds: ["remote"] },
|
||
{ text: "a chalk pit outside Ashford", kinds: ["remote"] },
|
||
{ text: "the top four floors of a car park in Coventry", kinds: ["structure", "settled"] },
|
||
{ text: "a tidal island off the Solway", kinds: ["remote"] },
|
||
{ text: "an aerodrome that appears on one map", kinds: ["structure", "records"] },
|
||
{ text: "a village with two war memorials", kinds: ["settled", "records", "structure"] },
|
||
{ text: "a Victorian pumping station under a retail park", kinds: ["structure"] },
|
||
{ text: "a stretch of canal with no towpath", kinds: ["remote"] },
|
||
{ text: "a boarding school out of term", kinds: ["structure", "records"] },
|
||
{ text: "a caravan site closed for the winter", kinds: ["structure"] },
|
||
{ text: "a market town with a working parish office", kinds: ["settled", "records", "structure"] },
|
||
{ text: "a colliery village three streets wide", kinds: ["settled", "structure"] },
|
||
{ text: "a county records office over a bank", kinds: ["settled", "records", "structure"] },
|
||
{ text: "a hospital annexe awaiting demolition", kinds: ["structure", "records"] }
|
||
];
|
||
|
||
/**
|
||
* Split, because the brief says "has been open for {duration}" and "since the
|
||
* spring" cannot follow "for". Spans are countable; vague dates are not, and only
|
||
* spans may be substituted into a slot that expects a length of time.
|
||
*/
|
||
export const DURATION_SPANS = ["four days", "eleven days", "three weeks", "nineteen hours",
|
||
"two months", "six days", "a fortnight"];
|
||
export const DURATION_VAGUE = ["the spring", "a date nobody will confirm", "the last hard frost"];
|
||
|
||
/* ============================================================
|
||
* THE GAP — the specific way the brief is wrong
|
||
* ============================================================ */
|
||
export const GAPS = [
|
||
{ id: "worse", name: "It is worse than briefed",
|
||
text: "The scale in the brief is out by an order of magnitude, and the brief was written before the last two reports came in.",
|
||
reveal: "This phase makes the true scale unmissable." },
|
||
{ id: "already_contained", name: "Somebody has already contained it",
|
||
text: "Somebody got there first and did the job badly. Their solution is holding, and it is the reason things are getting worse.",
|
||
reveal: "The team find the earlier fix before they find the anomaly." },
|
||
{ id: "not_hostile", name: "It is not hostile",
|
||
text: "The thing the brief calls a threat is frightened, and it is frightened of something the brief does not mention.",
|
||
reveal: "It runs from them at the first opportunity, in a direction that means something." },
|
||
{ id: "wrong_thing", name: "The brief describes the symptom",
|
||
text: "What they were sent for is a side effect. The cause is elsewhere in the same location and has not been noticed.",
|
||
reveal: "Dealing with the briefed problem makes the real one worse, visibly, within the hour." },
|
||
{ id: "agency_knew", name: "The agency knew",
|
||
text: "This is not the first team. The file has been edited, and the edit is visible if anybody reads the page numbers.",
|
||
reveal: "A previous team's kit, or their handwriting, somewhere it should not be." },
|
||
{ id: "local_stake", name: "Somebody local wants it",
|
||
text: "A person or group on the ground is protecting the anomaly, because it is the only thing keeping something else true.",
|
||
reveal: "The first helpful local turns out to have been steering them all day." },
|
||
{ id: "clock_running", name: "There is a deadline nobody mentioned",
|
||
text: "Something happens on a fixed date, and the brief was written before anyone worked out what.",
|
||
reveal: "A countdown that is legible once you know what you are looking at." },
|
||
{ id: "one_of_them", name: "It concerns one of them",
|
||
text: "One agent has a connection to this place or these people that the assigning officer either missed or did not miss.",
|
||
reveal: "Somebody uses that agent's name before it is given." },
|
||
{ id: "already_over", name: "It has already happened",
|
||
text: "The event the brief is trying to prevent took place before the team crossed. They are inside the aftermath and have not realised.",
|
||
reveal: "A date, written somewhere ordinary, that is in the past." },
|
||
{ id: "the_brief_is_bait", name: "The brief is bait",
|
||
text: "Somebody wanted a Custodian team at this location on this date, and the case file is how they arranged it.",
|
||
reveal: "Everything has been made slightly too easy up to the point where it is not." }
|
||
];
|
||
|
||
/* ============================================================
|
||
* WHO IS IN THE WAY — and the people, with names
|
||
* ============================================================ */
|
||
export const ADVERSARIES = [
|
||
{ id: "cleaners", name: "Contract cleaners", tier: "armed", npc: "Cleaner (contractor)", count: [2, 4],
|
||
motive: "Somebody is paying them to make sure nothing is recovered intact.",
|
||
tactics: "Professional, unenthusiastic, willing to withdraw. They will not die for it.",
|
||
leverage: "They can be bought, or convinced the invoice will not be honoured." },
|
||
{ id: "rival_desk", name: "A rival desk", tier: "dangerous", npc: "Cleaner (marksman)", count: [2, 3],
|
||
motive: "Another part of the agency wants this closed before your desk writes it up.",
|
||
tactics: "Correct paperwork, better equipment, no interest in a fight they can win by arriving first.",
|
||
leverage: "Clearance. Show them a higher authorisation and they must comply." },
|
||
{ id: "revenant", name: "A revenant", tier: "anomalous", npc: "Revenant", count: [1, 1],
|
||
motive: "It is looking for the person whose shape it is wearing.",
|
||
tactics: "It will pass anyone it does not recognise to reach someone it does.",
|
||
leverage: "Tell it where that person is. It will go, and that becomes somebody's problem." },
|
||
{ id: "locals", name: "People who live here", tier: "trained", npc: "The Keepers (waystation)", count: [3, 6],
|
||
motive: "They have made an arrangement with the thing and the arrangement is working.",
|
||
tactics: "Obstruction, misdirection, and eventually a shotgun. They are not evil and they are not wrong.",
|
||
leverage: "Offer them a better arrangement. The agency can, and hates to." },
|
||
{ id: "quiet_neighbours", name: "The quiet neighbours", tier: "armed", npc: "The Quiet Neighbours", count: [2, 5],
|
||
motive: "They were misfiled here generations ago and this is their last stable ground.",
|
||
tactics: "They watch. They negotiate. They do not bluff.",
|
||
leverage: "Somewhere else to be. It has to be real and it has to be shown." },
|
||
{ id: "duplicate", name: "A duplicate of an agent", tier: "dangerous", npc: "Duplicate (of an agent)", count: [1, 1],
|
||
motive: "It has the same brief, the same training, and one more crossing of experience.",
|
||
tactics: "It knows what the team will do because it is what it would do.",
|
||
leverage: "It wants to be the one who goes home. Offer it that and mean it." }
|
||
];
|
||
|
||
export const NAMES_FIRST = ["Marianne", "Errol", "Bess", "Hadley", "Ivo", "Constance", "Peter",
|
||
"Ada", "Roland", "Nell", "Gwilym", "Ottoline", "Sam", "Ruth", "Alec"];
|
||
export const NAMES_LAST = ["Trewin", "Kettle", "Ashgrove", "Bardsley", "Vane", "Pyle", "Mottram",
|
||
"Sallow", "Quiller", "Nye", "Harker", "Dunmore", "Speed", "Ferriby"];
|
||
|
||
export const NPC_ROLES = [
|
||
{ role: "the one who called it in", wants: "to be told they were right to call",
|
||
lies: "about how long they have known" },
|
||
{ role: "the person who owns the ground", wants: "the team gone before the weekend",
|
||
lies: "about who else has been here" },
|
||
{ role: "the local officer", wants: "a file they can close",
|
||
lies: "about the first report, which they lost" },
|
||
{ role: "the one who has seen it", wants: "somebody to say the word out loud",
|
||
lies: "about being alone when it happened" },
|
||
{ role: "the one profiting", wants: "the arrangement to continue",
|
||
lies: "about everything, fluently and well" },
|
||
{ role: "the child who is not frightened", wants: "to show somebody",
|
||
lies: "not at all, which is the problem" },
|
||
{ role: "the previous team's survivor", wants: "not to be taken back",
|
||
lies: "about what the fourth one did" }
|
||
];
|
||
|
||
/* ============================================================
|
||
* PHASES — the shape of the approach
|
||
* ============================================================ */
|
||
export const PHASE_KINDS = [
|
||
{ id: "approach", name: "Approach", verb: "getting there unobserved",
|
||
terrains: ["silted river flats", "a disused railway cutting", "overgrown allotments",
|
||
"a cliff path above the cove", "three miles of forestry track", "a flooded underpass"] },
|
||
{ id: "cordon", name: "The cordon", verb: "getting past whoever is already here",
|
||
terrains: ["a police outer cordon", "a farm gate with a man on it", "a coastguard station",
|
||
"a private road with cameras", "a village that has stopped talking to strangers"] },
|
||
{ id: "search", name: "The search", verb: "finding the thing itself",
|
||
terrains: ["forty rooms and a cellar", "two miles of foreshore at low water", "a plantation on a grid",
|
||
"a records office", "a caravan site in the dark"] },
|
||
{ id: "interior", name: "Inside", verb: "the place where the rules are different",
|
||
terrains: ["a pumping station below the water table", "the fourth floor and above",
|
||
"a chalk gallery", "the far side of the threshold", "a house with the wrong number of rooms"] },
|
||
{ id: "containment", name: "Containment", verb: "doing the thing they were sent to do",
|
||
terrains: ["the room it is in", "the doorway it keeps returning to", "a kitchen table and four chairs",
|
||
"the threshold itself", "a records room with the lights off", "wherever the decision has to be made"] },
|
||
{ id: "extraction", name: "Extraction", verb: "getting out with it",
|
||
terrains: ["back the way they came, in daylight", "a tidal causeway with ninety minutes",
|
||
"an open field and a mile of it", "the only road, now watched"] }
|
||
];
|
||
|
||
/* ============================================================
|
||
* WHAT MAKES IT HARD, AND WHAT GETS WORSE
|
||
* ============================================================ */
|
||
export const COMPLICATIONS = [
|
||
"The window is nineteen hours and the agency has not said what happens at twenty.",
|
||
"The team's regulator is inert. Nobody on the team knows how it starts.",
|
||
"The cover identity will not survive a document check, and there is a police presence.",
|
||
"One phase must be crossed twice, and it is worse the second time.",
|
||
"The only route in is public, in daylight, in front of people.",
|
||
"Local law enforcement has opened a file and is looking for the team.",
|
||
"There is a child involved and the containment answer does not account for children.",
|
||
"Comms do not work across. Whatever the team decides, they decide alone.",
|
||
"One of the team is recognised on arrival, by name, by somebody they have never met.",
|
||
"The weather closes the only route out four hours after they arrive."
|
||
];
|
||
|
||
export const CLOCKS = [
|
||
{ id: "public", name: "The public find out", costs: "Containment",
|
||
steps: ["a photograph is posted", "a local reporter arrives", "it is on the national news"] },
|
||
{ id: "spread", name: "It gets bigger", costs: "Containment",
|
||
steps: ["a second sighting", "a second location", "it is no longer one incident"] },
|
||
{ id: "deniable", name: "The desk backs away", costs: "Clearance",
|
||
steps: ["the liaison stops answering", "the authorisation is withdrawn", "the case becomes deniable"] },
|
||
{ id: "window", name: "The window closes", costs: "the way home",
|
||
steps: ["the margin narrows", "the return is Difficult", "the return is aimed at −30%"] },
|
||
{ id: "attention", name: "It notices them", costs: "everything",
|
||
steps: ["it knows somebody is here", "it knows how many", "it knows their names"] }
|
||
];
|
||
|
||
/* ============================================================
|
||
* WHAT IT TAKES OUT OF THEM, AND WHAT THEY GET
|
||
* ============================================================ */
|
||
export const COHERENCE_HOOKS = [
|
||
"Transposed materiel is the only thing that will open the inner door.",
|
||
"There is a paradox in plain sight and somebody will look at it.",
|
||
"The local law of reality is different in one specific, tempting way.",
|
||
"A duplicate of a team member is present and can be spoken to.",
|
||
"The fastest way through is to break something that is true here.",
|
||
"The window will be overstayed. It is only a question of by how long.",
|
||
"Somebody will have to go back through a second time.",
|
||
"There is a body that has to be carried, and it is one of ours."
|
||
];
|
||
|
||
/**
|
||
* Objectives. Given ids because an anomaly must be able to say which of these it
|
||
* can actually support: ordering a team to "recover the object" when the anomaly is
|
||
* a duplicated person is the single worst thing this generator did.
|
||
*/
|
||
export const RESOLUTIONS = [
|
||
{ id: "close", text: "Close it, and leave the public a version of events they can live with." },
|
||
{ id: "recover", text: "Recover the object and let the location resolve itself. It will, in about a year." },
|
||
{ id: "custody", text: "Take the person into custody. Custody, in the oldest sense the agency uses the word." },
|
||
{ id: "first_team",text: "Establish what happened to the first team and bring back whatever is left of them." },
|
||
{ id: "arrange", text: "Make an arrangement, file it, and never speak of the terms again." },
|
||
{ id: "map", text: "Nothing can be closed. Map it, mark it, and get out with the map." },
|
||
{ id: "handover", text: "Persuade somebody local to take on the job permanently, and never check on them again." },
|
||
{ id: "restore", text: "Put the record back the way it was, and make sure it stays put." }
|
||
];
|
||
|
||
export const DEBRIEF_QUESTIONS = [
|
||
"Who else knows, and can they be relied upon to be wrong about it?",
|
||
"Was anything brought back that was not on the manifest?",
|
||
"At what point did the team stop following the brief, and why?",
|
||
"Is the location safe to re-enter, and would you?",
|
||
"Which of you was alone, and for how long?",
|
||
"The file will say this was routine. Do you object to that?"
|
||
];
|
||
|
||
export const FAILURE_STATES = [
|
||
"The anomaly is still there and the agency has to send somebody else, which is a sentence in a file with names in it.",
|
||
"It is public. Containment becomes a press office problem and the desk remembers whose case it was.",
|
||
"It followed somebody home. That is next season's case and it has a Custodian's name on it.",
|
||
"The arrangement the locals had is broken and nothing has replaced it.",
|
||
"The team came back. Not all of them came back as themselves."
|
||
];
|
||
|
||
|
||
/* ============================================================
|
||
* GROUPS — so a generated case can reach the contact system
|
||
*
|
||
* The engine has a full first-contact pillar: three stages, standings, taboos
|
||
* learned in play, desires, a correct offer form, and rewards that the phase
|
||
* resolver already reads and applies. A generated case shipped with no group item
|
||
* at all, so none of it could ever fire. These are the people already on the
|
||
* ground when the team arrives.
|
||
* ============================================================ */
|
||
export const GROUPS = [
|
||
{ id: "parish", name: "The parish", population: 400,
|
||
description: "Four hundred people who have lived with it long enough to have manners about it.",
|
||
taboo: { id: "no_naming", text: "It is never named aloud indoors. Say what it is under a roof and the room empties.", penaltyIfViolated: -30 },
|
||
desire: { id: "burial", text: "A body returned. There is one they have never been able to bury.", offerBonusStep: "easy" },
|
||
offerForm: "A name, a date, and somewhere to put a stone — delivered outdoors." },
|
||
|
||
{ id: "keepers", name: "The keepers of the ground", population: 12,
|
||
description: "A dozen of them, and an arrangement none of them will describe to an outsider.",
|
||
taboo: { id: "no_photographs", text: "Nothing is photographed. A raised camera ends the conversation and starts a different one.", penaltyIfViolated: -30 },
|
||
desire: { id: "relief", text: "To be relieved. Somebody, anybody, official enough to take the duty on.", offerBonusStep: "easy" },
|
||
offerForm: "A written undertaking on agency paper, signed, with a date on it." },
|
||
|
||
{ id: "displaced", name: "The ones who came through", population: 30,
|
||
description: "Misfiled here generations ago. This is their last stable ground and they know exactly how stable it is.",
|
||
taboo: { id: "ground_speech", text: "They will not converse with anyone standing on the ground. Address them from a stair, a wall, a vehicle roof.", penaltyIfViolated: -30 },
|
||
desire: { id: "medicine", text: "Antibiotics. Theirs ran out two generations ago.", offerBonusStep: "easy" },
|
||
offerForm: "A sealed course of antibiotics, handed over from above ground level." },
|
||
|
||
{ id: "staff", name: "What is left of the staff", population: 6,
|
||
description: "Six people still turning up to a job at a site that closed. Nobody has told them to stop.",
|
||
taboo: { id: "no_authority", text: "Do not produce a warrant or an order. The last people who did took someone away.", penaltyIfViolated: -30 },
|
||
desire: { id: "wages", text: "To be paid. Eleven months, and they have kept the log the whole time.", offerBonusStep: "easy" },
|
||
offerForm: "Money, in cash, counted in front of them, and a receipt they can keep." }
|
||
];
|