feat: add location-based food storage loop
This commit is contained in:
@@ -51,8 +51,8 @@ func _test_selection_and_execution_are_separate() -> void:
|
||||
var selector := ActionSelectionSystem.new()
|
||||
var selection := selector.select_action(npc, village)
|
||||
_check(
|
||||
selection.action_id == SimulationIds.ACTION_EAT,
|
||||
"Selection should independently choose the urgent eat action"
|
||||
selection.action_id == SimulationIds.ACTION_WITHDRAW_FOOD,
|
||||
"Selection should independently choose food retrieval"
|
||||
)
|
||||
|
||||
func _test_target_resolution_and_travel_are_separate() -> void:
|
||||
|
||||
@@ -28,7 +28,7 @@ func _run() -> void:
|
||||
]
|
||||
var destinations: Array[Vector3] = []
|
||||
for marker_path in [
|
||||
"ActivityMarkers/FoodZone",
|
||||
"ActivityMarkers/PantryMarker",
|
||||
"ActivityMarkers/GuardZone",
|
||||
"ActivityMarkers/StudyZone",
|
||||
"ActivityMarkers/RestZone"
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
extends SceneTree
|
||||
|
||||
var failures: Array[String] = []
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||
manager.debug_logs = false
|
||||
root.add_child(manager)
|
||||
manager.set_process(false)
|
||||
|
||||
var pantry: StorageStateRecord = manager.get_pantry()
|
||||
pantry.withdraw(SimulationIds.RESOURCE_FOOD, 1000.0)
|
||||
manager._sync_village_food()
|
||||
var resource := ResourceStateRecord.from_dictionary({
|
||||
"schema_version": ResourceStateRecord.SCHEMA_VERSION,
|
||||
"node_id": "food_loop_bush",
|
||||
"action_id": String(SimulationIds.ACTION_GATHER_FOOD),
|
||||
"resource_id": String(SimulationIds.RESOURCE_FOOD),
|
||||
"amount_remaining": 10.0,
|
||||
"yield_per_action": 2.0,
|
||||
"reserved_by": -1,
|
||||
"enabled": true,
|
||||
"can_npcs_use": true,
|
||||
"can_player_use": true
|
||||
})
|
||||
manager.resource_states[resource.get_node_id()] = resource
|
||||
|
||||
var npc: SimNPC = manager.npcs[0]
|
||||
npc.hunger = 20.0
|
||||
npc.energy = 100.0
|
||||
npc.set_task(SimulationIds.ACTION_GATHER_FOOD, 1.0)
|
||||
npc.target_id = resource.get_node_id()
|
||||
resource.reserve(npc.id)
|
||||
npc.start_working()
|
||||
manager.simulate_tick()
|
||||
|
||||
_check(
|
||||
is_equal_approx(resource.get_amount_remaining(), 8.0)
|
||||
and is_equal_approx(
|
||||
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
|
||||
2.0
|
||||
),
|
||||
"Gathering should move food from the source into NPC inventory"
|
||||
)
|
||||
_check(
|
||||
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 0.0),
|
||||
"Gathering should not teleport food into storage"
|
||||
)
|
||||
|
||||
manager.simulate_tick()
|
||||
_check(
|
||||
npc.current_task == SimulationIds.ACTION_DEPOSIT_FOOD,
|
||||
"Carried food should select the deposit action"
|
||||
)
|
||||
manager.notify_npc_arrived(npc.id)
|
||||
manager.simulate_tick()
|
||||
_check(
|
||||
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 2.0)
|
||||
and is_equal_approx(
|
||||
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
|
||||
0.0
|
||||
),
|
||||
"Deposit should transfer carried food into the pantry"
|
||||
)
|
||||
|
||||
npc.hunger = 80.0
|
||||
manager.simulate_tick()
|
||||
_check(
|
||||
npc.current_task == SimulationIds.ACTION_WITHDRAW_FOOD,
|
||||
"Hungry NPC should retrieve food from storage"
|
||||
)
|
||||
manager.notify_npc_arrived(npc.id)
|
||||
manager.simulate_tick()
|
||||
_check(
|
||||
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 1.0)
|
||||
and is_equal_approx(
|
||||
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
|
||||
1.0
|
||||
),
|
||||
"Withdraw should transfer pantry food into NPC inventory"
|
||||
)
|
||||
|
||||
manager.simulate_tick()
|
||||
_check(
|
||||
npc.current_task == SimulationIds.ACTION_EAT,
|
||||
"NPC carrying food should select eat"
|
||||
)
|
||||
manager.notify_npc_arrived(npc.id)
|
||||
var hunger_before_eating := npc.hunger
|
||||
manager.simulate_tick()
|
||||
manager.simulate_tick()
|
||||
_check(
|
||||
npc.hunger < hunger_before_eating
|
||||
and is_equal_approx(
|
||||
npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD),
|
||||
0.0
|
||||
),
|
||||
"Eating should consume carried food and reduce hunger"
|
||||
)
|
||||
|
||||
var conserved_food := (
|
||||
resource.get_amount_remaining()
|
||||
+ pantry.get_amount(SimulationIds.RESOURCE_FOOD)
|
||||
+ npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD)
|
||||
)
|
||||
_check(
|
||||
is_equal_approx(conserved_food, 9.0),
|
||||
"Exactly one food unit should leave the world when eaten"
|
||||
)
|
||||
|
||||
if failures.is_empty():
|
||||
print("[TEST] Food storage loop passed")
|
||||
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://cpyi0mc2gjs7l
|
||||
@@ -35,8 +35,8 @@ func _test_registry_integrity() -> void:
|
||||
"Action lookup should return '%s'" % definition.action_id
|
||||
)
|
||||
_check(
|
||||
action_ids.size() == 7,
|
||||
"Registry should contain all seven executable prototype actions"
|
||||
action_ids.size() == 9,
|
||||
"Registry should contain all nine executable prototype actions"
|
||||
)
|
||||
|
||||
var profession_ids := SimulationDefinitions.get_profession_ids()
|
||||
|
||||
@@ -10,6 +10,7 @@ func _run() -> void:
|
||||
await _test_resource_state_round_trip()
|
||||
_test_legacy_npc_migration()
|
||||
_test_legacy_resource_migration()
|
||||
_test_legacy_world_storage_migration()
|
||||
_test_schema_rejection()
|
||||
|
||||
if failures.is_empty():
|
||||
@@ -182,6 +183,26 @@ func _test_legacy_npc_migration() -> void:
|
||||
"Legacy NPC migration should not invent an active travel target"
|
||||
)
|
||||
|
||||
func _test_legacy_world_storage_migration() -> void:
|
||||
var manager := _create_manager(55)
|
||||
var legacy_data: Dictionary = manager.create_state_record().to_dictionary()
|
||||
legacy_data["schema_version"] = SimulationStateRecord.LEGACY_SCHEMA_VERSION
|
||||
legacy_data.erase("storages")
|
||||
var migrated := SimulationStateRecord.from_dictionary(legacy_data)
|
||||
_check(migrated != null, "World schema v1 should migrate pantry storage")
|
||||
if migrated != null:
|
||||
_check(
|
||||
migrated.storages.size() == 1
|
||||
and is_equal_approx(
|
||||
migrated.storages[0].get_amount(
|
||||
SimulationIds.RESOURCE_FOOD
|
||||
),
|
||||
manager.village.food
|
||||
),
|
||||
"World migration should preserve legacy village food in pantry"
|
||||
)
|
||||
manager.free()
|
||||
|
||||
func _create_manager(seed_value: int) -> Node:
|
||||
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||
manager.simulation_seed = seed_value
|
||||
|
||||
Reference in New Issue
Block a user