# Theriapolis — Godot Port — Design & Implementation Plan

**Status:** Proposed (drafted 2026-04-30).
Targets the codebase state **after Phase 7 ships**: SAVE_SCHEMA_VERSION ≥ 7,
~700+ tests green, dungeons + PoI interiors + dialogue→combat handoff live,
17–19 screens in `Theriapolis.Game`, CodexUI as the in-game UI framework.

**Audience:** the agent who will land the Godot port. Read §3 (pre-flight audit)
before writing code — the size and shape of the rewrite surface determines milestone
ordering. Read §10 (risks) before committing to dates.

**Governing docs:**
- `theriapolis-rpg-implementation-plan.md` §12 (binding hard rules — all still
  apply post-port)
- `CharacterCreator.zip` (at repo root) — the **canonical visual + interaction
  spec** for character creation and, by extension, the design language for
  every other screen. See `README.md` inside the zip for the integration
  contract; `index.html` `<style>` block for the full design system; `src/*.jsx`
  for component structure. **This supersedes `theriapolis-codex-ui-implementation-plan.md`
  and the existing CodexUI implementation as design authority for the port.**
- `theriapolis-codex-ui-implementation-plan.md` — historical only. CodexUI is
  the "pale imitation" of the React prototype that we're discarding.
- `theriapolis-rpg-implementation-plan-phase7.md` §1.10 (Phase-7 carryover
  items — verify each is in `Core` before port begins)
- `CLAUDE.md` "Hard Rules" section — every rule remains in force; the port
  changes the rendering host, not the rules.

**All hard rules from the original plan §12 remain in force.** No engine code
in `Theriapolis.Core`, all RNG via `SeededRng`, all magic numbers in
`Constants.cs`, determinism + linear-feature exclusion contracts unchanged. The
architecture test gets a one-line update (the forbidden namespace becomes
`Godot` instead of `Microsoft.Xna`).

---

## 1. Goals & non-goals

### Goals

1. **Replace MonoGame + Myra + CodexUI with Godot 4 (C#).** Modern, composable
   `Control`-node UI; no more hand-rolling NineSlice and layout primitives.
2. **Preserve `Theriapolis.Core` byte-for-byte.** Zero changes to worldgen,
   RNG, polylines, settlements, dungeons, save schema, or any deterministic
   subsystem. The architecture test continues to enforce engine-free Core.
3. **Adopt the React prototype's design language as the new in-game UI.**
   `CharacterCreator.zip` is the spec: parchment / dark / blood themes via
   CSS custom properties, illuminated-codex typography (Cormorant Garamond,
   Crimson Pro, Cinzel, etc.), trait/skill hover popovers with viewport
   clamping, floating Aside summary, codex-styled stepper, foil/seal/rule
   visual vocabulary. Every screen — not just character creation — adopts
   this design language.
4. **Behavioural parity for every shipped screen.** Title, character
   creation, world map, play (tactical), combat HUD, inventory, save/load,
   level-up, shop, quest log, reputation, defeated, pause, interaction,
   dungeon, plus the Phase-7 dialogue→combat overlay. *Visual* parity with
   today's CodexUI is **not** a goal — the React prototype is the new
   baseline. Behavioural parity (what each screen *does*) is the goal.
5. **Ship the dark theme only.** The React prototype designed Parchment,
   Dark, and Blood; the user's call during M5 is that only Dark (leather +
   candlelight) is wanted for this game. Parchment and Blood are dropped
   from scope. No runtime theme switcher — there is one theme.
6. **Tests still green.** All ~700 Core/worldgen/determinism/save tests pass
   unchanged. Project builds and runs `dotnet test` and the headless
   `Theriapolis.Tools` CLI exactly as before.
7. **One Godot project replacing two C# projects** (`Theriapolis.Game` +
   `Theriapolis.Desktop`). The Godot project references `Theriapolis.Core`
   directly and ships as the new desktop entry point.

### Non-goals

- **No new gameplay features.** This is a port + UI rebuild, not a phase.
  Defer all "while we're in there" gameplay temptations. The character-creation
  surface gains a Subclass step (§5.6) only because Phase 6.5 already shipped
  subclass selection and the existing CodexUI surfaces it; the React prototype
  predates 6.5 and doesn't include it. This is a forced reconciliation, not new
  scope.
