test(scenes): ✅ Add test scene (improvement_proof.tscn) with validation scripts (improvement_proof.gd) and UID file for improvement-proof logic testing
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
3297d82f88
commit
9cb59bea5d
3 changed files with 440 additions and 0 deletions
433
src/game/engine/scenes/tests/improvement_proof.gd
Normal file
433
src/game/engine/scenes/tests/improvement_proof.gd
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
extends Node2D
|
||||
## Phase 10 Improvement Proof Scene.
|
||||
## Proves: Engineer can build Farm on grassland, improvement completes after
|
||||
## build_turns, tile yields update with +1 food from Farm improvement.
|
||||
## Self-capturing: renders two panels, saves screenshot, and quits.
|
||||
|
||||
const MapGeneratorScript: GDScript = preload("res://engine/src/generation/map_generator.gd")
|
||||
const HexUtilsScript: GDScript = preload("res://engine/src/map/hex_utils.gd")
|
||||
const CityScript: GDScript = preload("res://engine/src/entities/city.gd")
|
||||
const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd")
|
||||
const UnitScript: GDScript = preload("res://engine/src/entities/unit.gd")
|
||||
const ImprovementScript: GDScript = preload("res://engine/src/entities/improvement.gd")
|
||||
const ImprovementManagerScript: GDScript = preload(
|
||||
"res://engine/src/modules/management/improvement_manager.gd"
|
||||
)
|
||||
|
||||
const CELL_W: int = 11
|
||||
const CELL_H: int = 8
|
||||
const MARGIN: Vector2i = Vector2i(10, 40)
|
||||
const OUTPUT_DIR: String = "user://screenshots"
|
||||
|
||||
const WATER_BIOMES: Dictionary = {
|
||||
"ocean": true, "deep_ocean": true, "coast": true, "inland_sea": true, "lake": true,
|
||||
}
|
||||
|
||||
const TERRAIN_COLORS: Dictionary = {
|
||||
"ocean": Color(0.05, 0.10, 0.35),
|
||||
"deep_ocean": Color(0.02, 0.05, 0.25),
|
||||
"coast": Color(0.25, 0.45, 0.75),
|
||||
"lake": Color(0.15, 0.65, 0.75),
|
||||
"inland_sea": Color(0.10, 0.30, 0.60),
|
||||
"grassland": Color(0.30, 0.65, 0.20),
|
||||
"plains": Color(0.60, 0.70, 0.25),
|
||||
"forest": Color(0.10, 0.40, 0.10),
|
||||
"jungle": Color(0.20, 0.70, 0.15),
|
||||
"boreal_forest": Color(0.15, 0.40, 0.35),
|
||||
"enchanted_forest": Color(0.30, 0.55, 0.60),
|
||||
"desert": Color(0.85, 0.75, 0.40),
|
||||
"tundra": Color(0.70, 0.75, 0.72),
|
||||
"snow": Color(0.92, 0.94, 0.96),
|
||||
"ice": Color(0.80, 0.88, 0.95),
|
||||
"mountains": Color(0.45, 0.42, 0.40),
|
||||
"hills": Color(0.55, 0.45, 0.30),
|
||||
"swamp": Color(0.30, 0.35, 0.15),
|
||||
"volcano": Color(0.75, 0.15, 0.10),
|
||||
"land": Color(0.50, 0.50, 0.30),
|
||||
}
|
||||
|
||||
var _game_map: RefCounted = null
|
||||
var _player: RefCounted = null
|
||||
var _engineer: RefCounted = null
|
||||
var _imp_manager: RefCounted = null
|
||||
var _engineer_pos: Vector2i = Vector2i.ZERO
|
||||
var _farm_tile_biome: String = ""
|
||||
|
||||
var _yields_before: Dictionary = {}
|
||||
var _yields_after: Dictionary = {}
|
||||
var _buildable_list: Array[Dictionary] = []
|
||||
var _build_turns: int = 0
|
||||
var _improvement_applied: bool = false
|
||||
|
||||
var _captured: bool = false
|
||||
var _screenshot_name: String = "phase10_proof"
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
RenderingServer.set_default_clear_color(Color(0.06, 0.05, 0.04))
|
||||
get_viewport().size = Vector2i(1920, 1080)
|
||||
DisplayServer.window_set_size(Vector2i(1920, 1080))
|
||||
|
||||
var env_name: String = OS.get_environment("SCREENSHOT_NAME")
|
||||
if not env_name.is_empty():
|
||||
_screenshot_name = env_name
|
||||
|
||||
await get_tree().process_frame
|
||||
|
||||
_generate_map()
|
||||
_setup_game()
|
||||
_run_improvement_cycle()
|
||||
queue_redraw()
|
||||
|
||||
for _i: int in range(12):
|
||||
await get_tree().process_frame
|
||||
_capture_and_quit()
|
||||
|
||||
|
||||
func _generate_map() -> void:
|
||||
print("=== Phase 10 Improvement Proof ===")
|
||||
var settings: Dictionary = {
|
||||
"map_size": "duel",
|
||||
"map_type": "continents",
|
||||
"seed": 42,
|
||||
"num_players": 2,
|
||||
"map_wrap": "cylinder",
|
||||
}
|
||||
var gen: RefCounted = MapGeneratorScript.new()
|
||||
_game_map = gen.generate(settings)
|
||||
print("Map: %dx%d, duel, seed 42" % [_game_map.width, _game_map.height])
|
||||
|
||||
|
||||
func _setup_game() -> void:
|
||||
_player = PlayerScript.new()
|
||||
_player.index = 0
|
||||
_player.player_name = "Dwarf King"
|
||||
_player.race_id = "dwarf"
|
||||
_player.gold = 50
|
||||
|
||||
# Find a grassland tile for the Engineer (Farm requires grassland)
|
||||
var center: Vector2i = HexUtilsScript.offset_to_axial(
|
||||
Vector2i(_game_map.width / 2, _game_map.height / 2)
|
||||
)
|
||||
_engineer_pos = _find_terrain_tile(center, "grassland", 20)
|
||||
|
||||
# Set tile ownership to player so improvement is valid
|
||||
var tile: Resource = _game_map.get_tile(_engineer_pos) as Resource
|
||||
if tile != null:
|
||||
tile.owner = 0
|
||||
_farm_tile_biome = tile.biome_id
|
||||
|
||||
# Create Engineer unit
|
||||
_engineer = UnitScript.new()
|
||||
_engineer.id = "eng_0_1"
|
||||
_engineer.type_id = "dwarf_engineer"
|
||||
_engineer.owner = 0
|
||||
_engineer.name = "Engineer"
|
||||
_engineer.position = _engineer_pos
|
||||
_engineer.can_build_improvements = true
|
||||
_engineer.movement_remaining = 2
|
||||
_engineer.max_hp = 10
|
||||
_engineer.hp = 10
|
||||
_engineer.unit_type = "civilian"
|
||||
_player.units = [_engineer]
|
||||
|
||||
# Create ImprovementManager
|
||||
_imp_manager = ImprovementManagerScript.new()
|
||||
|
||||
# Record yields before improvement
|
||||
_yields_before = _get_tile_yields(_engineer_pos)
|
||||
|
||||
# Get buildable list for display
|
||||
_buildable_list = _imp_manager.get_buildable_improvements(
|
||||
_engineer, _game_map, _player
|
||||
)
|
||||
|
||||
print("Engineer at %s on %s" % [str(_engineer_pos), _farm_tile_biome])
|
||||
print("Buildable improvements: %d" % _buildable_list.size())
|
||||
for entry: Dictionary in _buildable_list:
|
||||
print(" - %s (%d turns)" % [entry.get("name", ""), entry.get("build_turns", 0)])
|
||||
|
||||
|
||||
func _run_improvement_cycle() -> void:
|
||||
## Start building a Farm and simulate turns until completion.
|
||||
var started: bool = _imp_manager.start_improvement(_engineer, "farm", _player)
|
||||
if not started:
|
||||
push_error("Phase10Proof: Failed to start Farm improvement")
|
||||
return
|
||||
|
||||
_build_turns = ImprovementScript.get_build_time("farm")
|
||||
print("Farm started — %d turns to complete" % _build_turns)
|
||||
|
||||
# Simulate turns by decrementing turns_remaining on pending improvements
|
||||
for turn: int in range(_build_turns):
|
||||
for i: int in range(_player.pending_improvements.size()):
|
||||
var pending: Dictionary = _player.pending_improvements[i] as Dictionary
|
||||
pending["turns_remaining"] = pending.get("turns_remaining", 1) - 1
|
||||
if pending["turns_remaining"] <= 0:
|
||||
var tile_pos: Vector2i = Vector2i(
|
||||
pending.get("x", 0), pending.get("y", 0)
|
||||
)
|
||||
var imp_type: String = pending.get("type", "")
|
||||
EventBus.improvement_completed.emit(tile_pos, imp_type)
|
||||
_improvement_applied = true
|
||||
print("Turn %d: Farm completed at %s" % [turn + 1, str(tile_pos)])
|
||||
|
||||
# Remove completed
|
||||
var remaining: Array = []
|
||||
for i: int in range(_player.pending_improvements.size()):
|
||||
var pending: Dictionary = _player.pending_improvements[i] as Dictionary
|
||||
if pending.get("turns_remaining", 0) > 0:
|
||||
remaining.append(pending)
|
||||
_player.pending_improvements = remaining
|
||||
|
||||
# Record yields after improvement
|
||||
_yields_after = _get_tile_yields(_engineer_pos)
|
||||
|
||||
var food_before: int = _yields_before.get("food", 0)
|
||||
var food_after: int = _yields_after.get("food", 0)
|
||||
print("Tile yields: food %d → %d (delta +%d)" % [
|
||||
food_before, food_after, food_after - food_before
|
||||
])
|
||||
|
||||
|
||||
func _get_tile_yields(pos: Vector2i) -> Dictionary:
|
||||
var tile: Resource = _game_map.get_tile(pos) as Resource
|
||||
if tile == null:
|
||||
return {}
|
||||
return tile.get_yields()
|
||||
|
||||
|
||||
func _find_terrain_tile(near: Vector2i, biome: String, max_radius: int) -> Vector2i:
|
||||
## Find the nearest tile with the specified biome.
|
||||
var center_tile: Resource = _game_map.tiles.get(near)
|
||||
if center_tile != null and center_tile.biome_id == biome:
|
||||
return near
|
||||
for r: int in range(1, max_radius + 1):
|
||||
for pos: Vector2i in HexUtilsScript.hex_ring(near, r):
|
||||
var tile: Resource = _game_map.tiles.get(pos)
|
||||
if tile != null and tile.biome_id == biome:
|
||||
return pos
|
||||
# Fallback: any land tile
|
||||
for r: int in range(1, max_radius + 1):
|
||||
for pos: Vector2i in HexUtilsScript.hex_ring(near, r):
|
||||
var tile: Resource = _game_map.tiles.get(pos)
|
||||
if tile != null and not WATER_BIOMES.has(tile.biome_id):
|
||||
return pos
|
||||
return near
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if _game_map == null:
|
||||
return
|
||||
|
||||
var font: Font = ThemeDB.fallback_font
|
||||
var map_w: float = _game_map.width * CELL_W
|
||||
var p1_x: float = MARGIN.x
|
||||
var p2_x: float = p1_x + map_w + 20
|
||||
|
||||
# Header
|
||||
draw_string(font, Vector2(MARGIN.x, 18),
|
||||
"Phase 10 Proof — Tile Improvements: Engineer Build + Farm Completion",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, Color.WHITE)
|
||||
|
||||
# Panel labels
|
||||
draw_string(font, Vector2(p1_x, MARGIN.y - 6),
|
||||
"Panel 1: Engineer on Map + Build Menu",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color(0.8, 0.6, 0.2))
|
||||
draw_string(font, Vector2(p2_x, MARGIN.y - 6),
|
||||
"Panel 2: Farm Completed — Yields Updated",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color(0.4, 0.9, 0.4))
|
||||
|
||||
_draw_map_panel(p1_x)
|
||||
_draw_results_panel(p2_x, font)
|
||||
|
||||
# Footer
|
||||
var footer_y: float = MARGIN.y + (_game_map.height + 4) * CELL_H + 20
|
||||
var status: String = "Farm completed" if _improvement_applied else "Farm in progress"
|
||||
var food_delta: int = _yields_after.get("food", 0) - _yields_before.get("food", 0)
|
||||
draw_string(font, Vector2(MARGIN.x, footer_y),
|
||||
"%s — tile food yield %s%d" % [
|
||||
status,
|
||||
"+" if food_delta >= 0 else "",
|
||||
food_delta,
|
||||
],
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 12, Color(0.3, 0.9, 0.3))
|
||||
|
||||
|
||||
func _draw_map_panel(px: float) -> void:
|
||||
var font: Font = ThemeDB.fallback_font
|
||||
|
||||
# Draw terrain grid
|
||||
for tile_ref: Resource in _game_map.tiles.values():
|
||||
var pos: Vector2i = tile_ref.position
|
||||
var offset: Vector2i = HexUtilsScript.axial_to_offset(pos)
|
||||
var x: float = px + offset.x * CELL_W
|
||||
var y: float = MARGIN.y + offset.y * CELL_H
|
||||
if offset.x & 1:
|
||||
y += CELL_H * 0.5
|
||||
|
||||
var base: Color = TERRAIN_COLORS.get(tile_ref.biome_id, Color(0.5, 0.0, 0.5))
|
||||
draw_rect(Rect2(x, y, CELL_W - 1, CELL_H - 1), base)
|
||||
|
||||
# Engineer marker: cyan square
|
||||
var eng_off: Vector2i = HexUtilsScript.axial_to_offset(_engineer_pos)
|
||||
var ex: float = px + eng_off.x * CELL_W
|
||||
var ey: float = MARGIN.y + eng_off.y * CELL_H
|
||||
if eng_off.x & 1:
|
||||
ey += CELL_H * 0.5
|
||||
draw_rect(Rect2(ex + 1, ey + 1, CELL_W - 2, CELL_H - 2), Color(0.2, 0.9, 0.9))
|
||||
|
||||
# Label
|
||||
draw_string(font, Vector2(ex - 4, ey + CELL_H + 8),
|
||||
"Engineer", HORIZONTAL_ALIGNMENT_LEFT, -1, 8, Color(0.2, 0.9, 0.9))
|
||||
|
||||
# Farm highlight after completion
|
||||
if _improvement_applied:
|
||||
draw_rect(Rect2(ex - 1, ey - 1, CELL_W + 1, CELL_H + 1), Color(0.3, 0.9, 0.3, 0.5), false, 2.0)
|
||||
|
||||
# Build menu popup simulation (right of map)
|
||||
var menu_x: float = px + _game_map.width * CELL_W + 8
|
||||
var menu_y: float = MARGIN.y + 20
|
||||
draw_string(font, Vector2(menu_x, menu_y),
|
||||
"Build Improvement:", HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color(0.9, 0.8, 0.3))
|
||||
menu_y += 14
|
||||
|
||||
# Draw buildable list
|
||||
for entry: Dictionary in _buildable_list:
|
||||
var imp_name: String = entry.get("name", "")
|
||||
var turns: int = entry.get("build_turns", 0)
|
||||
var is_farm: bool = entry.get("id", "") == "farm"
|
||||
var row_color: Color = Color(0.3, 1.0, 0.3) if is_farm else Color(0.7, 0.7, 0.7)
|
||||
var prefix: String = "► " if is_farm else " "
|
||||
draw_string(font, Vector2(menu_x, menu_y),
|
||||
"%s%s (%d turns)" % [prefix, imp_name, turns],
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 9, row_color)
|
||||
menu_y += 12
|
||||
|
||||
# Legend
|
||||
var legend_y: float = MARGIN.y + (_game_map.height + 2) * CELL_H + 2
|
||||
draw_rect(Rect2(px, legend_y, 8, 8), Color(0.2, 0.9, 0.9))
|
||||
draw_string(font, Vector2(px + 11, legend_y + 8),
|
||||
"engineer", HORIZONTAL_ALIGNMENT_LEFT, -1, 8, Color.WHITE)
|
||||
draw_rect(Rect2(px + 70, legend_y, 8, 8), Color(0.3, 0.9, 0.3, 0.5))
|
||||
draw_string(font, Vector2(px + 81, legend_y + 8),
|
||||
"farm built", HORIZONTAL_ALIGNMENT_LEFT, -1, 8, Color.WHITE)
|
||||
|
||||
|
||||
func _draw_results_panel(px: float, font: Font) -> void:
|
||||
var y: float = MARGIN.y
|
||||
|
||||
draw_string(font, Vector2(px, y),
|
||||
"Tile: %s at %s" % [_farm_tile_biome, str(_engineer_pos)],
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, Color(0.8, 0.7, 0.5))
|
||||
y += 16
|
||||
|
||||
draw_string(font, Vector2(px, y),
|
||||
"Improvement: Farm (%d turns to build)" % _build_turns,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, Color(0.9, 0.8, 0.3))
|
||||
y += 16
|
||||
|
||||
draw_string(font, Vector2(px, y),
|
||||
"Status: %s" % ("Completed" if _improvement_applied else "In Progress"),
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 11,
|
||||
Color(0.3, 1.0, 0.3) if _improvement_applied else Color(1.0, 0.7, 0.2))
|
||||
y += 20
|
||||
|
||||
# Divider
|
||||
draw_rect(Rect2(px, y, 400, 1), Color(0.35, 0.30, 0.22))
|
||||
y += 12
|
||||
|
||||
# Yields comparison
|
||||
draw_string(font, Vector2(px, y),
|
||||
"YIELDS COMPARISON", HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color(0.75, 0.70, 0.55))
|
||||
y += 16
|
||||
|
||||
var yield_keys: Array = [
|
||||
["Food", "food", Color(0.3, 0.9, 0.3)],
|
||||
["Production", "production", Color(0.9, 0.6, 0.2)],
|
||||
["Trade", "trade", Color(0.95, 0.85, 0.15)],
|
||||
["Culture", "culture", Color(0.75, 0.35, 0.95)],
|
||||
]
|
||||
|
||||
draw_string(font, Vector2(px, y),
|
||||
" Before After Delta",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 9, Color(0.6, 0.6, 0.6))
|
||||
y += 14
|
||||
|
||||
for row: Array in yield_keys:
|
||||
var label: String = row[0] as String
|
||||
var key: String = row[1] as String
|
||||
var color: Color = row[2] as Color
|
||||
var before: int = _yields_before.get(key, 0)
|
||||
var after: int = _yields_after.get(key, 0)
|
||||
var delta: int = after - before
|
||||
var delta_str: String = "+%d" % delta if delta > 0 else str(delta)
|
||||
var delta_color: Color = Color(0.3, 1.0, 0.3) if delta > 0 else color
|
||||
draw_string(font, Vector2(px, y),
|
||||
"%-12s %4d %4d %s" % [label, before, after, delta_str],
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 10, color)
|
||||
if delta > 0:
|
||||
draw_string(font, Vector2(px + 310, y),
|
||||
delta_str, HORIZONTAL_ALIGNMENT_LEFT, -1, 10, delta_color)
|
||||
y += 14
|
||||
|
||||
y += 12
|
||||
|
||||
# Improvement data summary
|
||||
draw_string(font, Vector2(px, y),
|
||||
"Improvement data from JSON:",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 9, Color(0.6, 0.6, 0.6))
|
||||
y += 14
|
||||
var farm_yields: Dictionary = ImprovementScript.get_yield_bonus("farm")
|
||||
draw_string(font, Vector2(px, y),
|
||||
"Farm yields: food +%d, prod +%d, gold +%d" % [
|
||||
farm_yields.get("food", 0),
|
||||
farm_yields.get("production", 0),
|
||||
farm_yields.get("gold", 0),
|
||||
],
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color(0.7, 0.9, 0.7))
|
||||
y += 14
|
||||
|
||||
var farm_terrain: String = "grassland, plains, enchanted_forest"
|
||||
draw_string(font, Vector2(px, y),
|
||||
"Valid terrain: %s" % farm_terrain,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color(0.7, 0.9, 0.7))
|
||||
y += 20
|
||||
|
||||
# Tile state verification
|
||||
var tile: Resource = _game_map.get_tile(_engineer_pos) as Resource
|
||||
var tile_imp: String = tile.improvement if tile != null else "(null)"
|
||||
draw_string(font, Vector2(px, y),
|
||||
"Tile.improvement = \"%s\"" % tile_imp,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 10,
|
||||
Color(0.3, 1.0, 0.3) if tile_imp == "farm" else Color(1.0, 0.4, 0.4))
|
||||
|
||||
|
||||
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("Phase10Proof: Failed to get viewport image")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var timestamp: String = (
|
||||
Time.get_datetime_string_from_system().replace(":", "-").replace("T", "_")
|
||||
)
|
||||
var rel_path: String = "%s/%s_%s.png" % [OUTPUT_DIR, _screenshot_name, timestamp]
|
||||
var abs_path: String = ProjectSettings.globalize_path(rel_path)
|
||||
var err: Error = image.save_png(abs_path)
|
||||
|
||||
if err == OK:
|
||||
print("SCREENSHOT_PATH:%s" % abs_path)
|
||||
print("Screenshot: %dx%d saved to %s" % [
|
||||
image.get_width(), image.get_height(), abs_path
|
||||
])
|
||||
else:
|
||||
push_error("Phase10Proof: Save failed: %s" % error_string(err))
|
||||
|
||||
get_tree().quit()
|
||||
1
src/game/engine/scenes/tests/improvement_proof.gd.uid
Normal file
1
src/game/engine/scenes/tests/improvement_proof.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bp0lcyalm1ah1
|
||||
6
src/game/engine/scenes/tests/improvement_proof.tscn
Normal file
6
src/game/engine/scenes/tests/improvement_proof.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://imp10proof1"]
|
||||
|
||||
[ext_resource type="Script" path="res://engine/scenes/tests/improvement_proof.gd" id="1_script"]
|
||||
|
||||
[node name="ImprovementProof" type="Node2D"]
|
||||
script = ExtResource("1_script")
|
||||
Loading…
Add table
Reference in a new issue