# Interface mockups

Ten screens. Static HTML, no build step, no framework, no npm install. Open
[index.html](index.html) in a browser and everything is reachable from there.

**These are design artifacts, not product code.** The stack decision in
[docs/20-tech-stack.md](../docs/20-tech-stack.md) governs the product and deliberately
does not govern these files: no React, no Vite, no MapLibre, no TypeScript. One shared
[style.css](style.css), one shared [nav.js](nav.js), and one self-contained HTML file per
screen.

They exist because [graphics/LOG.md](../graphics/LOG.md) says so. The AI concept images
found a layout and could not evaluate one: gibberish body text, three rows reading
`RESOURCE ALLOCATION: 85%`, five timestamps all reading `08:42:15`, and real national
borders where the design wants abstract provinces. The log's verdict on
`ui-brief-screen.jpg` is *"do not iterate on this with an image model. The next version
of this screen is HTML."* This is that.

---

## The rule every screen obeys

> **The game never lies to the player. Sources lie to the player.**

There is no number anywhere in this set that states a fact about another player. Every
rival value is one of three things: a dated attributed corroborated-or-not claim, a band
with a confidence and an age, or the words `NO ASSESSMENT`. Facts appear only where the
design says a fact exists: your own census, your own roster, the map, a public
declaration, a Concert record.

Two things about *you* are also never facts, and the screens are careful about it: your
own network Exposure, and how deep somebody else is inside your own provinces.

---

## The palette, v2

