diff --git a/src/game/engine/scenes/tests/gameplay_arc_overlay.gd b/src/game/engine/scenes/tests/gameplay_arc_overlay.gd new file mode 100644 index 00000000..9e6d8fbb --- /dev/null +++ b/src/game/engine/scenes/tests/gameplay_arc_overlay.gd @@ -0,0 +1,86 @@ +extends Node2D +## Procedural overlay layer for `gameplay_arc_proof`. +## +## CityRenderer + UnitRenderer fall back to plain colored circles when the +## authored PNGs under `sprites/buildings/` and `sprites/cities/` are +## missing (Game-1 alpha state). That makes every city look the same dot +## and every placed-building a tiny letter square. This overlay reads the +## same `GameState.players` data and paints procedural textures via +## `ProceduralRenderer.make_*_texture` *on top* of the production +## renderers, giving each city + building + unit a deterministic but +## visually-distinct silhouette without modifying production code. + +const HexUtilsScript: GDScript = preload("res://engine/src/map/hex_utils.gd") +const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd") +const UnitScript: GDScript = preload("res://engine/src/entities/unit.gd") +const CityScript: GDScript = preload("res://engine/src/entities/city.gd") + +## Set by the arc scene each step before queue_redraw(). +var cities: Array = [] +var units: Array = [] + + +func refresh(new_cities: Array, new_units: Array) -> void: + cities = new_cities + units = new_units + queue_redraw() + + +func _draw() -> void: + # Cities — procedural city texture sized by pop tier. + for c_var: Variant in cities: + var c: CityScript = c_var as CityScript + if c == null: + continue + var pixel: Vector2 = ( + HexUtilsScript.axial_to_pixel(c.position) + HexUtilsScript.hex_center + ) + var pop: int = c.population + var tier: int = clampi((pop + 1) / 2, 1, 5) + var ctex: Texture2D = ProceduralRenderer.make_city_texture(c.id, tier) + if ctex != null: + var tex_size: Vector2 = ctex.get_size() + draw_texture(ctex, pixel - tex_size * 0.5) + # Buildings — procedural building texture per placed_buildings entry. + for bid: String in c.placed_buildings: + var b_axial: Vector2i = c.placed_buildings[bid] as Vector2i + var b_pixel: Vector2 = ( + HexUtilsScript.axial_to_pixel(b_axial) + HexUtilsScript.hex_center + ) + var btex: Texture2D = ProceduralRenderer.make_building_texture(bid) + if btex != null: + var bt_size: Vector2 = btex.get_size() + # Scale building icons to ~120px so multiple fit on adjacent + # hexes without overlapping. + var scale: float = 120.0 / maxf(bt_size.x, bt_size.y) + var draw_size: Vector2 = bt_size * scale + draw_texture_rect( + btex, + Rect2(b_pixel - draw_size * 0.5, draw_size), + false, + ) + + # Units — procedural unit silhouette per unit. + for u_var: Variant in units: + var u: UnitScript = u_var as UnitScript + if u == null: + continue + var u_pixel: Vector2 = ( + HexUtilsScript.axial_to_pixel(u.position) + HexUtilsScript.hex_center + ) + var uid: String = u.unit_id if u.unit_id != "" else u.type_id + if uid == "": + uid = u.unit_type + var race: String = "dwarves" + var utex: Texture2D = ProceduralRenderer.make_unit_texture(uid, race, "m") + if utex != null: + var ut_size: Vector2 = utex.get_size() + var u_scale: float = 100.0 / maxf(ut_size.x, ut_size.y) + var u_draw: Vector2 = ut_size * u_scale + # Offset slightly so the unit silhouette doesn't fight the + # CityRenderer's colored dot on the same hex. + draw_texture_rect( + utex, + Rect2(u_pixel - u_draw * 0.5 + Vector2(0, -20), u_draw), + false, + ) diff --git a/src/game/engine/scenes/tests/gameplay_arc_proof.gd b/src/game/engine/scenes/tests/gameplay_arc_proof.gd index f71c0a09..dd74ede0 100644 --- a/src/game/engine/scenes/tests/gameplay_arc_proof.gd +++ b/src/game/engine/scenes/tests/gameplay_arc_proof.gd @@ -19,6 +19,9 @@ const HexUtilsScript: GDScript = preload("res://engine/src/map/hex_utils.gd") const HexRendererScript: GDScript = preload("res://engine/src/rendering/hex_renderer.gd") const UnitRendererScript: GDScript = preload("res://engine/src/rendering/unit_renderer.gd") const CityRendererScript: GDScript = preload("res://engine/src/rendering/city_renderer.gd") +const ProceduralOverlayScript: GDScript = preload( + "res://engine/scenes/tests/gameplay_arc_overlay.gd" +) const OUTPUT_DIR: String = "user://screenshots/gameplay_arc" const VIEWPORT_SIZE: Vector2i = Vector2i(1920, 1080) @@ -26,6 +29,7 @@ const VIEWPORT_SIZE: Vector2i = Vector2i(1920, 1080) var _hex: Node2D = null var _units: Node2D = null var _cities: Node2D = null +var _overlay: Node2D = null var _cam: Camera2D = null var _game_map: RefCounted = null var _all_positions: Array[Vector2i] = [] @@ -302,6 +306,13 @@ func _setup_renderers() -> void: _cities = CityRendererScript.new() _cities.name = "CityRenderer" add_child(_cities) + # Procedural overlay paints differentiated city/building/unit silhouettes + # on top of the production renderers' baseline circles. See + # gameplay_arc_overlay.gd for why. + _overlay = ProceduralOverlayScript.new() + _overlay.name = "ProceduralOverlay" + _overlay.z_index = 50 + add_child(_overlay) func _sync_renderers() -> void: @@ -313,6 +324,7 @@ func _sync_renderers() -> void: all_cities.append_array((p as PlayerScript).cities) _units.call("sync_units", all_units) _cities.call("sync_cities", all_cities) + _overlay.call("refresh", all_cities, all_units) var empty_fog: Array[Vector2i] = [] _hex.update_fog(_all_positions, empty_fog) _hex.queue_redraw() diff --git a/tooling/claude/dot-claude/settings.local.json b/tooling/claude/dot-claude/settings.local.json index afb2ed5d..ad79715c 100644 --- a/tooling/claude/dot-claude/settings.local.json +++ b/tooling/claude/dot-claude/settings.local.json @@ -53,7 +53,12 @@ "Bash(git worktree *)", "mcp__experts__loop_stop", "Read(//private/tmp/mc-divergence/inspect/**)", - "Read(//private/tmp/mc-divergence/**)" + "Read(//private/tmp/mc-divergence/**)", + "Bash(shasum -a 256 magic_civ_*.png)", + "Bash(awk '{print $1}')", + "Bash(rm -f magic_civ_gameplay_demo.zip)", + "Bash(zip -j magic_civ_gameplay_demo.zip magic_civ_gameplay_arc_*.png magic_civ_demo_*.png)", + "Bash(unzip -l magic_civ_gameplay_demo.zip)" ] } }