Implements the docs restructure half of the content-track design (the
currency item is code and stays unbuilt). Content stops being unbounded:
the work is now a countable checklist with an end.
content/roadmap.md — new. Three sections, only one of which is ever the
work:
Spine Greywater Docks + one dungeon, patron Ghaul the Yoke; the
cosmology touching ground. Plus what is already canon.
BOM ~57 discrete pieces for a complete, playable game, per
milestone. Numbers, not vibes.
Backlog Everything the slice does not need. Not a debt — where ideas
go to be safe.
Its rule: never author anything that is not in the Bill of Materials.
The BOM carries a Fixed/Variable column — the seven-campaign saga spec's
only ask of this table. ~37 fixed / ~20 variable: a second town costs the
20, not the 57. That is also the exact split v2 generation would need.
content/lore/canon-roadmap.md becomes a pointer (older specs and plans
reference the old path). An items-and-prices table does not belong under
lore/.
docs/roadmap.md — M4..M9 each gain a "Content it consumes →" line into
the BOM.
world-building skill — SKILL.md and evals.json now point at
content/roadmap.md, and the skill is told to respect the BOM rule: ideas
that arrive during authoring go to the Backlog, not into the world.
Build + --check green; generated content/world and content/server are
byte-identical. No Python touched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
109 lines
20 KiB
Markdown
109 lines
20 KiB
Markdown
# Roadmap
|
||
|
||
Source of truth for scope is [`CLAUDE.md`](../CLAUDE.md). This tracks sequencing.
|
||
|
||
## The game — building the tactical CRPG (charter §17)
|
||
|
||
> **The POC's one question — *does bounded AI dialogue feel alive?* — is answered.** Bounded NPC dialogue, validated moves, and the DM loop are built and live-proven (M0–M2). We are now building the game the mockups describe (§16, `/mockups`): a tactical CRPG across 11 screens.
|
||
|
||
The engine is done and proven. What's left is the **game on top of it** — recreated screen by screen against the mockups' shared visual system, on the canon log / DM–NPC pipeline / bounded-moves / Luck / character-creation model we already built.
|
||
|
||
### Reconcile as we build (charter §17)
|
||
|
||
- **Damage determinism** — §7/§10 (deterministic) vs the Combat HUD mock's damage ranges → combat brainstorm (M5).
|
||
- **Class names** — §8 names vs the mock's callings (same archetypes) → character-creation brainstorm (M4).
|
||
|
||
### Guarded direction — the seven-campaign saga ([spec](superpowers/specs/2026-07-12-character-reuse-seven-arc-design.md))
|
||
|
||
**One character, seven campaigns (one per demon king), one saga — then it ends.** The character, companions, deeds and level carry between campaigns; the world does not. Difficulty is chosen **once, at world-construction time**, from the campaign index (`tier` 1..7) — **never during play.**
|
||
|
||
The spec adds **no milestone and no content**. It exists because M5 and M9 are about to be built blank and would foreclose the shape. What it constrains is marked **⛨ saga** on the milestones below. World generation itself stays v2 — the spec only guarantees the `Saga` is the object that will feed it.
|
||
|
||
### Out of scope (never / later)
|
||
|
||
Multiplayer (never) · mobile (later) · AI-generated story skeletons (v2) · credits/payments/auth (retrofit onto the proxy, §4).
|
||
|
||
## Sequencing
|
||
|
||
Status: ✅ done · ▶ next · ○ planned. Each item is its own spec → plan → implementation cycle.
|
||
Each carries its **§2 side** (state = code owns it · text = AI owns it · both = it is two systems), what it **depends on**, and the **milestone goal** it advances.
|
||
|
||
M0–M2 built the **engine** (the contract, the client, the server loop, bounded dialogue) and answered the aliveness question. M3+ build the **game** — the CRPG screens/systems of the mockups, sequenced toward a playable vertical slice first, then breadth:
|
||
|
||
| Milestone | Goal it advances |
|
||
|---|---|
|
||
| **M0 — Foundations** ✅ | The contract and the client-side engine exist and are trustworthy. |
|
||
| **M1 — Prove the server loop** ✅ | A real model sits behind `/dm/narrate` and the pipeline is reusable. |
|
||
| **M2 — Prove aliveness** ✅ | **Does bounded AI dialogue feel alive?** — answered; bounded NPC dialogue + DM loop live-proven. |
|
||
| **M3 — Visual foundation & shell** ◀ *here* | 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)** | 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. |
|
||
|
||
---
|
||
|
||
### M0 — Foundations ✅
|
||
- ✅ **Canon log + origin JSON-Schema contract** — api validation, unified 422 envelope (Plan A). *§2: state · foundation.*
|
||
- ✅ **Client canon log engine** — typed GDScript model (invariants in the model), new-game construction (origin + world + creation → canon log), turn-to-turn maintenance mutators, pure `[FACT]`/`[MOVE]`/`[ADJUST_DISPOSITION]` tag extractor (Plan B). *§2: state · depends on the contract.*
|
||
|
||
### M1 — Prove the server loop ✅
|
||
- ✅ **Narrator / Ollama pipeline (server)** — the shared server-side model-call pipeline (Ollama httpx client, role→model routing config, prompt loader + canon-log digest renderer, §10 call logging, single-retry → typed 502) wired through `/dm/narrate`. Non-streaming. Both phases landed: **(1) pipeline foundation** (`config`/`routing`/`prompts`/`ollama_client`/`call_log`), **(2) Narrator online** (authored `narrator.md` body + `narrate` service + gated live smoke). Server returns raw prose; the client extracts tags. **The other four roles become thin drop-ins on this pipeline.** Live wire proven against qwen3.5; 49 tests. Merged to `dev` (`ddc00d4`). *§2: text · depends on the contract.*
|
||
|
||
### M2 — Prove aliveness ✅ *(the POC's one question — build complete + live-proven; the *feel* is confirmed in playtest)*
|
||
- ✅ **Client HTTP loop** — client posts its canon log to `/dm/narrate`, displays the prose, harvests `[FACT]` via its `TagExtractor` into its own log, and shows the degraded-DM authored fallback (§13) on any non-200. First end-to-end vertical slice: **state → HTTP → model → prose → screen → fact-harvest.** Pure `DmService` core behind an injectable transport seam; throwaway harness scene for the first on-screen prose. **Live-proven** against qwen3.5 through Docker (grounded scene prose, no numbers per §7). 67/67 client tests; merged to `dev` (`b949cd2`). Surfaced + fixed two Docker-dev gaps in M1 (prompts not packaged, no container→host-Ollama route) — M1's live proof had been venv-on-host only. *§2: state (client owns the loop, consumes text) · goal: put the Narrator on screen.*
|
||
- ✅ **Bounded NPC conversation** — `/npc/speak` with server-owned persona + `knowledge[]` (spoilers stay off the client) and client-computed `available_moves[]`; the client validates each emitted move against live state, applies the valid, silently drops the invalid, and always keeps the prose (§6). Server role is a thin drop-in on the M1 pipeline (`npc.run` mirrors `narrate.py`); the client added a pure `MoveValidator` (membership is the whole test) + a `MoveApplier` that writes town-NPC disposition to `GameState.npc_dispositions` (not the party log), plus `NpcContent.available_moves` as the single legality home. Free text goes straight to the NPC prompt (no Adjudicator needed — the moves are the NPC's). **Live-proven** against qwen3.5: Fenn answered in-voice, grounded strictly in his authored knowledge, no Luck leak (§7), with `reveal`/`offer_quest` moves landing. The live smoke also caught two real tag-format drifts a unit test can't — paren-less zero-arg moves and a dropped `MOVE:` prefix leaking into prose — both fixed by making `TagExtractor` tolerant/scrub-safe (backward-compatible; narrator path unaffected). Built on `feature/npc-conversation`; awaiting the human's `--no-ff` merge to `dev` (§18). *§2: both (text = the NPC's voice · state = the validated moves) · goal: answer the one question.*
|
||
- ✅ **Considering-state + transport timeout** — while a model call is in flight the client rotates authored in-character "the Master considers…" lines (§13 spirit — even *loading* is content; `ConsideringPhrases` + `ConsideringIndicator`, 3.0s cadence) instead of a frozen surface, and `HttpTransport` sets a request timeout (35s, rejects ≤0) so a hung call degrades to the authored fallback instead of spinning forever. Meets §14's anti-freeze *goal* by **reframing** the wait, on the *proven* non-streaming loop, with no new failure surface. **Human-confirmed live** including the degrade path (API down → fallback). Merged to `dev` (`099714d`); 110/110 client tests. *§2: text/UI · goal: the wait is framed (in-character) and bounded (never hangs).*
|
||
- ~~**Streaming** (§14)~~ — **SHELVED for the POC.** Real token streaming would genuinely *hide* the ~2s call, but it is the POC's largest transport rework (client `HTTPRequest` → poll-based `HTTPClient`) and adds mid-stream error/tag-boundary failure surface not worth the risk. The considering-state above reframes the wait instead and is tested and live. Design kept as a record (`specs/2026-07-10-narrator-streaming-design.md`, banner SHELVED); revive only if playtest proves the wait genuinely hurts. Conscious deviation from §14's "stream everything," recorded per §18.
|
||
|
||
Each screen is recreated as a Godot 4.7 Control-node scene against the mockups (§16); each `.dc.html`'s `Component` class is the behavior spec. Sequenced toward a playable vertical slice (shell → creation → combat → dialogue) first, then breadth.
|
||
|
||
### M3 — Visual foundation & shell ✅
|
||
- ✅ **Shared `Theme`** — palette tokens, the three font families, and reusable styleboxes (parchment card, dark panel, primary CTA, tab, item tile, tag/chip) from the mock README as a Godot `Theme` resource. Every screen pulls from it; build it first so the look is one system. Merged to `dev`. *§2: n/a (presentation) · depends on nothing · goal: one consistent visual identity.*
|
||
- ✅ **Main Window shell (2a)** — the exploration HUD: isometric world-view slot + the permanent DM "narration book" (wired to the proven `/dm/narrate` loop + considering-state), the turn-order rail, minimap slot, the slide-in system dock (Inventory/Character/Quest/Map/Party/Spellbook toggles), and the bottom command bar (HP/MP/gold, consumables, End Turn). The frame everything else lives in. **Established the editor-first UI-scene convention** (ADR [0001](adr/0001-editor-first-ui-scenes.md); how-to in `client/docs/README.md`) and added Button-base `DockButton`/`ParchmentButton` + a base `RichTextLabel` prose style to the theme. *§2: state (client owns the HUD, consumes DM text) · depends on the DM loop (M2) · goal: the game's main screen, on screen.*
|
||
- ○ **Title screen** — new game / continue / load / settings; the entry point. Author it editor-first (ADR 0001). **⛨ saga:** a fourth entry path (*begin the next campaign of an existing saga*) lands later — don't build it, just don't hardcode the flow as `new game → creation → world` so it can't be added. *§2: n/a · depends on the Theme · goal: first impression, into the world.*
|
||
|
||
### M4 — Character creation
|
||
*Content it consumes → [BOM](../content/roadmap.md#2-bill-of-materials): calling blurbs + the Bloodsworn's patron choice (fixed); origins (variable). **~4 pieces.***
|
||
- ○ **Character creation UI** — the mock's screen over our proven model: race + calling cards, six stats with **LCK hidden entirely** (§7), 3d6 floors, the **+3 additive-only spend-up** pool (§8, matches the `character-creation-stats` model), live origin blurb + nameplate. **Reconcile class names** here (§8 vs the mock's callings). Feeds new-game canon-log construction (already built). **⛨ saga:** `NewGame.construct` takes `creation` as a plain `Dictionary` — keep it that way. The creation *scene* produces that dict but must not become the only thing that can (a saga synthesizes it and skips the UI). Emit `race_id` + `calling_id` into it. *§2: state · depends on the creation model (M0) + Theme · goal: build a character.*
|
||
|
||
### M5 — Tactical combat (gridless)
|
||
*Content it consumes → [BOM](../content/roadmap.md#2-bill-of-materials): abilities for the 3 POC classes (fixed); 1 enemy family (4 units) + boss, **tier 1** (variable per tier). **~15 pieces.***
|
||
- ○ **Combat turn manager + HUD** — the Combat HUD `Component` state machine as GDScript: initiative order, the action economy (Action/Bonus/Move), ability resolution, click-to-target, victory/defeat; tokens on the iso board as **flavor only, no movement grid** (§10). Seeded per encounter (§10). **Reconcile damage determinism** here (§7/§10 vs the mock's ranges). Combat flavor is async optional Narrator calls (reuses M1). **⛨ saga — the level curve is no longer open:** design it to a **ceiling of L20 across 7 tiers** (tier N ≈ levels 3N−2 … 3N; exact bands tunable, the ceiling is not). **Enemy stat blocks carry `tier: 1..7`; there is no scaling multiplier, and there never will be** — a tier-6 world has *different monsters* (proximity to a king is the power scale), not a rat with 40× HP. Class abilities are planned to L20. The M5 content BOM (1 enemy family + boss) is **tier 1**. *§2: state (flavor text a thin AI layer) · depends on the pipeline for flavor · goal: real stakes.*
|
||
|
||
### M6 — Dialogue screen
|
||
*Content it consumes → [BOM](../content/roadmap.md#2-bill-of-materials): 6 NPCs — persona + `knowledge[]` + gated topics (§6), variable. **~6 pieces.***
|
||
- ○ **Dialogue UI + reshaped Adjudicator** — wrap the *proven* NPC free-text loop (`/npc/speak`, M2) in the mock's conversation screen: NPC portrait, live disposition meter, the NPC's spoken line + DM narration beat, and the **hybrid** input (choice chips with skill/approach tags **plus** the free-text line, §15). The **Adjudicator** (`/dm/adjudicate`, strict JSON §12, one-retry) lands here as the interpreter for the free-text "describe your own action" escape hatch — most turns use buttons. *§2: both (text = NPC voice · state = validated moves/actions) · depends on the NPC role (M2) · goal: conversation inside the game's UI.*
|
||
|
||
### M7 — Inventory & character sheet
|
||
*Content it consumes → [BOM](../content/roadmap.md#2-bill-of-materials): 6 weapons, 4 armour, 3 consumables, **1 cursed item** (+STR / −LCK — §7 says day-one). All fixed. **~14 pieces.** Prerequisite: the build tool gains `item` + `quest` namespaces (and `tier: 1..7`).*
|
||
- ○ **Inventory & equipment** — the paper-doll (12 slots) + Satchel grid, category tabs, item detail, encumbrance, derived stats; item seed-data from the mock's `items()` map as `Resource`s. *§2: state · depends on the Theme + item model · goal: gear the player manages.*
|
||
- ○ **Character sheet** — attributes (modifier-forward, LCK still hidden), saving throws, the "in a fight" derived grid, skills, talents, afflictions/conditions, the DM's-notes bio. *§2: state (display) · depends on the stat model · goal: the character, legible.*
|
||
|
||
### M8 — World systems
|
||
*Content it consumes → [BOM](../content/roadmap.md#2-bill-of-materials): the **price table in copper** (fixed); 3 map locations + the road, shop stock, the quest chain (variable). **~8 pieces.** The **currency change lands before M7** — store everything in copper, format at the display edge; it has a client consumer (the shell's purse).*
|
||
- ○ **World-map travel** — region map, location markers (settlement/dungeon/fog), roads + planned route, danger ratings, travel time, set-out. *§2: state · depends on the shell · goal: move between places.*
|
||
- ○ **Shop economy** — buy/sell with a fence: purses, stock/owned, the ~40%-sell loop, gritty disabled-CTA reasons, merchant reaction lines. *§2: state (merchant reaction = a thin authored/AI layer) · depends on inventory · goal: an economy loop.*
|
||
- ○ **Quest log** — active/completed/failed tabs, objectives checklists, rewards, tracked quest; reads the canon log's `active_quests` (§11). *§2: state · depends on the quest model · goal: track the story.*
|
||
|
||
### M9 — Framing, AI-flavor & persistence
|
||
*Content it consumes → [BOM](../content/roadmap.md#2-bill-of-materials): authored fallback per AI surface (§13); shrine + Luck-drift events; humiliation seeds. All fixed. **~10 pieces.***
|
||
- ○ **Save / load + pause / settings** — the pause overlay + data-driven settings; save/load over the canon log + game state (seeds preserved, §10). **⛨ saga — the save is two objects, not one:** a durable **`Saga`** (sheet, level, companions + approval, humiliations, deeds, `kings_slain`, `campaigns_completed`, signature item) and a per-campaign **`Campaign`** (`CanonLog` + `GameState` + world + seed). **Humiliations move to the `Saga`** and are *projected* into the canon log — today they live in the log and would die with the campaign. This is the §10 seed argument applied to persistence: cheap now, a rewrite once saves exist in the wild. *§2: state · depends on the systems existing · goal: persistence + framing.*
|
||
- ○ **Luck fully wired** — generation exists (Plan B); this adds **drift events** (real bed, shrine blessing, killing something praying), **one shrine**, **one cursed item** (+STR / −LCK). Visible only in prose (§7). Gates the Improviser. *§2: state · depends on the item + event systems · goal: the signature system, live.*
|
||
- ○ **Improviser** — `/dm/improvise`: minor off-script events, **§7-sandboxed by construction** — a validator that only applies whitelisted state changes (dignity, never progress; structurally incapable of ending a quest). Fires on Luck; hooks Cadwyn. *§2: both · depends on Luck being wired · goal: bad luck as a story, not a fine.*
|
||
- ○ **Banter** — `/party/banter`: companions comment on recent events; low stakes, high charm, **aggressively cacheable**; reads Brannoc's humiliation log (§9), stacking not overwriting. Prose only, no tags. *§2: text · depends on the humiliation log (M0) + events · goal: the comedy loop closes.*
|
||
- ○ **Authored-fallback sweep** — every AI-dependent surface has authored fallback *content* (§13), written as writing; the final pass so no surface can show the player an error. *§2: text · depends on every surface existing · goal: a degraded DM still has a voice.*
|
||
|
||
### Deferred (later / never)
|
||
Replicate provider (prod) · auth · metering · credits/payments (§4) — retrofit onto the pipeline later, the game doesn't notice. AI-generated story skeletons (v2 — the seeded story is authored for now). Streaming (§14) — SHELVED (`specs/2026-07-10-narrator-streaming-design.md`); the considering-state reframes the wait instead.
|
||
|
||
#### NPC & relationship depth (v2)
|
||
|
||
Three ideas mined from [NarrativeEngine-P](https://github.com/Sagesheep/NarrativeEngine-P) (MIT) — design borrowed, not code. All three are **§2: state** (code owns them; the AI only voices from what code decides), so they strengthen the §2 boundary rather than loosen it. They fold into the NPC role (§6) and canon log (§11); sequence them **with or after the Dialogue screen (M6)**, since they enrich the same conversation surface.
|
||
|
||
- ○ **Directional dispositions** — split the single per-pair disposition into two directed fields (A→B and B→A), so liking is asymmetric. Unrequited regard, undisclosed betrayal (player still trusts the captain who already sold them out), and the Cadwyn↔Brannoc mutual-but-differently-flavored dislike (§9) all become expressible. Player→NPC gates dialogue *tone*; NPC→player gates *offered moves* (§6 `available_moves`) — two arrows, two levers. Extends `party[]` in the canon log (§11) and `npc_dispositions` on the client; keep the −100..100 range, just double the field. *§2: state · depends on the disposition model existing (M2, done) · goal: relationships that aren't symmetric.*
|
||
- ○ **Witness tracking** — every logged event carries a `witnesses: [...]` list; a fact enters an NPC's `knowledge[]` (§6) only if they witnessed it or a witness told them. This is the missing **input-side rule** for the knowledge list — CLAUDE.md §6 says the list is "the entire content the NPC has to draw on" but never says how a fact gets *in*. Structurally prevents the blacksmith spoiling a twist he never saw, gives shady acts real "were you seen?" stakes without a stealth system, and turns rumor spread into a code operation (witness meets NPC → code copies the fact, optionally degraded) rather than AI invention. Adds a field to `recent_events[]`/`established_facts[]` (§11) and a propagation rule on character encounters. *§2: state · depends on the canon log event model (M0, done) + knowledge lists (§6) · goal: NPCs know only what they could plausibly know.*
|
||
- ○ **Personality Hexagon persona** — a richer NPC persona schema: six axes (Drive, Diligence, Boldness, Warmth, Empathy, Composure) supplied to the NPC prompt (§6) as structured voice-shaping input, replacing free-form persona blurbs. Server-owned (like `knowledge[]`), so it never ships to the client. Purely additive to the §6 prompt contract — it steers *how* an NPC speaks, not *what* it may do (the move vocabulary is unchanged). *§2: state (the schema) feeding text (the voice) · depends on the NPC role (M2, done) · goal: consistent, distinct NPC voices from data, not prose.*
|