# 020 — Verified current state

Baseline inspected from the working tree on **2026-10-12**. The baseline sections below preserve observations made before implementation. The feature was subsequently implemented under D37; paid optimization followed D41 and catalog expansion followed D42/D43. Current evidence is in `110-progress-and-handoff.md`; real TTS remains untested.

## Implemented state — 2026-10-13

- Canonical `millionaire.open_game()` preserves the original Subject and explicit demo context and selects the shared modern modal.
- The modal owns mode selection/settings/focus/closure. Both adapters provide screen/retirement hooks; native `doModal()` return also disposes the active adapter.
- `wcs/games/story/` contains the independent pure contract/engine, narrator prompt, one-shot AI source, adapter and view. It does not use the Millionaire rules controller.
- All 29 demo IDs have three authored packs under `resources/demo/story/<id>/` (87 total), including the complete eight-film Harry Potter collection. Explicit demo context and complete local files enable offline Story; title matching never selects a source. `scopes.json` supplies Story-only S01E01 metadata for eight episodic series; the shared Millionaire catalog and original Subject remain intact. Full scope/evidence: `140-demo-catalog.md` and `150-harry-potter-demos.md`.
- Existing generator primitives assemble `scripts/game_dialog/story_screens.py` into the same modern XML. There is no second generator or modal.
- `game_last_mode` and `story_difficulty` are implemented. The existing voice classes/clients are shared; cancelling owned voice cannot start stale speech or stop an unrelated video.
- Existing provider clients accept an optional call-specific output budget and private-content flag. Story uses 16000 tokens (32000 and a 300 s timeout for OpenAI/Anthropic high/xhigh/max), tools off, metadata-only diagnostic persistence and shared JSON extraction. These are Story-only ceilings, not changes to global settings.
- Story-aware demo commands, snapshots and life/count assertions reuse the existing game harness; exact commands are in `080` and `110`.

Read the following sections as the **pre-Story baseline**, not as outstanding implementation work.

## Original Millionaire execution chain

```text
Existing caller / action=milionar_game
  -> wcs.games.millionaire: Subject, open_game(), run(), start()
  -> DialogMilionarGame.MilionarGameModal
  -> MillionaireMyTVAdapter
  -> MillionaireGameController
  -> AiContent or DemoContent
  -> AIHandler.generate_millionaire_chat()
  -> AIProvider.send_prompt()
  -> configured provider client -> HTTP

Controller -> adapter -> game_view -> WCS.Game.* -> generated modern XML
```

## Existing files and useful seams

| Location | Verified role / reuse opportunity |
|---|---|
| `wcs/games/millionaire/__init__.py` | Canonical entry package; callers already converge here |
| `wcs/games/millionaire/subject.py` | `Subject`, query encoding/decoding, person subjects, optional metadata enrichment |
| `wcs/games/DialogMilionarGame.py` | Modal shell, title/settings/back behavior, focus, probe attachment, cleanup |
| `wcs/games/MillionaireMyTVAdapter.py` | Controller callbacks to properties and UI intent dispatch |
| `wcs/games/MillionaireGame.py` | Nine-round rules, first-error loss, lifelines, workers, countdown, prefetch, autoplay; also legacy display code |
| `wcs/games/game_view.py` | Pure modern view model; fixed `ROUNDS = 9`, money tiers, ladder and Millionaire-specific screen mappings |
| `wcs/games/content/__init__.py` | Existing per-round Millionaire source contract |
| `wcs/games/content/ai.py` | Continuous chat, round-angle variation, correct-letter placement, ordered history joining |
| `wcs/games/content/demo.py` | Authored offline source; `SilentVoice`; same Millionaire controller |
| `wcs/games/demo/catalog.py` | JSON loader/validator, exactly nine questions, fixed movie/series demo catalog |
| `wcs/games/demo/launch.py` | Offline launch through the canonical entry; fixed or random demo |
| `resources/demo/millionaire/` | 22 authored titles, including `titles/matrix.json`, plus moderator data |
| `wcs/games/game_settings.py` | Setting row definitions, localized values, storage, navigation control mapping |
| `wcs/games/designs.py` | Classic/modern XML selection and intent/focus IDs; modern is the default |
| `scripts/game_dialog/gen_modern_game.py` | Source of modern XML; tokens and reusable panel/button/text/answer components |
| `resources/skins/Default/1080i/games/DialogMilionarGameModern.xml` | Generated modern layout; do not hand-edit |
| `wcs/games/voice.py` | `TtsVoice` and `SilentVoice` boundary |
| `wcs/games/millionaire_tts.py` | Existing TTS composition, activity tracking, stop helper, provider/settings reuse |
| `wcs/ai/AIProvider.py` | Common routing, failed/successful call tracking, latency and session metadata |
| `wcs/ai/model_catalog.py` | Existing selected models/defaults; do not hardcode new model IDs |
| `wcs/ui/text_fit.py` | Existing offline text-width helper; limited supported font metrics |

