diff --git a/.project/objectives/p2-66-world-map-visual-proof.md b/.project/objectives/p2-66-world-map-visual-proof.md new file mode 100644 index 00000000..ed89f4f5 --- /dev/null +++ b/.project/objectives/p2-66-world-map-visual-proof.md @@ -0,0 +1,133 @@ +--- +id: p2-66 +title: "World-map visual proof scene that actually renders" +priority: p2 +status: stub +scope: game1 +category: tooling +owner: godot-renderer +created: 2026-05-10 +updated_at: 2026-05-10 +blocked_by: [] +follow_ups: [] +--- + +## Context + +`src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.tscn` is the +canonical "boot a real game session and screenshot the world map" proof +scene used by the phase-gate protocol. Today it produces a fully-black +1920×1080 frame even after the prerequisite fixes below, so the +proof-screenshot rail (`tools/screenshot.sh` / direct flatpak launch) +cannot validate the gameplay surface against `HEX_GEOMETRY.md` and the +worldmap-rendering design docs. + +Two prerequisite fixes that landed during the 2026-05-10 demo run while +diagnosing this: + +- `tools/screenshot.sh` — added `--socket=wayland`, + `--filesystem=xdg-run/`, `--env=WAYLAND_DISPLAY`, + `--display-driver wayland --rendering-driver opengl3`. flatpak + sandboxes outer env by default, so the previous version could never + reach a working Wayland display from a fresh ssh session. Without + this nothing in the proof-scene capture rail worked. +- `iter_7q_worldmap_visual_proof.gd` — added + `ThemeAssets.set_theme("age-of-dwarves")`. `main.gd` calls this on + app boot but proof scenes that bypass `main.gd` did not. +- `theme_assets.gd::load_sprite()` — added SVG fallback. Renderers + request `.png` paths but `tools/gen-fallback-sprites.py` emits SVG. + The mismatch was silently breaking sprite loading for any proof + scene that bypasses the editor-imported asset path. + +After all three fixes, sprite loads succeed (no `FAILED to load` +warnings except the one Game-3-only `tower_of_wizardry` icon) but +`get_viewport().get_texture().get_image()` still returns a black image. + +## Root cause (provisional) + +The active warning during instantiation is: + +> Can't change the size of a `SubViewport` with a `SubViewportContainer` +> parent that has `stretch` enabled. Set `SubViewportContainer.stretch` +> to `false` to allow changing the size manually. + +`world_map.tscn` mounts a `ViewportWindowManager` (`Control`) child +that creates SubViewports (background / split / floating tiers — see +`src/ui/viewport_window_manager.gd`). When `world_map.tscn` is the +**main scene** the Control fills the window naturally. When iter_7q +calls `add_child(WorldMapScene.instantiate())` from a `Node2D` parent, +the Control has no stretched parent, the SubViewports cannot resize, +and the composited output is empty. + +## Acceptance + +- ❌ `iter_7q_worldmap_visual_proof.tscn` produces a non-black image + with hex sprites visible at seed 42, duel size, 2 players, default + start position centered. +- ❌ Image is reproducible from a fresh ssh session via the canonical + command in this objective's "Reproducer" section. +- ❌ Phase-gate protocol can use the resulting frame as the proof + screenshot for any HEX_GEOMETRY / worldmap-rendering closure. + +## Three fix paths (pick one) + +1. **Make iter_7q a proper top-level `Control` with stretch.** + Restructure the proof scene so its root is a `Control` filling the + window; instantiate `world_map.tscn` as a child of that. Lowest + risk; does not touch world-map architecture. Likely 30 min. + +2. **Capture the SubViewport texture directly.** + In `_capture_and_quit`, walk to the background `SubViewport` (via + `_viewport_manager.get_background_viewport()` or similar) and call + `.get_texture().get_image()` on it instead of the main viewport. + Useful regardless of whether world_map is hosted as main scene or + child. Surgical; doesn't change rendering behavior. + +3. **Refactor world_map to render to the main viewport directly.** + Drop the SubViewport tiering for the bench/headless path. Largest + blast radius; touches production rendering and hud composition. + Avoid unless 1 + 2 both fail. + +## Reproducer (current failing path) + +``` +ssh apricot 'XDG_RUNTIME_DIR=/run/user/$(id -u) timeout 90 \ + flatpak run --user --socket=wayland --filesystem=xdg-run/wayland-proof \ + --unset-env=DISPLAY --env=WAYLAND_DISPLAY=wayland-proof \ + org.godotengine.Godot \ + --path ~/Code/project-buildspace/magic-civilization/src/game \ + --display-driver wayland --rendering-driver opengl3 \ + --rendering-method gl_compatibility --fixed-fps 10 \ + res://engine/scenes/tests/iter_7q_worldmap_visual_proof.tscn' +``` + +Captures `iter_7q_worldmap_visual_*.png` in flatpak userdata +`screenshots/`. Today the file is a fully-black 1920×1080 PNG. + +## Source-of-truth rails + +- **Rust crate**: none — pure presentation. +- **JSON path**: depends on `ThemeAssets.set_theme()` reading + `public/games/age-of-dwarves/data/`; sprite fallback writes + `public/games/age-of-dwarves/assets/sprites/{terrain,units}/*.svg`. +- **GDScript**: `iter_7q_worldmap_visual_proof.gd` and + `viewport_window_manager.gd` are the two files in scope. + +## Out of scope + +- Generating PNG sprite assets (Game-1 ships with SVG fallback per + `theme_assets.gd` change in this same session). +- Production sprite-pack delivery — separate Game-1 art ticket. +- Fixing `capture_screenshot.gd`'s `world_map` handler that bounces + back to the main menu — different bug, different scene path. + +## References + +- `src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.gd` +- `src/game/engine/scenes/world_map/world_map.tscn` +- `src/game/engine/scenes/world_map/world_map.gd:271` (`_start_game`) +- `src/game/engine/src/ui/viewport_window_manager.gd` +- `src/game/engine/src/autoloads/theme_assets.gd:50` (loader paths) +- `tools/gen-fallback-sprites.py` (SVG output pipeline) +- `tools/screenshot.sh` (flatpak Wayland fix this session) +- `.claude/instructions/phase-gate-protocol.md` (why this matters) diff --git a/src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.gd b/src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.gd index a702ebc6..e80111e5 100644 --- a/src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.gd +++ b/src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.gd @@ -1,6 +1,11 @@ -extends Node2D +extends Control ## Iter 7q — Real world_map visual Phase Gate. ## +## Root is `Control` (not `Node2D`) so the world_map SubViewportContainer +## children can stretch to the window — `Node2D` parents leave SubViewports +## unsized and the composited frame renders fully black under headless +## llvmpipe (see p2-66 "World-map visual proof scene that actually renders"). +## ## Programmatically runs the full New Game flow: ## 1. Load theme + world ## 2. Initialize game state diff --git a/src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.tscn b/src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.tscn index 60ad04ca..aa9b7298 100644 --- a/src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.tscn +++ b/src/game/engine/scenes/tests/iter_7q_worldmap_visual_proof.tscn @@ -2,5 +2,7 @@ [ext_resource type="Script" path="res://engine/scenes/tests/iter_7q_worldmap_visual_proof.gd" id="1"] -[node name="Iter7qVisualProof" type="Node2D"] +[node name="Iter7qVisualProof" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 script = ExtResource("1")