Files
code_of_conquest_dnd/docs/roadmap.md
Phillip Tarrant 96432bee87 docs(spec): considering-state + transport timeout; park streaming
Pivot M2 anti-freeze from streaming to a lighter considering-state: a rotating
in-character 'the Master considers…' indicator (authored content, §13 spirit)
on the proven non-streaming loop, plus the HttpTransport request-timeout fix so
a hung call yields to the fallback instead of spinning forever. Meets §14's
anti-freeze goal by reframing the wait; near-zero new failure surface. Streaming
design PARKED (banner + roadmap ○), revisit if playtest shows the wait hurts.
Conscious §14 deviation recorded per §18.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 14:37:20 -05:00

9.1 KiB
Raw Blame History

Roadmap

Source of truth for scope is CLAUDE.md. This tracks sequencing.

POC — the one question (charter §17)

Does bounded AI dialogue feel alive, or does it feel like a chatbot in a costume?

Test that before building anything else. ~30 minutes of play.

POC scope

  • One town, two NPCs with authored knowledge lists
  • One dungeon: three fights, one boss
  • Two companions: Cadwyn, Brannoc
  • Three playable classes: Sellsword, Assassin, Priest
  • Luck fully wired: generation, drift, one shrine, one cursed item
  • Full proxy, full role split, full canon log
  • Authored fallback text for every AI surface

Out of scope (charter §17)

AI-generated story skeletons · multiplayer (never) · mobile · credits/payments/auth · companions leaving · positional combat · more than three classes.

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.

The milestones are ordered to reach the POC's one question as early as possible, then build the game around a validated answer:

Milestone Goal it proves
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 alivenesshere Does bounded AI dialogue feel alive? — the whole POC question (§17).
M3 — Make it a game The dialogue lives inside real stakes: a playable ~30-min dungeon slice.
M4 — POC ship-readiness Every AI surface degrades with a voice, never an error (§13).

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)

  • 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 shows a rotating in-character "the Master considers…" indicator (authored content, §13 spirit — even loading is content) instead of a frozen surface, and the HttpTransport request timeout is set so a hung call gives way 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 essentially no new failure surface. §2: text/UI · depends on the client loop · goal: the wait is framed (in-character) and bounded (never hangs).
  • Streaming (§14) — PARKED (specs/2026-07-10-narrator-streaming-design.md). Server streams Ollama → client to genuinely hide the ~2s call. Deferred behind the considering-state: it is the POC's largest transport rework (client HTTPRequest → poll-based HTTPClient) and adds mid-stream error/tag-boundary failure surface — too much risk for "polish, not proof" while the aliveness question is already answered. Revisit only if playtesting shows the wait hurts immersion (long responses or a slow prod model). Conscious deviation from §14's "stream everything," recorded here per §18. §2: text · goal: polish, not proof.

M3 — Make it a game (the playable ~30-min slice — dungeon first, per the sequencing call)

  • Adjudicator/dm/adjudicate: free text → a legal action or an in-world rejection, strict JSON (§12), small/fast model, the one-retry-with-error-appended path. Unlocks the hybrid input model (§15) the playable slice needs — the "what do you do?" boundary. §2: both (text = the in-world reason · state = the chosen action) · depends on the pipeline · goal: free-text world actions.
  • Combat — deterministic, seeded per encounter from save state (§10); positionless turn-based; resource-management as the one interesting axis; Luck influences only crit thresholds + borderline status checks (§7). The dungeon: three fights, one boss. Combat flavor text is async, optional Narrator calls (reuses M1). "Ship the boring one" (§10) — mostly state work, low AI risk. §2: state (flavor text = a thin AI layer on top) · depends on the pipeline for flavor · goal: real stakes.
  • Luck fully wired — generation exists (Plan B); this adds drift events (sleeping in a real bed, a shrine's blessing, killing something praying), one shrine, one cursed item (e.g. +STR / LCK). Visible only in prose, never as a number (§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 — output passes a validator that can only apply a whitelisted set of state changes (dignity, never progress; structurally incapable of ending a quest). Fires on Luck; hooks Cadwyn. §2: both (text = the event · state = the whitelisted change) · 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. The cheapest role, landed last. §2: text · depends on the humiliation log (Plan B) + events to comment on · goal: the comedy loop closes.

M4 — POC ship-readiness

  • Authored fallback text — full sweep — every AI-dependent surface has authored fallback content (§13), written as writing, not error handling. Each role lands with its own fallback; this is the final pass that guarantees no surface can show the player an error. §2: text · depends on every surface existing · goal: a degraded DM still has a voice.

Deferred beyond POC

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).