diff --git a/docs/roadmap.md b/docs/roadmap.md index a40ccf3..f596bd0 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -37,17 +37,24 @@ M0–M2 built the **engine** (the contract, the client, the server loop, bounded | **M2 — Prove aliveness** ✅ | **Does bounded AI dialogue feel alive?** — answered; bounded NPC dialogue + DM loop live-proven. | | **M3 — Visual foundation & shell** ✅ | The `Theme` + the exploration HUD; every screen inherits the look and lives in the shell. | | **M4 — Character creation** ✅ | The player enters the world; the proven stat/Luck model gets its UI. | -| **M5 — Tactical combat (gridless)** ◀ *here* | Real stakes: the Combat HUD turn manager. | +| **M5 — Tactical combat (gridless)** | Real stakes: the Combat HUD turn manager. | | **M6 — Dialogue screen** | The proven NPC free-text loop inside the mock's conversation UI. | | **M7 — Inventory & character sheet** | State the player can see and manage. | | **M8 — World systems** | World-map travel, a shop economy, the quest log. | | **M9 — Framing, AI-flavor & persistence** | Title/pause, save/load, and the Improviser/Banter/Luck-drift roles + fallback sweep. | -### Landed out of band +**▶ Next (before M5):** *Provider flexibility — Ollama Cloud + a backend switch* (in **Landed / planned out of band**, below). A laptop-dev enabler: it lets the proxy serve the same Ollama-hosted models over an API key from any machine, so development continues away from the homelab. Not a game-screen milestone — a cross-cutting infra item taken now because the alternative is a laptop with no AI. -Not every item is a milestone. These shipped on their own because a later milestone -would have been more expensive to build on top of the old shape. +### Landed / planned out of band +Not every item is a milestone. Some shipped on their own because a later milestone +would have been more expensive to build on top of the old shape; some are planned +cross-cutting work that does not belong to a single screen. + +- ▶ **Provider flexibility — Ollama Cloud + a backend switch** *(next; not yet specced — a future session builds it).* Development spans two machines: a desktop with the homelab Ollama, and a laptop with no homelab reach. Today the proxy's model-call pipeline (M1) only knows a **local** Ollama, so the laptop has no AI at all. This adds **Ollama Cloud** as a backend so the same Ollama-hosted models are reachable over an **API key** from anywhere, and a **switch** to flip between `local-ollama` (desktop, free/offline) and `ollama-cloud` (laptop) — and the door stays open for prod's Replicate (§4) as a third backend. + - **§4 is load-bearing — the key and the provider choice live server-side, in the proxy.** The client never holds an API key and never learns which backend serves a role (§4). Concretely: extend the proxy's Ollama httpx client (M1) to target an **Ollama-compatible cloud endpoint** with an **`Authorization: Bearer `** header, the key read from the proxy's environment / `.env`, never a client binary. The "flip a switch" is a **proxy config/env** value selecting the backend (endpoint + key + per-role model ids) — this is the M1 role→model routing config growing a *backend* axis, a deploy/config change, not a client patch, exactly as §4 intends. + - **The client's Settings-button angle (reconcile, don't silently adopt):** the "config file surfaced by the title **Settings** button" is a real feature, but under §4 it splits in two. **Legit client setting:** the **proxy base URL / profile** (§4 lets the client know *one base URL*) — a persisted client config choosing "laptop proxy" vs "desktop proxy," editable via the M9 Settings screen. **NOT a client setting:** the AI **provider and API key** — those stay in the proxy; the Settings screen must never hold the key or pick the backend, or the §4 boundary leaks into a shippable binary. So the honest "flip a switch" is *two* switches — a proxy-side provider/key config (this item, the immediate laptop-dev enabler) and a client-side base-URL/profile that **folds into M9's settings + persistence**. For laptop work right now, only the proxy switch is needed. + - *§2: text (the provider serves text; the client stays provider-agnostic) · depends on the M1 pipeline · goal: AI on any machine, switchable — and a proxy that is already multi-backend before Replicate (§4) arrives.* - ✅ **Currency — copper with real denominations** ([spec](superpowers/specs/2026-07-12-currency-copper-design.md)). `1g = 100s = 10,000c`, **stored entirely in copper** as one `GameState.purse_copper` int, formatted only at the display edge (`◈ 3s 47c`). `coin.json` → `copper`/`silver`/`gold`. Money is **not** an inventory item: `give_item`/`accept_item` carry no quantity and `gifts_given` is a global one-shot, so a lone `copper` id would have been givable once, for one copper, per campaign — the denominations are **move tokens** instead (`give_item(gold)` = +10,000c), routed to the purse and never into `inventory`, so money cannot surface in an inventory grid by construction. Taken before M7 while `content/world/items/` held two files. **Prices remain M8's content BOM — nothing is priced yet.** *§2: state · goal: money that means something.* - ✅ **A move applies at most once per reply** — found by the review of the above. `TagExtractor` returns *every* tag occurrence and `MoveValidator` is a membership test, so a model repeating a tag N times picked an amount **in unary** (§2 breach: the AI choosing a number). Now deduped by (name, id) across a reply, and `adjust_disposition`'s `MAX_DELTA` caps the reply's **net** swing rather than each tag — it was per-tag, so three `+15`s moved standing by 45, the exact "wholesale swing" the constant forbids. *§2: state · goal: the bounded vocabulary stays bounded.*