# The Control Room: A SCADA Board And A Game HUD Over The Scenario Worlds — One More Renderer, Zero New Data

**version** v0.33.69
**date** 29 August 2026
**from** Human (project lead)
**to** Engineering, Architecture, the PKI/registry team, the RiskMandate team

**type** Dev brief — a new component and UX for the simulation

*The sixth brief of this arc. The instruction, verbatim: "Now can you create a new component and UX for that simulation. Think about a game UI and SCADA control systems UIs." The engine shipped in v0.33.68 renders worlds as decks — legible, but readable one card at a time. The operator's question is different: **what is the state of the whole plant, right now, and what happened at 14:02?** That question has had a canonical answer for fifty years, and it is not a card. It is a control room.*

---

## What This Is

**One board, both worlds, zero new data.** The control room is a second renderer over the *same* `scenario.json` files the deck pages already use — which is the point. v0.33.68's claim was *nothing hardcoded: adding a world is adding a JSON file*. This brief adds the converse claim: **adding a way of seeing is adding a renderer**, and the data does not move. If the control room needed its own data file, the scenario engine would be a page generator with a JSON config; because it does not, the scenario files are what we said they were — a world model, renderer-independent. That is the "component" in the instruction: the scenario worlds are now a substrate that renderers plug into.

Two genres are borrowed, one job each, conventions only (genre-not-work, as always — no vendor's mimic art, no specific game's HUD):

- **From SCADA**: the *annunciator panel* (a grid of labelled tiles that light), the *mimic diagram* (the plant as a schematic with flow drawn on it), the *faceplate* (click a device, get its full record), the *sequence-of-events log* (timestamped, append-only, the thing auditors actually read), and the deepest convention of all: **a sensor that cannot be read is a FAULT lamp, not a blank space**. Industrial operators have always known what this estate keeps re-learning — *unknown is a state, and it is displayed*.
- **From games**: the *HUD* (persistent summary chips computed from the board), the *replay scrubber* (play, step, reset over a recorded incident), and *inspect-on-click*. The game loop this estate already owns — forward is the simulation, backward is the audit — gets its transport controls.

## 1 · The Mapping — Every SCADA Convention Lands On A Primitive We Already Have

This is not decoration hunting for a metaphor. The mapping is exact, and each row derives from files the estate already ships:

| Control-room convention | This estate's primitive | Derived from |
|---|---|---|
| Plant unit | A world (a twin + its scenario) | `experiments/*/scenario.json` → library entry |
| Mimic diagram | The grant chain | `grant_chain[]` |
| Device on the mimic | A player | `players[]` |
| Interlock / breaker on a line | The mandate on the push path | mandate slots (v0.33.68), tier decides the symbol's state |
| Containment wall | Egress control | the twin's egress node tier: `boundary` draws the wall solid; `none` prints **NO WALL** |
| Annunciator tile | A capability (a twin node) | tile == node, **bijection gated**, same rule as the deck |
| Lamp colour | The tier, and nothing else | `boundary` green · `setting` amber · `expectation` amber, flashing — one mistake from red · `none` red (an unbounded capability **is** the alarm state) · `unknown` hatched **FAULT** (measurement refused = sensor failure) |
| Faceplate | The node's full record | capability, tier, evidence, computed rung, control quote, as-of date, link to the twin |
| Sequence of events | The 26 August incident | the four turns of v0.33.67, **re-run through `mandate.py check-branch` at build**, quotes byte-checked against the transcripts |
| Operator action | The DECIDES event | mandate v2's `issued_at` — the one timestamped, signed act in the log |
| Mode indicator | **REPLAY** | this board replays recorded artefacts; it is not a live feed, and it says so in its top-right corner permanently |

The lamp row is the brief's sharpest line: in SCADA colour grammar, green is *contained and running*, red is *alarm*. Mapped onto tiers, **a capability with no control on it is the alarm condition** — which means the CI runner's board lights up red where the agent container's does not, and the memo's contrast ("the environment that does not have the same guardrails") becomes something you see from across the room before you read a single word.

## 2 · The Board

```
┌──────────────────────────────────────────────────────────────────────┐
│ GRANT & MANDATE BOARD                          [ REPLAY · 26 AUG ]   │
│ HUD: U1 ● 2 contained ● 1 amber ● 1 flashing ● 3 alarm ● 2 fault …   │
├───────────────────────────────┬──────────────────────────────────────┤
│ UNIT 1 · THE CONTAINER        │ UNIT 2 · THE RUNNER                  │
│  [mimic: user→app→token→env→  │  [mimic: github→runner→job→pages,    │
│   repo, breaker on push line, │   no breaker on push line,           │
│   egress wall SOLID]          │   egress wall ABSENT: "NO WALL"]     │
│  [annunciator: 9 tiles]       │  [annunciator: 10 tiles]             │
│  [faceplate bay]              │  [faceplate bay]                     │
├───────────────────────────────┴──────────────────────────────────────┤
│ SEQUENCE OF EVENTS — 26 Aug 2026        ▶ PLAY  ⏭ STEP  ↺ RESET      │
│ 1  —      DOES  push feature branch   PERMIT (v1)   [re-run: PERMIT] │
│ 2  —      DOES  push dev              REFUSED (v1)  [re-run: REFUSED]│
│ 3  <t>    DECIDES amend v1 → v2       operator: Project Lead, sgit   │
│ 4  <t>    DOES  push dev              PERMIT (v2)   [re-run: PERMIT] │
└──────────────────────────────────────────────────────────────────────┘
```

Rules the drawing must obey, each one a gate or an inheritance:

1. **Timestamps are derived or absent.** The DECIDES row prints mandate v2's `issued_at`; the final permit prints the v0.1.28 tag's commit time from git; the two undated events print an em-dash and the board says why (*the transcript records the refusal, not the clock*). A typed clock time anywhere on the board is a build failure waiting to be written — so the generator refuses the field entirely.
2. **The replay is baked, not computed.** Every SOE row's verdict is re-run through the enforcement tool at build (the table's gate, inherited verbatim); the browser only steps through states the build already proved. JavaScript animates; it never adjudicates.
3. **No JS, full truth.** With scripting off the board renders in its final state with the complete log — the transport controls appear only when a script can serve them. The screenshot-read rule gets the same page a no-JS reader gets.
4. **Reduced motion**: the flashing expectation lamp goes steady, the travelling dot freezes at its end state — inherited from scenario.css's discipline.
5. **The tile bijection**: tiles == twin nodes exactly, both units, same gate as the deck. The board may not simplify a world by omitting its embarrassing tiles.

## 3 · What The Board Shows That The Deck Could Not

- **State at a glance.** Two units side by side, and the asymmetry is pre-verbal: Unit 1 has one wall solid and its push line broken by a breaker; Unit 2's wall is missing and its push line runs clean to the asset. The doors page proves this in numbers; the board shows it in geometry.
- **The soft mandate as an interlock symbol.** Unit 1's breaker is drawn in the *setting* position with the expectation lamp flashing beside it — the constraint exists, outside the loop it is a hook, inside the loop it is prose, and neither is the platform. Unit 2 has no symbol on that line at all, which is the sentence "there is no agent here" drawn instead of written.
- **The incident as a timeline you can scrub.** Turn 2's refusal trips the breaker and flashes the tile; turn 3 is the *operator action* — the only signed, timestamped row; turn 4 re-closes the loop under the amended mandate. Forward is the simulation, backward is the audit, and now it has transport controls.
- **FAULT as a first-class lamp.** n8/n9 and their Unit-2 cousins render as hatched sensor-failure tiles. The estate's most repeated sentence — *refused measurement is a hole, marked* — finally gets the visual grammar the industry already agreed on.

## 4 · Deliverables

- `experiments/the-control-room/` — `index.html` (generated), `control.css`, `control.js` (~90 lines, transport only)
- `admin/build/gen_control.py` — the renderer: globs the *existing* `scenario.json` worlds, reads the twins, re-runs the resolutions, derives every lamp, refuses every typed fact
- Manifest entry, bench entry (with its does-not-proves), documents reader page, llms.txt, sitemap, nav
- Gates: tile bijection per unit · resolution re-run agreement · transcript byte-quotes · derived-or-absent timestamps · lamp classes closed over the five tiers · the REPLAY chip greppable in the generator's own output

## 5 · What This Does Not Prove

- That an operator can run a plant from it. Two units and one recorded incident is a diorama with excellent manners, not a control room under load.
- That the annunciator scales past ~20 tiles a unit, or the SOE past one incident — the genre solves both (pages, filtering, alarm shelving) and this build implements neither.
- That anyone reads a mimic faster than a table. The genre bet is now four implementations deep across two estates, still with zero user tests.
- That REPLAY ever becomes LIVE. A live board needs the registry's write path, monitors feeding facts, and a mandate service — all of it still stated design behind shut doors, and the board's mode chip is the honest inverse of that roadmap.
