feat(@projects): add world-map visual proof scene documentation

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-10 03:51:36 -07:00
parent bff4c1dc46
commit f783d24dc7
3 changed files with 142 additions and 2 deletions

View file

@ -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/<socket>`, `--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)

View file

@ -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

View file

@ -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")