Initial commit: Theriapolis baseline at port/godot branch point

Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Christopher Wiebe
2026-04-30 20:40:51 -07:00
commit b451f83174
525 changed files with 75786 additions and 0 deletions
+707
View File
@@ -0,0 +1,707 @@
# Theriapolis — `CodexUI` Module
## Design & Implementation Plan for a Custom MonoGame UI Layer
**Status:** Proposed. Targets the codebase state as of Phase 5 M5 complete
(329 tests green, Myra 1.0.0.204 still in tree, character creator written
against Myra in [CharacterCreationScreen.cs](Theriapolis.Game/Screens/CharacterCreationScreen.cs)).
**Audience:** A future Claude Code session implementing this module. The
session won't have any of the conversation history that produced this plan
— this document is the complete brief.
**Goal:** Replace the Myra-built character creator with a custom MonoGame
SpriteBatch-based UI module that **exactly mimics the visual design** from
Claude Design's React prototype. Build the module so it can be reused for
future stylized screens (title, inventory, save/load showcase). Keep Myra
in tree for utility screens (pause menu, slot picker) — this is a hybrid,
not a replacement.
---
## 1. Visual source of truth
The complete visual reference is at:
```
_design_handoff/character_creation/from_design/
├── README.md — design's own integration notes
├── index.html — full HTML + CSS (the `<style>` block is the design system)
├── src/ — React JSX components (data flow, state model)
└── data/ — JSON content (identical to Content/Data/)
```
**The agent must run the React prototype before starting:**
```bash
cd _design_handoff/character_creation/from_design
python3 -m http.server 8000
# open http://localhost:8000/
```
Click through every step. Observe the parchment background, gilded card
borders, serif display fonts (Cinzel for headings, Cormorant Garamond for
body), monospace tags (JetBrains Mono), the stepper's locked/active/complete
states, the card hover/selected states, the bonus pills with sourced
popovers, the drag-drop ability slots, the trait hover popovers. **Every
visual detail in `CodexUI` should match what the React prototype shows.**
The CSS variables block at the top of `index.html`'s `<style>` is the
authoritative color/typography/spacing reference. See §A1 below for the
extracted token table.
The current Myra port is in
[Theriapolis.Game/Screens/CharacterCreationScreen.cs](Theriapolis.Game/Screens/CharacterCreationScreen.cs)
— the *behavior* and *state model* port faithfully (validation, step
gating, click-pick-place stat assignment, auto-assign, roll history). The
*visual* port does not. Read it for behavior, not appearance.
---
## 2. Goals & non-goals
### Goals
1. **Pixel-perfect visual match to the React design** — parchment
background, gilded card frames with selected-state glow, serif display
fonts, codex header layout, stepper with ✕/→/✓ marks, card grids with
hover and selected states.
2. **Reuse the existing MonoGame SpriteBatch rendering stack** — same
`SpriteBatch`, same `SpriteFont` system, same content-pipeline patterns
the world/tactical renderers use. No new framework dependency.
3. **Co-exist with Myra**, which stays in tree for utility screens (pause
menu, save/load slot picker). The Game1 screen stack treats `CodexUI`
screens and Myra screens identically — both implement `IScreen`.
4. **Reusable primitive set.** The character creator is the first
deliverable but not the only one. Title screen, inventory, save/load
showcase will all call into the same `CodexUI` primitives.
5. **Runtime-checkable assets.** Missing fonts or textures should fail
loudly at content-load time with a clear message, not at first
render. Same loud-fail discipline as `ContentLoader`.
### Non-goals
- **Replacing Myra entirely.** Pause menu, slot picker, debug overlays
stay Myra. They don't need to be beautiful.
- **A general-purpose UI framework.** This is a small set of widgets
hand-tailored to the Theriapolis codex aesthetic. It is not meant to
scale to arbitrary game UIs.
- **Cross-platform mobile/touch.** Desktop only — same as the existing game.
- **Animations.** A subtle hover transition is fine; full motion design
(e.g. card-flip transitions, ink-bleed entrances) is out of scope.
- **Localization / RTL.** English LTR only. The font and layout choices
assume English.
- **Accessibility passes** (screen reader, high-contrast mode, reduced
motion). Worth noting as a future concern; not in v1.
---
## 3. Stack & constraints
- **MonoGame DesktopGL 3.8.2** (already in `Theriapolis.Game.csproj` and
`Theriapolis.Desktop.csproj`).
- **C# 12 / .NET 8** (`<LangVersion>12</LangVersion>`).
- **`Theriapolis.Core` must remain MonoGame-free** — enforced by
`Architecture/CoreNoDependencyTests.cs`. All `CodexUI` code lives in
`Theriapolis.Game/`.
- **Existing screen contract** — `Theriapolis.Game/Screens/IScreen.cs`:
`Initialize(Game1)`, `Update(GameTime)`, `Draw(GameTime, SpriteBatch)`,
`Deactivate()`, `Reactivate()`. New `CodexScreen` base class implements
this.
- **Content pipeline** — Desktop project's `.csproj` already wildcards
`..\Content\Gfx\**` to output. Add a new `Content/Gfx/codex/` subfolder
for the new asset set. Desktop `RuntimeIdentifiers` currently include
`win-x64; linux-x64; osx-x64; osx-arm64` — assets must be cross-platform
PNGs, no platform-specific paths.
- **Fonts** — see §6 for two options (MonoGame Content Pipeline vs
SpriteFontPlus / FontStashSharp). Pick one before starting.
---
## 4. Module file layout
```
Theriapolis.Game/
CodexUI/
Core/
CodexScreen.cs // base class — IScreen + widget tree + input dispatch
CodexWidget.cs // base class for every widget; bounds, hover, click, parent ref
CodexLayout.cs // layout helpers: row, column, grid, padding, alignment
CodexInput.cs // wraps MouseState + KeyboardState; edge-detect, hover delta
CodexTheme.cs // color tokens, density tokens, font tokens (see §A1)
NineSlice.cs // 9-slice texture renderer for scalable bordered panels
Atlas.cs // strongly-typed loader for the codex asset set
Widgets/
CodexLabel.cs // wrapped serif text, multi-line, alignment
CodexButton.cs // primary / ghost / small variants per the design
CodexPanel.cs // 9-sliced parchment panel with optional title bar
CodexCard.cs // selectable card with hover + selected glow + click handler
CodexChip.cs // trait / skill / language pill (multiple subtypes)
CodexBonusPill.cs // small +N / N pill with sourced popover
CodexStepper.cs // 7-step horizontal stepper with locked/active/complete states
CodexAbilityRow.cs // ability slot + drop target + final-score readout + bar
CodexPoolDie.cs // draggable d20-like value tile for the stat pool
CodexCheckboxRow.cs // skill row with check/locked/unavailable states
CodexTextBox.cs // single-line text input (for the name field)
CodexHoverPopover.cs // floating popover with title + description + tag (trait hover)
CodexOrnamentRule.cs // horizontal hairline rule with central ornament glyph
Screens/
CodexCharacterCreationScreen.cs // the wizard, ported from Myra
CodexAside.cs // right-column live summary panel
Steps/
StepClade.cs // one file per step, mirrors steps.jsx
StepSpecies.cs
StepClass.cs
StepBackground.cs
StepStats.cs // most complex; drag-drop ability assignment
StepSkills.cs
StepReview.cs
Drag/
DragDropController.cs // captures source on TouchDown, follows cursor with ghost,
// resolves drop on TouchUp; Escape cancels
```
**14 widget files + 7 step files + 6 core files + 2 screen files = ~29
new C# files.** Plan for ~3000 lines of code total (rough order of
magnitude — codex aesthetic widgets are mostly drawing logic + state).
---
## 5. Visual primitive catalog
Each row maps a React design element → the C# `CodexUI` widget that
implements it. The agent should keep this table next to the design open in
a browser as they implement.
| React selector / component | CodexUI widget | Notes |
|----------------------------|----------------------|-------|
| `body { background: parchment }` | `CodexScreen` background | Tile a parchment PNG over the full viewport |
| `.codex-header` (title + folio sub) | `CodexLabel` (display font) | Two-line stacked label, serif large + small |
| `.stepper` row | `CodexStepper` | 7 step bullets with `locked / active / complete` visual states; ✕ / Roman / ✓ marks |
| `.step.active` | `CodexStepper` active state | Gilded ring around the bullet, ink-darkened name |
| `.card` | `CodexCard` | 9-sliced parchment frame; hover = subtle gild halo; selected = strong gild halo |
| `.card.selected` | `CodexCard.IsSelected` | Stronger gild halo + corner ornament |
| `.scent-aura` overlay | `CodexCard.SpecialAura` | Optional radial gild glow behind the card on hover (clade step only) |
| `.sigil` | Sprite atlas | One PNG per clade (canidae, felidae, etc.) — clade icons |
| `.mod` (`.pos` / `.neg`) | `CodexBonusPill` | Small bordered pill, gilded for + and rust-red for |
| `.trait-chips` row | `CodexLayout.WrapRow`| Horizontal flex-wrap of chips — needs a flow layout helper |
| `.trait-name` chip | `CodexChip` (Trait variant) | Bordered pill; hover = full popover with name + description + tag |
| `.skill-chip` (`.from-bg` / `.from-cls`) | `CodexChip` (Skill variant) | Two color variants based on source; popover shows name + governing ability + description |
| `.language-chip` | `CodexChip` (Language variant) | Italic serif text in a bordered pill |
| `.popover` (rich) | `CodexHoverPopover` | Floating panel above/below trigger; viewport-clamped; appears after small hover delay |
| `.btn.primary` | `CodexButton.Primary`| Filled gild background, ink text, embossed border |
| `.btn.ghost` | `CodexButton.Ghost` | Transparent fill, ink border, ink text |
| `.btn.small` | `CodexButton.Small` | Smaller padding/font, used for inline reroll/auto-assign |
| `.pool` row + `.die` | `CodexPoolDie` + `DragDropController` | Draggable value tile; visual = a stylized small ornamented stone |
| `.ability-row` (full) | `CodexAbilityRow` | Composite: name + bonus pill + slot + final score + horizontal progress bar |
| `.slot` (drop target) | `CodexAbilityRow.Slot` | Empty = dashed border; filled = gilded; drop target tracked by DragDropController |
| `.bar` + `.bar-fill` | `CodexAbilityRow.ProgressBar` | Inset 9-slice; fill colored by relative score |
| `.skill-grid-by-ability` | `CodexLayout.Grid` | 3-column grid; each column = `.skill-group` |
| `.skill-group` | `CodexPanel` (no border, just heading) | Heading is the ability long-name + short-tag |
| `.skill-row` | `CodexCheckboxRow` | `[locked]` / `[unavailable]` / `[checked]` / default; click toggles when class option |
| `input[type=text]` (name) | `CodexTextBox` | Single-line, serif italic, gilded underline |
| `.review-grid` blocks | `CodexPanel` | Each block = small panel with heading + content + `Edit ` link |
| `.kit-grid` items | `CodexLayout.WrapRow` of `CodexChip` | One chip per starting-kit item; equipped items get a corner mark |
| `.divider` / horizontal rule | `CodexOrnamentRule` | Two hairlines flanking a small central diamond glyph |
Anything in the React design that doesn't appear above (e.g. the Tweaks
panel, the fonts dropdown) is **out of scope** — the wizard ships with
one fixed theme baked in.
---
## 6. Asset list
### 6.1 Textures (PNG, sized for 1× rendering at the target window resolution)
Place under `Content/Gfx/codex/`. Add the directory pattern to
`Theriapolis.Desktop.csproj`'s `<Content Include>` block (already wildcards
`Gfx/**`).
| Asset | Size | Purpose | Notes |
|---|---|---|---|
| `parchment_bg.png` | 512×512 (tileable) | Full-viewport background | Subtle wear pattern; tiles seamlessly |
| `parchment_card.png` | 192×192 9-slice | Card body fill | 24 px corners, 16 px inner edges |
| `gild_frame.png` | 64×64 9-slice | Card border (default) | 16 px corners, gilded line |
| `gild_frame_selected.png` | 64×64 9-slice | Card border (selected) | Thicker gild + inner glow |
| `gild_button_primary.png` | 96×32 9-slice | Primary button background | Gild fill with embossed bevel |
| `ink_button_ghost.png` | 96×32 9-slice | Ghost button border | Ink line, no fill |
| `wax_seal.png` | 64×64 sprite | Confirm button accent | Optional; red wax with crest |
| `ornament_diamond.png` | 16×16 sprite | Center of horizontal rule | Small gold diamond |
| `stepper_bullet_locked.png`| 24×24 sprite | Stepper bullet (locked) | ✕ over greyed parchment |
| `stepper_bullet_active.png`| 24×24 sprite | Stepper bullet (active) | Gild filled with Roman numeral |
| `stepper_bullet_done.png` | 24×24 sprite | Stepper bullet (complete)| Gild ring with ✓ |
| `chip_trait.png` | 96×24 9-slice | Trait chip background | Subtle parchment fill, ink border |
| `chip_skill_bg.png` | 96×24 9-slice | Skill chip — from background | Gild edge variant |
| `chip_skill_class.png` | 96×24 9-slice | Skill chip — from class | Standard ink edge variant |
| `chip_language.png` | 96×24 9-slice | Language chip | Italicized rendering, ink border |
| `pool_die.png` | 32×40 sprite | Stat pool tile | Stylized small standing stone with engraved face |
| `slot_empty.png` | 64×40 9-slice | Empty ability slot | Dashed ink border |
| `slot_filled.png` | 64×40 9-slice | Filled ability slot | Gilded border, parchment fill |
| `bar_track.png` | 16×8 9-slice | Progress bar track | Inset shadow |
| `bar_fill.png` | 16×8 9-slice | Progress bar fill | Gilded gradient |
| `popover_bg.png` | 96×96 9-slice | Hover popover background | Parchment with darker ink border, drop shadow |
| `clade_sigil_canidae.png` | 48×48 sprite | Canidae icon | Wolf-head heraldry mark |
| `clade_sigil_felidae.png` | 48×48 sprite | Felidae icon | Lion-head heraldry mark |
| `clade_sigil_mustelidae.png` | 48×48 sprite | Mustelidae icon | |
| `clade_sigil_ursidae.png` | 48×48 sprite | Ursidae icon | |
| `clade_sigil_cervidae.png` | 48×48 sprite | Cervidae icon | |
| `clade_sigil_bovidae.png` | 48×48 sprite | Bovidae icon | |
| `clade_sigil_leporidae.png`| 48×48 sprite | Leporidae icon | |
**Total: 27 asset files.** All can be hand-painted in Aseprite or
generated via Pixellab (the Tile Generation tooling already in this repo
— see `theriapolis-tile-generation-handoff.md` for the existing prompt
patterns). The 7 clade sigils are the most art-direction-sensitive
authoring task; the rest are repeating border patterns and small icons.
### 6.2 Fonts
The design uses **Cinzel** (small caps display), **Cormorant Garamond**
(serif body), **JetBrains Mono** (small caps tags), with **EB Garamond**
and **Spectral** as alternates. All five are available on Google Fonts as
TTF.
Pick **one** of two integration paths:
**Path A: MonoGame Content Pipeline** (preferred for shipped games)
- Install MonoGame Content Builder Tool: `dotnet tool install -g
dotnet-mgcb-editor`
- Add `Content.mgcb` to `Theriapolis.Desktop` and reference it in the
`.csproj` (commented-out reference is already there from Phase 0).
- For each font + size pair (Cinzel-Bold-32, Cinzel-Bold-18,
CormorantGaramond-Italic-14, etc.), add a `SpriteFont` entry to
`Content.mgcb` with the source TTF and the desired character set.
- The `.mgcb` build produces `.xnb` files that MonoGame's `ContentManager`
loads at runtime.
- **Pros:** baked atlases, fastest runtime load, no extra runtime deps.
- **Cons:** requires `mgcb` tool installed, fixed font sizes (one .xnb per
size).
**Path B: SpriteFontPlus / FontStashSharp** (faster iteration)
- Add NuGet: `FontStashSharp.MonoGame`.
- Load TTF files at runtime, render glyphs into a runtime-managed atlas.
- Any size at any time without rebuilding content.
- **Pros:** no content pipeline setup, runtime size flexibility.
- **Cons:** extra runtime dependency, slightly slower first-render per new
glyph/size, atlas churn risk if sizes vary widely.
**Recommendation:** Path B for v1 (faster iteration during the styling
pass), revisit Path A if profiling shows atlas churn.
In either case, register the loaded fonts on a `CodexTheme.Fonts` static
table keyed by semantic name:
```csharp
public static class CodexFonts {
public static SpriteFont DisplayLarge; // Cinzel-Bold-32 (codex header)
public static SpriteFont DisplayMedium; // Cinzel-Bold-22 (h2)
public static SpriteFont DisplaySmall; // Cinzel-Bold-16 (h3)
public static SpriteFont SerifBody; // CormorantGaramond-Regular-16
public static SpriteFont SerifItalic; // CormorantGaramond-Italic-14
public static SpriteFont MonoTag; // JetBrainsMono-Medium-10
public static SpriteFont MonoTagSmall; // JetBrainsMono-Medium-9
}
```
Loaded once at game startup; all widgets read from this table.
---
## 7. Theme tokens
Extract from the React design's `:root` CSS variables. The agent should
reproduce these exactly. See appendix §A1 for the full table; here's the
canonical names the C# code should use:
```csharp
public static class CodexColors {
// Backgrounds
public static Color Bg = HexColor("#f3ead4"); // parchment
public static Color BgDeep = HexColor("#e8dcb8"); // worn parchment shadow
public static Color Panel = HexColor("#ede0bf"); // card fill
public static Color PanelInset = HexColor("#e3d4a8"); // sunken inner panel
// Inks
public static Color Ink = HexColor("#2a1f12"); // primary serif text
public static Color InkSoft = HexColor("#5a4a32"); // secondary text
public static Color InkMute = HexColor("#8a7858"); // tertiary text
// Accents
public static Color Gild = HexColor("#a47118"); // gilded borders + select halo
public static Color GildBright = HexColor("#d4a23e"); // hover halo / pressed primary
public static Color Seal = HexColor("#8a2818"); // wax-red, danger / negative bonus
public static Color Rule = HexColor("#c8b88a"); // hairline rule color
}
public static class CodexDensity {
public const int RowGap = 6;
public const int ColGap = 8;
public const int CardPad = 14;
public const int PanelPad = 16;
public const int ChipPad = 6;
public const int ButtonPad = 10;
}
```
Density tokens correspond to the React design's `--gap` family; CardPad
and PanelPad let cards and aside panels keep consistent breathing room.
---
## 8. Layout primitives
The widget tree is hand-built in C# (no XAML). Every widget exposes:
```csharp
public abstract class CodexWidget {
public Rectangle Bounds { get; protected set; } // screen-space, set by parent during layout
public bool Visible { get; set; } = true;
public bool Enabled { get; set; } = true;
public CodexWidget? Parent { get; set; }
public virtual Point Measure(Point available); // returns desired size
public virtual void Arrange(Rectangle bounds); // sets Bounds; recurses to children
public virtual void Update(GameTime gt, CodexInput input);
public virtual void Draw(SpriteBatch sb, GameTime gt);
// Hit-testing helpers
public bool ContainsPoint(Point p) => Bounds.Contains(p);
}
```
Layout containers are widgets that arrange their children:
- **`Column`** — vertical stack with `Spacing` (default `RowGap`), optional
`Padding`, optional `HAlign` (left / center / right / stretch).
- **`Row`** — horizontal stack with same parameters; default `ColGap`.
- **`Grid`** — fixed-column grid; takes `Columns` (count) and a list of
child widgets, lays them left-to-right then wraps.
- **`WrapRow`** — flow layout: lay children left-to-right, wrap to next
line when width exceeded.
- **`Padding`** — single-child decorator that adds breathing room.
- **`Center`** — single-child decorator that centers the child horizontally
and vertically within its assigned bounds.
Implementation note: do **measure-then-arrange** in two passes to support
content-driven sizing (a card sizes to fit its name + chips). Avoid React-
style flex math; just stack and clamp.
---
## 9. Input + state primitives
`CodexInput` wraps `MouseState` + `KeyboardState` and produces edge-detected
events:
```csharp
public sealed class CodexInput {
public Point MousePosition { get; private set; }
public Point MouseDelta { get; private set; }
public bool LeftButtonDown { get; private set; }
public bool LeftJustPressed { get; private set; }
public bool LeftJustReleased { get; private set; }
public bool RightJustPressed { get; private set; }
public bool KeyJustPressed(Keys k);
public bool KeyDown(Keys k);
public string TextEnteredThisFrame { get; private set; } // for CodexTextBox
}
```
`CodexScreen.Update` calls `_input.Tick()` once per frame, then dispatches
to the widget tree. Each widget tracks its own hover/pressed state from
`input.MousePosition` against `Bounds`.
For hover popovers (`CodexHoverPopover`):
- Track `_hoverEnterTime` per widget; show popover after `HoverDelay = 0.4s`.
- On show, position popover relative to trigger widget; clamp to viewport
edges; flip above/below if not enough room below.
- Hide on `MouseLeft` from both trigger and popover bounds.
For text input (`CodexTextBox`):
- Subscribe to `Game1.Window.TextInput` event in the screen's
`Initialize`; route incoming `char` to the focused TextBox via
`CodexInput.TextEnteredThisFrame`.
- Handle Backspace, Enter, arrow keys via `KeyJustPressed`.
---
## 10. Drag-and-drop
`DragDropController` is a screen-level helper that owns the drag lifecycle:
```csharp
public sealed class DragDropController {
public bool IsDragging { get; private set; }
public object? Payload { get; private set; } // what's being dragged
public CodexWidget? GhostWidget { get; private set; } // visual that follows the cursor
public void BeginDrag(object payload, CodexWidget ghost);
public void Update(GameTime gt, CodexInput input); // position ghost; detect drop or cancel
public void Draw(SpriteBatch sb); // draw ghost on top of everything else
public event System.Action<object, Point>? OnDrop; // (payload, dropPositionScreen)
public event System.Action<object>? OnCancel;
}
```
Source widgets call `controller.BeginDrag(payload, ghost)` on `LeftJustPressed`
when their bounds contain the cursor. Drop targets register interest with
the controller; on `LeftJustReleased`, the controller hit-tests
registered targets at the drop position and fires `OnDrop`.
Escape cancels: in `Update`, if `IsDragging && input.KeyJustPressed(Keys.Escape)`,
call `OnCancel` and clear the drag.
For the character creator's stat pool:
- Source: each `CodexPoolDie` has a `payload = { from: "pool", value: int, idx: int }`.
- Drop target: each `CodexAbilityRow.Slot`.
- Slot-to-pool drop: the pool itself registers as a drop target.
- Slot-to-slot drop: the slot is both source and target.
Match the payload shape from the React design's `handleDrop` /
`dropToPool` functions in `steps.jsx` so the logic is portable.
---
## 11. Character creator port
Once the primitives exist, the character creator port is mostly
"translate `steps.jsx` to C# screen + step files." The state model is
already implemented in the existing
[CharacterCreationScreen.cs](Theriapolis.Game/Screens/CharacterCreationScreen.cs)
— port that state model verbatim, swap the Myra widgets for `CodexUI`
widgets.
Steps map 1:1 to the design's `steps.jsx`:
| React component | C# step file | Key widgets |
|---|---|---|
| `StepClade` | `Steps/StepClade.cs` | Predator/prey section headers, `CodexCard` grid with `CodexBonusPill` mods + `CodexChip` traits + clade sigil sprite |
| `StepSpecies` | `Steps/StepSpecies.cs` | 3-per-row grid filtered by clade; size + speed + mods + traits |
| `StepClass` | `Steps/StepClass.cs` | 2-per-row grid with `★ Suits Clade` ribbon overlay; level-1 features as chips |
| `StepBackground` | `Steps/StepBackground.cs` | 2-per-row grid; flavor body text + feature chip + skill chips |
| `StepStats` | `Steps/StepStats.cs` | Method tabs, pool row of `CodexPoolDie`, six `CodexAbilityRow`s with drag-drop, roll history line |
| `StepSkills` | `Steps/StepSkills.cs` | 3-column grid by ability; `CodexCheckboxRow` per skill |
| `StepReview` | `Steps/StepReview.cs` | Name input (`CodexTextBox`), `CodexPanel` summary blocks with `Edit ` buttons, starting-kit chip wrap |
The `CodexAside` panel mirrors the React `<Aside />` from `app.jsx` — five
summary blocks (Name, Lineage, Calling+History, Abilities, Skills) plus
the optional Selected detail line.
The wizard validation, step gating, and "Confirm & Begin" handoff to
`WorldGenProgressScreen` are unchanged from the existing implementation —
copy that logic verbatim.
---
## 12. Integration with the existing screen system
### 12.1 Game1 changes
Game1 already pushes `CharacterCreationScreen` on the new-game flow (see
[TitleScreen.cs](Theriapolis.Game/Screens/TitleScreen.cs)). The change:
```csharp
// In TitleScreen.OnNewWorldClicked, the only diff is the screen type:
_game.Screens.Push(new CodexUI.Screens.CodexCharacterCreationScreen(seed));
```
The old Myra `CharacterCreationScreen.cs` can be **deleted** when the
new screen ships and its tests pass — it's a 1:1 functional replacement.
### 12.2 Asset loading
Add a single `CodexAtlas` instance owned by `Game1`:
```csharp
// In Game1.LoadContent (or whatever the existing pattern is):
CodexAtlas.LoadAll(GraphicsDevice, ContentDataDirectory);
```
`CodexAtlas` loads every PNG in `Content/Gfx/codex/` and exposes them as
strongly-typed properties. The fonts load from the chosen path (mgcb or
FontStashSharp) at the same time.
Loud-fail on missing assets:
```csharp
// In CodexAtlas.LoadAll:
foreach (var name in REQUIRED_ASSETS) {
string path = Path.Combine(gfxDir, "codex", name);
if (!File.Exists(path))
throw new FileNotFoundException($"Codex asset missing: {path}");
}
```
### 12.3 Coexistence with Myra
The Myra screens (`PauseMenuScreen`, `SaveLoadScreen`, etc.) keep using
`Myra.Graphics2D.UI.Desktop`. The new `CodexScreen` base class doesn't
touch Myra at all. The screen stack treats them identically — both
implement `IScreen`. No conflicts.
A reasonable convention: `CodexUI` namespaces live under
`Theriapolis.Game.CodexUI.*`, Myra-using screens live in
`Theriapolis.Game.Screens.*` (existing). The two systems never share
widget types.
---
## 13. Milestones
Suggested implementation order. Each milestone is a ship point — branch
mergeable, tests passing, something visually demonstrable.
**M-A — Core + atlas + fonts (12 days).**
- Implement `CodexWidget`, `CodexLayout`, `CodexInput`, `CodexTheme`.
- Implement `NineSlice` renderer.
- Implement `CodexAtlas` with all 27 asset slots — using **placeholder
PNGs** (solid colors with text labels) so the structure works before
art is authored.
- Load fonts via chosen path.
- Build a test screen that draws a parchment background + 9-sliced card
+ a label so end-to-end pipeline is verified.
**M-B — Atomic widgets (23 days).**
- `CodexLabel`, `CodexButton` (3 variants), `CodexPanel`, `CodexChip` (3
variants), `CodexBonusPill`, `CodexOrnamentRule`, `CodexTextBox`.
- Each widget is verified in the test screen.
**M-C — Composite widgets (2 days).**
- `CodexCard` (with hover + selected states), `CodexStepper`,
`CodexCheckboxRow`, `CodexAbilityRow`, `CodexPoolDie`,
`CodexHoverPopover`.
- `DragDropController`.
**M-D — Character creator port (34 days).**
- `CodexCharacterCreationScreen` shell + `CodexAside`.
- All 7 step files.
- Wire to existing `CharacterBuilder` (no changes there).
- Replace `TitleScreen.OnNewWorldClicked` to push the new screen.
**M-E — Asset authoring + visual polish (24 days, mostly art).**
- Replace placeholder PNGs with finished art (parchment textures,
gilded borders, clade sigils, ornament glyphs, wax seal).
- Final color/spacing pass against the React design with the prototype
open side-by-side.
- Smoke-test by running the game and clicking through the wizard.
**M-F — Cleanup (½ day).**
- Delete the old Myra `CharacterCreationScreen.cs`.
- Update `_design_handoff/character_creation/IMPLEMENTATION_STATUS.md`
to reflect "now matches the design."
- Update `CLAUDE.md` if any project conventions changed.
**Total: ~1014 days.** Front-loaded with infrastructure (M-A through
M-C); the wizard port (M-D) is mostly translation; the visual polish
(M-E) is where the codex aesthetic lands.
---
## 14. Testing strategy
### 14.1 What to test
- **Unit-test `NineSlice`.** Given a source rect and a target rect,
verify it produces the right 9 sub-quads. No MonoGame dependency
needed if the math is in a static helper.
- **Unit-test `CodexLayout` containers.** Measure + arrange of nested
rows and columns produces expected child bounds.
- **Unit-test `DragDropController` lifecycle.** Begin → update positions
→ drop on registered target → fires OnDrop with correct payload.
- **Smoke-test the screen.** Headless render — push the screen,
call Update + Draw a few frames, assert no exceptions. (Existing test
pattern: see `Theriapolis.Tests/` — add a new `CodexUI/` folder.)
### 14.2 What NOT to test
- Pixel-exact render output. Visual regression testing on rendered
output is fragile; defer to manual visual check against the React
prototype.
- Font kerning / metrics. Trust the font system.
### 14.3 Architecture test
Add to `Theriapolis.Tests/Architecture/CoreNoDependencyTests.cs`: assert
that `Theriapolis.Game.CodexUI.*` types do **not** reference Myra (so
we don't accidentally cross-contaminate). The reflection check is the
same pattern as the existing Core-no-MonoGame check.
---
## 15. Open questions
These should be answered before M-A starts:
1. **Font path: A or B?** Recommend B (FontStashSharp) for v1 iteration
speed, switch later if profiling demands.
2. **Asset authoring source.** Hand-painted in Aseprite, or generated via
the Pixellab tooling already in the repo? The clade sigils benefit
from hand-direction; the 9-slice borders are simple enough to author
either way.
3. **Window resolution assumption.** The React design assumes ~1280×800
minimum; what's the game's target? Confirm before authoring assets at
1× scale (vs 2× or 3×).
4. **Theme switching.** The design's Tweaks panel supports light/dark/
custom themes. The plan assumes the game ships with one fixed theme
(the parchment codex). Confirm before adding theme-switching support
to `CodexTheme`.
5. **Animation.** The plan calls hover transitions out of scope. Confirm
no card-flip / step-transition animations are wanted in v1.
6. **Aside detail panel scope.** The current Myra port uses a click-to-show
detail line in the aside panel as a substitute for hover popovers. With
real hover popovers (`CodexHoverPopover`), is the aside detail line
still wanted? Probably yes for keyboard navigation (no hover when no
pointer).
---
## 16. What this plan does **not** cover
- Title screen redesign. Save for after the character creator ships and
the primitives are proven.
- Inventory screen redesign. Same — defer until M3+ inventory mechanics
shake out and the codex primitives are mature.
- HUD / combat overlay redesign. M5 ships those in Myra; rebuilding them
with `CodexUI` is a much later concern (different aesthetic — combat
HUD wants high-contrast functional, not parchment-decorative).
- The pause menu. Stays Myra. It's utility UI.
---
## Appendix A1 — Color & typography tokens (extracted from `index.html`)
Run this to extract the canonical CSS variables yourself:
```bash
grep -E '^\s*--' _design_handoff/character_creation/from_design/index.html | head -40
```
The definitive list at the time of writing:
```css
--bg: #f3ead4; /* parchment */
--bg-deep: #e8dcb8;
--panel: #ede0bf;
--panel-inset: #e3d4a8;
--ink: #2a1f12;
--ink-soft: #5a4a32;
--ink-mute: #8a7858;
--gild: #a47118;
--gild-bright: #d4a23e;
--seal: #8a2818;
--rule: #c8b88a;
--serif-display: 'Cinzel', 'Cormorant Garamond', serif;
--serif-body: 'Cormorant Garamond', 'EB Garamond', serif;
--mono: 'JetBrains Mono', monospace;
```
(If the React design has been updated since this plan was written,
re-extract from `index.html` and update `CodexColors` / `CodexFonts`
accordingly.)
---
*Theriapolis CodexUI Implementation Plan — 2026-04-25*
*Author: Claude (Opus 4.7) for LO, in continuity with the Phase 5 plan series.*
*Implementer: a future Claude Code session — read this whole file before starting M-A.*