- **No mobile / web export** in this port. Godot supports them; we're not
  exercising them yet. (The React prototype is web-native; if a future phase
  wants a true web client, that's a separate effort.)
- **No save-format change.** Saves written by the MonoGame build must load in
  the Godot build and vice versa. SAVE_SCHEMA_VERSION does not bump.
- **No Godot-side determinism.** Core is the deterministic boundary. Anything
  Godot does with rendering/animation/UI state is presentation-only.
- **No embedding the React prototype.** We are not shipping a webview, an
  Electron sidecar, or any HTML rendering inside Godot. The zip is a *design
  spec*, not a build artifact.
- **No data-schema migration in this port.** If `CharacterCreator.zip`'s
  `data/*.json` shape diverges from the live `Content/Data/*.json` (it almost
  certainly does — the zip predates Phase 6.5), the live schema wins. The
  React prototype's data files are reference material for *UI structure*, not
  for *content*.

---

## 2a. The CharacterCreator.zip baseline

The user has a React-via-Babel character-creation prototype (Claude Design
output, ~345 KB) that they prefer over the in-game CodexUI implementation.
CodexUI was a "pale imitation" of this prototype; rather than re-port the
imitation, we go back to the source. The prototype is at the repo root as
`CharacterCreator.zip` and contains:

- `index.html` — host page with the **complete design system** in a single
  `<style>` block: CSS custom properties for three themes (parchment / dark /
  blood), density toggle (compact/normal), font pairings, page layout,
  stepper, cards, chips, popovers, drag affordances. This is the visual
  source of truth.
- `src/main.jsx`, `src/app.jsx` — wizard shell, 7-step state machine,
  per-step validation, forward-navigation gating, `Aside` summary
  computation (sums clade + species ability mods).
- `src/steps.jsx` — all 7 step components (`StepClade`, `StepSpecies`,
  `StepClass`, `StepBackground`, `StepStats`, `StepSkills`, `StepReview`).
- `src/data.jsx` — runtime constants (ABILITIES, SKILL_LABEL, SKILL_DESC,
  SKILL_ABILITY, SIZE_LABEL, LANGUAGES, ITEM_NAME, STANDARD_ARRAY,
  abilityMod, signed). The Godot port copies the *constants*, not the JS.
- `src/trait-hint.jsx` — the hover-popover primitives (TraitName,
  LanguageChip, SkillChip, BonusPill) with viewport clamping behaviour.
  Reusable across every screen.
- `src/portrait.jsx` — placeholder portrait component (silhouette /
  heraldry / placeholder modes).
- `data/{clades,species,classes,backgrounds}.json` — content data **as
  shaped for the UI**. Field-level docs in the README §"File map".
- `README.md` — explicit integration instructions written for downstream
  developers; covers state shape, drag-drop payloads, popover clamping,
  theme system. Read this once before starting M5.

**What we use from the zip:**
- The CSS design system → ported to a Godot `Theme` resource (M5).
- The 7-step wizard structure, validation rules, Aside layout → ported to
  Godot Control nodes (M6).
- The hover-popover behaviour → reusable Godot widget (M5).
- The drag-drop ability-assignment payload contract → Godot's drag-drop API.

**What we don't use from the zip:**
- The React/Babel runtime — irrelevant in Godot.
- The HTML/JSX components — replaced by Godot scenes + C# scripts.
- The `data/*.json` files — `Content/Data/*.json` (the live game data) is
  authoritative for content. The zip's data files are reference material for
  UI structure only.

## 2b. Why now / why Godot

The MonoGame UI ceiling — Myra is dead-upstream and CodexUI is hand-rolled —
means every new screen costs disproportionate time. Phase 7 added two more
screens (dungeon, dialogue→combat) on top of an already 17-screen surface; the
trend is bad. Godot's `Control` node tree, theme system, and editor-driven
scene composition are an order of magnitude cheaper for the kind of UI
authoring this game needs.

The port is *cheap relative to the alternatives* because `Theriapolis.Core`
was designed engine-agnostic from day one (architecture test enforced). Every
deterministic system — the part of this codebase that is *expensive* to
rebuild — survives unchanged. We are replacing the rendering shell and the UI
toolkit, not the game.

Alternatives considered and rejected:

- **Stay on MonoGame, replace Myra only** (e.g. ImGui.NET, FontStashSharp +
  CodexUI). Cheaper short-term, but doubles down on a hand-rolled UI
  framework with no editor tooling. Doesn't fix the actual ceiling.
- **Avalonia + MonoGame.** Awkward windowing model; mixing two render loops
  is fragile.
- **Unity.** Heavier port (Mono → Unity's CLR, asset pipeline overhaul,
  licensing), worse 2D pixel-art tooling.
- **Stride.** Less mature 2D pipeline, smaller community.

Godot 4 with C# wins on: native 2D pixel-art workflow, editor scene authoring,
mature `Control`-node UI, MIT licence, `.NET 8` first-class support.

---

## 3. Pre-flight audit (what we're rewriting)

Run before kickoff to confirm post-Phase-7 numbers; the table below is the
**2026-04-30 snapshot** and is only the starting point.

| Subsystem            | LOC    | Files | Notes                                             |
|----------------------|-------:|------:|---------------------------------------------------|
| `Theriapolis.Game/CodexUI`         | 4,492  | 28 | Hand-rolled UI framework — biggest rewrite        |
| `Theriapolis.Game/Screens`         | 5,014  | 17 | Screen logic; ~50% UI wiring, ~50% game logic     |
| `Theriapolis.Game/Rendering`       | 1,317  |  9 | World/tactical renderers, camera, atlases, sprites|
| `Theriapolis.Game/Input,Platform,UI`|   548  |  6 | Input mapping, save paths, clipboard               |
| `Theriapolis.Game/Game1.cs` + ScreenManager | 166 | 2 | App shell + screen stack                  |
| `Theriapolis.Desktop`              |     37 |  1 | Will be deleted; Godot project replaces it        |
| **Total rewrite**    | **~11,500** | **63** |                                            |

**Untouched:**
- `Theriapolis.Core` (all generation, simulation, save/load, polylines)
- `Theriapolis.Tools` (CLI worldgen-dump, settlement-report, tile-inspect)
- `Theriapolis.Tests` (xUnit)
- `Content/Data/*.json` (biomes, factions, dungeon templates, etc.)
- `Content/Gfx/*.png` (sprites, tiles, atlases)
- `Content/Fonts/*.ttf`

**Re-run before kickoff:**
```bash
# Confirm Core is still MonoGame-free (must return zero matches)
grep -rn "Microsoft\.Xna\|MonoGame" Theriapolis.Core/

# Confirm test count
dotnet test --no-build --logger "console;verbosity=minimal" | tail
```

---

## 4. Architecture

### 4.1 New project layout

```
Core (no engine deps)              ← unchanged
Tools (ImageSharp, headless)       ← unchanged
Tests (xUnit)                      ← unchanged
Theriapolis.Godot/                 ← NEW; replaces Game + Desktop
    project.godot
    Theriapolis.Godot.csproj       ← references Core
    Scenes/                        ← .tscn scene files (one per screen)
    UI/                            ← .tscn + .gd-style C# Control scripts
    Theme/                         ← Godot Theme resource for CodexUI styling
    Rendering/                     ← Camera2D, world/tactical Node2D scripts
    Input/                         ← Godot InputMap + adapter to Core actions
    Platform/                      ← SavePaths, Clipboard (Godot APIs)
    Autoload/                      ← Game state singleton bridging to Core
```

### 4.2 The Core boundary

Core's public surface is unchanged. The Godot side talks to Core through the
same APIs `Theriapolis.Game` used:

- `WorldGenerator.Generate(seed, dataDir)` → `WorldState`
- `Save.Write(path, snapshot)` / `Save.Read(path)` (whatever the Phase-7 save
  API ended up named — verify post-7)
- Resolver, encounter lifecycle, dungeon stamping, quest engine — all
  invoked exactly as before.

### 4.3 The architecture test

Update `Architecture/CoreNoDependencyTests.cs` to forbid the `Godot`
namespace in `Theriapolis.Core` in addition to (or instead of) MonoGame.
This is a one-line change. **Keep both bans for the duration of the port** so
the in-flight branch can't accidentally regress.

### 4.4 Render model

The seamless-zoom contract (CLAUDE.md "Seamless Zoom Model") survives. The
implementation moves:

| Concept                  | MonoGame                    | Godot                                   |
|--------------------------|-----------------------------|-----------------------------------------|
| Camera                   | `Rendering/Camera2D.cs` (custom) | `Camera2D` node + zoom property    |
| World tile rendering     | `WorldMapRenderer` + `SpriteBatch` | `TileMap` or `Node2D._Draw` over `WorldState` |
| Tactical tile rendering  | `TacticalRenderer` + atlas  | `TileMap` with chunked `set_cell` updates |
| Polyline rendering       | `LineFeatureRenderer` (SpriteBatch lines) | `Line2D` node per polyline      |
| Sprite (player/NPC)      | `PlayerSprite`/`NpcSprite`  | `AnimatedSprite2D`                       |
| Atlas loading            | `TileAtlas`/`TacticalAtlas`/`CodexAtlas` | `AtlasTexture` resources       |
| Game loop                | `Game1.Update`/`Draw`       | `_Process` / `_Draw` on root node       |

`Line2D` for polylines is a meaningful upgrade — anti-aliased, width-tapered,
texture-able lines without the manual quad strip work `LineFeatureRenderer`
does today.

### 4.5 UI model

The React prototype's design system maps onto Godot's `Theme` + `Control`
system. The mapping below is from **the React prototype** (the new spec), not
from CodexUI (which is being deleted). For CodexUI → React-prototype
correspondences, see the existing CodexUI source as a behavioural reference
only; do not preserve its visual choices.

| React prototype (CharacterCreator.zip)              | Godot equivalent                                       |
|-----------------------------------------------------|--------------------------------------------------------|
| CSS custom properties (`--bg`, `--ink`, `--gild`, ...)  | Single `Theme` resource constructed in `CodexTheme.cs`|
| `.codex-header`, `.codex-title`, `.codex-sub`       | `PanelContainer` + `Label` with display font theme     |
| `.stepper` / `.step` / `.step.active/.complete/.locked` | Custom `HBoxContainer` script with state-driven theme |
| `.page` (two-column main + aside)                   | `HSplitContainer` or `HBoxContainer` (fixed ratio)     |
| `.card` (Calling/History/Species cards)             | `PanelContainer` + `StyleBoxTexture` (nine-slice)      |
| `.chip` (skill/feature/language chips)              | `PanelContainer` + `Label`, themed                     |
| `.btn.primary` / `.btn.ghost`                       | `Button` with theme variations                         |
| `.aside` (right-rail summary)                       | `MarginContainer` + `VBoxContainer` (signal-bound)     |
| `.popover` (TraitName/SkillChip/BonusPill hover)    | Custom `PopupPanel` widget — viewport-clamp logic      |
| HTML5 `dataTransfer` drag-drop in StepStats         | `_GetDragData` / `_CanDropData` / `_DropData`          |
| Cormorant Garamond / Crimson Pro / Cinzel / Uncial  | `FontFile` resources bundled in `res://`               |
| `loadData()` (fetch JSON)                           | Existing `ContentLoader` (Core) → unchanged            |

**Theme switching is a runtime player setting**, not a developer toggle. The
React prototype's `tweaks-panel.jsx` is **not** ported — it was a dev
affordance. Theme selection lives in the existing settings screen. The
prototype's density toggle (compact vs. normal spacing) is **dropped from
scope**: ship only the normal spacing values (`--gap: 24px`, `--pad: 28px`).

The character-creation flow is the densest UI in the game and ships first
(M6) as the pilot for the design language. Every other screen inherits the
Theme + widget vocabulary established there.

---

## 5. Milestones

Ten milestones, each independently demoable. Build them in this order; each
unblocks the next.

### M0. Pre-flight (½ day)

- Create `port/godot` branch off `main` post-Phase-7.
- Re-run the §3 audit; record actual LOC and screen count.
- Install Godot 4.x (mono build) + `.NET 8` SDK; confirm `dotnet --version`
  matches Core's target.
- Create `Theriapolis.Godot.csproj` referencing `Theriapolis.Core`;
  hello-world scene loads + builds.

**Exit criteria:** `dotnet build` of the new csproj succeeds; Godot opens the
empty project; CI (or local `dotnet test`) is unchanged from main.

### M1. Headless parity (1 day)

Goal: prove Core works untouched under Godot's csproj.

- From a Godot autoload script, call `WorldGenerator.Generate(12345)` and
  log the FNV hash of the resulting `WorldState`.
- Confirm hash matches `Theriapolis.Tools worldgen-dump --seed 12345`.
- Update `Architecture/CoreNoDependencyTests.cs` to forbid `Godot.*` in Core
  (in addition to MonoGame).

**Exit criteria:** identical worldgen hash from Tools CLI and Godot host. No
test regressions.

### M2. World map render (3 days)

The lowest-risk visual milestone — pure data → pixels, no UI.

- `Rendering/WorldMapNode.cs`: `Node2D` with `_Draw` rendering tile colours
  from `WorldState.Tiles`. Mirror `WorldMapRenderer.cs` colour logic.
- `Camera2D` node with mouse-wheel zoom; reproduce
  `C.WORLD_TILE_PIXELS`-aware coordinate model.
- `LineFeatureNode.cs`: instantiate one `Line2D` per polyline in
  `WorldState.Rivers`/`Roads`/`Rails`. Width and colour from constants.
- Manual visual diff vs `Theriapolis.Tools worldgen-dump --seed 12345
  --out world.png`; pixel-diff utility optional.

**Exit criteria:** generate world from seed 12345 in Godot, compare to tools
PNG output side-by-side; rivers/roads/rails visually correct, including
bridges and wye-junctions. No determinism drift.

### M3. Asset pipeline (2 days)

- Copy `Content/Data/*.json` and `Content/Gfx/*.png` into the Godot project's
  `res://` filesystem; configure `.csproj` to keep them in sync (or symlink).
- Convert each PNG atlas into Godot `AtlasTexture` resources. Tile atlas, NPC
  atlas, CodexUI atlas — three separate themes.
- Write a `ContentLoader` autoload that exposes the same `string
  ContentDataDirectory` / `ContentGfxDirectory` contract Game1 had, so
  Core's data loading code (`Loaders.cs` etc.) is unchanged.
- Verify `biomes.json` / `factions.json` / dungeon room templates load.

**Exit criteria:** Core can read all data files via the Godot path; PNG atlases
render correctly in a test scene.

### M4. Tactical render (3 days)

- `Rendering/TacticalNode.cs`: `TileMap` showing the 3×3 world-tile window
  around the player (`C.TACTICAL_WINDOW_WORLD_TILES`).
- Streaming: re-implement the 64-tile chunk loader from `TacticalRenderer.cs`
  using `TileMap.SetCell` calls bounded to the visible viewport.
- `PlayerSprite` / `NpcSprite` → `AnimatedSprite2D` nodes. Walk-cycle frames
  drive from the existing per-sprite frame-index logic (Core-side).
- Camera follow + smooth zoom from world-map view → tactical view at the
  zoom threshold.

**Exit criteria:** walk around a generated world, tactical view streams
chunks correctly, sprites animate, camera transitions are smooth.

### M5. Codex design system in Godot (4 days)

Port the React prototype's design system to a Godot Theme. This is the
foundation every subsequent screen builds on.

- Extract the `<style>` block from `CharacterCreator.zip/index.html` into a
  written audit (one-time, ~½ day): every CSS custom property → Theme
  constant; every selector → Theme type/variation; every nine-slice candidate
  identified.
- `Theme/codex.tres`: Godot Theme with the parchment palette as default —
  `--bg` / `--ink` / `--gild` / `--seal` / `--rule` mapped to Theme constants;
  display + body fonts as Theme default fonts; standard spacing/radius
  constants.
- ~~Theme variations for Parchment + Blood~~ — dropped during M5 per
  user decision; Dark only.
- Fonts: copy Cormorant Garamond, Crimson Pro, Spectral, EB Garamond, Cinzel,
  Uncial Antiqua, JetBrains Mono into `res://Fonts/`. Bundle as `FontFile`
  resources at the sizes the prototype uses (verify by spot-rendering each).
- `UI/Widgets/CodexPopover.cs`: reusable hover-popover Control implementing
  the `TraitName` / `SkillChip` / `BonusPill` pattern from
  `src/trait-hint.jsx`. Includes the viewport-clamp + flip-above-or-below
  behaviour the React version has (README §"Hover popovers and viewport
  clamping" — `documentElement.clientWidth/Height`, not `window.inner*`).
- `UI/Widgets/CodexStepper.cs`: the `.stepper` / `.step` widget — Roman
  numerals, locked/active/complete states, click-to-jump with gating.
  Reused by character creation and any future multi-step flow.
- "Kitchen sink" scene showing each primitive (button primary/ghost, card,
  chip, popover, stepper, aside, page header, validation banner) under all
  three themes. Side-by-side comparison against screenshots taken from the
  React prototype (open `index.html` locally with `python3 -m http.server`).

**Exit criteria:** kitchen-sink scene visually matches React-prototype
screenshots across all three themes; the popover widget passes a manual
viewport-edge test (trigger near each edge, popover stays in-bounds).

### M6. Title + character creation (5 days)

The character-creation flow is M5's first real customer. Direct port of the
React prototype's structure.

- `Scenes/TitleScreen.tscn`: vertical button stack on a parchment field with
  the codex title (Cinzel / Uncial Antiqua treatment) and a version label.
  Plain — exists mostly to validate the design system in a non-trivial
  composition.
- `Scenes/CharacterCreation.tscn`: top-level scene matching `app.jsx`:
  - `codex-header` band: title + folio counter ("Folio III of VIII").
  - `CodexStepper` (from M5) bound to the 8 steps (see below).
  - `page` two-column body: per-step scene (left) + Aside (right).
  - `nav-bar`: Back / validation banner / Next.
- One scene per step under `Scenes/CharacterCreation/Steps/`. Direct
  port of `src/steps.jsx`:
  1. `StepClade` — clade picker grid; selecting a clade auto-defaults
     species per `app.jsx` `useEffect([cladeId])`.
  2. `StepSpecies` — species cards filtered by clade.
  3. `StepClass` — calling cards; level-1 features filtered from
     `level_table` per the prototype's contract.
  4. `StepSubclass` — **NEW vs. React prototype**. The prototype is
     pre-Phase-6.5 and has no subclass picker. **Confirmed scope: a
     dedicated step**, not an inline picker on the Calling card.
     Adopts the M5 design vocabulary (cards laid out the same way as
     `StepClass`, one card per available subclass for the chosen
     calling). Data shape from the live `Content/Data/classes.json`.
  5. `StepBackground` — history cards.
  6. `StepStats` — ability assignment, **drag-drop**. Reproduce the
     payload contract from `steps.jsx` `handleDrop` /
     `dropToPool` exactly:
     - pool→slot, slot→slot (swap), slot→pool
     - payload shape: `{from:"pool", value, idx}` or
       `{from:"slot", value, ability}`
     Use Godot's `_GetDragData` / `_CanDropData` / `_DropData`.
     Roll-history list mirrors `statHistory` array.
  7. `StepSkills` — class skill picks above background-locked skills.
     Skill chips use the M5 `CodexPopover` (SkillChip + BonusPill).
  8. `StepReview` — name entry + final summary. The "Confirm & Begin"
     button does what `app.jsx` step's TODO told us to: hand the
     final character object to Core (see README §4 "Wire the
     'Confirm & Begin' handoff").
- `UI/Aside.tscn` + script: ports `Aside` from `app.jsx` — clade/species
  flavor, total ability scores including bonuses (sum of
  `statAssign[ab] + clade.ability_mods[ab] + species.ability_mods[ab]`),
  size + speed, skill list. Bound to a Core `CharacterDraft` model via
  Godot signals.
- Validation: per-step `Validate()` mirroring `validate(i)` in `app.jsx`
  exactly (clade picked, species picked, ..., assigned 6/6 abilities, picked
  N/N skills, name non-empty).
- Forward-navigation gating: a step is locked iff some earlier step has
  unmet validation, per the prototype's `firstIncomplete` rule. Backward
  navigation always allowed.

**Schema reconciliation sub-task (½ day, kicks off M6):** before writing UI,
diff the React prototype's `data/*.json` against `Content/Data/*.json`.
For each entity (clade / species / class / background), produce a one-page
field map noting where the live schema is a superset (Phase 6.5 added
fields), where it differs in shape, and where the prototype assumed a
field the live schema lacks. Live schema wins on all conflicts. The map
becomes the contract the step scripts read against.

**Exit criteria:** create a character end-to-end across all 8 steps, hash
the resulting `PlayerCharacter` blob, confirm it matches the same character
created via the MonoGame build using the same inputs (modulo subclass step,
which the React prototype lacks). Side-by-side screenshots of each step in
parchment / dark / blood themes match the prototype's design language.

### M7. Play loop screens (5 days)

Port in dependency order; each is ~½–1 day.

1. `WorldGenProgressScreen` — progress bar driven by Core stage callbacks.
2. `WorldMapScreen` — wraps the M2 world map node + minimap + UI overlays.
3. `PlayScreen` — wraps the M4 tactical node + HUD overlay (HP, stamina,
   minimap).
4. `PauseMenuScreen` — `PopupPanel` overlay; pauses Core sim tick.
5. `SaveLoadScreen` — `ItemList` of saves; `Save.Read`/`Save.Write` calls
   unchanged.
6. `InteractionScreen` — dialogue tree UI; densest text rendering after
   character creation.

**Exit criteria:** load a save from the MonoGame build, walk around, save,
reload — bytes identical.

### M8. Combat + dungeon screens (4 days)

The Phase-7-shipped surface.

1. `CombatHUDScreen` — turn order, ability bar, target reticle, damage
   numbers. Damage-number animations move from `SpriteBatch` to
   `AnimatedSprite2D` or tween-driven `Label` nodes.
2. `DungeonScreen` (Phase-7) — variant of `PlayScreen` for bounded
   interiors; same tactical render path, different camera bounds + exit-tile
   logic.
3. `LevelUpScreen` — stat allocation grid; reuse character-creation widgets.
4. `InventoryScreen` / `ShopScreen` — drag-drop between containers using
   Godot's native drag-drop API (replaces `DragDropController.cs`).
5. `QuestLogScreen` / `ReputationScreen` / `DefeatedScreen` — mostly
   text-list views.
6. **Dialogue → combat handoff** (Phase-7): the
   `InteractionScreen` "settle this here" branch closes the popup and
   instantiates `CombatHUDScreen.tscn` with the encounter pre-loaded. The
   Phase-7 plumbing on the Core side is unchanged.

**Exit criteria:** play through Old Howl mine end-to-end (Phase-7 showcase
content): walk in, combat, loot, walk out, return to overworld.

### M9. Input, save paths, clipboard (1 day)

The platform layer.

- `Input/InputMap.tres`: every binding from `InputManager.cs` + the new
  Godot `InputMap` actions. Adapt to Core via an `InputAdapter` autoload
  exposing the same enum-typed action API the screens use today.
- `PlayerController` → `_PhysicsProcess`-driven node consuming the adapter.
- `Platform/SavePaths.cs` → use `OS.GetUserDataDir()` (Godot's
  cross-platform per-user dir); confirm the directory matches MonoGame's
  `LocalApplicationData` path so saves remain interoperable, *or* migrate
  on first run with an explicit migration helper.
- `Platform/Clipboard.cs` → `DisplayServer.ClipboardSet`/`ClipboardGet`.
- **Window mode: borderless fullscreen at native desktop resolution.**
  In `project.godot`, set `display/window/size/mode = 3`
  (`Fullscreen` — borderless, native resolution). Stretch settings:
  `display/window/stretch/mode = "canvas_items"`,
  `stretch/aspect = "expand"`. Design the Theme so type and spacing
  remain legible across common resolutions (1920×1080, 2560×1440,
  3840×2160). The React prototype's `width=1440` viewport meta is
  *not* a constraint — the codex aesthetic should fill the screen.
  Add a developer-only `--windowed` CLI flag (or F11 toggle) for
  iteration.

**Exit criteria:** every keybinding works; saves written by the MonoGame
build load in the Godot build.

### M10. Cleanup, packaging, decommission (2 days)

- Delete `Theriapolis.Game/` and `Theriapolis.Desktop/`. Remove from the
  solution.
- Drop MonoGame + Myra packages from `Theriapolis.Core.csproj` (there should
  be none, but verify) and the new Godot csproj's transitive graph.
- Update `CLAUDE.md`:
  - "Build & Run Commands": replace `dotnet run --project Theriapolis.Desktop`
    with the Godot launch command.
  - "Project Architecture": replace `Theriapolis.Game` paragraph with
    `Theriapolis.Godot`.
  - Architecture-test description: forbid `Godot.*` (not `Microsoft.Xna.*`)
    in Core.
- Configure Godot export presets for Windows / Linux / macOS desktop.
- Smoke-test export builds on each target platform.

**Exit criteria:** `Theriapolis.Game` / `Theriapolis.Desktop` are gone;
`dotnet test` is green; an exported Windows build runs and plays through Old
Howl mine.

---

## 6. Total estimate

| Phase | Work                              | Days |
|-------|-----------------------------------|-----:|
| M0    | Pre-flight                        |  0.5 |
| M1    | Headless parity                   |  1   |
| M2    | World map render                  |  3   |
| M3    | Asset pipeline                    |  2   |
| M4    | Tactical render                   |  3   |
| M5    | Codex design system in Godot      |  4   |
| M6    | Title + character creation        |  5   |
| M7    | Play loop screens                 |  5   |
| M8    | Combat + dungeon screens          |  4   |
| M9    | Input, save paths, clipboard      |  1   |
| M10   | Cleanup, packaging, decommission  |  2   |
| **Total** |                               | **30.5** |

≈ **6–7 calendar weeks** assuming a single full-time engineer, no
significant blockers, and the post-Phase-7 baseline is genuinely stable. Pad
20% (~7–8 weeks) for the unknowns called out in §10. M5 grew by 1 day vs.
the original plan to absorb the design-system audit and the reusable popover
+ stepper widgets, both of which pay back in M6–M8. M6 grew by 1 day to
absorb the schema reconciliation and the Subclass step. If the agent doing
the port can run M2/M3/M4 in parallel via worktrees, shave 3–4 days.

---

## 7. Determinism & save compatibility

The bright line: **anything that touches `WorldState`, `PlayerCharacter`,
`Save.*`, or the Phase-7 dungeon/encounter state is Core's job, not Godot's.**

- All RNG is Core. No `randf()`, no `RandomNumberGenerator` Godot calls in
  gameplay paths. Only presentation-layer randomness (e.g. animation jitter)
  may use Godot RNG, and only in code paths that don't feed back into Core.
- Save compatibility test: `Tests/SaveCompat/MonogameToGodotTests.cs` reads
  a fixture save written by the MonoGame build (committed to the repo) and
  asserts every Core-loadable field round-trips identically.
- Per-stage hash tests (`WorldgenDeterminismTests`, `Phase23DeterminismTests`,
  any Phase-7 additions) continue to run unchanged.

---

## 8. Testing strategy

### What stays
- All `Theriapolis.Tests/` xUnit tests run unchanged. They reference Core
  only and don't care about the host.
- All `Theriapolis.Tools/` CLI behaviour (`worldgen-dump`,
  `settlement-report`, `tile-inspect`) is unchanged.

### What's added
- `Tests/SaveCompat/MonogameToGodotTests.cs` — one fixture save per major
  schema event (post-Phase-3, post-Phase-6, post-Phase-7), each loaded and
  hashed for equality.
- `Tests/Architecture/NoGodotInCoreTests.cs` — twin of the existing MonoGame
  ban; reflection check that `Theriapolis.Core.dll` has no `Godot.*`
  references.
- A small `Theriapolis.Godot/Tests/` Godot-side script set (one .tscn per
  visual milestone) for manual visual-diff comparison vs MonoGame screenshots.
  Not run in CI.

### What's gone
- Any tests that exercise `Theriapolis.Game/`-specific code (there should be
  none — Game has no test coverage today by design — verify before deleting).

---

## 9. Migration & rollback

- The port runs on a `port/godot` branch; `main` keeps shipping MonoGame
  patches if needed during the 6–7 week window.
- After M10 ships, MonoGame branch is tagged `monogame-final` and frozen.
  No back-merges from `main` into `port/godot` after M5 (CodexUI parity
  point) — too much surface diverges.
- **Rollback plan:** if the port stalls past M6 with unsolvable Godot-side
  blockers, abandon `port/godot` and reconsider §2's "ImGui.NET / replace
  Myra only" alternative. M0–M5 work is salvageable for a future attempt.

---

## 10. Risks & open questions

### Real risks

1. **Data-schema reconciliation.** `CharacterCreator.zip/data/*.json`
   predates Phase 6.5 (subclasses, levelling, hybrids, scent tags). The live
   `Content/Data/*.json` has additional fields and possibly different shapes
   per Phase-6.5 deviations. The React prototype's UI assumes the older
   shape. *Mitigation:* the M6 schema-reconciliation sub-task produces a
   field map before any UI code is written; the live schema wins on all
   conflicts; the React component's data assumptions are adapted to
   match. Do this audit in writing — do not let it leak into ad-hoc
   step-by-step "oh we also need..." discoveries.
2. **Phase-6.5 features absent from the React prototype.** Specifically: no
   subclass picker (README §"Known caveats"), no levelling beyond level 1,
   no hybrid-character clade pairing, no scent-mask UI. *Mitigation:* the
   plan adds an explicit Subclass step (M6 step #4); levelling stays in
   `LevelUpScreen` (M8) where it already lives; hybrids are a clade-picker
   variant (M6 StepClade); scent UI lives elsewhere (likely InventoryScreen
   or a future settings surface). All of these adopt the M5 design
   language; none invent new visual idioms.
3. **Codex-aesthetic completeness.** The React prototype only designed the
   character-creation surface. Other screens (combat HUD, world map
   overlays, dungeon UI) need design decisions extending the codex
   vocabulary. *Mitigation:* M5's reusable widget set (popover, stepper,
   panel/card/chip, button variants) covers ~80% of every screen; the
   remaining ~20% (combat-specific overlays, minimap framing) gets one
   design decision per screen made during M7/M8 with the M5 vocabulary
   as the base. If a screen needs a brand-new visual idiom, flag it
   instead of inventing one — defer to a polish pass.
4. **Tactical chunk streaming performance.** `TileMap.SetCell` is well
   optimised but the 64-tile chunk loader was tuned against `SpriteBatch`.
   *Mitigation:* if M4 reveals a perf issue, fall back to `MultiMeshInstance2D`
   for tile rendering.
5. **Save path divergence.** `OS.GetUserDataDir()` defaults to
   `%APPDATA%/Godot/app_userdata/<project>` on Windows; MonoGame writes
   under `%LOCALAPPDATA%/Theriapolis`. *Mitigation:* override Godot's
   user-data path explicitly to match the MonoGame path, or ship a one-shot
   migration on first launch (preferred).
6. **C# hot-reload ergonomics in Godot.** Godot 4's C# tooling is solid but
   not as snappy as MonoGame's iteration loop. *Mitigation:* set up
   editor-side debugging early (M0), accept slower iteration.
7. **Drag-drop fidelity** (M6 stats step + M8 inventory). Godot's native
   drag-drop is event-driven; the React prototype uses HTML5 `dataTransfer`
   with a documented payload shape. *Mitigation:* port the payload contract
   verbatim (`{from:"pool", value, idx}` / `{from:"slot", value, ability}`),
   serialise as `Variant` dictionaries; if native API doesn't suffice for a
   custom drag preview, write a thin `_GuiInput`-driven shim on `Control`
   nodes.

### Non-risks (called out so we don't worry about them)

- **Core porting.** Core does not port. It is referenced.
- **Determinism.** Determinism lives in Core and is not touched.
- **Test breakage.** Tests reference Core only.
- **Tools CLI.** Tools references Core only.

### Resolved decisions (locked in 2026-04-30)

1. **Subclass step UX.** Dedicated 8th step, not an inline picker on the
   Calling card. Implementation in M6 step #4.
2. **Density toggle.** Dropped from scope. Ship only the prototype's normal
   spacing values (`--gap: 24px`, `--pad: 28px`). The React prototype's
   compact mode was a dev-time experiment, not a player feature.
3. **Window mode.** Borderless fullscreen at native desktop resolution by
   default. The Theme must scale legibly across 1920×1080 / 2560×1440 /
   3840×2160. F11 (or `--windowed`) toggles to a windowed mode for
   iteration. Configured in M9.
4. **Theme.** Dark only (leather + candlelight palette). Parchment and
   Blood dropped from scope during M5; no runtime theme switcher.

### Open questions for the user / project lead

1. **Pixel-art shader stack.** CodexUI uses MonoGame `SpriteBatch` with
   point filtering; do any tactical-render screens depend on
   `SamplerState.PointClamp` behaviour that needs reproducing in Godot?
   (Default Godot project import settings set `Filter: Nearest` for pixel
   art — likely fine, confirm at M5.)
2. **Audio.** Phase 7 may or may not have introduced audio. If so, M9 grows
   to include `AudioStreamPlayer` integration.
3. **Multi-monitor + DPI.** With borderless-fullscreen as the default, the
   Godot build will pick the *primary* monitor's native resolution at
   launch. Confirm that's the desired behaviour, or add a monitor-selection
   setting. DPI scaling on high-density displays may need explicit handling;
   defer to M9.

---

## 11. Done definition

The port is complete when, simultaneously:

1. `Theriapolis.Game/` and `Theriapolis.Desktop/` are deleted from the repo.
2. `dotnet test` is green (≥ Phase-7 test count, none removed except
   verified Game-only tests, plus the new save-compat + architecture tests).
3. A save written by `monogame-final` loads in the Godot build, plays through
   Old Howl mine, saves, reloads — all without errors.
4. The Godot build exports cleanly to Windows desktop and runs the same
   end-to-end smoke test.
5. `CLAUDE.md` is updated to reflect the new project layout and build
   commands; the architecture test forbids `Godot.*` in Core.
6. `theriapolis-rpg-implementation-plan-godot-port.md` (this file) gets a
   "Status: Shipped 20XX-XX-XX" header and a §12 deviation table for any
   milestone that landed differently than planned.
