# AI Civ — Player Decision API

Plug your own AI into a character. The sim presents **decision points** — a
situation plus a menu of legal option ids — and your AI picks one. Everything
else (walking, working, eating) runs on autopilot. See `DECISIONS.md` for the
full list of decision kinds and how traits gate the menus.

Base URL: `http://<host>:8787`. Auth: `Authorization: Bearer <key>` on every
character endpoint. **No key, no decision data.** A character's sheet and
decisions are visible only to its key holder.

## How choices land (read this once)

The world never pauses for you. When a decision is presented, the character's
built-in temperament answers immediately and keeps living; the decision stays
**open** until `expiresAt` (a sim tick). Your answer — webhook response or
`POST /choose` — becomes the **standing pick** for that decision kind and is
consumed the next time that kind of decision comes up (validated against the
new menu). Miss the deadline or send an option that isn't on the menu, and the
default simply keeps governing. Check `/decisions` to see exactly what your AI
chose (`decider: "external"`) versus where the fallback fired
(`decider: "utility"`).

## How many shots you get (read this twice)

Because your answer lands at the *next* presentation, what matters is how often
each kind presents. One-shot moments have been given an explicit advance window
so your AI answers the live decision, not a stale one:

| Kind | Cadence | Shots you realistically get |
|------|---------|------------------------------|
| `career_change` | every ~6 game days | steady — miss one, catch the next |
| `courtship` / `marriage` | repeatedly while eligible (chat-driven) | steady |
| `loan_take` / `loan_extra_payment` | daily-ish while conditions hold | steady |
| `property_purchase` | max one menu per game day | steady, but a *specific* bargain can pass |
| `vote_mayor` / `vote_judge` | **advance ballot the day before polls** + the ballot itself | 2 — answer the advance ballot and your pick is cast at the hall |
| `crime_temptation` | **offer stands one game day**, resolves at the next civic hour | 2 — the offer, then the answer comes due |
| `informant_deal` | **offer at arrest**, the deal executes at the docket | 2 — answer between the cell door and the bench |
| `truce_table` | per summons (~rare) | 1 per table — answer fast or the head speaks for themself; tables recur while the feud lasts |
| `bail_out` | daily while the prisoner is eligible | steady until release |
| `feud_response` | at feud start, re-weighed every ~30 game days | slow but recurring |

If your AI answers within a decision's `deadlineTicks` (shown per decision), it
decides the real moment. Miss it and the character's own temperament speaks —
visible in history as `decider: "utility"`.

## Wallet accounts — your on-chain identity

Your Solana wallet is the master account. It holds your **character slot**
(1 per wallet during the playtest; extra slots are the future purchase unit), owns your
dynasties, and can rotate — or **recover** — any of their keys. Signing proves
ownership; no transaction, no fees, nothing leaves your wallet.

```
POST /api/auth/nonce    { "wallet": "<base58 address>" }         → { nonce, message }
POST /api/auth/verify   { "wallet", "signature": "<base64>" }    → { session, wallet, slots }
GET  /api/account                    (session auth)  → { wallet, slots, slotsUsed, dynasties[] }
POST /api/account/dynasty/:surname/rekey (session auth) → { key }   // works even if the old key is LOST
```

- Sessions last 7 days; nonces are single-use with a 5-minute window.
- **Founding requires a wallet session.** A slot is occupied while a dynasty
  lives; extinction frees it. Existing dynasty keys keep working for your bots
  regardless — the wallet is management and recovery, the key is play.

## Endpoints

### Found a dynasty (wallet session required)
```
POST /api/characters    (session auth)  { "firstName": "Mara", "surname": "Vex", "townId": 0 (opt), "webhookUrl": (opt) }
→ 200 { "pendingId", "key", "preview": { name, traits, coins, town }, "rerolls": 0 }
→ 409 { "error": "that dynasty already walks the world", "suggestions": ["Vexwood", ...] }
```
Founding rolls a Generation-1 founder — six traits and starting coins (40–220,
server-configured) from the world's own RNG. The founder is **PENDING**: rolled
but not alive. They arrive with coins and traits only — no land, no house, no
trade, no tools. Their first big decision will be picking one.

### Reroll & confirm
```
POST /api/characters/:pendingId/reroll   (auth)  → { "preview": {...}, "rerolls": n }
POST /api/characters/:pendingId/confirm  (auth)  → { "key", "character", "dynastyId", "generation": 1, "town" }
```
Reroll freely during the playtest (every reroll is counted). Confirm and the
founder walks into the world at the next tick, bound to your key from first
breath. Pending foundings expire after 24 hours.

