Commit Graph

16 Commits

Author SHA1 Message Date
Christopher Wiebe 067038de45 M6.13a: Show character sex in Aside lineage block
Sex is a character-level field set at Step 0 — surface it in the
right-rail summary alongside clade and species. Lands as a Sex row
under the lineage cells in both purebred (single Sex cell + filler)
and hybrid (Sex applies to the whole character, spans below the
sire/dam clade and species rows) layouts. Falls back to the empty
"—" placeholder when not yet picked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 21:39:33 -07:00
Christopher Wiebe e1fb988969 M6.13: Sex picker + species variant schema
Adds character Sex (male/female) as a top-level CharacterDraft field
required for every character, and species variants as a layer on top
of the base species record. Two variant axes:

  - "sex": auto-resolves from CharacterDraft.Sex for purebreds; for
           hybrids, pinned by parent role (sire = male, dam = female)
           by definition. No picker needed beyond Step 0.
  - "lineage": explicit per-species pick (Ram-Folk's sheep/goat). Has
               its own picker on Step 1 (purebred path under the grid;
               hybrid path embedded into the per-parent pick column).

Schema (Theriapolis.Core/Data):
  - SpeciesDef gains VariantAxis (string) and Variants (array of
    SpeciesVariantDef { Id, Name, Traits, Detriments }).
  - JSON content not yet populated — that's M6.14.

CharacterDraft adds:
  - Sex (required by Step 0 validation)
  - SpeciesVariant / SireSpeciesVariant / DamSpeciesVariant
  - ResolveVariantId(species, role) that returns the active variant
    id for a given context — used by Aside to layer variant traits
    onto the base species traits.

Step 0 (StepClade): sex picker row above the hybrid toggle. Two
toggle buttons radio-style.

Step 1 (StepSpecies): purebred path renders a lineage picker below
the grid when the picked species has VariantAxis == "lineage";
hybrid path embeds a lineage picker at the top of each parent's
pick column. Hover popovers summarise each variant's contents.

Validation: Sex is required at Step 0. Lineage variant required at
Step 1 for any picked species (purebred or per-hybrid-parent) with
VariantAxis == "lineage".

Aside: AddVariantContent layers the resolved variant's extra
traits/detriments onto the base species rendering, for both purebred
and hybrid paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 21:03:56 -07:00
Christopher Wiebe 8bf9eba2a7 M6.8: Hybrid trait pickers (Phase B)
Per theriapolis-rpg-clades.md "Building a Hybrid": hybrids now pick
two clade traits from the dominant parent + one from the other (2/1
split keyed off DominantParent), and one species trait + one species
detriment from each parent. All clade detriments still inherit fully
from both parents. Universal hybrid detriments unchanged.

CharacterDraft gains six new fields (sire/dam clade-trait arrays,
sire/dam species trait/detriment ids) and a CladeTraitLimit(lineage)
helper. Step 0/1 validators enforce the picks; Aside renders only the
chosen subset for hybrids.

Cascading clears: clade swap clears that lineage's bonus + clade
traits + (if species also invalidated) species pick; species swap
clears that lineage's species trait/detriment; dominant flip trims
overflow from the end (non-destructive when possible); hybrid-off
clears all six new fields.

Toggle buttons in both steps wire MouseEntered/Exited into
PopoverLayer so the player can read each trait's description on
hover (detriment buttons get the red-tinted "DETRIMENT" popover).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 20:12:56 -07:00
Christopher Wiebe e3f0296e6f M6.7: Parchment theme pass
Lights up the M5 codex design system across the wizard. Default
palette swaps from dark leather to aged-parchment cream with
sealing-wax red selection emphasis, matching the React prototype's
default theme variant. CodexTheme.Build() is applied at the wizard
root so every step + Aside + popover cascades through it.

Theme additions:
- Parchment palette in CodexPalette (Dark retained as alt)
- Type variations registered for Card, CodexPopover, Pill,
  PillDetriment, AbilityToken, AbilitySlot, SkillRow — without
  SetTypeVariation, panel-stylebox lookup falls through to Godot's
  default dark slate, which is what was happening to every bare
  PanelContainer before this pass.
- panel_hover stylebox on Card (gild border) wired via CodexCard's
  MouseEntered/Exited helper; panel_selected bumped to 3px seal-red
  border + soft shadow so selection reads at a glance.

Card selection refactor:
- Replaced the warm-cream Modulate hint on cards with stylebox swaps
  via the new CodexCard.SetSelected helper. The Modulate approach
  was a no-op on cream-on-cream parchment; the stylebox swap looks
  the same on either palette.
- Step intros + Aside section headers now use the existing Eyebrow /
  H2 / H3 / CardName / CardMeta / CardBody label variations.
- Confirm button on Step VIII uses the PrimaryButton variation.

Popover + chip behaviour:
- PopoverLayer is now MouseFilter=Ignore so clicks/scroll/hover all
  pass through. Adjacent chips fire reliably even when the previous
  popover overlaps them spatially.
- Dropped the 80ms grace timer; chip MouseExited closes immediately.
- TraitChip MouseFilter Stop → Pass so clicks bubble up to the
  parent card's GuiInput (selecting the card).

Misc:
- Wizard._Ready inserts a backing Panel so the parchment Bg fills
  the canvas — Wizard root is a plain Control, which paints nothing.
- CodexTheme font lookup tries Cormorant-Medium before -Regular and
  globalizes res://Fonts/ for runtime FontFile load (the previous
  fallback used ContentPaths which points at a sibling data tree).