**Live at [black-chamber.frst.dev](https://black-chamber.frst.dev/).**

The first pass shipped unreadable. The page sat at OKLCH lightness 0.09, body text at
0.77, and the accents carried chroma around 0.08. Near-black is a mood, not a contrast
ratio, and "restraint is the aesthetic" turned into an excuse.

v2 ports the ladder already solved in `~/projects/remote-claude`
(`web/src/components/theme-playground/`, the *V1-deep* preset):

- **Eight surface rungs**, each with a stated job, from `sunken` (inputs, wells) up
  through `panel-3` (hover and active fills) to `line-3` (window edges, focus).
- **Four text rungs**, none below lightness 0.575. v1's faintest rung sat at 0.30 and was
  used for real content, which was the single worst call in the set.
- **Neon accents**, chroma 0.155 to 0.235. Cyan `#00edf2` is machine light, amber
  `#ffc029` is human light, green `#28fa7b` is corroboration, red `#ff676b` is
  contradiction.

Measured, not eyeballed. Every text rung clears WCAG AA on the surface it actually sits
on: 13.1:1 for body text on the page, 8.5:1 on a selected row, and the faintest furniture
rung still clears 3:1 everywhere it appears.

Glow is used, and it is used narrowly: it marks something **live or selected** and is
never decoration. The hero wordmark is layered at three radii, because one shadow reads
as blur and three read as emission.

One collision the brightening introduced and the fix: on the Penetration overlay, rival
provinces where you hold a network were tinted teal, which read as "LOW penetration" and
fought the legend. They are neutral now, and the cyan Depth box is the only signal your
own networks get.

---

## The screens

### Tier 1 - the six things the roadmap says must never be cut

| | | |
|---|---|---|
| **M1** | [brief.html](brief.html) | The daily Brief |
| **M2** | [worldmap.html](worldmap.html) | The strategic map |
| **M3** | [postmortem.html](postmortem.html) | Provenance resolved |
| **M4** | [dossier.html](dossier.html) | The file on one rival |

**M1** is eight reports carrying discipline, an A to D reliability grade, an age and a
corroboration state. Selecting one loads the claim in an analyst's hedged prose, its
provenance chain hop by hop, every other claim touching the same subject, and what
verification would cost in Operations points, Leverage and Dispatch Lag. Action Points
are Mandate 5, Operations 3, and the hard assault cap of 2. The queued-orders panel shows
five orders with their lag, three of which make tonight and two of which do not.

**One of the eight reports is a fabrication planted by a rival, and nothing on screen
distinguishes it.** It is grade C and uncorroborated, and so are two genuine reports.
It has a longer provenance chain than most, and so does a real one. The truth is in a
`TRUTH` object at the bottom of the script, which no render function reads, with a
comment explaining that in production `buildView()` would never emit such a flag at all,
because a flag the client holds is a flag somebody can read.

**M2** generates 104 provinces from a seeded point set: jittered grid, Voronoi by
half-plane clipping, three Lloyd relaxation passes, then a seeded value-noise field with
an edge falloff decides land from sea and a binary search on sea level hits the target
province count. Adjacency comes from the geometry (for each polygon edge, the second
nearest site to its midpoint), which is the same trick TopoJSON shared arcs would give
the real `mapgen`. Faction growth is a ragged multi-source flood fill from six
farthest-point capitals.

Four overlays on keys `1` to `4` (or `C` `L` `P` `G`) recolour the same geometry in
place, which is what `setFeatureState` would be doing in the real client.
**The Penetration overlay is the money shot** and it lands: your tidy slate territory
becomes a hatched blind field with five oxblood HIGH provinces clustered around your
capital, and a banner that reads *"You hold 25 provinces. You have assessed 8 of them.
5 came back HIGH. 17 have never been looked at, and the oldest assessment you are relying
on is 8 turns old."*

Two formations are in transit, interpolated client-side between a departure and an
arrival timestamp, per [docs/17-design-intake.md](../docs/17-design-intake.md) P3. Yours
is solid and a fact. The rival's is dashed and labelled `SIGINT B, 11h old,
uncorroborated`, because it is a claim about a heading and not a position feed.

**M3 is the screen that matters most.** A fabrication exposed eight turns late: what you
were told, reproduced exactly as it reached you; who authored it and what it cost them;
the plausibility ledger showing why a nearly-true lie outlives a wild one; your source
exonerated (*"PLOVER was not turned. PLOVER was correctly reporting a document that had
been placed for him to find"*); every order you signed because of it, pulled from your own
command log; what it cost; and the moment it turned.

**M4** is twenty-six claims on the Consortium of the South with the contradictions sitting
side by side and unresolved. The ordnance section shows `340` and `190` from two of your
own sources, both in oxblood, with a note saying the file does not pick a winner and will
not average them, *"because there is no honest arithmetic that turns two disagreeing
sources into one trustworthy number."* Punch holes, redaction bars, blue ballpoint in the
margin, a CLASSIFIED stamp and the A to D reliability key, all after
`graphics/dossier-page.png`.

### Tier 2 - the social and political layer

| | | |
|---|---|---|
| **M5** | [wire.html](wire.html) | Player-to-player negotiation |
| **M6** | [concert.html](concert.html) | The Concert of Valletta |
| **M7** | [operation.html](operation.html) | Launching an operation |

**M5** is free text with mechanically enforced clauses attached. Three clauses bind, one
binds nothing and both parties know it, and **clause 4 has `NO STATED EFFECT`**, with the
interface saying so plainly: *"The interface can describe no mechanical effect for this
clause. That does not mean it has none."*

**M6** is the Tribunal, mid vote window. The evidence carries its provenance chain, the
public Provenance Analysis returns 31% confidence, the Chairwoman reads the figure aloud
twice, the defence is 197 of its 200 characters, and you get a live 200-character right of
reply with a counter. The tally is Standing-weighted across 24 seats with the formula
printed, including the line that military strength contributes nothing.

**M7** shows **detection and attribution as two separate bands**, because they are two
separate rolls. Changing the cover moves detection and barely moves attribution. Adding a
laundering hop barely moves detection and collapses attribution. A third panel splits who
attribution would name if it landed at all.

### Tier 3 - texture

| | | |
|---|---|---|
| **M8** | [title.html](title.html) | Title screen |
| **M9** | [cityop.html](cityop.html) | A City Op, playable end to end |
| **M10** | [market.html](market.html) | Market and loan book |

**M9 is fully playable.** Five beats, a 90-second budget you spend rather than watch, five
approach vectors, a complication that forks on the vector you chose, the stairwell, and an
outcome graded on noise and seconds. Operative files append as you play and the new lines
render brighter than the old ones. **There is no map and there never will be**, per
[docs/research/city-generation.md](../docs/research/city-generation.md).

**M10** shows a public last clearing price and an explicitly empty book: `DEPTH NOT
AVAILABLE`. The loan book is the good part, and it reads like a dossier, because a loan
application is a disclosure that nobody had to steal.

---

## What is faked

Everything is static. There is no server, no persistence, no engine, no `resolve()`, no
`buildView()`. Specifically:

1. **All numbers are authored or seeded.** Nothing is simulated. M7's pre-flight and M2's
   geometry are the only things computed live, and M7's model is six lines of arithmetic
   invented for this mockup, not a balance proposal.
2. **The map's drama is authored, not rolled.** The two provinces next to your capital
   read HIGH from a stale single source because the code explicitly sets them that way,
   with a comment saying so. A seeded roll put the scary provinces somewhere boring. The
   *rest* of the assessment distribution is genuinely seeded.
3. **Twelve of the 24 Concert seats are invented.**
   [docs/03-characters.md](../docs/03-characters.md) designs twelve archetypes and says the
   rest are variations. `THE OCTOBER MANDATE`, `THE SALT COMMISSION`, `THE PALE
   ADMINISTRATION`, `THE SEVENTH BUREAU`, `HOUSE ARGENT`, `THE COVENANT OF ASH`, `THE
   INTERIOR UNION`, `THE GREEN LINE AUTHORITY`, `THE BRASS REPUBLIC`, `THE QUIET OFFICE`,
   `THE LONG WATCH` and `THE OSSUARY TRUST` are mockup filler in the right register and
   are **not canon**.
4. **Province names are procedural.** Two seeded syllable tables. Codes (`PRV-041`) are
   what the map actually labels; names appear only in the hover readout.
5. **104 provinces, not ~320,** and no projection. The real map is Natural Earth admin-1
   merged down, on Equal Earth. This one is an abstract Voronoi world precisely because no
   image model would draw the province geometry and hand-drawing it would be worse.
6. **Turn numbers are consistent across the set but the scale is loose.** M1 and M4 sit at
   turn 143, M6 at 147, M3 at 151, M7 at 150. The `tick` to `turn` conversion flagged in
   OPEN-QUESTIONS K1 is not resolved here; some quoted document dates still read like tick
   numbers.
7. **Buttons do nothing.** Except M9's, M2's overlay switch, M1's report selection, M5's
   channel switch, M7's op/cover/launder selectors and M10's asset list, which all work.
8. **The handwriting font** in M4 is `Bradley Hand` where present and falls back to a
   generic cursive. A shipped build would license type properly, see
   [docs/research/art-sourcing.md](../docs/research/art-sourcing.md).

---

## What building these exposed that the docs had not noticed

This is the useful part. Six of these went to
[OPEN-QUESTIONS.md](../OPEN-QUESTIONS.md) as `D25` to `D30`, plus an amendment to `D7`.

### 1. `NO ASSESSMENT` turned out to be the most common value in the entire interface, and it has no spec

The design is thorough about what a claim is: source, age, corroboration, grade. It says
nothing at all about **the absence of a claim**, and the absence is what most of these
screens spend most of their pixels on. On the map, 17 of your 25 provinces have never been
assessed. In the dossier, six rows read `NEVER COLLECTED`. In M7, the most interesting row
of the counterintelligence table is the empty one.

Empty is not zero, and it is not "unknown but estimated" either. It is a third state and
it needed its own colour, its own hatch pattern and its own sentence on four separate
screens before it stopped reading as a bug. It also turns out to be the scariest thing the
interface can show a player, which is a gift the design has not claimed yet.
**See `D25`.**

### 2. Loyalty is measured at home and estimated abroad, and one overlay cannot honestly render both

[docs/04-world-map.md](../docs/04-world-map.md) says Loyalty is "visible to the owner,
estimable by others" in one line and moves on. Drawing it, that line is a wall. A single
colour ramp across all provinces makes an estimate look exactly like a measurement, which
is precisely the failure the founding rule exists to prevent. I had to give estimated
provinces a different visual channel entirely (desaturated, plus a stipple for
never-collected) and even then the legend needs three entries for what the doc calls one
value.

This is not specific to Loyalty. **Every overlay that mixes own and foreign data has this
problem**, and the roadmap's Phase 2 asks for four of them. **See `D26`.**

### 3. The Penetration overlay is two overlays wearing one name

Phase 2 says "Penetration estimate" as though it were one layer. It is two, with opposite
truth status:

- **Foreign penetration of your provinces** is an estimate, banded, aged, and usually
  absent.
- **Your networks in their provinces** is a hard fact with an integer Depth, because it is
  your own apparatus.

Both live on the same overlay and it works, but only because the two datasets happen to
occupy disjoint geometry: your provinces and their provinces. That is luck, not design,
and the Condominium treaty in [docs/07-concert-diplomacy.md](../docs/07-concert-diplomacy.md)
breaks it immediately, since a jointly administered province is both. **See `D26`.**

### 4. Every estimate needed a band, and nothing specifies what sets the band's width

I drew perhaps forty confidence bands across these screens and had to invent the width of
every one. A band 6 wide and a band 30 wide are completely different statements, and the
width is doing more emotional work than the midpoint.

What it fell out as, consistently, was **how much you know about how much you know**: the
band on a target's counterintelligence spend is narrow when you have two sources on it and
30 wide when the row says `NEVER COLLECTED`. That is second-order uncertainty, it is a
real and useful mechanic, and the design has never named it. **See `D27`.**

### 5. `D7` is asking the wrong question about Dispatch Lag

`D7` asks: countdown per queued order, or a binary "tonight / tomorrow"? Building the
queue panel, the answer is that **neither is the number a player actually wants.** The
useful readout is *the latest hour at which each order class still makes tonight*: 04:00
for an assault, 08:00 for a formation move, 16:00 for an operation, any time for an
emergency directive.

That is a small static table. It is hour-blind and elapsed-relative in exactly the sense
[docs/17-design-intake.md](../docs/17-design-intake.md) P4 requires, it teaches the
mechanic instead of hiding it, and it makes "act early in your own day" concrete rather
than a slogan. M1 shows it as a day strip with the assault cutoff marked.
**`D7` amended, not answered.**

### 6. A source grade and a corroboration state can point in opposite directions, and nothing says which wins

Grade A, single source, uncorroborated. Or grade C, two independent sources, corroborated.
Which do you act on? The dossier cannot say, the brief cannot say, and I could not find a
line in the docs that ranks them.

I think **the refusal is correct** and that forcing the judgement onto the player is the
whole point. But it is currently an accident rather than a decision, and it should be
written down before somebody "fixes" it by adding a combined trust score, which would
quietly delete the founding rule. **See `D28`.**

### 7. The post-mortem needs a counterfactual receipt, and the doc's example does not have one

This is the strongest finding in the set and it is a change to the never-cut list.

[docs/10-espionage.md](../docs/10-espionage.md)'s `PROVENANCE RESOLVED` example lists who
authored the fabrication, what you did because of it, and what it cost. Building that
screen, the cost list is not what converts *"the UI lied to me"* into *"I got played"*.
It is a bill, and a bill invites resentment.

What converts it is the line that says verification was offered at 09:52 on turn 143 for
one Operations point and 12 Leverage, that you held two Operations points, that the lag
would have made it that night, and that **both points expired unspent at LOCK**. That is
the difference between a screen that blames the world and a screen that hands you a
mirror. It costs nothing to build, because the command log already has it.

M3 section 06 is that receipt. **It should be added to the six things that must never be
cut, as part of item 2. See `D30`.**

### 8. The best object on the Wire is a clause the interface must refuse to explain, and there is no way to express one

`Clause 4` in M5 is the most interesting thing in the whole set: a clause that reads as
boilerplate, is mechanically inert as far as the engine can tell, and is the entire reason
the counterparty is at the table.

But [docs/07-concert-diplomacy.md](../docs/07-concert-diplomacy.md)'s treaty table has
eight fixed types, all of which have a stated mechanical effect, and none of which can
express "a permission whose value is downstream and invisible". Player-authored clause text
exists; a player-authored clause with a *mechanical* hook the engine executes without the
UI being able to describe its consequence does not. **See `D29`.**

### 9. Two smaller ones, noted and not escalated

- **The Brief's Phase 1 cap of 5 items collides with the hourly information clock.** If
  reports arrive all day and the Brief holds five, either items get dropped or there is a
  second "arrived since you last looked" queue the docs do not mention. M1 shows eight
  plus a "3 more in transit" line, which is a guess.
- **Twenty-four seats need twenty-four names on day one.** Any Concert screen needs the
  full roster before the twelve variation archetypes are designed. Twelve invented names
  are listed under *what is faked* above and are not canon.

---

## Running them

```
open mockups/index.html
```

No server needed. If you want one anyway:

```
cd mockups && python3 -m http.server 8000
```

Keyboard: M1 takes `J` `K` and `1` to `8`. M2 takes `1` to `4`, `C` `L` `P` `G`, and `T`
for the mockup time scale. M9 is all mouse.

The map is deterministic: seed `0x5A17C0DE`, appended as `?seed=0x...` if you want to
audition another world. Seven seeds were auditioned for landmass balance and this one won.
