feat: add weak-villager and damaged-roof village needs

This commit is contained in:
Rijad Zuzo
2026-08-11 00:32:22 +02:00
parent 37f262fb84
commit 82e44a9f5d
10 changed files with 547 additions and 45 deletions
+230
View File
@@ -0,0 +1,230 @@
extends SceneTree
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_test_feed_weak_villager()
_test_repair_home_roof()
_finish()
func _test_feed_weak_villager() -> void:
var manager := _create_manager(951)
var weak: SimNPC = manager.npcs[0]
var supplier: SimNPC = manager.npcs[1]
_empty_pantry(manager)
for npc in manager.npcs:
npc.position = Vector3(40.0 + npc.id * 10.0, 0.0, 0.0)
weak.position = Vector3.ZERO
supplier.position = Vector3(2.0, 0.0, 0.0)
weak.hunger = 95.0
weak.is_starving = true
weak.energy = 10.0
manager.economy.withdraw_to_inventory(weak, SimulationIds.RESOURCE_FOOD, 1.0)
weak.task_state = SimNPC.TASK_STATE_IDLE
weak.task_complete = true
manager.call("_select_action_if_idle", weak, SimNPC.TASK_STATE_IDLE)
var opportunity: OpportunityStateRecord = manager.get_active_opportunity()
_check(
(
opportunity != null
and opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER
and opportunity.get_interested_npc_id() == weak.id
and opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD
),
"A starving weak villager with an empty pantry should open a feed-weak-villager need"
)
if opportunity == null:
manager.free()
return
supplier.position = Vector3(2.0, 0.0, 0.0)
var supplier_relationship: RelationshipStateRecord = (
manager.relationship_system.get_relationship(supplier.id, weak.id)
)
_check(
(
supplier_relationship != null
and (
supplier_relationship.increase_trust(0.15, RelationshipStateRecord.NO_CAUSE_EVENT)
> 0.0
)
),
"Feed-weak setup should make the supplier a trusted known helper"
)
supplier.add_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
var helper: OpportunityHelperResult = manager.get_active_opportunity_helper()
_check(
(
helper != null
and helper.helper_npc_id == supplier.id
and helper.action_id == SimulationIds.ACTION_DEPOSIT_FOOD
),
"A trusted informed supplier should be derived as the capable helper"
)
_check(
is_equal_approx(
manager.economy.deposit_inventory(supplier, SimulationIds.RESOURCE_FOOD), 1.0
),
"A real supplier should restock the pantry for the weak villager"
)
_check(
manager.get_active_opportunity() != null,
"Stocking the pantry alone must not close a feed-weak-villager need"
)
weak.add_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
_check(manager.economy.consume_npc_food(weak), "The weak villager should eat the supplied food")
var resolved: OpportunityStateRecord = manager.get_latest_opportunity_for_npc(weak.id)
var resolution: EconomicEventRecord = manager.get_opportunity_resolution_event(resolved)
_check(
(
resolved != null
and resolved.get_status() == OpportunityStateRecord.STATUS_RESOLVED
and resolution != null
and (StringName(resolution.data["event_type"]) == SimulationIds.EVENT_ITEM_CONSUMED)
and int(resolution.data["actor_id"]) == weak.id
),
"A weak-villager need should resolve only when the interested villager actually eats"
)
var saved_json: String = manager.serialize_state()
var restored := _create_manager(952)
_check(
restored.restore_state_from_json(saved_json),
"A feed-weak-villager need should survive save and restore"
)
_check(
(
restored.get_state_checksum() == manager.get_state_checksum()
and (
restored.get_latest_opportunity_for_npc(weak.id).get_status()
== OpportunityStateRecord.STATUS_RESOLVED
)
),
"Restore should preserve the resolved feed-weak history and checksum"
)
restored.free()
_empty_pantry(manager)
weak.hunger = 95.0
weak.is_starving = true
weak.energy = 5.0
weak.task_state = SimNPC.TASK_STATE_IDLE
weak.task_complete = true
manager.call("_select_action_if_idle", weak, SimNPC.TASK_STATE_IDLE)
var reopened: OpportunityStateRecord = manager.get_active_opportunity()
_check(
(
reopened != null
and reopened.get_opportunity_type() == SimulationIds.OPPORTUNITY_FEED_WEAK_VILLAGER
),
"A renewed weak condition should open a new feed-weak-villager need"
)
var created_tick: int = reopened.get_created_tick()
var invalidated: OpportunityStateRecord = manager.opportunity_system.maintain_open_opportunity(
created_tick + manager.get_knowledge_review_interval(),
manager.get_knowledge_review_interval(),
manager.npcs,
manager.get_pantry(),
manager.get_woodpile()
)
_check(
(
invalidated != null
and invalidated.get_status() == OpportunityStateRecord.STATUS_INVALIDATED
and (
invalidated.get_invalidation_reason()
== SimulationIds.OPPORTUNITY_INVALIDATED_EVIDENCE_STALE
)
),
"An unresolved weak-villager need should close as stale after one simulated day"
)
manager.free()
func _test_repair_home_roof() -> void:
var manager := _create_manager(953)
var homeowner: SimNPC = manager.npcs[0]
var supplier: SimNPC = manager.npcs[1]
var woodpile: StorageStateRecord = manager.get_woodpile()
woodpile.withdraw(SimulationIds.RESOURCE_WOOD, woodpile.get_amount(SimulationIds.RESOURCE_WOOD))
manager.economy.sync_resource(SimulationIds.RESOURCE_WOOD)
manager.village.safety = 15.0
homeowner.position = Vector3.ZERO
var definition := SimulationDefinitions.get_action(SimulationIds.ACTION_SLEEP)
manager.call("_apply_action_completion", homeowner, SimulationIds.ACTION_SLEEP, definition)
var opportunity: OpportunityStateRecord = manager.get_active_opportunity()
_check(
(
opportunity != null
and opportunity.get_opportunity_type() == SimulationIds.OPPORTUNITY_REPAIR_HOME_ROOF
and opportunity.get_interested_npc_id() == homeowner.id
and opportunity.get_resource_id() == SimulationIds.RESOURCE_WOOD
),
"A sleepless night in a low-safety village should open a damaged-roof wood need"
)
if opportunity == null:
manager.free()
return
supplier.add_inventory(SimulationIds.RESOURCE_WOOD, 1.0)
_check(
is_equal_approx(
manager.economy.deposit_inventory(supplier, SimulationIds.RESOURCE_WOOD), 1.0
),
"A real supplier should deposit wood for the roof repair"
)
var resolved: OpportunityStateRecord = manager.get_latest_opportunity_for_npc(homeowner.id)
_check(
(
resolved != null
and resolved.get_status() == OpportunityStateRecord.STATUS_RESOLVED
and manager.get_active_opportunity() == null
),
"A real wood deposit should resolve the damaged-roof need"
)
manager.free()
func _empty_pantry(manager: Node) -> void:
var pantry: StorageStateRecord = manager.get_pantry()
pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD))
manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
func _create_manager(seed_value: int = 951) -> 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] Village care opportunities passed: weak villager + damaged roof")
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)
@@ -0,0 +1 @@
uid://cw18rdabwuedd