- StepStats final-score Label rendered at font_size 22 to match the
  AbilityToken die.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 22:04:24 -07:00
Christopher Wiebe bb986d49f9 M6.6: StepReview signing + hybrid math revision
- New Step VIII (Review): name input and Confirm button that
  saves the finalized character to user://character.tres.
- Hybrid lineage rules simplified per project decision: drop
  the "no-stack on overlap, take +1 free elsewhere" rule from
  theriapolis-rpg-clades.md. Hybrids now pick one ability mod
  from each parent clade and they sum if they overlap.
  Removes HybridFreeAbility, the free-bonus picker row, and the
  overlap special case from AbilityCalc + WizardValidation.
- StepClade bonus rows now mutate in place (sync ButtonPressed)
  instead of tearing down on every Refresh — the old path freed
  the very button mid-Pressed-signal, leaving stale buttons next
  to the new ones.
- StepSkills drops the redundant "Calling: X · History: Y" meta
  line; both are already shown in the Aside summary.
- Aside hybrid section adds dual-species traits and the
  universal-hybrid detriment pills.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 20:51:55 -07:00
Christopher Wiebe 0e5d4b7425 M6.5: StepSkills + ability-bonus breakdown + Aside redesign
The skills step lands as the last data-driven character creation step
(only Sign — name + confirm — remains for M6.6). Brought a few cross-
cutting refactors with it.

