test(scenes): Update test cases to verify mixed-yield bonuses and biome penalties in auto-play scoring

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-04-15 18:28:24 -07:00
parent 8c1456b24c
commit ff591b1fec

View file

@ -682,7 +682,7 @@ func _pick_research(player: RefCounted) -> void:
func _score_site(pos: Vector2i, game_map: RefCounted) -> float:
## Score a hex as a city site. Food*2 + production*1.5 + resources.
## Score a hex as a city site. Food*2 + production*1.5 + resources + mixed-yield bonus.
var tile: Resource = game_map.get_tile(pos)
if tile == null:
return 0.0
@ -691,7 +691,14 @@ func _score_site(pos: Vector2i, game_map: RefCounted) -> float:
return 0.0
var score: float = 0.0
var y: Dictionary = tile.get_yields(-1)
score += float(y.get("food", 0)) * 2.0 + float(y.get("production", 0)) * 1.5
var center_food: int = int(y.get("food", 0))
var center_prod: int = int(y.get("production", 0))
score += float(center_food) * 2.0 + float(center_prod) * 1.5
# Heavy penalty for food-starved center biomes (mountain/tundra/desert).
if tile.biome_id in ["mountains", "tundra", "desert"]:
score -= 5.0
var neighbor_food: int = 0
var neighbor_prod: int = 0
for n: Vector2i in HexUtilsScript.get_neighbors(pos):
var norm: Vector2i = HexUtilsScript.normalize_position(n, game_map.width, game_map.height, game_map.wrap_mode)
var nt: Resource = game_map.get_tile(norm)
@ -700,10 +707,19 @@ func _score_site(pos: Vector2i, game_map: RefCounted) -> float:
if nt.biome_id in water:
score += 0.5
continue
var ny: Dictionary = nt.get_yields(-1)
score += float(ny.get("food", 0)) * 0.5 + float(ny.get("production", 0)) * 0.3
if nt.resource_id != "":
if nt.biome_id == "river":
score += 2.0
var ny: Dictionary = nt.get_yields(-1)
var nf: int = int(ny.get("food", 0))
var np: int = int(ny.get("production", 0))
neighbor_food += nf
neighbor_prod += np
score += float(nf) * 0.5 + float(np) * 0.3
if nt.resource_id != "":
score += 3.0
# Mixed-yield reward: at least one food + one prod in neighbors.
if neighbor_food >= 1 and neighbor_prod >= 1:
score += 2.0
return score
@ -737,15 +753,12 @@ func _has_settler(player: RefCounted) -> bool:
return false
func _try_found_city(player: RefCounted, _game_map: RefCounted) -> void:
func _try_found_city(player: RefCounted, game_map: RefCounted) -> void:
for u: Variant in player.units:
if u.get("can_found_city") == true and u.is_alive():
if _world_map != null and _world_map.has_method("_select_unit"):
_world_map._select_unit(u)
if _world_map != null and _world_map.has_method("_on_found_city_pressed"):
_world_map._on_found_city_pressed()
_decide_settler(u, player, game_map)
if not player.cities.is_empty():
_founded_city = true
print(" Founded city at %s" % u.position)
return
@ -909,24 +922,7 @@ func _command_unit(unit: Variant, player: RefCounted, game_map: RefCounted) -> v
_command_worker(unit, player, game_map)
return
if unit.get("can_found_city") == true:
# Check distance from existing cities
var too_close: bool = false
for c: Variant in player.cities:
if HexUtilsScript.hex_distance(unit.position, c.position) < 5:
too_close = true
break
# Score current tile quality
# Check tile is land (not water)
var tile: Resource = game_map.get_tile(unit.position)
var is_water: bool = tile != null and tile.biome_id in ["ocean", "coast", "deep_ocean", "lake"]
if too_close or is_water:
_explore(unit, player, game_map)
else:
if _world_map != null and _world_map.has_method("_select_unit"):
_world_map._select_unit(unit)
if _world_map != null and _world_map.has_method("_on_found_city_pressed"):
_world_map._on_found_city_pressed()
print(" Founded city at %s" % unit.position)
_decide_settler(unit, player, game_map)
return
var target_pos: Vector2i = _find_attack_target(player)
@ -936,6 +932,65 @@ func _command_unit(unit: Variant, player: RefCounted, game_map: RefCounted) -> v
_explore(unit, player, game_map)
func _decide_settler(unit: Variant, player: RefCounted, game_map: RefCounted) -> void:
## State-based founding: score reachable sites, move to the best; only found at a local max.
const MIN_FOUND_SCORE: float = 4.0
const MAX_WAIT_TURNS: int = 40
const LOOK_AHEAD_MULT: int = 3
var water: Array = ["ocean", "coast", "deep_ocean", "lake"]
var reach: int = max(1, int(unit.max_movement) * LOOK_AHEAD_MULT)
var reachable: Dictionary = PathfinderScript.movement_range(game_map, unit.position, reach, "land")
var best_pos: Vector2i = unit.position
var best_score: float = _score_site(unit.position, game_map)
var current_score: float = best_score
for pos: Vector2i in reachable:
var tile: Resource = game_map.get_tile(pos)
if tile == null or tile.biome_id in water:
continue
var too_close: bool = false
for c: Variant in player.cities:
if HexUtilsScript.hex_distance(pos, c.position) < 5:
too_close = true
break
if too_close:
continue
var s: float = _score_site(pos, game_map)
if s > best_score:
best_score = s
best_pos = pos
print("[SITE] turn=%d current=%.1f best=%.1f chosen=%s" % [
_turn_count, current_score, best_score, str(best_pos)
])
var current_valid: bool = true
var cur_tile: Resource = game_map.get_tile(unit.position)
if cur_tile == null or cur_tile.biome_id in water:
current_valid = false
for c: Variant in player.cities:
if HexUtilsScript.hex_distance(unit.position, c.position) < 5:
current_valid = false
break
if best_pos == unit.position and current_valid:
if _world_map != null and _world_map.has_method("_select_unit"):
_world_map._select_unit(unit)
if _world_map != null and _world_map.has_method("_on_found_city_pressed"):
_world_map._on_found_city_pressed()
print(" Founded city at %s (score %.1f)" % [unit.position, current_score])
return
var waiting: bool = current_score < MIN_FOUND_SCORE and _turn_count < MAX_WAIT_TURNS
var should_wait: bool = (not current_valid) or waiting
if best_pos != unit.position and should_wait:
_move_toward(unit, best_pos, game_map)
return
if current_valid:
if _world_map != null and _world_map.has_method("_select_unit"):
_world_map._select_unit(unit)
if _world_map != null and _world_map.has_method("_on_found_city_pressed"):
_world_map._on_found_city_pressed()
print(" Founded city at %s (good-enough, score %.1f)" % [unit.position, current_score])
else:
_explore(unit, player, game_map)
func _command_worker(unit: Variant, player: RefCounted, game_map: RefCounted) -> void:
## Build an improvement on the current tile if possible; else walk toward a city.
if _improvement_manager == null: