extends SceneTree var failures: Array[String] = [] var war_outcomes: Array[StringName] = [] func _initialize() -> void: call_deferred("_run") func _run() -> void: _test_combat_and_wolf() _test_wolf_closes_distance_before_striking() _test_npc_death_signals_observe_authoritative_death() _test_war_motivation() _test_player_combat_and_restore() _test_raid_plunder_uses_authoritative_pantry() _finish() func _test_combat_and_wolf() -> void: var manager := _create_manager(981) manager.update_player_combatant(Vector3(100.0, 0.0, 100.0)) var npc: SimNPC = manager.npcs[0] npc.position = Vector3.ZERO var npc_combatant: CombatantStateRecord = manager.get_combatant( SimulationIds.npc_combatant_id(npc.id) ) _check( ( npc_combatant != null and is_equal_approx(npc_combatant.get_health(), 100.0) and npc_combatant.get_faction_id() == SimulationIds.FACTION_VILLAGE ), "Each living villager should gain a village combatant record" ) var wolf_id: StringName = manager.spawn_wolf(Vector3(1.0, 0.0, 0.0)) _check(wolf_id != &"", "A hungry wolf should spawn as a hostile combatant") var wolf: CombatantStateRecord = manager.get_combatant(wolf_id) _check(wolf.is_hostile(), "A spawned wolf should be hostile") manager.simulate_tick() _check( wolf.get_health() == 40.0 and npc_combatant.get_health() < 100.0, "A nearby wolf should wound the villager with its claw" ) var hurt_found := _has_event(manager, SimulationIds.EVENT_COMBATANT_HURT) _check(hurt_found, "The wolf's attack should record a combatant_hurt fact") for _tick in 40: manager.simulate_tick() _check( npc.is_dead or not wolf.is_alive(), "A prolonged wolf fight should resolve with a death on one side" ) manager.free() func _test_war_motivation() -> void: var strong_manager := _create_manager(982) _set_all_strength(strong_manager, 9.0) strong_manager.war_resolved.connect(_on_war_resolved) for _tick in 400: strong_manager.simulate_tick() var strong_plan: StringName = strong_manager.get_tribe_war_plan() var strong_raiders := _count_raiders(strong_manager) _check( ( strong_plan in [SimulationIds.WAR_PLAN_PLANNED, SimulationIds.WAR_PLAN_NONE] and strong_raiders == 0 ), "A strong village should make the hungry tribe plan but never dare to raid" ) strong_manager.free() var weak_manager := _create_manager(983) _set_all_strength(weak_manager, 1.0) var weak_outcomes: Array[StringName] = [] weak_manager.war_resolved.connect(func(outcome): weak_outcomes.append(outcome)) for _tick in 200: weak_manager.simulate_tick() _check( weak_manager.get_tribe_war_plan() == SimulationIds.WAR_PLAN_PLANNED, "A hungry tribe should first watch a weak food-rich village before striking" ) for _tick in 200: weak_manager.simulate_tick() _check( weak_manager.get_tribe_war_plan() == SimulationIds.WAR_PLAN_RAIDING, "A weak village with plenty of food should be raided once the tribe commits" ) _check(_count_raiders(weak_manager) > 0, "The raid should spawn hostile raiders") var resolved := false for _tick in 600: weak_manager.simulate_tick() if weak_manager.get_tribe_war_plan() != SimulationIds.WAR_PLAN_RAIDING: resolved = true break _check( resolved and not weak_outcomes.is_empty(), "The raid should resolve into a war outcome with recorded consequences" ) weak_manager.free() func _test_wolf_closes_distance_before_striking() -> void: var manager := _create_manager(986) var target: SimNPC = manager.npcs[0] target.position = Vector3.ZERO for index in range(1, manager.npcs.size()): manager.npcs[index].position = Vector3(80.0 + index, 0.0, 80.0) manager.update_player_combatant(Vector3(120.0, 0.0, 120.0)) var target_combatant: CombatantStateRecord = manager.get_combatant( SimulationIds.npc_combatant_id(target.id) ) var wolf_id: StringName = manager.spawn_wolf(Vector3(10.0, 0.0, 0.0)) var wolf: CombatantStateRecord = manager.get_combatant(wolf_id) var health_before := target_combatant.get_health() var distance_before := wolf.get_position().distance_to(target.position) manager.simulate_tick() _check( is_equal_approx(target_combatant.get_health(), health_before), "The 18 metre wolf hunt radius should not become a remote melee strike" ) _check( wolf.get_position().distance_to(target.position) < distance_before, "An alerted wolf should move its authoritative position toward its defender" ) manager.free() func _test_npc_death_signals_observe_authoritative_death() -> void: var manager := _create_manager(987) var npc: SimNPC = manager.npcs[0] npc.current_task = SimulationIds.ACTION_GATHER_FOOD npc.task_state = SimNPC.TASK_STATE_TRAVELING npc.target_id = &"missing_test_resource" npc.has_travel_target = true var death_observed := [false] var task_change_observed := [false] manager.npc_died.connect( func(dead_npc: SimNPC): death_observed[0] = ( dead_npc == npc and dead_npc.is_dead and dead_npc.current_task == SimulationIds.ACTION_DEAD and dead_npc.target_id == &"" and not dead_npc.has_travel_target ) ) manager.npc_task_changed.connect( func(changed_npc: SimNPC, old_task: StringName, new_task: StringName): task_change_observed[0] = ( changed_npc == npc and changed_npc.is_dead and old_task == SimulationIds.ACTION_GATHER_FOOD and new_task == SimulationIds.ACTION_DEAD ) ) var combatant: CombatantStateRecord = manager.get_combatant( SimulationIds.npc_combatant_id(npc.id) ) manager.conflict_system.call( "_apply_damage", combatant, combatant.get_health(), SimulationIds.PLAYER_ACTOR_ID ) _check( death_observed[0] and task_change_observed[0], "NPC death and task listeners should observe an already-dead, released simulation record" ) manager.free() func _test_player_combat_and_restore() -> void: var manager := _create_manager(984) var friendly: SimNPC = manager.npcs[0] _check( is_equal_approx(manager.player_attack(SimulationIds.npc_combatant_id(friendly.id)), 0.0), "Simulation authority should reject a player attack against a friendly villager" ) var distant_wolf_id: StringName = manager.spawn_wolf(Vector3(20.0, 0.0, 0.0)) _check( is_equal_approx(manager.player_attack(distant_wolf_id), 0.0), "Simulation authority should reject an out-of-range sword strike" ) var wolf_id := distant_wolf_id var wolf: CombatantStateRecord = manager.get_combatant(wolf_id) wolf.set_position(Vector3.ZERO) _check( is_equal_approx(manager.player_attack(wolf_id), 24.0), "The player's sword strike should deal its configured damage" ) _check(is_equal_approx(wolf.get_health(), 16.0), "The wolf should survive one sword strike") _check( is_equal_approx(manager.player_attack(wolf_id), 0.0), "Simulation authority should enforce the configured player attack cooldown" ) var sword := SimulationItems.get_weapon(SimulationIds.ITEM_SWORD) manager.advance_player_combat_time(sword.attack_cooldown) manager.player_attack(wolf_id) _check(not wolf.is_alive(), "A second sword strike should kill the wolf") _check( _has_event(manager, SimulationIds.EVENT_COMBATANT_KILLED), "The player's kill should record a combatant_killed fact" ) _check( manager.get_living_hostile_combatants().is_empty(), "The dead wolf should leave the living-hostile set" ) manager.get_player_state().take_damage(35.0) manager.call("_sync_player_combatant") var saved_json: String = manager.serialize_state() var restored := _create_manager(985) _check( restored.restore_state_from_json(saved_json), "Combatants and factions should survive schema-v14 save and restore" ) _check( ( restored.get_state_checksum() == manager.get_state_checksum() and restored.get_faction(SimulationIds.FACTION_TRIBE) != null and not restored.get_combatant(wolf_id).is_alive() and is_equal_approx( restored.get_combatant(&"player_combatant").get_health(), restored.get_player_state().get_health() ) ), "Restore should preserve hostile state, factions, checksum, and player health parity" ) restored.free() manager.free() func _test_raid_plunder_uses_authoritative_pantry() -> void: var manager := _create_manager(988) var pantry: StorageStateRecord = manager.get_pantry() pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD)) pantry.deposit(SimulationIds.RESOURCE_FOOD, 10.0) manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD) var village_faction: FactionStateRecord = manager.get_faction(SimulationIds.FACTION_VILLAGE) var tribe: FactionStateRecord = manager.get_faction(SimulationIds.FACTION_TRIBE) village_faction.set_food(10.0) var tribe_food_before := tribe.get_food() manager.conflict_system.call("_finish_war", SimulationIds.WAR_PLAN_ABORTED, &"village_lost") _check( ( is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 4.0) and is_equal_approx(manager.village.food, 4.0) and is_equal_approx(village_faction.get_food(), 4.0) and is_equal_approx(tribe.get_food(), tribe_food_before + 6.0) ), "Raid plunder should transfer real pantry food instead of mutating only faction display state" ) manager.free() func _set_all_strength(manager: Node, strength: float) -> void: for npc in manager.npcs: npc.strength = strength func _count_raiders(manager: Node) -> int: var count := 0 for combatant in manager.conflict_system.get_all_combatants(): if combatant.get_kind() == SimulationIds.COMBATANT_KIND_RAIDER: count += 1 return count func _has_event(manager: Node, event_type: StringName) -> bool: for event in manager.economic_events: if StringName(event.data["event_type"]) == event_type: return true return false func _on_war_resolved(_outcome: StringName) -> void: war_outcomes.append(_outcome) func _create_manager(seed_value: int = 981) -> Node: var manager: Node = load("res://simulation/SimulationManager.gd").new() manager.simulation_seed = seed_value manager.debug_logs = false var home_positions: Array[Vector3] = [ Vector3(0.0, 0.0, 0.0), Vector3(2.0, 0.0, 0.0), Vector3(10.0, 0.0, 0.0), Vector3(12.0, 0.0, 0.0), Vector3(30.0, 0.0, 30.0), Vector3(40.0, 0.0, 40.0), ] manager.home_positions = home_positions root.add_child(manager) manager.set_process(false) return manager func _finish() -> void: if failures.is_empty(): print("[TEST] Conflict and war passed: combat, wolves, motivation, raids") quit(0) return for failure in failures: push_error("[TEST] " + failure) quit(1) func _check(condition: bool, message: String) -> void: if not condition: failures.append(message)