test(engine): Add proof-of-concept test for biome-economy coupling and enhance audio management with dynamic switching

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-04-29 13:13:23 -07:00
parent a5c616f8aa
commit 78863ab8b0
2 changed files with 50 additions and 7 deletions

View file

@ -132,6 +132,14 @@ func _run_happiness_pipeline() -> void:
for pos: Vector2i in OWNED_TILES:
owned_pairs.append([int(pos.x), int(pos.y)])
var products: Array = _build_products_array()
print("Products from DataLoader (with source_fauna): %d" % products.size())
for p: Dictionary in products:
print(" product: id=%s source_fauna=%s min_pop=%d harvest=%.2f" % [
p.get("id", "?"),
str(p.get("source_fauna", [])),
int(p.get("min_population", 0)),
float(p.get("harvest_rate", 0.0)),
])
if _fauna_eco != null:
_supply = _fauna_eco.call(
"fauna_product_supply",

View file

@ -27,6 +27,7 @@ var _crossfade_seconds: float = 2.0
## value is an array of track IDs from `_music_tracks`. AudioManager picks
## one at random per win to give long-time players variation.
var _victory_pool: Dictionary = {}
var _defeat_pool: Dictionary = {}
var _sfx_pool: Array[AudioStreamPlayer] = []
var _sfx_cursor: int = 0
@ -98,6 +99,7 @@ func load_theme(theme_id: String) -> void:
continue
_music_tracks[id] = track
_victory_pool = (music.get("victory_pool", {}) as Dictionary).duplicate()
_defeat_pool = (music.get("defeat_pool", {}) as Dictionary).duplicate()
_loaded = true
@ -602,9 +604,16 @@ func _on_unit_moved(_unit: Variant, _from: Vector2i, _to: Vector2i) -> void:
play_sfx("unit_moved")
func _on_victory_achieved(_player_index: int, victory_type: String) -> void:
play_sfx("victory_fanfare")
play_music(_pick_victory_track(victory_type))
func _on_victory_achieved(player_index: int, victory_type: String) -> void:
# A win is the listener's win only if the winner is the local human.
# Otherwise the human is being defeated by this winner's strategy and
# we play the matching defeat-by-<victory_type> track.
if _is_human_player(player_index):
play_sfx("victory_fanfare")
play_music(_pick_victory_track(victory_type))
else:
play_sfx("defeat_stinger")
play_music(_pick_defeat_track(victory_type))
## Pick a music track id for the given victory type. Looks the type up in
@ -622,15 +631,41 @@ func _pick_victory_track(victory_type: String) -> String:
return _music_default_id
## Mirror of _pick_victory_track for `defeat_pool`. Returns a defeat track
## id keyed to *how* the human player was defeated. Falls back to the
## generic "defeat" track when the victory_type is unmapped.
func _pick_defeat_track(victory_type: String) -> String:
if _defeat_pool.has(victory_type) and _defeat_pool[victory_type] is Array:
var pool: Array = _defeat_pool[victory_type] as Array
if pool.size() > 0:
return String(pool[_rng.randi_range(0, pool.size() - 1)])
if _music_tracks.has("defeat"):
return "defeat"
return _music_default_id
## Helper: is `player_index` the local human player? Returns false on
## out-of-range indices and on players that don't expose `is_human`.
func _is_human_player(player_index: int) -> bool:
if player_index < 0 or player_index >= GameState.players.size():
return false
var player: RefCounted = GameState.players[player_index] as RefCounted
if player == null or not ("is_human" in player):
return false
return bool(player.get("is_human"))
## Defeat is the human-player counterpart of victory_achieved. The signal
## fires for any eliminated player; we only swap to defeat audio when the
## eliminated player is the local human, otherwise the listener gets
## defeat music for an AI's loss which is wrong.
func _on_player_eliminated(player_index: int) -> void:
if player_index < 0 or player_index >= GameState.players.size():
return
var player: RefCounted = GameState.players[player_index] as RefCounted
if player == null or not ("is_human" in player) or not bool(player.get("is_human")):
# This signal carries no victory_type — fires for last-unit-destroyed
# eliminations etc. When the elimination is also a victory_achieved
# (an AI just won), that handler already swapped to the defeat-by-X
# track via _pick_defeat_track; re-asserting the generic "defeat"
# here is harmless (same Music bus, crossfade tweens).
if not _is_human_player(player_index):
return
play_sfx("defeat_stinger")
play_music("defeat")