a802fb318f2bc0716b133b631694740d1d71957a
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a802fb318f |
M7.6: Hostile encounter stub + marker-scale fix — closes M7
EncounterTrigger.FindHostileTrigger now polls each tactical-mode
tick. Edge-detected by NPC actor id: a fresh hostile entering
ENCOUNTER_TRIGGER_TILES range fires the stub exactly once — console
log with name/allegiance/template, save-flash toast "Combat HUD
lands with M8 — encounter logged: {name}". Player keeps moving;
the same hostile won't re-fire until you've left the trigger ring
and come back.
Deviation from the M7 plan §6.6: the plan proposed autosaving on
hostile detect so M8 testing would have fresh combat starts. Wired
that, then walked into a wolf and got a respawn loop — SaveTo →
CaptureBody → _streamer.FlushAll evicts every loaded chunk → NPCs
respawn with fresh actor ids on the next tactical tick → fresh id
breaks edge detection → stub re-fires → autosave again → loop. The
visible symptom was grey untiled chunks and a screen-filling red
blob (an unscaled NPC marker on the spawn frame at zoom 32 renders
at ~307 screen px radius). M8 owns combat-start autosave anyway:
at that point CombatHUDScreen captures combatant state before
FlushAll, so the loop can't form. Removed the SaveTo here; comment
in code records the reason.
Marker scale stamped at construction. NPCs spawn inside _Process
(EnsureLoadedAround → OnChunkLoaded → MountNpcMarker) *after* the
per-frame counter-scale loop has already iterated _npcMarkers, so
a new marker would render at Scale=(1,1) for one frame. New
CounterScaleVec() helper reads the current camera zoom and is
stamped into Scale at marker construction (both PlayerMarker on
spawn/restore and every NpcMarker). The player path also reorders
SetInitialZoom to run BEFORE the player marker constructs so the
counter-scale picks the post-zoom value rather than the fit-zoom
default.
That closes the M7 milestone — title → wizard → worldgen → play →
pause / save / load / dialogue / hostile-stub all wired, only the
M7.4 polish items left were the bugs surfaced in play-testing.
Next: §11.1 cross-build save-bytes parity test (user-driven), then
M8 (combat HUD, inventory, level-up, shop, quest log, dungeon).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
289c918d6c |
M7.5: Interact prompt + dialogue overlay
InteractionScreen — CanvasLayer overlay (Layer=50, WhenPaused) that
pauses the tree on open and runs a Core DialogueRunner against
playScreen-owned aggregates (Reputation, Flags, ContentResolver,
QuestEngine). Codex-themed centered panel ~760 px with header
(NPC name, role line "Innkeeper of Millhaven" via
LastIndexOf('.')-split TitleCase, bias profile · disposition tag
from EffectiveDisposition.Breakdown, optional Scent Literacy line
for scent_broker / scent_literacy / master_nose feature holders),
history scrollback (last C.DIALOGUE_HISTORY_LINES, per-speaker
text colour for NPC / PC / Narration), numbered option list
(skill checks prefixed [SKILL DC N]), footer hint. Input: 1-9
top-row + numpad, Enter to dismiss when over, Esc / F to leave.
Godot's Key enum is long-backed for unicode + modifier bits, so
the arithmetic cast through int is explicit.
Effect routing on each ChooseOption: drains
runner.Context.StartQuestRequests into _playScreen.QuestEngine.Start
with a freshly-rebound QuestContext (PlayerCharacter pinned each
call) — quest journal UI is M8 but the engine fires immediately.
runner.Context.ShopRequested raises a "Shop ships with M8" toast
via PlayScreen.Toast and clears the flag so re-entry doesn't loop.
Stub NPCs (no dialogue_id, missing tree, or null content) get the
"(They have nothing to say yet.)" panel + Goodbye button — same
fallback the MonoGame source ships.
PlayScreen interact tick. Tactical-mode _Process now polls
EncounterTrigger.FindInteractCandidate(_actors) and caches the
result in _interactCandidate (cleared at world-map zoom). HUD
appends "[F] Talk to {DisplayName}" when non-null. Edge-detected
F press → AddChild(new InteractionScreen(npc, this)), candidate
cleared immediately so a held F can't stack overlays. M8 will
wire real LOS into the FindInteractCandidate losBlocked callback;
M7.5 ships with the default AlwaysClear.
Added internal accessors PlayScreen now surfaces so the overlay
doesn't poke private state: Reputation, Flags, QuestEngine, World,
WorldSeed, ClockSeconds, PlayerPosition, Content,
BuildQuestContextForDialogue(), Toast(text). All scoped internal —
not part of any public API.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
116193c1e3 |
M7.4: Pause menu + save-from-pause, slot rows show wall-clock time
PauseMenuScreen — CanvasLayer overlay on PlayScreen, Layer=50 with ProcessMode=WhenPaused so it stays responsive while the tree is paused. _Ready sets GetTree().Paused=true; Resume/Close/QuitToTitle unpause first. Two sub-states share one VBoxContainer-and-status-label panel: main menu (Resume / ★ Level Up / Save Game / Quicksave / Quit to Title) and slot picker. Save Game flips to the picker, click a slot to write, back returns to main. Esc backs out of picker to main on first press, closes the overlay on a second. CodexTheme applied at the panel root since overlays mount outside PlayScreen's Control tree and theme cascade doesn't cross CanvasLayer boundaries. Level-up button surfaces only when LevelUpFlow.CanLevelUp(pc) returns true (matches MonoGame), and is rendered disabled with a "ships with M8" tooltip — fresh L1 characters won't see it in M7 play-tests. Quit to Title autosaves first (matches MonoGame). A failed autosave doesn't block the quit; better to let the user leave than trap them. Save-from-pause writes to an internal status label inside the panel rather than PlayScreen's save-flash toast — the toast lives on the paused tree branch and would freeze mid-fade. PlayScreen Esc now AddChild(new PauseMenuScreen(this)) instead of BackToTitle. Added paused-guard + Echo filter in _Input. New public PlayerCharacter() accessor lets the pause panel call CanLevelUp. HUD hint updated to "F5 quicksaves · Esc opens pause". SaveSlotFormat — shared helper between SaveLoadScreen (load picker) and PauseMenuScreen (save picker) so both surfaces render rows with the same prefix + in-game time + wall-clock time. Parses SavedAtUtc, converts to local time, renders relative (today/yesterday), short (MMM d, HH:mm within year), or full (yyyy-MM-dd HH:mm), with "<unknown>" fallback for empty headers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
8e2efdd878 |
M7.3: Save/load round-trip — F5 quicksave, Continue → slot picker
SavePaths ported verbatim from Theriapolis.Game/Platform/. Same OS directories as MonoGame (%LOCALAPPDATA%\Theriapolis\Saves on Windows, ~/Library/Application Support/Theriapolis/Saves on macOS, $XDG_DATA_HOME/Theriapolis/saves on Linux) so saves round-trip across the two builds without migration. PlayScreen save layer. Wired PlayerReputation + Flags + QuestEngine + QuestContext + _killedByChunk + _pendingEncounterRestore in _Ready, even though M7.3 doesn't actively drive any of those — they're round-trip-required, so a save written by the MonoGame build with non-empty rep/flags/quest state loads here and re-saves without data loss. SaveTo/BuildHeader/CaptureBody/ApplyRestoredBody are field-for-field ports of the MonoGame methods (Phase 5 M3 + M5, Phase 6 M2 + M4); CaptureBody flushes the streamer first so chunk deltas land in the store before serialisation. HandleChunkLoaded now honours _killedByChunk so a killed spawn stays dead across chunk reload + save round-trip. F5 quicksaves to the autosave slot. Save-flash toast (bottom-center Label, fade-out via Modulate.A) confirms each write. _Ready branches on session.PendingRestore: when set (load path), calls ApplyRestoredBody and skips the new-game spawn; otherwise spawns at the Tier-1 anchor with the M6 character. The mid-combat encounter snapshot is captured on save but the push to CombatHUDScreen is the M8 stub (logs a console diagnostic). SaveLoadScreen — load-only slot picker. Header-only deserialise per row (SaveCodec.DeserializeHeaderOnly reads just the JSON prefix, body untouched), so opening the picker is cheap even with many large saves. Slot label matches MonoGame's SlotLabel() format exactly. Incompatible / unreadable rows render disabled with the reason inline. TitleScreen Continue. Enable-gate replaced — was "user://character.json exists" (M7.1 placeholder), now scans SavesDir for *.trps + checks SaveCodec.IsCompatible. OnContinue swaps to SaveLoadScreen instead of the print stub. Manual play-test loop confirmed: F5 in run #1, quit, relaunch, Continue → Autosave row → progress bar → PlayScreen with character restored at saved tile. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
bf0041605f |
M7.1-7.2: Play-loop hand-off — Wizard → WorldGen → PlayScreen
Lands the M7 plan's first two sub-milestones on port/godot. theriapolis-rpg-implementation-plan-godot-port-m7.md is the design doc (six screens collapse to four scenes + a camera mode, with per-screen behavioural contracts and a six-step sub-milestone breakdown). M7.1 — WorldGenProgressScreen + GameSession autoload + wizard hand-off rewrite. GameSession holds the cross-scene state that outlives any single screen: seed, post-worldgen Ctx, pending character (from the M6 wizard) and pending save snapshot (for M7.3's load path). Wizard forwards StepReview.CharacterConfirmed upward, and TitleScreen swaps to the progress screen instead of just printing the build summary. The progress screen runs the 23-stage pipeline on a background thread, drives a ProgressBar from ctx.ProgressCallback, and writes the full exception trace to user://worldgen_error.log on failure. Escape cancels at the next stage boundary and returns to title. M7.2 — PlayScreen with a walking character. Extracted WorldRenderNode from the M2+M4 WorldView demo so PlayScreen and WorldView mount the same renderer (biome image + polylines + bridges + settlement dots + tactical chunk lifecycle + PanZoomCamera + per-frame layer visibility + line-width counter-scaling). PlayScreen owns the streamer (M7.3 save needs it), composes ContentResolver + ActorManager + WorldClock + AnchorRegistry + PlayerController, spawns the player at the Tier-1 anchor, and wires resident + non-resident NPC spawning from chunk-load events with allegiance-tinted markers. PlayerController ported engine-agnostic to Theriapolis.Godot/Input/. Takes pre-resolved dx/dy/dt/isTactical/isFocused instead of poking MonoGame InputManager + Camera2D, so the arithmetic that advances PlayerActor.Position and WorldClock.InGameSeconds is bit-identical to the MonoGame version — saves round-trip cleanly. Click-to-travel in world-map mode (camera zoom < TacticalRenderZoomMin), WASD step in tactical mode with axis- separated motion + encumbrance + sub-second clock carry. HUD overlay top-left shows HP/AC/seed/tile/biome/view-mode/time. Esc returns to title (M7.4 replaces this with a pause menu). Namespace gotcha: Theriapolis.GodotHost.Input shadows the engine's Godot.Input static class for any file under the GodotHost namespace tree. Files needing keyboard polls (WorldView, PlayScreen) fully qualify as Godot.Input.IsKeyPressed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
2db442be7e |
M6.20: TitleScreen entry point with parchment-themed button stack
Default boot now lands on a centered title screen instead of the M0 hello-world label. Vertical button stack — New Character, Continue, Quit — over the codex parchment field, with the H1 codex title and a PORT / GODOT · M6.20 version chip in the bottom-right. Continue is disabled until user://character.json exists; clicking it prints a placeholder until the M7 play loop can pick the persisted state up. New Character swaps the title for the wizard scene under the Main parent. The wizard's existing "← Title" back-button on Step 0 now actually does something — TitleScreen wires its BackToTitle signal to a parent-side swap that reinstates the title screen when the player backs out. --wizard command-line flag still skips straight to the wizard for fast-path development. Layout uses SetAnchorsAndOffsetsPreset (LayoutPreset.FullRect) on the backing panel and CenterContainer — manual AnchorRight = 1 doesn't fill in code because Godot's anchor setters preserve visual position by adjusting offsets, leaving the control at 0×0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |