Skip to main content
Glama
README.md
# brink

A single-player, cinematic geopolitical roleplay: **you are the supreme executive of a great power,
handling only the geopolitical brief** — taking calls from world leaders, hearing your advisors,
making decisions — in a persistent world grounded in current real-world geopolitics.

Built as a fork of [DMCP](https://github.com/shawnrushefsky/dmcp) (LLM-as-DM, MCP server as
persistent state + tools), with a geopolitical rules core ported from the `BalanceOfTerror` engine.

**To play it, read [docs/MANUAL.md](docs/MANUAL.md).**

**The design brief is [REBOOT-DESIGN-AND-REQUIREMENTS.md](REBOOT-DESIGN-AND-REQUIREMENTS.md)** and it
is the authority on everything below. Read it first if you're working on the code.

## The shape of it

- **The LLM is the Game-Master**: it classifies a fuzzy action into which mechanic fires, narrates,
  and calls tools. It **never adjudicates**.
- **The MCP server is referee + ledger**: authoritative resolution, persistence, and the handful of
  rules the GM cannot talk its way out of.

### Three hard rules that constrain every change here

1. **Show, don't tell.** If the player-facing surface ever states a disposition or a raw value —
   `DOCTRINE: HAWK`, `cohesion 80.0`, `(DOVE advisor)` — that is a **bug**, not a style preference.
   The player deduces state from behavior, dialogue, and consequence.
2. **Resolve, then narrate.** The GM never narrates an outcome before the server returns it. It
   calls the resolve tool, receives an authoritative outcome payload carrying an explicit narration
   constraint, and narrates *from* that payload. The fiction can be florid about *the how*; it can
   never lie about *the what*.
3. **No credential in the working tree.** The GM runs keyless, on a Claude subscription (OAuth).
   There is no standing pay-per-token API key anywhere in this repo, and development works against a
   mock GM backend. See §2.4 of the brief — this is a requirement, not guidance.

## Fork discipline (§10 of the brief)

Changes sort into three buckets, and **generic mechanism must never share a commit with geopolitical
content**. That separation is what keeps the offer-back-upstream workflow mechanical instead of
archaeological.

| Bucket | What | Where it lives |
|---|---|---|
| **Use as-is** | DMCP's persistence spine, factions, relationships-with-history, resources, locations, secrets/visibility, dice/checks, pause/resume, web UI | untouched |
| **Offer back upstream** | Generic, game-agnostic mechanism: atomicity, the declarative constraint layer, conserved resources, on-expiry consequences | `offer/*` branches — see [docs/IMPROVEMENTS-TO-DMCP.md](docs/IMPROVEMENTS-TO-DMCP.md) |
| **brink-private** | Geopolitical rules content, GM persona, situation-room UI, current-events refresh, avatar/voice | `main` |

The line: *"a resource can be declared conserved"* is a DMCP feature; *"prestige is conserved across
three great powers"* is our game.

**[docs/IMPROVEMENTS-TO-DMCP.md](docs/IMPROVEMENTS-TO-DMCP.md)** is the full account of what changed in
DMCP and which parts are generic enough to lift — written for DMCP users deciding whether any of it is
worth taking, as much as for upstream.

### Branch convention

- `upstream` remote tracks DMCP. `git fetch upstream && git merge upstream/main` pulls their changes.
- Work intended for upstream branches from `upstream/main` as **`offer/<topic>`**, touches **only
  DMCP-generic files**, and carries no geopolitical content — so it cherry-picks cleanly.
  (`offer/`, not `upstream/`: a local branch named `upstream/foo` collides with the `upstream` remote's
  own ref namespace, and git will happily let you create the ambiguity.)
- Everything else branches from `main`.
- Upstream's own README is preserved in history: `git show upstream/main:README.md` (setup
  instructions and the full ~170-tool inventory).

## Development

```bash
npm ci               # install
npm run build        # tsc + client build
npm run dev          # run the server
npm run test:run     # vitest
npm run lint         # eslint      (CI gate)
npm run typecheck    # tsc --noEmit (CI gate)
```

Local MCP testing: `npx @modelcontextprotocol/inspector node dist/index.js`

**Write lots of tests.** DMCP arrived with essentially no coverage of business logic, so there is no
upstream safety net under the invariants this game depends on. TDD is mandatory — failing test first.
See [CLAUDE.md](CLAUDE.md) for the test harness and the codebase gotchas worth knowing before you start.

## Status

**Tranche 1 is complete.** The text-first vertical slice runs:

```bash
npm run build:server        # once
npm run play -- --real      # Claude as the game-master
npm run play                # placeholder game-master, spends nothing
```

Graphics amplify a loop that already works; they cannot rescue one that doesn't — so this had to
exist before anything cinematic.

What's in place:

- **The referee.** All three enforced rules from §4.2 have real mechanisms: prestige is a `conserved`
  constraint set moved only through an atomic transfer; DEFCON is `bounded` *and* `resolve_only`, so
  it has exactly one writer; declared on-expiry consequences are applied by the server itself, so a
  countdown lands regardless of the fiction.
- **The rules core**, ported from the `BalanceOfTerror` Python engine as pure functions with injected
  RNG, verified against that engine's own test suite.
- **The resolve path**, which makes resolve-then-narrate enforceable: the payload carries an explicit
  narration constraint, and tests re-read the database after every call to prove prose and ledger
  cannot disagree.
- **The GM layer**: system prompt, persona seeds whose dispositions exist only as behavior, a
  `GMBackend` seam with a deterministic offline mock, and **the real game-master** — Claude Code driven
  headlessly under a subscription, with **no credential anywhere in the tree**.

Verified live: the GM streams an outcome-neutral reaction, calls the referee itself, and narrates only
after the payload lands. DEFCON moved 5→4 with a matching audit row — and since DEFCON carries a
`resolve_only` constraint, that write had no other route available to it.

Tracked in [the issues](https://github.com/JavaDerek/brink/issues).

## Contributing, and security

- **[CONTRIBUTING.md](CONTRIBUTING.md)** — how to get set up, and the two rules that catch people out.
- **[SECURITY.md](SECURITY.md)** — how to report something, what leaves your machine, and why there is
  no API key in this repository.
- **[docs/PUBLISHING.md](docs/PUBLISHING.md)** — the repository topology, and the two routes work takes
  out of here: a tree copy to the public repo, and `offer/*` branches to DMCP.

## License

MIT. Copyright is shared: DMCP's original notice is retained alongside brink's own — see
[LICENSE](LICENSE).

Maintenance

ActivitySlowing
ResponsivenessNo issues