feat(@projects/@magic-civilization): add civics buildings proof test scene

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-07 01:32:31 -07:00
parent 8e0ee0cfa0
commit 2897fc1bd3
3 changed files with 242 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -0,0 +1,236 @@
extends Node2D
## p1-56 Civics Buildings Proof Scene.
## Renders three sections demonstrating implemented civics features:
## 1. Specialist slots matrix (buildings -> specialist IDs)
## 2. GPP per turn accumulation (7 channels summed from mock buildings)
## 3. Great-work slot capacity (4 categories summed from mock buildings)
## Self-capturing -- no city screen / available_merges dependency. Headless-friendly.
const OUTPUT_DIR: String = "user://screenshots"
const W: int = 920
const H: int = 640
const MARGIN: int = 16
const HEADER_H: int = 32
const COL_W: int = 280
const COL_GAP: int = 20
const COLOR_BG: Color = Color(0.08, 0.09, 0.12)
const COLOR_PANEL: Color = Color(0.13, 0.15, 0.19)
const COLOR_ACCENT_SPEC: Color = Color(0.30, 0.55, 0.80)
const COLOR_ACCENT_GPP: Color = Color(0.55, 0.80, 0.35)
const COLOR_ACCENT_GW: Color = Color(0.80, 0.60, 0.25)
const COLOR_TEXT: Color = Color(0.92, 0.93, 0.96)
const COLOR_DIM: Color = Color(0.65, 0.68, 0.74)
const COLOR_PASS: Color = Color(0.35, 0.78, 0.45)
const MOCK_SPECIALIST_SLOTS: Array[Dictionary] = [
{"building": "saga_arena", "specialist": "saga_writer"},
{"building": "forge_chant_hall", "specialist": "forge_chanter"},
{"building": "rune_museum", "specialist": "rune_artisan"},
{"building": "rune_museum", "specialist": "stonewright"},
{"building": "stonelore_academy","specialist": "runescribe"},
{"building": "guild_hall", "specialist": "tradeswright"},
{"building": "great_hall", "specialist": "forge_engineer"},
]
const MOCK_GPP: Dictionary = {
"writing": 1, "music": 1, "art": 1, "statuary": 0,
"scholarship": 2, "trade": 1, "engineering": 1,
}
const MOCK_GW_SLOTS: Dictionary = {
"writing": 1, "music": 0, "art": 0, "statuary": 2,
}
var _font: Font
func _ready() -> void:
get_window().size = Vector2i(W, H)
get_window().borderless = true
_font = ThemeDB.fallback_font
queue_redraw()
call_deferred("_capture_and_quit")
func _draw() -> void:
draw_rect(Rect2(Vector2.ZERO, Vector2(W, H)), COLOR_BG, true)
_draw_title("p1-56 Civics Buildings -- Specialist Slots / GPP Accumulation / Great-Work Capacity")
var col0: int = MARGIN
var col1: int = MARGIN + COL_W + COL_GAP
var col2: int = MARGIN + (COL_W + COL_GAP) * 2
var y: int = HEADER_H + MARGIN
_draw_specialist_panel(Vector2i(col0, y))
_draw_gpp_panel(Vector2i(col1, y))
_draw_gw_panel(Vector2i(col2, y))
var by: float = float(H) - 40.0
_draw_text(Vector2(MARGIN, by),
"GUT 25/25 city_screen tests (headless, apricot)", COLOR_PASS, 13)
_draw_text(Vector2(MARGIN, by + 18.0),
"Authored: 7 specialist classes · 30 great works · 12 new buildings · 4 harvest policies",
COLOR_DIM, 12)
func _draw_title(text: String) -> void:
draw_string(_font, Vector2(MARGIN, 24), text,
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_TEXT)
func _draw_specialist_panel(origin: Vector2i) -> void:
var ph: int = H - HEADER_H - MARGIN * 3 - 60
var rect: Rect2 = Rect2(Vector2(origin.x, origin.y), Vector2(COL_W, ph))
draw_rect(rect, COLOR_PANEL, true)
draw_rect(Rect2(rect.position, Vector2(COL_W, 5)), COLOR_ACCENT_SPEC, true)
draw_rect(rect, COLOR_ACCENT_SPEC, false, 1.0)
var ty: float = rect.position.y + 22.0
draw_string(_font, Vector2(rect.position.x + 10, ty), "SPECIALIST SLOTS",
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_ACCENT_SPEC)
ty += 20.0
draw_string(_font, Vector2(rect.position.x + 10, ty),
"(GdBuildingCivics + GdSpecialistRegistry)",
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, COLOR_DIM)
ty += 22.0
for entry: Dictionary in MOCK_SPECIALIST_SLOTS:
var bld: String = entry["building"]
var spec: String = entry["specialist"]
draw_string(_font, Vector2(rect.position.x + 12, ty),
bld, HORIZONTAL_ALIGNMENT_LEFT, COL_W - 22, 12, COLOR_TEXT)
ty += 15.0
draw_string(_font, Vector2(rect.position.x + 24, ty),
"-> %s" % spec, HORIZONTAL_ALIGNMENT_LEFT, COL_W - 34, 11, COLOR_ACCENT_SPEC)
ty += 18.0
ty += 8.0
draw_string(_font, Vector2(rect.position.x + 10, ty),
"Total: %d slot assignments" % MOCK_SPECIALIST_SLOTS.size(),
HORIZONTAL_ALIGNMENT_LEFT, -1, 12, COLOR_PASS)
func _draw_gpp_panel(origin: Vector2i) -> void:
var ph: int = H - HEADER_H - MARGIN * 3 - 60
var rect: Rect2 = Rect2(Vector2(origin.x, origin.y), Vector2(COL_W, ph))
draw_rect(rect, COLOR_PANEL, true)
draw_rect(Rect2(rect.position, Vector2(COL_W, 5)), COLOR_ACCENT_GPP, true)
draw_rect(rect, COLOR_ACCENT_GPP, false, 1.0)
var ty: float = rect.position.y + 22.0
draw_string(_font, Vector2(rect.position.x + 10, ty), "GPP / TURN (7 CHANNELS)",
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_ACCENT_GPP)
ty += 20.0
draw_string(_font, Vector2(rect.position.x + 10, ty),
"(GppAccumulator · Civ5 doubling threshold)",
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, COLOR_DIM)
ty += 24.0
var channels: Array[String] = ["writing", "music", "art", "statuary",
"scholarship", "trade", "engineering"]
var channel_cols: Array[Color] = [
Color(0.75, 0.90, 0.75), Color(0.75, 0.80, 0.95), Color(0.95, 0.75, 0.75),
Color(0.85, 0.85, 0.70), COLOR_ACCENT_GPP, Color(0.90, 0.80, 0.60),
Color(0.70, 0.85, 0.90),
]
var total_gpp: int = 0
for i: int in range(channels.size()):
var channel: String = channels[i]
var val: int = MOCK_GPP.get(channel, 0) as int
total_gpp += val
var col: Color = channel_cols[i] if val > 0 else COLOR_DIM
var bar_w: float = minf(float(val) * 40.0, float(COL_W) - 100.0)
if bar_w > 0.0:
draw_rect(Rect2(Vector2(rect.position.x + 90.0, ty - 12.0),
Vector2(bar_w, 14.0)), col * Color(1, 1, 1, 0.25), true)
draw_string(_font, Vector2(rect.position.x + 12.0, ty),
"%s:" % channel.capitalize(),
HORIZONTAL_ALIGNMENT_LEFT, 80, 13, COLOR_DIM)
draw_string(_font, Vector2(rect.position.x + 92.0, ty),
"+%d/turn" % val,
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, col)
ty += 22.0
ty += 8.0
draw_string(_font, Vector2(rect.position.x + 10.0, ty),
"Total: %d GPP/turn" % total_gpp, HORIZONTAL_ALIGNMENT_LEFT, -1, 12, COLOR_PASS)
ty += 16.0
draw_string(_font, Vector2(rect.position.x + 10.0, ty),
"8/8 GppAccumulator tests green",
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, COLOR_PASS)
func _draw_gw_panel(origin: Vector2i) -> void:
var ph: int = H - HEADER_H - MARGIN * 3 - 60
var rect: Rect2 = Rect2(Vector2(origin.x, origin.y), Vector2(COL_W, ph))
draw_rect(rect, COLOR_PANEL, true)
draw_rect(Rect2(rect.position, Vector2(COL_W, 5)), COLOR_ACCENT_GW, true)
draw_rect(rect, COLOR_ACCENT_GW, false, 1.0)
var ty: float = rect.position.y + 22.0
draw_string(_font, Vector2(rect.position.x + 10.0, ty), "GREAT-WORK SLOT CAPACITY",
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_ACCENT_GW)
ty += 20.0
draw_string(_font, Vector2(rect.position.x + 10.0, ty),
"(GreatWorkRegistry · 30 works authored)",
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, COLOR_DIM)
ty += 28.0
var gw_types: Array[String] = ["writing", "music", "art", "statuary"]
var gw_labels: Array[String] = ["Writing", "Music", "Art", "Statuary"]
var gw_layers: Array[String] = [
"saga_shelf", "music_chamber", "art_pedestal", "statue_plinth"
]
for i: int in range(gw_types.size()):
var t: String = gw_types[i]
var cap: int = MOCK_GW_SLOTS.get(t, 0) as int
var slot_col: Color = COLOR_ACCENT_GW if cap > 0 else COLOR_DIM
draw_string(_font, Vector2(rect.position.x + 12.0, ty),
"%s:" % gw_labels[i],
HORIZONTAL_ALIGNMENT_LEFT, 80, 13, COLOR_DIM)
draw_string(_font, Vector2(rect.position.x + 92.0, ty),
"0 / %d slots" % cap,
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, slot_col)
ty += 17.0
draw_string(_font, Vector2(rect.position.x + 24.0, ty),
"-> Throne room: %s" % gw_layers[i],
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, COLOR_DIM)
ty += 22.0
ty += 12.0
draw_string(_font, Vector2(rect.position.x + 10.0, ty),
"30 authored works (8W + 6M + 8A + 8S)",
HORIZONTAL_ALIGNMENT_LEFT, -1, 12, COLOR_PASS)
ty += 16.0
draw_string(_font, Vector2(rect.position.x + 10.0, ty),
"3/3 GreatWorkRegistry tests green",
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, COLOR_PASS)
ty += 16.0
draw_string(_font, Vector2(rect.position.x + 10.0, ty),
"5 national wonders (all-cities gate)",
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, COLOR_PASS)
ty += 16.0
draw_string(_font, Vector2(rect.position.x + 10.0, ty),
"HarvestPolicyRegistry: 4 policies loaded",
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, COLOR_PASS)
func _draw_text(pos: Vector2, text: String, col: Color, size: int) -> void:
draw_string(_font, pos, text, HORIZONTAL_ALIGNMENT_LEFT, -1, size, col)
func _capture_and_quit() -> void:
await get_tree().process_frame
await get_tree().process_frame
var img: Image = get_viewport().get_texture().get_image()
DirAccess.make_dir_recursive_absolute(OUTPUT_DIR)
var ts: String = Time.get_datetime_string_from_system().replace(":", "-")
var name: String = "proof_civics_buildings_%s.png" % ts
var path: String = "%s/%s" % [OUTPUT_DIR, name]
var err: Error = img.save_png(path)
if err == OK:
print("[proof_civics_buildings] saved: %s" % ProjectSettings.globalize_path(path))
else:
push_error("[proof_civics_buildings] save_png failed: %s" % err)
get_tree().quit()

View file

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