## AI behavior

Millionaire asks for welcome, question, and feedback via a continuous chat, with a separate tip call. Welcome/first question and feedback/next question may run concurrently. The game explicitly calls `use_tools=False`, requests JSON mode, and receives parsed data from `AIHandler`.

Provider routing covers OpenAI, Anthropic, Google, Mistral, and Groq. HTTP, parsing differences, model parameters, key checks, prices, and usage tracking already belong to those clients and the common provider layer. Story must not implement them again.

The current prompt includes a short plot context, truncated to approximately 600 characters for a single work. It asks for known facts but has no full-source retrieval or factual verification. Existing JSON parsing also contains lenient defaults for a missing correct letter; Story must validate before allowing play rather than silently turn a malformed answer into A.

## Actual launch paths

| Context | Files to inspect when integrating |
|---|---|
| Plugin action | `wcs/router.py` (`milionar_game`) |
| Offline demos | `main.py` fast path (`milionar_demo`), `wcs/games/demo/launch.py` |
| Home topics/demo rows/random demo | `wcs/skin/widgets.py`, `wcs/skin/actions.py`, `wcs/skin/slots.py`, `wcs/skin/hero_buttons.py` |
| Library/context game action | `wcs/library2/menu.py`, `wcs/library2/clicks.py`, `wcs/skin/library_actions.py`, `wcs/skin/context.py` |
| MyTV/MyCinema | `wcs/ai/DialogMyTV.py` (`_show_game_dialog`); MyCinema shares related behavior |
| Add-media dialog | `wcs/ai/DialogAddMedia.py` |
| Search/detail/list dialogs | `wcs/ui/dialogs/DialogWcsSearch.py`, `DialogWcsDetails.py`, `DialogWcsMyList.py` |
| Persons | `DialogActorDetails.py`, `DialogDirectorDetails.py`, `DialogSeriesCreators.py`, `wcs/skin/detail.py` |
| Detail XML action | `resources/skins/Default/1080i/details_extras/DialogWcsDetails.xml` |
| Playback notification | `wcs/playback/dialogs/media_info.py` (`_open_game`) |

Playback notification closes itself and launches through `RunPlugin`; the underlying video continues. Preserve this behavior for the common launcher. Do not pause or stop the video as part of Story.

## Baseline traps identified before implementation

- `Subject` already has `year` and `tmdb_id`, but query serialization/`from_params` do not currently round-trip every field (notably year/genre). Some direct launches omit identity/art fields. Fix only fields needed to preserve the selected Story scope.
- An episode commonly has `media_type='series'` or `'tv'` plus season/episode numbers. Do not require `media_type='episode'` to recognize it.
- A season-only subject is not established by every current route. Preserve caller facts; never silently promote an episode to a whole-series Story.
- `MilionarGameModal._return_to_title()` currently knows Millionaire timers, voice and ladder internals. `onAction` also reads `game_view.screen_of(adapter.controller)`. These need a narrow adapter-owned boundary for a shared shell.
- `game_view.LivesUsed` currently counts used **lifelines**, not player lives. Do not repurpose that field for Story.
- `designs.py`, old game classes, old XML, and legacy code still exist. The refactor plan describes a target, not fully completed code.
- Shared provider usage metadata is not a generated-pack cache. The existing dev probe can persist complete response bodies; treat that carefully when adding Story instrumentation.
- Existing settings use relative visibility offsets. Append targeted new settings safely; do not shift the provider block casually.
- The worktree has extensive pre-existing tracked and untracked changes. Their ownership and test status were not inferred from `git status`.

## Existing verification assets

`tests/test_millionaire_entry.py`, `test_millionaire_settings.py`, `test_millionaire_ai_content.py`, `test_millionaire_demo.py`, `test_millionaire_modern.py`, `test_millionaire_golden.py`; `scripts/game_harness/{env,session,scenario,golden,checks,game}.py`; `wcs/devtools/game_probe.py`; recorded Millionaire headless properties and live frames under `scripts/game_harness/golden/`.

Those goldens describe Millionaire, not Story. Existing source/focus/snapshot assumptions are Millionaire-specific and must be adapted narrowly. No existing test was run or claimed passing during planning.

Related document: `docs/plan/millionaire-refactor/000-plan.md`. Coordinate shared-file changes if that work resumes, but do not require its full cleanup as a prerequisite for Story. `docs/features/009-milionar-game.md` still describes older inline behavior; source code is authoritative for this plan.
