diff --git a/art/maps/bh-control.svg b/art/maps/bh-control.svg
new file mode 100644
index 0000000..c95ea31
--- /dev/null
+++ b/art/maps/bh-control.svg
@@ -0,0 +1,143 @@
+
diff --git a/art/maps/bh-control.webp b/art/maps/bh-control.webp
new file mode 100644
index 0000000..9879017
Binary files /dev/null and b/art/maps/bh-control.webp differ
diff --git a/art/maps/bh-creche.svg b/art/maps/bh-creche.svg
new file mode 100644
index 0000000..26a9a19
--- /dev/null
+++ b/art/maps/bh-creche.svg
@@ -0,0 +1,192 @@
+
diff --git a/art/maps/bh-creche.webp b/art/maps/bh-creche.webp
new file mode 100644
index 0000000..5b6ad64
Binary files /dev/null and b/art/maps/bh-creche.webp differ
diff --git a/art/maps/bh-lobby.svg b/art/maps/bh-lobby.svg
new file mode 100644
index 0000000..c058b7d
--- /dev/null
+++ b/art/maps/bh-lobby.svg
@@ -0,0 +1,137 @@
+
diff --git a/art/maps/bh-lobby.webp b/art/maps/bh-lobby.webp
new file mode 100644
index 0000000..da5163f
Binary files /dev/null and b/art/maps/bh-lobby.webp differ
diff --git a/art/maps/bh-registry.svg b/art/maps/bh-registry.svg
new file mode 100644
index 0000000..3a714bd
--- /dev/null
+++ b/art/maps/bh-registry.svg
@@ -0,0 +1,127 @@
+
diff --git a/art/maps/bh-registry.webp b/art/maps/bh-registry.webp
new file mode 100644
index 0000000..d6dadb8
Binary files /dev/null and b/art/maps/bh-registry.webp differ
diff --git a/art/maps/bh-sickbay.svg b/art/maps/bh-sickbay.svg
new file mode 100644
index 0000000..12b1f65
--- /dev/null
+++ b/art/maps/bh-sickbay.svg
@@ -0,0 +1,175 @@
+
diff --git a/art/maps/bh-sickbay.webp b/art/maps/bh-sickbay.webp
new file mode 100644
index 0000000..d7d1385
Binary files /dev/null and b/art/maps/bh-sickbay.webp differ
diff --git a/art/maps/bh-sump.svg b/art/maps/bh-sump.svg
new file mode 100644
index 0000000..6189942
--- /dev/null
+++ b/art/maps/bh-sump.svg
@@ -0,0 +1,165 @@
+
diff --git a/art/maps/bh-sump.webp b/art/maps/bh-sump.webp
new file mode 100644
index 0000000..1cf649d
Binary files /dev/null and b/art/maps/bh-sump.webp differ
diff --git a/art/maps/bh-vigil.svg b/art/maps/bh-vigil.svg
new file mode 100644
index 0000000..24948b1
--- /dev/null
+++ b/art/maps/bh-vigil.svg
@@ -0,0 +1,273 @@
+
diff --git a/art/maps/bh-vigil.webp b/art/maps/bh-vigil.webp
new file mode 100644
index 0000000..929825f
Binary files /dev/null and b/art/maps/bh-vigil.webp differ
diff --git a/docs/scenarios/STANDING_ORDER.md b/docs/scenarios/STANDING_ORDER.md
index fdfa23f..6a58a6c 100644
--- a/docs/scenarios/STANDING_ORDER.md
+++ b/docs/scenarios/STANDING_ORDER.md
@@ -609,8 +609,35 @@ is much harder at four**, and the table above says how much.
## Locations — quick reference
-| # | Location | Act | One line |
-|---|---|---|---|
+**The underground is seven blueprint scenes** in the Adventure, one sheet each, drawn as
+the 1962 drawing office issued them with fifty years of pencil amendments. They are
+2800×2000 at five feet to the square, so tokens and the Foundry grid line up. **Nothing on
+a sheet gives away a GM secret**; the pencil notes are what the people below wrote.
+The section drawing (SO-H04) is the overview.
+
+| # | Location | Act | Blueprint scene | One line |
+|---|---|---|---|---|
+| 1 | Wetherlaw farm | 1 | — | Coal soot, and Joan with a brush |
+| 2 | The soot edge | 1 | — | A straight line of soot across the fields |
+| 3 | The vent | 1 | — | Warm, smells of coal; the crossing |
+| 4 | The postal pipe | 1/2 | — | How Centre's letters arrive |
+| 5 | The adit | 1 | — | Filled with rubble in 1975 |
+| 6 | Lobby and Enquiries | 1 | **Sheet 1** | Mr Ogden; the crossing lands in the blast lock |
+| 7 | Control Room and studio | 2/3 | **Sheet 2** | Sowerby, the letters, the safe, the tape |
+| 8 | Registry | 2 | **Sheet 3** | Pennyfeather and the Order, in the strongroom |
+| 9 | Sick bay | 2/3 | **Sheet 4** | Marlow's side room, Larkhall's office, the delivery room |
+| 10 | Crèche | 2/3 | **Sheet 5** | Kit, Nell, the drawings, the dormitory speaker |
+| 11 | Pipe room | 2 | **Sheet 2** | Centre's in-tray |
+| 12 | Plant floor | 3 | **Sheet 6** | The transmitter room — the mast route |
+| 13 | Vigil hall | 2/3 | **Sheet 6** | The chant; Thursday's assembly |
+| 14 | The vegetable tunnels | 2 | **Sheet 7** | Lamps, beds, trout tanks, hens — the run route |
+| 15 | The Sump | 2 | **Sheet 7** | Four cells; the detention ends here |
+
+**Which sheet when:** Sheet 1 for Act One's arrival and interview; Sheets 7 and 1–3 for the
+detention, wherever the guards catch them; Sheet 2 for getting the tape; Sheet 5 for the
+cure; Sheet 6 for the broadcast.
+
+---|---|---|---|
| 1 | Wetherlaw farm | 1 | Coal soot, and Joan with a brush |
| 2 | The soot edge | 1 | A straight line of soot across the fields |
| 3 | The vent | 1 | Warm, smells of coal; the crossing |
diff --git a/docs/scenarios/STANDING_ORDER_ART.md b/docs/scenarios/STANDING_ORDER_ART.md
index f338f3b..60ecc8c 100644
--- a/docs/scenarios/STANDING_ORDER_ART.md
+++ b/docs/scenarios/STANDING_ORDER_ART.md
@@ -14,6 +14,11 @@ and the **quarantine unit** — are bestiary entries and their prompts live in
the art; only the uses below changed. **Mr Smith must never be drawn** — a face is the one
thing the case says nobody can recall — and no prompt for him belongs in this sheet.
+**v1.1 added seven blueprint scenes** of the underground, `art/maps/bh-*.webp`. They are
+drawn, not generated, by `tools/bellhouse-plans.mjs` (white line on blueprint blue, Courier
+lettering, pencil amendments in yellow); edit the script and run it with `--render`. No
+prompt belongs here for them.
+
---
## Art direction for this scenario
diff --git a/packs/standingorder/000003.log b/packs/standingorder/000003.log
index c25efb8..5bf1397 100644
Binary files a/packs/standingorder/000003.log and b/packs/standingorder/000003.log differ
diff --git a/tools/bellhouse-plans.mjs b/tools/bellhouse-plans.mjs
new file mode 100644
index 0000000..b161914
--- /dev/null
+++ b/tools/bellhouse-plans.mjs
@@ -0,0 +1,428 @@
+/**
+ * STANDING ORDER — the Bellhouse blueprints: seven floor plans of the Seat, one per
+ * Foundry scene.
+ *
+ * Drawn from declared rooms rather than generated, for culland-hall.svg's reason: a room,
+ * a door and a label are load-bearing, and a generator cannot be trusted with any of them.
+ * Every plan is 2800x2000 at 100px to the square (5 ft), so the Foundry grid lands on the
+ * drawn one. Labels are what a 1962 drawing office would have written, plus the pencil
+ * amendments of fifty years below; nothing on a plan tells the players a GM secret.
+ *
+ * node tools/bellhouse-plans.mjs write art/maps/bh-*.svg
+ * node tools/bellhouse-plans.mjs --render and render each to .webp (rsvg-convert, cwebp)
+ */
+import { writeFileSync } from "node:fs";
+import { execFileSync } from "node:child_process";
+import path from "node:path";
+
+const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
+const OUT = path.join(ROOT, "art", "maps");
+const S = 100, W = 28, H = 20;
+
+const esc = s => String(s).replace(/&/g, "&").replace(//g, ">");
+const p = n => +(n * S).toFixed(1);
+
+/* ---------------------------------------------------------------- primitives */
+
+function sheet() {
+ const g = [];
+ const api = {
+ g,
+ room(x, y, w, h, label, { sub, n, cls = "rm", lx = 0.3, ly = 0.75 } = {}) {
+ g.push(``);
+ if (label) g.push(`${esc(label)}`);
+ if (sub) g.push(`${esc(sub)}`);
+ if (n != null) {
+ g.push(``
+ + `${n}`);
+ }
+ },
+ /* A door on a wall: `v` for a vertical wall at x, else horizontal at y. A gap in the
+ wall and a quarter-circle swing, the way a drawing office draws one. */
+ door(x, y, v, len = 1, flip = false) {
+ const L = p(len), X = p(x), Y = p(y), d = flip ? -1 : 1;
+ if (v) {
+ g.push(``,
+ ``);
+ } else {
+ g.push(``,
+ ``);
+ }
+ },
+ /* An open archway: just the gap. */
+ arch(x, y, v, len = 2, cls = "gap") {
+ const X = p(x), Y = p(y), L = p(len);
+ g.push(v ? ``
+ : ``);
+ },
+ box(x, y, w, h, label, size = 20) {
+ g.push(``);
+ if (label) g.push(`${esc(label)}`);
+ },
+ chair(x, y) { g.push(``); },
+ bed(x, y, vert = true, w = 0.9, l = 2) {
+ const [bw, bh] = vert ? [w, l] : [l, w];
+ g.push(``);
+ g.push(vert ? ``
+ : ``);
+ },
+ shelf(x, y, w, h) {
+ g.push(``);
+ const lines = [];
+ if (w >= h) for (let i = x + 0.25; i < x + w; i += 0.25) lines.push(`M${p(i)} ${p(y)}V${p(y + h)}`);
+ else for (let j = y + 0.25; j < y + h; j += 0.25) lines.push(`M${p(x)} ${p(j)}H${p(x + w)}`);
+ g.push(``);
+ },
+ reels(x, y, label = "TAPE") {
+ g.push(``,
+ ``,
+ ``,
+ `${esc(label)}`);
+ },
+ safe(x, y) {
+ g.push(``,
+ ``,
+ `SAFE`);
+ },
+ speaker(x, y) {
+ g.push(``,
+ ``);
+ },
+ round(x, y, r, label) {
+ g.push(``);
+ if (label) g.push(`${esc(label)}`);
+ },
+ stairs(x, y, w, h, label = "STAIR") {
+ g.push(``);
+ const d = [];
+ for (let j = y + 0.3; j < y + h; j += 0.3) d.push(`M${p(x)} ${p(j)}H${p(x + w)}`);
+ g.push(``,
+ `${esc(label)}`);
+ },
+ blast(x, y, h) {
+ g.push(``);
+ const d = [];
+ for (let j = y; j < y + h; j += 0.2) d.push(`M${p(x) - 22} ${p(j) + 20}l44 -20`);
+ g.push(``);
+ },
+ /* The drawing office's own lettering, outside a room. */
+ text(x, y, t, cls = "sub", extra = "") {
+ g.push(`${esc(t)}`);
+ },
+ /* Pencil: fifty years of amendments in a different hand. */
+ pencil(x, y, t, rot = -2) {
+ g.push(`${esc(t)}`);
+ },
+ arrow(x1, y1, x2, y2) {
+ g.push(``);
+ },
+ raw(s) { g.push(s); }
+ };
+ return api;
+}
+
+const STYLE = `
+ .rm { fill:#154a8a; stroke:#e8f1fb; stroke-width:7; }
+ .rm2 { fill:#11417d; stroke:#e8f1fb; stroke-width:5; }
+ .rock { fill:#0e3668; stroke:#9fbde0; stroke-width:4; stroke-dasharray:18 10; }
+ .gap { stroke:#154a8a; stroke-width:12; }
+ .rgap { stroke:#0e3668; stroke-width:12; }
+ .swing { fill:none; stroke:#e8f1fb; stroke-width:3; }
+ .fur { fill:#1b579c; stroke:#cfe0f3; stroke-width:3; }
+ .fine { fill:none; stroke:#cfe0f3; stroke-width:2; }
+ .blast { fill:#0a2f5c; stroke:#e8f1fb; stroke-width:4; }
+ .arw { stroke:#e8f1fb; stroke-width:4; }
+ .grid { stroke:#2a5d9c; stroke-width:1; }
+ .numc { fill:#e8f1fb; }
+ .num { font-family:'Courier New',Courier,monospace; font-weight:700; font-size:36px;
+ fill:#123f78; text-anchor:middle; }
+ .lbl { font-family:'Courier New',Courier,monospace; font-weight:700; font-size:30px;
+ fill:#f4f8fd; letter-spacing:2px; }
+ .sub { font-family:'Courier New',Courier,monospace; font-size:21px; fill:#b9d0ea; }
+ .fl { font-family:'Courier New',Courier,monospace; font-size:18px; fill:#e8f1fb;
+ text-anchor:middle; }
+ .ttl { font-family:'Courier New',Courier,monospace; font-weight:700; font-size:34px;
+ fill:#f4f8fd; letter-spacing:3px; }
+ .tb { font-family:'Courier New',Courier,monospace; font-size:20px; fill:#dbe8f6; }
+ .pen { font-family:'Bradley Hand','Noteworthy','Marker Felt',cursive; font-size:27px;
+ fill:#ffe98a; }
+`;
+
+function render(no, title, level, draw, { legend = [1, 15.6] } = {}) {
+ const s = sheet();
+ draw(s);
+
+ const grid = [];
+ for (let i = 1; i < W; i++) grid.push(`M${i * S} 0V${H * S}`);
+ for (let j = 1; j < H; j++) grid.push(`M0 ${j * S}H${W * S}`);
+
+ // Title block, bottom right, as a drawing office would box it.
+ const tb = [19, 16.5, 8.7, 3.3];
+ const [tx, ty, tw, th] = tb.map(p);
+ const block = `
+
+
+
+ REGIONAL SEAT OF GOVERNMENT 12 · BELLHOUSE
+ SHEET ${no} OF 7 · ${esc(level)}
+ ${esc(title)}
+ SCALE: ONE SQUARE = 5 FT
+ DRG RSG12/${String(no).padStart(2, "0")} · ISSUED 1962 · COPY NOT TO LEAVE THE SEAT`;
+
+ // Legend, bottom left.
+ const [lx, ly] = legend.map(p);
+ const key = `
+
+ KEY
+
+
+ DOOR
+
+ 7
+ LOCATION No.
+ pencil
+ = AMENDED AT THE SEAT, 1974–2026
+
+ = UNLINED MINE WORKINGS
+ 1 SQUARE = 5 FT · N ↑`;
+
+ return `
+`;
+}
+
+/* ---------------------------------------------------------------- the sheets */
+
+export const PLANS = [
+
+{ key: "bh-lobby", no: 1, title: "LOBBY & ENQUIRIES", level: "LEVEL 1 · ENTRY", draw: s => {
+ s.text(0.3, 0.6, "FROM ADIT (SEALED 1975)");
+ s.room(1, 6, 4, 6, "BLAST LOCK", { sub: "outer lock" });
+ s.blast(1, 8, 2);
+ s.raw(``);
+ s.pencil(1.6, 11.6, "air goes wrong here", -3);
+ s.room(5, 6, 4, 6, "DECONTAM.", { sub: "showers · lockers" });
+ for (const y of [8, 9.3, 10.6]) s.round(6, y, 0.3);
+ s.shelf(7.9, 7.4, 0.8, 4.2);
+ s.room(9, 3, 10, 12, "LOBBY", { n: 6, sub: "public waiting" });
+ s.box(14.5, 5.2, 3.8, 0.8, "ENQUIRIES COUNTER");
+ s.box(17.5, 6, 0.8, 2.4);
+ for (const x of [10, 11, 12]) for (const y of [9, 11, 13]) { s.chair(x, y); }
+ s.box(9.8, 8.4, 2.9, 0.4); s.box(9.8, 10.4, 2.9, 0.4); s.box(9.8, 12.4, 2.9, 0.4);
+ s.box(15, 14.4, 3.6, 0.35, "NOTICEBOARD", 14);
+ s.pencil(14.6, 13.9, "housing list — see Mr Ogden", -1);
+ s.room(19, 3, 5, 5, "ENQUIRIES", { sub: "Mr Ogden" });
+ s.box(20, 4.6, 2, 1, "DESK"); s.chair(21, 5.8); s.shelf(23.1, 3.6, 0.7, 3.6);
+ s.round(20.3, 7.2, 0.25, "☎");
+ s.room(19, 8, 5, 4, "INTERVIEW", { sub: "the door does not lock" });
+ s.box(20.4, 9.8, 2.2, 1.1, "TABLE");
+ s.chair(20.6, 9.3); s.chair(21.8, 9.3); s.chair(20.6, 11); s.chair(21.8, 11);
+ s.room(19, 12, 5, 3, "WARDENS' POST");
+ s.shelf(19.4, 13.4, 2.5, 0.5); s.text(19.4, 14.6, "truncheon rack", "sub");
+ s.room(12, 0, 3, 3, null, { cls: "rm2" }); s.text(12.2, 1.3, "MAIN", "lbl"); s.text(12.2, 1.9, "CORRIDOR", "sub");
+ s.arrow(13.5, 2.6, 13.5, 0.3);
+ s.door(5, 8.5, true, 1); s.door(9, 8.5, true, 1);
+ s.door(19, 4.8, true, 1, true); s.door(19, 9.2, true, 1, true); s.door(19, 13, true, 1, true);
+ s.arch(12.5, 3, false, 2);
+ s.arrow(0.5, 0.9, 0.5, 5.5);
+}},
+
+{ key: "bh-control", no: 2, title: "CONTROL ROOM", level: "LEVEL 2 · GOVERNMENT", draw: s => {
+ s.room(0.5, 1, 27, 2, "MAIN CORRIDOR", { cls: "rm2", ly: 1.25 });
+ s.room(1, 4, 12, 9, "CONTROL ROOM", { n: 7, sub: "Controller's floor" });
+ s.box(1.8, 5.5, 10.4, 0.9, "CONSOLE · DIALS · TANNOY DESK");
+ for (let x = 2.5; x < 12; x += 1.4) s.chair(x, 6.6);
+ s.box(3.5, 8, 6, 2.6, "SITUATION MAP · REGION 12", 20);
+ for (const [x, y] of [[3.8, 7.4], [5.6, 7.4], [7.4, 7.4], [3.8, 10.8], [5.6, 10.8], [7.4, 10.8]]) s.chair(x, y);
+ s.round(11.4, 11.4, 0.55, "SCOPE");
+ s.pencil(9.8, 12.6, "SEALED 1974 — by order", -2);
+ s.room(14, 4, 6, 5, "CONTROLLER", { sub: "E. Sowerby" });
+ s.box(15.2, 5.8, 2.4, 1.1, "DESK"); s.chair(16.1, 7.1);
+ s.shelf(19.1, 4.6, 0.7, 3.8); s.text(17.4, 8.7, "letters from Centre", "sub");
+ s.room(14, 9, 6, 6, "STUDIO", { sub: "broadcast · 1962" });
+ s.reels(14.6, 10.6, "TAPE DECK");
+ s.safe(18.6, 10.2);
+ s.box(15, 12.6, 2.6, 0.9, "MIXER"); s.chair(15.9, 13.7);
+ s.round(18.8, 13.6, 0.3, "MIC");
+ s.pencil(14.3, 14.7, "new tape rec'd from Centre 2019", -1);
+ s.room(21, 4, 6, 5, "PIPE ROOM", { n: 11, sub: "Centre's in-tray" });
+ s.round(24, 6.6, 0.55, "PIPE");
+ s.box(21.5, 6.1, 1.8, 0.8, "IN-TRAY");
+ s.pencil(21.4, 8.7, "every 90 days", -2);
+ s.room(21, 9, 6, 6, "COMMS", { sub: "teleprinters · switchboard" });
+ s.box(21.6, 10.8, 1.3, 1, "TP"); s.box(23.2, 10.8, 1.3, 1, "TP");
+ s.box(21.6, 12.6, 4.8, 0.9, "SWITCHBOARD"); s.chair(22.3, 13.8); s.chair(24.3, 13.8);
+ s.pencil(24.6, 10.6, "no line out", 4);
+ s.door(3, 4, false, 1, true); s.door(16, 4, false, 1, true); s.door(23, 4, false, 1, true);
+ s.door(14, 11, true, 1, true); s.door(14.8, 9, false, 1, true); s.door(23.5, 9, false, 1, true);
+}},
+
+{ key: "bh-registry", no: 3, title: "REGISTRY", level: "LEVEL 2 · GOVERNMENT", draw: s => {
+ s.room(0.5, 1, 27, 2, "MAIN CORRIDOR", { cls: "rm2", ly: 1.25 });
+ s.room(1, 4, 9, 7, "PUBLIC COUNTER", { n: 8 });
+ s.box(1, 7.4, 6.5, 0.7, "COUNTER");
+ for (const x of [2, 3.5, 5, 6.5]) s.chair(x, 5.6);
+ s.box(5.4, 4.3, 2, 0.45, "TAKE A NUMBER", 13);
+ s.pencil(1.4, 10.6, "complaints: allow 11 weeks", -2);
+ s.room(10, 4, 12, 11, "RECORDS");
+ for (let x = 11; x < 21.5; x += 1.5) s.shelf(x, 5.6, 0.6, 7.6);
+ s.text(10.4, 14.5, "BIRTHS · DEATHS · HOUSING · RATIONS · 1974–", "sub");
+ s.room(22, 4, 5, 5, "REGISTRAR", { sub: "A. Pennyfeather" });
+ s.box(23, 5.8, 2.2, 1, "DESK"); s.chair(23.8, 7.1);
+ s.room(22, 9, 5, 6, "STRONGROOM", { sub: "standing orders" });
+ s.safe(25.6, 9.9); s.shelf(22.5, 11.2, 3.8, 0.7); s.shelf(22.5, 13.3, 3.8, 0.7);
+ s.room(1, 11, 9, 4, "TYPING POOL");
+ for (const x of [1.8, 4.3, 6.8]) { s.box(x, 12.3, 1.8, 0.8); s.chair(x + 0.7, 13.4); }
+ s.door(3.6, 4, false, 1, true); s.door(24, 4, false, 1, true);
+ s.door(10, 5.2, true, 1, true); s.door(22, 6, true, 1, true); s.door(24.8, 9, false, 0.9, true);
+ s.arch(8, 7.4, true, 0.7);
+ s.door(4, 11, false, 1);
+}},
+
+{ key: "bh-sickbay", no: 4, title: "SICK BAY", level: "LEVEL 3 · WELFARE", draw: s => {
+ s.room(0.5, 1, 27, 2, "WELFARE CORRIDOR", { cls: "rm2", ly: 1.25 });
+ s.room(1, 4, 6, 5, "RECEPTION", { n: 9 });
+ s.box(1.6, 5.7, 3, 0.7, "DESK");
+ for (const x of [1.6, 2.6, 3.6, 4.6]) s.chair(x, 7.1);
+ s.room(7, 4, 12, 6, "GENERAL WARD", { sub: "eighteen beds" });
+ for (let x = 7.5; x < 17; x += 1.1) { s.bed(x, 5.2); s.bed(x, 7.9, true, 0.9, 1.9); }
+ s.room(19, 4, 4, 3, "SIDE ROOM", { sub: "Mrs Marlow" });
+ s.bed(21.8, 4.3, true, 0.9, 1.8); s.chair(21, 5.4); s.shelf(19.3, 6.2, 1.6, 0.5);
+ s.room(23, 4, 4, 3, "DISPENSARY");
+ s.shelf(23.3, 5.5, 3.4, 0.6);
+ s.room(19, 7, 8, 3, "MEDICAL OFFICER", { sub: "Dr Larkhall" });
+ s.box(23, 8.2, 2, 1, "DESK"); s.shelf(26.1, 7.3, 0.6, 2.4); s.chair(23.7, 9.3);
+ s.room(7, 10, 7, 5, "DELIVERY", { sub: "maternity" });
+ s.bed(10, 11.8, false, 1, 2.2); s.round(12.9, 12.4, 0.35, "COT");
+ s.pencil(7.4, 14.6, "Cotter — due Thurs", -2);
+ s.room(14, 10, 6, 5, "THEATRE");
+ s.box(15.8, 11.6, 2.4, 1, "TABLE"); s.round(18.9, 11, 0.4); s.shelf(14.3, 14, 3, 0.6);
+ s.room(1, 9, 6, 6, "ISOLATION");
+ s.room(1, 11, 3, 4, null); s.room(4, 11, 3, 4, null);
+ s.bed(1.8, 12.4); s.bed(4.8, 12.4);
+ s.pencil(1.3, 10.6, "unused since '78", -1);
+ s.door(3.5, 4, false, 1, true); s.door(24.5, 4, false, 1, true);
+ s.door(7, 6, true, 1, true); s.door(19, 5, true, 1, true); s.door(19, 8.2, true, 1, true);
+ s.door(10, 10, false, 1); s.door(16.2, 10, false, 1);
+ s.door(3.2, 9, false, 1, true); s.door(2.5, 11, false, 0.8); s.door(5.2, 11, false, 0.8);
+ s.door(21.2, 7, false, 0.8, true);
+}},
+
+{ key: "bh-creche", no: 5, title: "CRECHE & SCHOOL", level: "LEVEL 3 · WELFARE", draw: s => {
+ s.room(0.5, 1, 27, 2, "WELFARE CORRIDOR", { cls: "rm2", ly: 1.25 });
+ s.room(1, 4, 5, 4, "CLOAKS");
+ for (let y = 5.3; y < 7.8; y += 0.5) s.raw(``);
+ s.room(1, 8, 5, 5, "STAFF", { sub: "K. Marlow" });
+ s.bed(1.5, 10.2); s.box(3.4, 9.6, 1.8, 0.8, "DESK");
+ s.room(6, 4, 10, 7, "SCHOOLROOM", { n: 10 });
+ s.box(7, 5.2, 5, 0.25, "", 12); s.text(7, 5.9, "BLACKBOARD", "sub");
+ s.box(12.8, 5.6, 2, 0.9, "TEACHER");
+ for (let x = 7; x < 15; x += 2) for (let y = 6.9; y < 10; y += 1.2) { s.box(x, y, 1.3, 0.6); }
+ s.pencil(12.4, 10.7, "atlas: 1971 edition", -2);
+ s.room(16, 4, 11, 7, "PLAYROOM");
+ s.raw(``);
+ s.text(16.3, 5.4, "pinned drawings along this wall", "sub");
+ s.box(18, 6.6, 4, 2.6, "RUG", 20);
+ for (const [x, y] of [[23.5, 7], [25.4, 6.4], [24.6, 8.8], [17.2, 9.9]]) s.round(x, y, 0.35);
+ s.box(25.3, 9.6, 1.4, 1.1, "TOYS", 16);
+ s.room(6, 11, 14, 5, "DORMITORY", { sub: "under-twelves" });
+ for (let x = 6.6; x < 19.5; x += 0.95) { s.bed(x, 12.3, true, 0.7, 1.3); s.bed(x, 14.3, true, 0.7, 1.3); }
+ s.speaker(13, 11.35);
+ s.pencil(13.8, 11.95, "speaker fitted 2019 — 03:00 nightly", -1);
+ s.room(20, 11, 7, 5, "WASHROOM");
+ for (let x = 21.8; x < 26.8; x += 1.2) s.round(x, 12.9, 0.3);
+ s.box(21, 14, 5, 0.8, "BATHS");
+ s.door(3, 4, false, 1, true); s.door(6, 5, true, 1); s.door(16, 6.4, true, 1);
+ s.door(10.4, 11, false, 1); s.door(2.8, 8, false, 1); s.door(6, 11.6, true, 1, true);
+ s.door(20, 13, true, 1);
+}, legend: [0.3, 16.1] },
+
+{ key: "bh-vigil", no: 6, title: "VIGIL HALL & PLANT", level: "LEVEL 3 · ASSEMBLY", draw: s => {
+ s.room(1, 1, 14, 13, "VIGIL HALL", { n: 13, ly: 12.5 });
+ s.box(3, 1.5, 10, 1.8, "PLATFORM", 22);
+ s.box(7.6, 3.4, 0.8, 0.5, "", 12); s.text(8.6, 3.9, "lectern", "sub");
+ s.box(3.5, 1.1, 5, 0.3, "", 10);
+ s.text(9, 1.35, "ROLL OF HONOUR", "sub");
+ for (let y = 4.8; y < 11.5; y += 0.8) for (let x = 2; x < 12.8; x += 0.55) {
+ if (x > 7.4 && x < 8.3) continue;
+ s.raw(``);
+ }
+ s.speaker(1.5, 4.5); s.speaker(1.5, 10);
+ s.pencil(9.6, 12.4, "999 seats — add one", -2);
+ s.room(15, 1, 2, 18, null, { cls: "rm2" });
+ s.text(15.3, 17.4, "CORR.", "sub");
+ s.room(17, 1, 10, 9, "PLANT FLOOR", { n: 12 });
+ s.box(17.8, 2.4, 3.4, 2.6, "COAL BOILER");
+ s.box(22, 2.4, 2.2, 2, "GEN 1"); s.box(24.6, 2.4, 2.2, 2, "GEN 2");
+ s.box(17.8, 6.2, 8.6, 0.6, "DUCTING");
+ s.box(17.8, 7.6, 2.4, 1.6, "PUMPS"); s.box(21, 7.6, 2.4, 1.6, "FILTERS");
+ s.pencil(23.6, 8.8, "coal from No.2 road", -2);
+ s.room(17, 10, 10, 5.5, "TRANSMITTER", { sub: "feed to mast" });
+ s.shelf(18, 11.8, 5, 0.9); s.text(18, 13.2, "valve racks", "sub");
+ s.box(24, 11.6, 2.2, 1.4, "PANEL");
+ s.raw(``);
+ s.text(24.3, 15.1, "MAST CABLE ↑ MOOR", "sub");
+ s.door(15, 5.5, true, 1); s.door(15, 11, true, 1);
+ s.door(17, 4, true, 1, true); s.door(17, 12, true, 1, true);
+ s.arrow(16, 18.8, 16, 19.9); s.arrow(16, 1.2, 16, 0.1);
+}, legend: [1, 15.6] },
+
+{ key: "bh-sump", no: 7, title: "TUNNELS & SUMP", level: "LEVEL 4 · WORKINGS", draw: s => {
+ s.room(0.5, 1.5, 27, 3, "No.2 COAL ROAD", { cls: "rock", n: 14, sub: "growing beds under lamps" });
+ for (let x = 6; x < 26; x += 2.4) s.shelf(x, 2.2, 2, 0.6);
+ for (let x = 6; x < 26; x += 2.4) s.shelf(x, 3.7, 2, 0.6);
+ s.pencil(20, 1.3, "workings beyond: not surveyed", -1);
+ s.room(9, 4.5, 2, 3, null, { cls: "rock" }); s.room(17, 4.5, 2, 3, null, { cls: "rock" });
+ s.room(0.5, 7.5, 18.5, 3, "No.3 COAL ROAD", { cls: "rock", sub: "trout tanks" });
+ for (let x = 6; x < 16.5; x += 2.8) s.box(x, 8.1, 2.3, 1.8, "TANK", 16);
+ s.room(19, 6.5, 8.5, 5, "HEN RUN", { cls: "rock" });
+ for (let x = 20; x < 27; x += 1.3) s.box(x, 9.9, 0.9, 0.9);
+ s.pencil(20.3, 8.6, "eggs → Welfare, Tues/Fri", -2);
+ s.stairs(13, 10.5, 3, 4, "SUMP STAIR");
+ s.room(1, 11.5, 12, 2, "THE SUMP", { n: 15, ly: 1.3 });
+ for (const x of [1, 4, 7, 10]) s.room(x, 13.5, 3, 4.5, null);
+ for (const [i, x] of [1, 4, 7, 10].entries()) {
+ s.text(x + 0.3, 14.1, `CELL ${i + 1}`, "sub");
+ s.bed(x + 0.5, 15.3);
+ s.door(x + 1.9, 13.5, false, 0.9);
+ }
+ s.box(8.8, 11.9, 2, 0.8, "WARDEN");
+ s.pencil(1.3, 18.6, "locks: 1962 pattern — oil them", -1);
+ s.arch(13.5, 10.5, false, 2, "rgap"); s.arch(13, 12, true, 1);
+ s.arch(9, 4.5, false, 2, "rgap"); s.arch(9, 7.5, false, 2, "rgap");
+ s.arch(17, 4.5, false, 2, "rgap"); s.arch(17, 7.5, false, 2, "rgap");
+ s.door(19, 8.5, true, 1);
+ s.arrow(1.3, 3, 0.1, 3);
+ s.text(0.6, 1.2, "← to Welfare stair", "sub");
+}, legend: [17.5, 12.1] }
+];
+
+/* The legend on sheet 7 sits above the title block; the title block never moves. */
+
+if (import.meta.url === `file://${process.argv[1]}`) {
+ const doRender = process.argv.includes("--render");
+ for (const pl of PLANS) {
+ const svg = render(pl.no, pl.title, pl.level, pl.draw, pl.legend ? { legend: pl.legend } : {});
+ const svgPath = path.join(OUT, `${pl.key}.svg`);
+ writeFileSync(svgPath, svg);
+ if (doRender) {
+ const png = path.join(OUT, `${pl.key}.png`);
+ execFileSync("rsvg-convert", ["-w", "2800", "-h", "2000", svgPath, "-o", png]);
+ execFileSync("cwebp", ["-quiet", "-q", "88", png, "-o", path.join(OUT, `${pl.key}.webp`)]);
+ execFileSync("rm", [png]);
+ }
+ console.log(` ${pl.key}${doRender ? " (+webp)" : ""}`);
+ }
+}
diff --git a/tools/build-packs.mjs b/tools/build-packs.mjs
index f3fadb9..ea3ed36 100644
--- a/tools/build-packs.mjs
+++ b/tools/build-packs.mjs
@@ -1058,6 +1058,19 @@ function cullandScene() {
color: "#6674d8", alpha: 0, distance: 5, units: "m" },
flags: { ringbrp: { key: "bellhouse" } }
};
+ const plans = SO.SO_SCENES.map((sc, i) => ({
+ ...cullandScene(),
+ _id: idFor(`scene:${sc.key}`),
+ name: sc.name,
+ navName: sc.nav,
+ background: { src: `systems/ringbrp/${sc.src}` },
+ initial: { x: 1400, y: 1000, scale: 0.4 },
+ backgroundColor: "#123f78",
+ grid: { type: 1, size: 100, style: "solidLines", thickness: 1,
+ color: "#e8f1fb", alpha: 0.08, distance: 5, units: "ft" },
+ sort: (i + 1) * 100,
+ flags: { ringbrp: { key: sc.key } }
+ }));
const advId = idFor("adventure:standing_order");
const adventure = {
@@ -1085,14 +1098,14 @@ function cullandScene() {
{ ...showJournal.doc, pages: showJournal.pages },
{ ...facesJournal.doc, pages: facesJournal.pages }
],
- scenes: [bellhouse], combats: [], tables: [], macros: [], cards: [], playlists: [], folders: [],
+ scenes: [bellhouse, ...plans], combats: [], tables: [], macros: [], cards: [], playlists: [], folders: [],
sort: 0, ...BASE()
};
await writePack("standingorder", [[`!adventures!${advId}`, adventure]]);
console.log(` standing STANDING ORDER — ${castDocs.length} cast, ${SO.SO_PAGES.length} GM pages, `
+ `${SO.SO_HANDOUTS.length} handouts, ${showJournal.pages.length} plates, `
- + `${facesJournal.pages.length} faces`);
+ + `${facesJournal.pages.length} faces, ${1 + plans.length} scenes`);
}
// OPEN DAY — the prologue. A much smaller adventure than BLACK PLATFORM: no case file,
diff --git a/tools/scenario-standingorder.mjs b/tools/scenario-standingorder.mjs
index e1771e3..71fe86f 100644
--- a/tools/scenario-standingorder.mjs
+++ b/tools/scenario-standingorder.mjs
@@ -170,6 +170,18 @@ export const SO_PLATES = [
{ name: "9 · Bellhouse — section", src: "art/maps/bellhouse-section.webp" }
];
+/* The underground as seven blueprint scenes, drawn by tools/bellhouse-plans.mjs. Numbers
+ are the Locations table's. 2800x2000 at 100px to the square, like Culland Hall. */
+export const SO_SCENES = [
+ { key: "bh_lobby", name: "Bellhouse 1 · Lobby and Enquiries (6)", nav: "Lobby", src: "art/maps/bh-lobby.webp" },
+ { key: "bh_control", name: "Bellhouse 2 · Control Room and Pipe Room (7, 11)", nav: "Control", src: "art/maps/bh-control.webp" },
+ { key: "bh_registry", name: "Bellhouse 3 · Registry (8)", nav: "Registry", src: "art/maps/bh-registry.webp" },
+ { key: "bh_sickbay", name: "Bellhouse 4 · Sick Bay (9)", nav: "Sick Bay", src: "art/maps/bh-sickbay.webp" },
+ { key: "bh_creche", name: "Bellhouse 5 · Creche and School (10)", nav: "Creche", src: "art/maps/bh-creche.webp" },
+ { key: "bh_vigil", name: "Bellhouse 6 · Vigil Hall and Plant Floor (12, 13)", nav: "Vigil Hall", src: "art/maps/bh-vigil.webp" },
+ { key: "bh_sump", name: "Bellhouse 7 · Coal Roads and the Sump (14, 15)", nav: "Sump", src: "art/maps/bh-sump.webp" }
+];
+
/* The detention detail. Truncheons, not firearms: the Seat's armoury is locked and the
Controller holds the key, and the order that sends them says DETAIN. Tactics are written
for the GM; the harness does not model restraint, so its figures are the ceiling. */