feat(@projects/@magic-civilization): add strategic stockpile and wonder proof tests

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-10 23:59:03 -07:00
parent 9405415f7d
commit eecb0aabff
5 changed files with 344 additions and 2 deletions

View file

@ -138,9 +138,9 @@ Status legend:
| # | File | Source | Proves | Status |
|---|---|---|---|---|
| 77 | `magic_civ_demo_action_civics_buildings.png` | `proof_civics_buildings` | Civic-axis × buildings cross-grant table | ✅ |
| 78 | *civics panel 3 axes* | new proof | Authority/Labor/Economy picker | 🔴 |
| 78 | `magic_civ_demo_ui_civics.png` | `civics_panel_proof` (new) | 3 axis cards (Authority=Monarchy, Labor=Guilds, Economy=Mercantilism) + per-axis descriptions, ANARCHY banner with "3/5 turns remaining" 60%-fill bar | ✅ |
| 79 | *anarchy warning dialog* | extend | Switching civic prompt | 🔴 |
| 80 | *anarchy HUD countdown* | new proof | 5-turn anarchy timer | 🔴 |
| 80 | *anarchy HUD countdown* | extend | 5-turn anarchy timer (already shown on civics_panel — extract as standalone if needed) | 🟡 covered |
| 81 | *post-anarchy active* | extend | New civic + zeroed counter | 🔴 |
## K. Procedural visuals (6 slots)

View file

@ -0,0 +1,177 @@
extends Node2D
## Strategic stockpile HUD proof — renders the resource-bar overlay a
## player sees on the world map's top HUD: iron, wood, grain, stone,
## gold, plus their per-turn deltas and tooltip-style supply chains.
## Self-contained mock state; no GameState dependency.
const OUTPUT_DIR: String = "user://screenshots"
const VIEWPORT_SIZE: Vector2i = Vector2i(1280, 720)
const CAPTURE_DELAY: float = 0.6
const COLOR_BG: Color = Color(0.07, 0.08, 0.11)
const COLOR_PANEL: Color = Color(0.13, 0.15, 0.19)
const COLOR_PANEL_BORDER: Color = Color(0.30, 0.33, 0.42)
const COLOR_TITLE: Color = Color(0.92, 0.78, 0.32)
const COLOR_TEXT: Color = Color(0.92, 0.93, 0.96)
const COLOR_DIM: Color = Color(0.62, 0.65, 0.72)
const COLOR_POSITIVE: Color = Color(0.55, 0.85, 0.45)
const COLOR_NEGATIVE: Color = Color(0.90, 0.45, 0.35)
const RESOURCES: Array[Dictionary] = [
{
"name": "Iron Ore",
"icon_color": Color(0.66, 0.66, 0.72),
"amount": 14,
"per_turn": 2,
"source": "2× iron deposit, 1× smelter",
},
{
"name": "Wood",
"icon_color": Color(0.55, 0.40, 0.20),
"amount": 38,
"per_turn": 5,
"source": "Forest tiles ×3, lumber mill ×1",
},
{
"name": "Grain",
"icon_color": Color(0.85, 0.75, 0.32),
"amount": 22,
"per_turn": -1,
"source": "Granary surplus, 4 mouths to feed",
},
{
"name": "Stone",
"icon_color": Color(0.70, 0.70, 0.65),
"amount": 9,
"per_turn": 1,
"source": "Quarry on plains-mountain edge",
},
{
"name": "Gold",
"icon_color": Color(0.93, 0.80, 0.28),
"amount": 142,
"per_turn": 8,
"source": "Market + trade, less unit upkeep",
},
]
var _captured: bool = false
func _ready() -> void:
get_viewport().size = VIEWPORT_SIZE
DisplayServer.window_set_size(VIEWPORT_SIZE)
RenderingServer.set_default_clear_color(COLOR_BG)
DataLoader.load_theme("age-of-dwarves")
ThemeAssets.set_theme("age-of-dwarves")
queue_redraw()
await get_tree().create_timer(CAPTURE_DELAY).timeout
_capture_and_quit()
func _draw() -> void:
var font: Font = ThemeDB.fallback_font
draw_string(
font, Vector2(40, 48),
"Strategic Stockpile — Empire HUD",
HORIZONTAL_ALIGNMENT_LEFT, -1, 26, COLOR_TITLE,
)
draw_string(
font, Vector2(40, 74),
"Per-resource amount + per-turn delta + supply source",
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_DIM,
)
# ── HUD row (compact, mirrors top-bar layout) ────────────────────────
var hud_y: int = 110
var hud_h: int = 56
draw_rect(Rect2(Vector2(40, hud_y), Vector2(1200, hud_h)), COLOR_PANEL)
draw_rect(
Rect2(Vector2(40, hud_y), Vector2(1200, hud_h)),
COLOR_PANEL_BORDER, false, 2.0,
)
var cell_w: int = 1200 / RESOURCES.size()
for i: int in range(RESOURCES.size()):
var r: Dictionary = RESOURCES[i]
var x: int = 40 + i * cell_w + 16
var icon_pos: Vector2 = Vector2(x, hud_y + hud_h * 0.5)
draw_circle(icon_pos, 14, r["icon_color"] as Color)
draw_string(
font, Vector2(x + 26, hud_y + 24),
str(r["amount"]),
HORIZONTAL_ALIGNMENT_LEFT, -1, 20, COLOR_TEXT,
)
var delta: int = int(r["per_turn"])
var delta_text: String = ("+%d" % delta) if delta >= 0 else str(delta)
var delta_color: Color = COLOR_POSITIVE if delta >= 0 else COLOR_NEGATIVE
draw_string(
font, Vector2(x + 26, hud_y + 46),
delta_text + "/turn",
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, delta_color,
)
# ── Tooltip-style detail rows ────────────────────────────────────────
var detail_y: int = 210
draw_string(
font, Vector2(40, detail_y - 12),
"Supply chains (hover-tooltip detail)",
HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TITLE,
)
var row_h: int = 60
for i: int in range(RESOURCES.size()):
var r2: Dictionary = RESOURCES[i]
var ry: int = detail_y + 10 + i * row_h
draw_rect(Rect2(Vector2(40, ry), Vector2(1200, row_h - 8)), COLOR_PANEL)
draw_rect(
Rect2(Vector2(40, ry), Vector2(1200, row_h - 8)),
COLOR_PANEL_BORDER, false, 1.5,
)
draw_circle(Vector2(64, ry + (row_h - 8) * 0.5), 12, r2["icon_color"] as Color)
draw_string(
font, Vector2(92, ry + 22),
String(r2["name"]),
HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_TEXT,
)
draw_string(
font, Vector2(92, ry + 44),
String(r2["source"]),
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, COLOR_DIM,
)
# Right-aligned amount + delta.
draw_string(
font, Vector2(1020, ry + 30),
"%d (%s)" % [
int(r2["amount"]),
("+%d" % int(r2["per_turn"])) if int(r2["per_turn"]) >= 0 else str(int(r2["per_turn"])),
],
HORIZONTAL_ALIGNMENT_LEFT, -1, 18,
COLOR_POSITIVE if int(r2["per_turn"]) >= 0 else COLOR_NEGATIVE,
)
func _capture_and_quit() -> void:
if _captured:
return
_captured = true
DirAccess.make_dir_recursive_absolute(
ProjectSettings.globalize_path(OUTPUT_DIR)
)
var image: Image = get_viewport().get_texture().get_image()
if image == null:
push_error("strategic_stockpile_proof: viewport image null")
get_tree().quit(1)
return
var timestamp: String = Time.get_datetime_string_from_system().replace(
":", "-"
).replace("T", "_")
var path: String = "%s/strategic_stockpile_proof_%s.png" % [OUTPUT_DIR, timestamp]
var abs_path: String = ProjectSettings.globalize_path(path)
var err: Error = image.save_png(abs_path)
if err == OK:
print("SCREENSHOT_PATH:%s" % abs_path)
print("strategic_stockpile_proof: %dx%d saved" % [
image.get_width(), image.get_height()
])
else:
push_error("strategic_stockpile_proof: save failed: %s" % error_string(err))
get_tree().quit()

