Marks M4-c done (the Main coordinator wires new game -> creation -> shell, the shell consumes the real created character, the saga fourth-path seam is kept addable) and flips M4 -> done / M5 -> next in the milestone table. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
120 lines
28 KiB
Markdown
120 lines
28 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** ✅ | 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. |
|
||
| **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
|
||
|
||
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.
|
||
|
||
- ✅ **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.*
|
||
|
||
---
|
||
|
||
### 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). Built and committed (`a77bf03`, `f071392`); this entry was stale — the roadmap had not caught up and M4-c would have been misled into thinking it still had to build one. **⛨ 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.***
|
||
- ✅ **Creation model (M4-a)** — the rules a character is made of, no UI ([spec](superpowers/specs/2026-07-12-creation-model-design.md)). Race/calling/skill **mechanics** are static tables in code (`client/scripts/rules/`); their **blurbs** are hand-written content (`content/world/{callings,races}/`, loaded by `ContentDB`, parity-tested), so the content BOM does not block the engine. `CharacterSheet` **stores only what was rolled or chosen** (attributes, race, calling, skills, current hp/mp) and **derives** the rest (`max_hp`, `ac`, `save`, `skill_bonus`, `spell_dc`) — so no derived number can drift from its inputs, and M5's level curve is a function change rather than a stored-field migration. **`NewGame.construct` carries a seed, not a stat block:** it rebuilds the RNG and rolls the attributes itself, so it never trusts a number handed to it (§2), and the same seed yields an identical character, hidden Luck included (§10) — pinned by a golden-vector test. The roll is **3d6 with a hard floor of 8** (straight 3d6 puts ~9% on a primary the +3 pool cannot rescue; a character bad at the one thing he is *for* is the "fine" §7 forbids). **LCK is unspendable, not merely hidden.** Open seams marked honestly: `ac()` is `10 + DEX` until armor exists (M7), `max_mp()` is a `TUNABLE` placeholder (M5 owns the curve), L1 talents are id stubs (M5). **Live-proven end-to-end** (2026-07-12): a beastfolk cutpurse built by the pipeline → canon log → schema validation → `Player: Vexcca, a beastfolk cutpurse` in the digest (no numeric Luck, no stats, no snake_case id) → real narration from qwen3.5. 250 client tests, 76 api (live layer included). *§2: state · goal: the character exists in code.*
|
||
- ✅ **Contract migration** — the two contract migrations this milestone owed have **landed**: the canon log's `player` block is now `{name, race_id, calling_id, luck_descriptor}` (the Narrator/NPCs describe the player as e.g. "a beastfolk cutpurse"), and an origin's build-constraints key is now `allowed_callings` (`content/origins/deserter.json` lists all seven real calling ids; the pre-migration key and its two dead class ids are gone from the codebase). Schemas, docs, fixtures, and the world-building skill's content template all agree.
|
||
- ✅ **Character creation UI (M4-b)** — the mock's screen over M4-a's model: four race cards and seven nameplate calling cards (blurb + mechanics shown only for the *chosen* calling), the **+3 additive spend pool** with `−` flooring at the rolled base, proficiency chips gated by the chosen calling's pool (plus the Human bonus-skill row), five ability cards — **never six, and the word "luck" appears nowhere on the screen** (§7) — and the DM origin panel composing race + calling fragments. Five mock/model reconciles settled in the spec's §4 (skill picking added, seven callings not five, blurb-on-chosen-only, `+`/`−` both present, the portrait `‹ ›` arrows cut for lacking art). **Architecture:** a pure, node-free `CreationDraft` (`RefCounted`) holds every rule — changing calling clears stale skill picks, changing race drops a now-granted pick, re-roll clears the spend and only the spend, `errors()` delegates to `NewGame.validate` rather than keeping a second copy of the rules; `CreationCopy` is a pure static formatter that derives every display string (hit die, armour word, saves, skill count) from the `Races`/`Callings`/`Skills` tables, so retuning a calling retunes its card without touching a string literal; the `.tscn` owns the layout per ADR 0001 and `character_creation.gd` only binds. **The §2 property holds without the screen having to be trustworthy:** the screen shows its five numbers by calling the same newly-public `NewGame.roll_attributes(seed)` that `construct` calls internally — the screen never hands a number to anything, only a seed and four choices; `construct` rolls the identical attributes again, itself, off the same seed. Re-roll is unlimited (the floor of 8 already prevents a dead primary), and every re-roll silently re-rolls the hidden LCK draw riding the same RNG stream — the screen says nothing about this, ever. Emits `creation_confirmed(creation: Dictionary)`; does not call `construct` itself, so a saga can synthesize the same Dictionary later and skip the scene entirely. **Test count: 250 → 302.** **The honest seams:** three rounds of adversarial review found ten guard-shaped assertions across this milestone that could not fail against the bug they named (substring collisions, a BBCode wrapper masking an empty ContentDB, a test asserting the script's own loop bound, a trivial origin fixture, assertions already true before the action under test ran, a theme-drift guard checking three hardcoded variations instead of all of them, a `Label` sweep that missed `RichTextLabel`, and tests that called handlers directly instead of pressing nodes) — all rewritten against the actual artifact and reverted-and-reconfirmed-red per `docs/traps.md`'s standard; the new species are folded into that file. *§2: state · depends on the creation model (M0) + Theme · goal: build a character.*
|
||
- ✅ **Title → creation → shell flow (M4-c)** — the *new game* path is wired end-to-end ([spec](superpowers/specs/2026-07-15-m4c-title-creation-shell-flow-design.md)). A script-first **`Main`** coordinator node (`scenes/Main.tscn`, now the `main_scene`) owns the Title → Creation → Shell sequence in one file, swapping screens via **instantiate → inject → add_child** — the injection seam `change_scene_to_file` cannot provide, and the reason it, not a linear chain, holds the flow. The **Title became emit-only** (it announces `menu_activated`; the coordinator routes it — it no longer knows what comes next), and the **shell stopped faking a character**: new `injected_log`/`injected_state` seams + a `ShellState.for_character(state, log)` factory mean the created character's **HP/MP/purse, location, and opening narration are real** (`seed()`/`_build_seed_log()` stay as the F6/standalone fallback, exactly like `service == null`). The flow carries only a plain `creation` Dictionary; `NewGame.construct` re-rolls the character from its seed itself (§2). **⛨ saga honored structurally:** `_show_creation()` and `_start_campaign(creation)` are separate methods, so the fourth entry path ("begin the next campaign of an existing saga") is an *added `menu_activated` arm* that synthesizes a Dictionary and calls `_start_campaign` directly — no existing edge changes. **§13:** a `construct` that returns `{ok:false}` (unreachable via the CTA gate, but the coordinator distrusts its caller anyway) logs and stays on creation — never a crash, never a half-built shell. Built subagent-per-task with a per-task + final whole-branch review; every new guard reverted-and-reconfirmed-red per `docs/traps.md`. **Test count: 302 → 327.** The coordinator was named `Main` (not `GameFlow`) so the boot scene is self-evident; `TitleScreen` is unchanged and shown *by* `Main`. *§2: n/a (flow) · depends on M4-b + the Title screen (M3) · goal: a playable start-to-shell loop.*
|
||
|
||
### 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 value type already landed** (see *Landed out of band*, above) — everything is stored in copper and formatted at the display edge, so the Shop economy inherits a real purse. What M8 still owes is the **numbers**: nothing is priced yet.*
|
||
- ○ **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.*
|