diff --git a/.project/SCREENSHOT_REGISTRY.md b/.project/SCREENSHOT_REGISTRY.md index aac0f9b4..6fa6bd4d 100644 --- a/.project/SCREENSHOT_REGISTRY.md +++ b/.project/SCREENSHOT_REGISTRY.md @@ -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) diff --git a/src/game/engine/scenes/tests/strategic_stockpile_proof.gd b/src/game/engine/scenes/tests/strategic_stockpile_proof.gd new file mode 100644 index 00000000..9ac1e772 --- /dev/null +++ b/src/game/engine/scenes/tests/strategic_stockpile_proof.gd @@ -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() diff --git a/src/game/engine/scenes/tests/strategic_stockpile_proof.tscn b/src/game/engine/scenes/tests/strategic_stockpile_proof.tscn new file mode 100644 index 00000000..18cec997 --- /dev/null +++ b/src/game/engine/scenes/tests/strategic_stockpile_proof.tscn @@ -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") diff --git a/src/game/engine/scenes/tests/wonder_built_banner_proof.gd b/src/game/engine/scenes/tests/wonder_built_banner_proof.gd new file mode 100644 index 00000000..6f00991a --- /dev/null +++ b/src/game/engine/scenes/tests/wonder_built_banner_proof.gd @@ -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() diff --git a/src/game/engine/scenes/tests/wonder_built_banner_proof.tscn b/src/game/engine/scenes/tests/wonder_built_banner_proof.tscn new file mode 100644 index 00000000..c5f493ec --- /dev/null +++ b/src/game/engine/scenes/tests/wonder_built_banner_proof.tscn @@ -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")