ee5439285cb02fcf3453e4c8b82afd92e696a243
10 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
f57ea0b70c |
Fix ChunkStreamer.EnsureLoadedAround leaving pre-warmed chunks stuck
EnsureLoadedAround skipped Get() for any active chunk already in _inflight. That worked for the MonoGame TacticalRenderer, which calls Get() during its own draw loop and incidentally drains pre-warm tasks. But subscribers to OnChunkLoaded (e.g. the Godot port) saw no event when a previously-pre-warmed chunk transitioned into the active set on a later frame — the chunk stayed in _inflight forever, presenting as permanently-uncached gaps in the rendered world. Fix: drop the !_inflight.ContainsKey(cc) guard. Get() already handles all three paths (cache hit, inflight drain, fresh generate), so passing every active chunk through Get() guarantees OnChunkLoaded fires once per chunk regardless of how it was scheduled. Same flavour of bug as M1's MoistureGen FastNoiseLite race — cross-process / event-driven consumers exercise paths the in-process pull-based test fixtures never hit. 708/708 tests still pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
b3da447673 |
Fix MoistureGen/TemperatureGen non-determinism (FastNoiseLite race)
FastNoiseLite lazily populates its internal _perm[512] table on the first GetNoise call via EnsurePerm(). When called concurrently from a Parallel.For loop, threads race on this initialization and may read a partially-populated table, producing different moisture/temperature values per row across runs. Empirical: a 10-run worldgen-hash sweep on seed 12345 produced 4+ distinct moisture hashes and 3+ distinct temperature hashes. All other channels (elevation, biomes, settlements, polylines) remained stable; biomes only because their bucket thresholds happened to absorb the upstream float noise. The fix is the same one ElevationGenStage:125-130 and BorderDistortionGenStage: 102-104 already apply: call GetNoise once on the main thread before the Parallel.For so _perm is fully initialized when worker threads start reading. MoistureGenStage and TemperatureGenStage were missing this; now they have it. WorldgenDeterminismTests didn't catch this because xUnit's WorldCache fixture runs both pipeline variants in the same process, where consecutive runs hit the same JIT/thread-pool state and produce the same corrupted output. The Godot port surfaced it by invoking Core from a fresh process with different threading. Verified: post-fix 10-run sweep produces stable hashes on all six channels (0xA8F99BB9795D8CF8 moisture, 0xAA05F3FB1523F6C3 temperature, seed 12345). 708/708 tests still pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
b451f83174 |
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> |