Scenes/Steps/StepSkills.cs:
  Direct port of StepSkills in src/steps.jsx — all 18 skills laid out
  in 6 ability-grouped panels (STR/DEX/CON/INT/WIS/CHA), 2-column
  grid. Background-granted skills appear pre-checked and locked;
  user picks `class.SkillsChoose` more from `class.SkillOptions`.
  Hover the skill name → popover with the codex flavor description
  (limited to the title only — hovering checkboxes / source tags
  doesn't trigger the popover, avoids interference with adjacent
  rows' click targets). Fixed-width [✓] / [ ] / [—] checkbox slot
  so toggling doesn't shift the row layout.

UI/SkillsCatalog.cs (new):
  Static skill table — JSON id, display label, governing ability,
  and the codex SKILL_DESC text ported verbatim from src/data.jsx.
  Mirrors Theriapolis.Core.Rules.Stats.SkillId; descriptions live
  here because backgrounds.json and classes.json don't carry them.

UI/AbilityCalc.cs (new):
  Final-score math — base assignment + clade and species mods, with
  per-source breakdown for the bonus popover ("+1 from Canidae · +2
  from Wolf"). Hybrid mode tags each clade source with its lineage
  ("(sire)" / "(dam)"). Used by both StepStats and the Aside so the
  two views can never disagree on what a +N badge means.

UI/BackgroundAvailability.cs (new):
  Extracted from StepBackground — shared rules table for hybrid-only
  and clade-restricted backgrounds. Now also consulted by StepClade
  when the player changes lineage: the currently-selected background
  is auto-cleared if the new lineage no longer satisfies its rule
  (e.g., Pack-Raised clears when switching from Canidae to Felidae,
  Passer clears when toggling Hybrid off). Implemented via
  Resource.Duplicate + Patch on the duplicate to evaluate the
  hypothetical post-patch state without committing prematurely.

StepStats.cs:
  Per-row layout extended: ability label | slot | bonus chip | final
  | d20 mod. Bonus chip is a TraitChip with the per-source breakdown
  in its hover description. Auto-assign now sorts empty abilities by
  AbilityCalc.TotalBonus DESCENDING (with class.PrimaryAbility as
  tiebreaker) — biggest pool value lands on the ability already
  receiving the biggest lineage bonus, maximising final scores.

Aside.cs (significant redesign):
  - Name centered at top.
  - Lineage details: 2-column grid, full-width.
    - Purebred:  Clade | Species, then Calling | Background, then
                 Subclass | (empty).
    - Hybrid:    SIRE ★ | DAM (centered + underlined column heads),
                 Clade | Clade, Species | Species, then the same
                 calling/background/subclass rows.
  - Attributes: STR/DEX/CON/INT/WIS/CHA each with bonus chip (omitted
    when +0), final score, d20 modifier. Self-contained min-width
    table so it can't widen the panel beyond its alloc.
  - Pills: traits, detriments, level-1 features, background feature,
    skill chips (BG-locked + user-chosen). All hoverable for descriptions.
  - Whole panel wraps in a ScrollContainer so an over-tall summary
    scrolls in place instead of pushing the wizard layout off-screen.
  - Width nudged 320 → 360px. Smaller font on label tags, autowrap
    on value labels so long names ("Hybrid Underground") wrap rather
    than push the panel wider.

Card grids: changed all five card-grid steps (Clade, Species, Class,
Subclass, Background) from SizeFlagsHorizontal.ExpandFill →
ShrinkCenter. Cards stay at their CustomMinimumSize 200 wide and
the grid horizontally centers in PageMain. The right-side gap
between content and Aside is now uniform regardless of how many
cards or whether the last row is partial — fixes the "Clade tab
feels too padded, Background tab too tight" perception.

Closes M6.5. Per guide §12, what's left in M6: M6.6 (StepReview —
name + summary + Confirm handoff per guide §11) and M6.7 (parchment
Theme pass).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 18:10:05 -07:00
Christopher Wiebe ce87eb11ad M6.4: Card-grid steps + hybrid origin + clade-restricted backgrounds
Per GODOT_PORTING_GUIDE.md §12, the four "easy" card-grid steps land
together (Species / Calling / Subclass / History), plus three real
features that emerged during testing: cross-step validation gating,
hybrid origin, and clade-restricted background availability.

New step files (Scenes/Steps/):
  StepSpecies.cs    — cards filtered by clade; for hybrids shows two
                      stacked grids (Sire / Dam).
  StepClass.cs      — all classes; class change clears chosen skills
                      and the previously-selected subclass.
  StepSubclass.cs   — subclasses filtered by ClassDef.SubclassIds.
  StepBackground.cs — backgrounds filtered by hybrid + clade rules
                      (see below).

UI/WizardValidation.cs (new):
  Static per-step validators against CharacterDraft. Replaces the
  per-instance Validate() route on the wizard side — Wizard now
  computes the lock state for every step in the flow, not just the
  current one. Mirrors app.jsx's firstIncomplete rule exactly.

  Bug it fixes: previously the wizard checked only the current step's
  validity, so picking a clade let you skip directly to Abilities
  without picking species/calling/etc.

UI/CharacterDraft.cs:
  Phase 6.5 hybrid fields — IsHybrid, SireCladeId, SireSpeciesId,
  DamCladeId, DamSpeciesId, DominantParent. EffectiveCladeId /
  EffectiveSpeciesId resolve to the dominant parent's lineage when
  hybrid; downstream steps don't need to care which path. Helpers
  HasClade(id) and HasAnyCladeOfKind(kind) feed the background
  availability rules.

StepClade.cs:
  Hybrid toggle splits the picker into Sire + Dam grids with a
  Dominant Lineage radio. Validation refuses same-clade Sire+Dam.
  Switched to build-once + mutate-in-place: cards are created once
  during Build(), Refresh just updates Modulate per selection state.
  Tearing down + rebuilding inside the click callback caused
  duplicates because Free() defers when the freed node is mid-signal.

StepBackground.cs:
  Availability rules table — predicates per restricted background id.
  Hybrid-only: passer, hybrid_underground, former_chattel.
  Clade-restricted: warren_runner (Leporidae), pack_raised (Canidae),
  herd_city_born (any prey clade).
  Hybrids match if either parent satisfies the rule.

Other steps (Species/Class/Subclass/Background):
  Refresh dispatched via Callable.From(Refresh).CallDeferred() so the
  rebuild runs after the click handler completes — same Free()-during-
  signal bug as StepClade hit, fixed via deferral instead of mutate-
  in-place because the card lists are dynamic (clade- / class- /
  hybrid-flag-dependent).

Wizard.cs:
  - RebuildStepperStates uses WizardValidation.FirstIncomplete to lock
    every step past the first unsatisfied one.
  - OnStepperClicked checks every step in [0..target-1].
  - UpdateChrome's banner uses WizardValidation for the active step.
  - Scroll preservation moved here (snapshot before step.Refresh
    fires, restore in _Process); StepStats's local copy removed.

Wizard.tscn:
  Scroll node marked unique_name_in_owner so Wizard can grab it.
  PopoverLayer's TraitChip is reused throughout the new step cards.

Aside.cs:
  Hybrid-aware summary — shows "Sire (dominant)" / "Dam" lineage rows
  when IsHybrid; otherwise the existing Clade / Species rows.

Verified end-to-end:
  - Walk Clade → Species → Calling → Subclass → History → Abilities
  - Stepper locks every step past first unsatisfied
  - Hybrid toggle works both directions, dominant changes lineage
  - Hybrid-only and clade-restricted backgrounds appear / disappear
    based on lineage
  - Scroll position preserved across selections
  - Drag-drop still works on Abilities

Closes M6.4. Per guide §12, next is M6.5 — StepSkills (class-driven
choice list with TraitChip per skill).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:24:33 -07:00
Christopher Wiebe ba3ebe7ff3 M6.3: Trait popovers — shared PopoverLayer + TraitChip triggers
Per GODOT_PORTING_GUIDE.md §6 (and §12 build order — popovers before
the easy card-grid steps because traits/skills/bonuses surface them
everywhere). One reusable popover panel; lightweight chip triggers.

Scenes/Widgets/PopoverLayer.cs:
  CanvasLayer added once as a child of Wizard.tscn. Owns one
  PanelContainer + close Timer; static Instance for chip-side access.
  ShowFor(trigger, ...) populates and positions the popover at the
  trigger's global rect with viewport clamp + flip-above logic
  (mirrors src/trait-hint.jsx). 80 ms grace period when moving from
  trigger to popover so the popover stays open across the gap.
  Detriment popovers get a red Modulate as a placeholder for the
  seal-coloured StyleBox the theming pass will install.

Scenes/Widgets/TraitChip.cs:
  Lightweight PanelContainer + Label trigger. On MouseEntered asks
  PopoverLayer.Instance to show; on MouseExited schedules close.
  Pill styling deferred to theming (default Godot panel for now;
  TraitChip / TraitChipDetriment styleboxes will land alongside
  the parchment Theme pass).

Wizard.tscn:
  PopoverLayer added as a top-level CanvasLayer child so popovers
  float above every step's content regardless of where the trigger
  is in the tree.

Steps/StepClade.cs:
  Replaces the placeholder "{n} traits, {m} detriments" line with an
  HFlowContainer of TraitChip per trait + per detriment. Hover any
  chip → popover shows name + description (+ DETRIMENT tag for the
  detriment chips).

  Also: cards switched from Button to PanelContainer for content-
  driven height. Button isn't a Container, so its intrinsic min
  size didn't aggregate from the inner vbox — at higher trait
  counts the chips overflowed into the cards below. PanelContainer
  is a Container, so the card grows with its content. GuiInput
  handles the click-to-select; selected state shown via Modulate
  tint until the proper StyleBox swap lands in theming.

Closes M6.3. Per guide §12, next is M6.4 — easy card-grid steps
(Species / Calling / Subclass / History) variations on the StepClade
pattern, then StepSkills, then StepReview.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 20:57:02 -07:00
Christopher Wiebe 4d3db17a89 M6.2: Step V Abilities — drag-drop assignment + roll/auto-assign
Per GODOT_PORTING_GUIDE.md §7, the highest-risk piece in the wizard.
Three reusable widgets + the orchestrating step.

Scenes/Widgets/AbilityToken.cs:
  Draggable Control with Value + Origin metadata. _GetDragData returns
  a Dictionary payload {kind, value, from, ability, idx} per guide
  §7.1. MouseFilter = Pass so clicks propagate to the parent slot
  for the click-to-return affordance (later removed; see commit body).
  Drag preview is a dimmed duplicate.

Scenes/Widgets/AbilitySlot.cs:
  PanelContainer drop target per guide §7.2. Accepts any
  ability_value payload via _CanDropData / _DropData and emits
  Dropped(payload). Each slot owns one ability id (STR/DEX/CON/INT/
  WIS/CHA).

Scenes/Widgets/AbilityPool.cs:
  HBoxContainer drop target per guide §7.3. Accepts only slot→pool
  drops (returning an assigned value to the pool); pool→pool drops
  are no-ops.

Scenes/Steps/StepStats.cs:
  Direct port of StepStats in steps.jsx per guide §7.4. Standard
  array (default) and roll-4d6-drop-lowest method tabs; Reroll
  button visible in roll mode; Auto Assign sorts the remaining pool
  descending and places the largest values into empty slots ordered
  by class.PrimaryAbility. Three drag-drop cases (pool→slot,
  slot→slot swap, slot→pool) all delegate to a single Patch call,
  then the entire token tree rebuilds from the new draft state on
  the Changed signal — handlers don't reparent anything manually.

Issues hit during development and resolved before commit:
  - Initial click-to-return on slot pre-empted drag-from-slot every
    time (the GuiInput fired on mouse-down, before Godot detected
    the drag). Removed click-to-return — drag is the canonical
    interaction; that matches the React prototype anyway.
  - Token MouseFilter = Stop blocked clicks from reaching the slot
    layer; switched to Pass which still works as a drag source.
  - Refresh() teardown + rebuild reset the parent ScrollContainer's
    scroll to 0 every drop. CallDeferred / SetDeferred / CreateTimer
    all raced because layout settles over multiple frames; the fix
    that worked was capturing scroll position pre-rebuild and
    restoring in _Process the next frame.

Wizard.cs:
  StepTypes[5] = typeof(StepStats); the Abilities step is now
  reachable. (StepTypes[1..4, 6..7] still null — coming in M6.3+.)

Verified: all three drag scenarios + click handling + auto-assign
+ method switch + reroll work; scroll position holds across drops.

Closes M6.2. Next per guide §12: M6.3 — popover system (TraitChip +
shared PopoverLayer) before adding more easy card-grid steps.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 20:36:19 -07:00
Christopher Wiebe ee5439285c M6.1: Character creation wizard foundation (.tscn + Resource-based draft)
Pivots from M5's code-built UI to the editor-authorable .tscn pattern
recommended in GODOT_PORTING_GUIDE.md, after a session of fighting
Godot idioms with code-only layout. Default theme only; the parchment
Theme lands last per the guide's §12 build order so layout bugs surface
as layout bugs, not theming bugs.

GODOT_PORTING_GUIDE.md:
  Authored by Claude Design as the canonical port reference. Maps the
  React prototype's structure onto Godot 4.6 with concrete code sketches
  and a build-order recommendation. Drove the M6 architecture.

Fonts/:
  Cormorant Garamond (Medium + MediumItalic) and Crimson Pro (Regular +
  Italic + SemiBold) under OFL — the React prototype's serif-display
  and serif-body families. Not yet wired through CodexTheme.Build()
  because theming is deferred; CodexTheme.LoadFontFromFonts already
  picks them up automatically when the Theme pass lands.

Scenes/Wizard.tscn + Wizard.cs:
  Wizard shell per guide §4: codex-header (title + folio counter) +
  Stepper + Page (StepHost + Aside) + NavBar (Back / validation / Next).
  All node lookups via unique-name (%) syntax; layout authored as a
  scene file you can open in the editor. Step lifecycle drives the
  Aside via signal binding. Stepper logic mirrors app.jsx — locked
  iff some EARLIER step is unsatisfied; "type not yet implemented"
  doesn't lock.

Scenes/Aside.tscn + Aside.cs:
  Right-rail summary per guide §10. Single Refresh() rebuild on
  CharacterDraft.Changed; cheap enough not to bother with partial
  updates. Width 320 (was 380 before the layout overflow fix).

Scenes/Steps/IStep.cs + StepClade.cs:
  Per-step Bind(draft) + Validate() contract. StepClade renders the
  3-column clade card grid; click commits via CharacterDraft.Patch
  which triggers the Resource.Changed signal that Aside and Wizard
  both subscribe to.

UI/CharacterDraft.cs:
  Resource (not Node) per guide §2.1. Mirrors app.jsx's `state` shape
  exactly. Patch(dictionary) emits the inherited Resource.Changed
  signal — listeners use `draft.Changed += handler` regardless of
  which field changed. CodexContent provides lazy-loaded immutable
  content tables (Clades, Species, Classes, Subclasses, Backgrounds).

Main.{cs,tscn}: Node → Control
  When Main was a Node, Control children couldn't anchor to a real
  parent rect — they sat at (0,0) at intrinsic min size. With wide
  step content (3-column 200-px-card grid), the Wizard's min size
  pushed the navbar beyond the viewport's right edge, hiding the Next
  button on smaller windowed viewports. Making Main a full-rect-
  anchored Control gives child scenes a proper rect to lay out in.

UI/Widgets/CodexStepper.cs:
  Anchored the inner vbox to fill the button rect. Without this, the
  vbox sat at the button's top-left at intrinsic size and labels
  rendered in the corner — visible as the active-step label being
  off-center from the highlight bar.

Verified at 1152x720 windowed and (separately) at fullscreen:
  - 3-column card grid fits inside Wrap margins + Aside without
    horizontal overflow
  - Stepper labels centered under their highlight bars
  - Next button visible after clade selection; future steps switch
    to "coming soon" placeholder when clicked
  - Aside summary fills in CLADE block on selection

Closes M6.1.  Next per guide §12 build order: M6.2 — StepStats with
drag-drop (highest-risk piece, de-risk before easy steps).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 19:35:03 -07:00
Christopher Wiebe 953bb985ad M5: Codex design system (dark theme) + kitchen-sink
Programmatic Theme builder + reusable popover and stepper widgets,
ported from CharacterCreator.zip's :root design tokens. Kitchen-sink
scene exercises every primitive for visual eyeballing.

CodexPalette.cs:
  Color tokens lifted verbatim from the React prototype's `:root`
  block (--bg, --ink, --gild, --seal, etc.). Variable names mirror
  the CSS so the audit trail stays readable. Spacing locked at the
  prototype's normal density (--gap=24, --pad=28, --radius=2).

  Scope cut: only the Dark theme ships. The React prototype designed
  Parchment, Dark, and Blood as switchable variations — user direction
  during M5 is that only Dark (leather + candlelight) is wanted for
  this game. Parchment/Blood code dropped, plan doc updated to match
  (§1 goal #5, §4.5 UI map, §5 M5 scope, §10 resolved decisions #4).
  No runtime theme switcher.

CodexTheme.Build():
  Programmatically constructs a Godot Theme from CodexPalette.Dark
  plus CodexSpacing/CodexType tokens. Configures Panel, Card,
  CodexPopover styleboxes; Label variations for H1..H4, CodexTitle,
  Eyebrow, Meta, ValidationOk/Error, CardName/Body/Meta, StepperNum/
  Name; Button + PrimaryButton + GhostButton variants; LineEdit,
  CheckBox, scrollbar styling.

  Fonts: looks for CormorantGaramond / CrimsonPro / JetBrainsMono
  TTFs in res://Fonts/ (or Content/Fonts/) and graceful-falls-back to
  Godot defaults if missing. M5 ships with no fonts in repo; user can
  drop them in later for typography parity with the React prototype.

CodexPopover.cs:
  Hoverable text trigger + floating PanelContainer, mirrors
  src/trait-hint.jsx. Viewport-clamps horizontally and vertically;
  flips above the trigger if there's no room below; 80 ms grace
  period when moving cursor from trigger to popover. Detriment
  variant uses the seal-coloured stylebox. Future TraitName /
  SkillChip / BonusPill widgets layer className differences on top.

CodexStepper.cs:
  Roman-numeral horizontal stepper with Pending / Active / Complete /
  Locked states. Active step gets a 2-px gild underline, Complete
  shows a ✓ in seal-red, Locked shows ✕ + 0.45 modulate. Emits
  StepClicked(int) for non-locked rows. M5 is decorative — M6 wires
  the signal to the character-creation state machine.

KitchenSink.cs + Main.cs --codex-test:
  Verification scene rendering every primitive (header, stepper,
  buttons, inputs, cards, trait popovers). Clicks log to console.
  Fonts default to Godot's Noto Sans until res://Fonts/ is populated.

Closes M5 of theriapolis-rpg-implementation-plan-godot-port.md.
Next: M6 (title + character creation).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 20:29:22 -07:00
Christopher Wiebe 42d66c00c3 M4: Tactical render + unified seamless-zoom WorldView
Implements the seamless-zoom contract from CLAUDE.md: one Camera2D
covers both world-map and tactical scales; layers fade in/out at zoom
thresholds; polyline widths and the player marker counter-scale with
zoom so on-screen sizing stays consistent across the full range.

Layers (bottom-up in WorldView):
  Biome sprite     — 256x256 ImageTexture scaled by WORLD_TILE_PIXELS;
                     always visible (acts as backdrop past the tactical
                     streaming radius).
  TacticalChunks   — TacticalChunkNode children added on chunk-loaded
                     event; visible only when zoom ≥ 4.
  Polylines/Bridge — Line2D children; always visible. Width recomputed
                     each frame as baseScreenPx / camera.Zoom so the
                     on-screen stroke is constant (4 px highway, 3 px
                     post road, 2 px dirt road, 4.5/3/2 for major-river/
                     river/stream, 4/2 for rail tie/line, 6 for bridge).
  Settlements      — SettlementDot children; hidden when zoom ≥ 2 (you
                     are visually "inside" them at tactical scale).
  PlayerMarker     — Always visible; Scale = 1/zoom keeps it at
                     PLAYER_MARKER_SCREEN_PX on-screen across all zooms.

TacticalAtlas:
  Loads PNGs from Content/Gfx/tactical/{surface,deco}/ via ContentLoader
  with name_0.png/name_1.png/... variant probing (silent miss). Falls
  back to procedurally-generated solid placeholders matching MonoGame's
  TacticalAtlas colour table so missing art doesn't break rendering.

TacticalChunkNode:
  One Node2D per cached chunk, positioned at (OriginX, OriginY) in
  world-pixel space. _Draw iterates the 64x64 tile grid once and Godot
  caches the rasterised CanvasItem; subsequent frames blit instead of
  re-issuing 4096 DrawTextureRect calls.

ChunkStreamer integration:
  WorldView listens to OnChunkLoaded / OnChunkEvicting and adds /
  removes TacticalChunkNode children. Streaming radius is computed
  dynamically from the viewport size and camera zoom plus a 2-tile
  buffer, so chunk loads always cover the visible viewport with margin.
  Chunks only stream when zoom ≥ 4 (tactical is visible).

Main.cs:
  --world-map [seed]            → WorldView, fit-to-viewport zoom
  --tactical  [seed] [tx] [ty]  → WorldView, zoom 32 at given tile
  Both flags converge on the same scene; mouse wheel transitions
  seamlessly between modes.

ContentLoader silent miss:
  Removed the "Missing texture" PrintErr — atlas variant probing
  legitimately tries name_3.png that doesn't exist, and the noise
  drowned the console. Genuine asset failures still surface via
  AssetTest's count summary.

Deleted (replaced by WorldView):
  Theriapolis.Godot/Rendering/WorldMapView.cs
  Theriapolis.Godot/Rendering/TacticalView.cs (created earlier in M4,
  never committed — superseded before commit).

Closes M4 of theriapolis-rpg-implementation-plan-godot-port.md.
Next: M5 (codex design system).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 20:08:14 -07:00
Christopher Wiebe a23cf8bd97 M3: Asset pipeline — ContentPaths + ContentLoader + asset-test
Formalises Content/ access from the Godot host. Content lives at the
repo root (sibling of Theriapolis.Godot/), not duplicated under res://,
so the MonoGame branch and headless Tools keep reading from the same
single source of truth.

ContentPaths.cs:
  Static ContentRoot/DataDir/GfxDir resolved once via res:// walk-up
  and cached. Replaces two inline ResolveDataDir copies in SmokeTest
  and WorldMapView.

ContentLoader.cs:
  LoadGfx(relativePath) -> ImageTexture, cached by relative path.
  Bypasses the res:// import pipeline because Content/ lives outside
  the project — fine for static pixel-art assets at native size, and
  the project default texture filter is already Nearest. Cache is
  per-process, never evicted (full atlas <1 MB).

AssetTest.cs + Main.cs --asset-test flag:
  Smoke-tests the pipeline. Walks Content/Data and Content/Gfx,
  reports counts, attempts to load every PNG, prints per-subdir
  breakdown. Quits with non-zero on any failure.

Verified post-refactor (--asset-test):
  53 JSON files in Data, 50 PNG files in Gfx (8 tactical/deco +
  42 tactical/surface), 50/50 loaded, 0 failures.

Verified no regressions:
  --smoke-test (M1) still produces canonical FNV hashes.
  --world-map 12345 (M2) still produces 3 rivers / 91 roads /
  226 settlements / 0 rails / 0 bridges.

Scope note: plan mentioned "tile/NPC/CodexUI atlases — three
separate themes". Only tactical/ exists in Content/Gfx today; NPC
and CodexUI atlases never landed during MonoGame development. M3
ships what's actually present. ContentLoader.LoadGfx works for any
future sub-directories without changes.

Closes M3 of theriapolis-rpg-implementation-plan-godot-port.md.
Next: M4 (tactical render).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 19:13:51 -07:00
Christopher Wiebe 59784048cd M2: World map render in Godot
Renders the full worldgen output as a Godot scene at visual parity with
worldgen-dump's PNG output: biome tiles, rivers/roads/rails as Line2D
polylines, settlements as filled circles. Pan + zoom via Camera2D.

WorldMapView.cs:
  - Loads Content/Data via res:// walk-up, runs WorldGenerator.RunAll
  - Tile palette built from BiomeDef.ParsedColor() — same source as the
    PNG dump, so colours are identical
  - Tiles rendered as a 256x256 Image scaled by WORLD_TILE_PIXELS to
    cover world-pixel space (matches polyline coord system)
  - Polyline draw order mirrors LineFeatureRenderer.cs: roads (smaller
    first) -> rivers -> rail tie underlay -> rail line. Bridges as
    short Line2Ds; settlements as SettlementDot (Node2D + _Draw circle)
  - Line widths in world-pixel space, tuned for visibility at world-map
    zoom; M4 will add zoom-aware width scaling for tactical view
  - Camera fits the whole world (95% of viewport) on first frame

PanZoomCamera.cs:
  - Mouse-wheel zoom centered on cursor (cursor world-point stays fixed)
  - Middle/right click + drag to pan
  - MinZoom/MaxZoom configurable per-instance

Main.cs:
  - --world-map [seed] flag launches the view (default seed 12345)
  - Arg parser now reads both GetCmdlineArgs and GetCmdlineUserArgs so
    callers don't need to remember the "--" separator
  - --smoke-test path and M0 hello-world fallback unchanged

Visual diff against world_seed12345.png (generated by
worldgen-dump --seed 12345) confirmed manually: same biome palette, same
rivers/roads topology, same settlement placement and tier colours.
3 rivers, 91 roads, 226 settlements (138 PoIs), 0 rails (ENABLE_RAIL=false),
0 bridges (this seed has no road/river crossings). All match the PNG.

Settlement dot sizes iterated twice from user feedback — final values
in tile units, scaled to world-pixel space, so they shrink at world-map
zoom and grow toward tactical zoom (the right "scale with the map"
behaviour).

Closes M2 of theriapolis-rpg-implementation-plan-godot-port.md.
Next: M3 (asset pipeline).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 19:04:02 -07:00
Christopher Wiebe 57fe6bf173 M1: Headless parity verified between Tools and Godot
Proves Theriapolis.Core works untouched under Godot's csproj — the worldgen
pipeline produces byte-identical output whether invoked from the Tools CLI
or from inside the Godot process. This is the determinism contract surviving
the port.

Architecture test:
  CoreNoDependencyTests now forbids Godot.* and GodotSharp in addition to
  Microsoft.Xna and MonoGame. Both bans stay in force for the duration of
  the port so neither engine can leak into Core.

Determinism oracle:
  New worldgen-hash Tools command runs the full pipeline and prints FNV-1a
  hashes for every channel (elevation, moisture, temperature, biomes,
  settlements, polylines) plus per-stage hashes. Pairs with the Godot
  smoke-test for cross-process verification.

Godot-side smoke test:
  SmokeTest.cs runs WorldGenerator.RunAll inside the Godot process; Main.cs
  fires it on --smoke-test <seed>. Resolves Content/Data via res:// walk-up.
  M0 hello-world behaviour preserved when launched without the flag.

Verification (seed 12345):
  - dotnet run -- worldgen-hash and Godot --headless --smoke-test agree on
    all 6 channels and all 14 per-stage hashes (diff produces zero output)
  - 10-run sweeps stable on both sides post-determinism-fix
  - dotnet test: 708/708 pass

Closes M1 of theriapolis-rpg-implementation-plan-godot-port.md.
Next: M2 (world map render).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 21:38:15 -07:00
Christopher Wiebe 59e86af7a2 M0: Scaffold Theriapolis.Godot project (hello-world)
First milestone of the Godot port. Establishes the new project structure
and verifies the toolchain is wired up end-to-end.

- Godot.NET.Sdk/4.6.2 targeting net8.0; references Theriapolis.Core
- project.godot configured for borderless fullscreen at native resolution
  (per port plan §10 resolved decisions); F11 toggles to windowed
- Main.tscn + Main.cs hello-world; nearest-neighbor texture filtering
- icon.svg placeholder (T in gild on dark)
- Added to Theriapolis.sln

Verification:
- dotnet build Theriapolis.Godot.csproj: 0 errors, 0 warnings
- dotnet build Theriapolis.sln: 0 errors (6 pre-existing warnings unrelated)
- dotnet test: 708/708 pass in 26s (unchanged from master)
- Godot 4.6.2 opens project; fullscreen + F11 toggle confirmed visually

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:52:35 -07:00