2026-04-30 20:40:51 -07:00
|
|
|
|
# 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.
|
2026-05-01 20:29:22 -07:00
|
|
|
|
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.
|
2026-04-30 20:40:51 -07:00
|
|
|
|
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 |
|
|
|
|
|
|
|-----------------------------------------------------|--------------------------------------------------------|
|
2026-05-01 20:29:22 -07:00
|
|
|
|
| CSS custom properties (`--bg`, `--ink`, `--gild`, ...) | Single `Theme` resource constructed in `CodexTheme.cs`|
|
2026-04-30 20:40:51 -07:00
|
|
|
|
| `.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.
|
2026-05-01 20:29:22 -07:00
|
|
|
|
- ~~Theme variations for Parchment + Blood~~ — dropped during M5 per
|
|
|
|
|
|
user decision; Dark only.
|
2026-04-30 20:40:51 -07:00
|
|
|
|
- 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.
|
2026-05-01 20:29:22 -07:00
|
|
|
|
4. **Theme.** Dark only (leather + candlelight palette). Parchment and
|
|
|
|
|
|
Blood dropped from scope during M5; no runtime theme switcher.
|
2026-04-30 20:40:51 -07:00
|
|
|
|
|
|
|
|
|
|
### 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.
|