View file

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://engine/scenes/tests/strategic_stockpile_proof.gd" id="1"]
[node name="StrategicStockpileProof" type="Node2D"]
script = ExtResource("1")

View file

@ -0,0 +1,153 @@
extends Node2D
## Wonder-built banner proof — renders the centre-screen announcement a
## player sees when a world wonder completes. Mock state covers the
## 4-tier wonder family (palace / monument / shrine / archive) with
## owner attribution + first-mover bonus copy.
const OUTPUT_DIR: String = "user://screenshots"
const VIEWPORT_SIZE: Vector2i = Vector2i(1280, 720)
const CAPTURE_DELAY: float = 0.6
const COLOR_BG: Color = Color(0.04, 0.04, 0.07)
const COLOR_BANNER_BG: Color = Color(0.10, 0.09, 0.06)
const COLOR_BANNER_BORDER: Color = Color(0.92, 0.78, 0.32)
const COLOR_GLOW: Color = Color(0.92, 0.78, 0.32, 0.18)
const COLOR_TITLE: Color = Color(1.0, 0.85, 0.32)
const COLOR_TEXT: Color = Color(0.92, 0.93, 0.96)
const COLOR_DIM: Color = Color(0.65, 0.68, 0.74)
const COLOR_OWNER: Color = Color(0.95, 0.65, 0.15)
const WONDER_ID: String = "Great Anvil of Khazad-dûm"
const WONDER_TIER: int = 4
const OWNER_NAME: String = "Karak Ankor"
const FIRST_MOVER_BONUS: String = "+8 production/turn in this city, +2 happiness empire-wide"
const FLAVOR_TEXT: String = (
"From bedrock and starsteel, the Great Anvil rises. Its first strike echoes "
"across the deep, and the dwarven clans know — a new age has begun."
)
var _captured: bool = false
func _ready() -> void:
get_viewport().size = VIEWPORT_SIZE
DisplayServer.window_set_size(VIEWPORT_SIZE)
RenderingServer.set_default_clear_color(COLOR_BG)
DataLoader.load_theme("age-of-dwarves")
ThemeAssets.set_theme("age-of-dwarves")
queue_redraw()
await get_tree().create_timer(CAPTURE_DELAY).timeout
_capture_and_quit()
func _draw() -> void:
var font: Font = ThemeDB.fallback_font
# Outer faint glow rect (visual "spotlight" framing).
var banner_w: int = 880
var banner_h: int = 340
var bx: float = (VIEWPORT_SIZE.x - banner_w) * 0.5
var by: float = (VIEWPORT_SIZE.y - banner_h) * 0.5
draw_rect(
Rect2(Vector2(bx - 24, by - 24), Vector2(banner_w + 48, banner_h + 48)),
COLOR_GLOW,
)
# Inner banner.
draw_rect(Rect2(Vector2(bx, by), Vector2(banner_w, banner_h)), COLOR_BANNER_BG)
draw_rect(
Rect2(Vector2(bx, by), Vector2(banner_w, banner_h)),
COLOR_BANNER_BORDER, false, 3.0,
)
# Title: "WONDER COMPLETE" tag.
draw_string(
font, Vector2(bx + 32, by + 50),
"✦ WONDER COMPLETE ✦",
HORIZONTAL_ALIGNMENT_LEFT, -1, 22, COLOR_DIM,
)
# Wonder name large.
draw_string(
font, Vector2(bx + 32, by + 100),
WONDER_ID,
HORIZONTAL_ALIGNMENT_LEFT, -1, 32, COLOR_TITLE,
)
# Tier badge.
draw_string(
font, Vector2(bx + 32, by + 134),
"Tier %d World Wonder" % WONDER_TIER,
HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_DIM,
)
# Owner line.
draw_string(
font, Vector2(bx + 32, by + 174),
"Built by %s — first to complete" % OWNER_NAME,
HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_OWNER,
)
# Bonus copy.
draw_string(
font, Vector2(bx + 32, by + 206),
FIRST_MOVER_BONUS,
HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TEXT,
)
# Flavor text (line-wrapped manually since draw_string is single-line).
var line1: String = (
"From bedrock and starsteel, the Great Anvil rises. Its first strike echoes"
)
var line2: String = (
"across the deep, and the dwarven clans know — a new age has begun."
)
draw_string(
font, Vector2(bx + 32, by + 254),
line1,
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_DIM,
)
draw_string(
font, Vector2(bx + 32, by + 274),
line2,
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_DIM,
)
# Confirm button.
var btn_w: int = 200
var btn_h: int = 40
var btn_x: float = bx + banner_w - btn_w - 28
var btn_y: float = by + banner_h - btn_h - 24
draw_rect(
Rect2(Vector2(btn_x, btn_y), Vector2(btn_w, btn_h)),
Color(0.18, 0.16, 0.10),
)
draw_rect(
Rect2(Vector2(btn_x, btn_y), Vector2(btn_w, btn_h)),
COLOR_BANNER_BORDER, false, 1.5,
)
draw_string(
font, Vector2(btn_x + 50, btn_y + 26),
"Continue",
HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_TITLE,
)
func _capture_and_quit() -> void:
if _captured:
return
_captured = true
DirAccess.make_dir_recursive_absolute(
ProjectSettings.globalize_path(OUTPUT_DIR)
)
var image: Image = get_viewport().get_texture().get_image()
if image == null:
push_error("wonder_built_banner_proof: viewport image null")
get_tree().quit(1)
return
var timestamp: String = Time.get_datetime_string_from_system().replace(
":", "-"
).replace("T", "_")
var path: String = "%s/wonder_built_banner_proof_%s.png" % [OUTPUT_DIR, timestamp]
var abs_path: String = ProjectSettings.globalize_path(path)
var err: Error = image.save_png(abs_path)
if err == OK:
print("SCREENSHOT_PATH:%s" % abs_path)
print("wonder_built_banner_proof: %dx%d saved" % [
image.get_width(), image.get_height()
])
else:
push_error("wonder_built_banner_proof: save failed: %s" % error_string(err))
get_tree().quit()

View file

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://engine/scenes/tests/wonder_built_banner_proof.gd" id="1"]
[node name="WonderBuiltBannerProof" type="Node2D"]
script = ExtResource("1")