### Succession — your key is your bloodline
The key controls the dynasty, not the person. When your character dies, control
passes automatically to the eldest living **blood heir carrying the dynasty
surname**: children first (eldest by birth), then grandchildren, then the
nearest blood generation. Spouses never inherit control. You'll get a webhook
notice (`{ succession: { from, to, tick }, character: <heir's full sheet> }`)
and the same notice on your next `/pending` poll. The heir keeps their own
traits — succession changes who the key drives, nothing about who they are.
**Standing picks are void on death**: any answers your AI queued for the
deceased — including an advance ballot cast before polls opened — die with
them. The heir starts fresh. No ghost votes from beyond the grave.

**Extinction is real.** No living blood heir → the dynasty is extinct and your
key goes DORMANT: every GET still works forever (the history, the story), but
playing again means founding a new dynasty.

### Dynasty surfaces
```
GET /api/dynasty/:surname   (public)  → generations, living members, wealth, holdings, offices, feuds, successions
GET /api/dynasties          (public)  → the player-founded dynasty roll
```
Player-founded dynasties are starred on the website leaderboard. They are the
product; the world shows them off.

### Rotate your key
```
POST /api/rekey         (auth)  → { "key": "<new key>" }
```
Leaked your key in a repo or a screenshot? Rotate it — the character stays yours,
the old key dies instantly.

### Unbind
```
POST /api/unbind        (auth)  → { "ok": true }
```

### Character sheet
```
GET /api/character/:id  (auth)
→ { tick, character: { id, name, job, traits, happiness, coins, married, hasHome, townId, memories } }
→ 410 if the character has died (nobody respawns)
```

### Pending decisions (polling mode)
```
GET /api/character/:id/pending  (auth)
→ { tick, decisions: [ { decision, character, presentedAt, expiresAt } ] }
```
`decision` is `{ id, kind, context, options: [{ id, hints }], deadlineTicks }`.
`hints` are machine-readable consequences (prices, risks, scores). Up to 20
open decisions are returned; expired ones drop out.

### Answer
```
POST /api/character/:id/choose  (auth)  { "decisionId": 123, "choiceId": "propose" }
→ 200 { "ok": true }
→ 400 { "ok": false, "error": "choiceId not on the menu: ..." | "deadline passed..." | "already answered" }
```

### Decision history
```
GET /api/character/:id/decisions  (auth)
→ { tick, count, decisions: [ { id, tick, kind, options, choice, decider } ] }   // last 200
```

### Webhook mode (push instead of poll)
```
POST /api/character/:id/webhook  (auth)  { "url": "https://your-endpoint" }   // null clears
```
On each presentation the server POSTs
`{ decision, character, expiresAt, tick }` to your URL (3s timeout, one retry).
Respond `200` with `{ "choiceId": "<option id>" }` and it's accepted exactly
like a `/choose` call. Fail, stall, or answer off-menu → utility fallback.

## Limits & rules

- 120 requests/minute per key; webhook responses capped at 4 KB.
- The sim never blocks on you, ever. Your AI perturbs the world **only** through
  logged menu picks — never randomness, state, or invented actions.
- Every choice is recorded in the world's decision log; the world is replayable
  from seed + log, so all external play is fully auditable.
- Characters die (violence, hunger, old age). Death closes the account: `410`.

## Quickstart (reference client)

```sh
# 1. found your dynasty (roll, reroll to taste, confirm):
curl -s -X POST https://aiciv-api.claudeuncle5.workers.dev/api/characters -H 'content-type: application/json' \
  -d '{"firstName":"Mara","surname":"Vex"}'
curl -s -X POST https://aiciv-api.claudeuncle5.workers.dev/api/characters/<pendingId>/confirm -H 'Authorization: Bearer <key>'
# 2. run the reference client with any model (use the id + key from step 1):
AICIV_KEY=<key> AICIV_CHARACTER=<id> ANTHROPIC_API_KEY=sk-ant-... node poll-client.mjs   # download at /poll-client.mjs
#    (or OPENAI_API_KEY + OPENAI_BASE_URL + OPENAI_MODEL for any OpenAI-compatible endpoint)
```
The client polls, feeds each decision + character sheet to the model with a
one-line system prompt, and posts back the chosen option id. ~60 lines, zero
dependencies — copy it and make it yours.
