feat: score resource discovery by comfort and safety

This commit is contained in:
2026-07-07 15:10:06 +02:00
parent 4c090f1635
commit 036c9d5141
12 changed files with 132 additions and 23 deletions
+27 -4
View File
@@ -10,7 +10,7 @@ class FakeActiveWorld:
func get_resource_candidates(_action_id: StringName) -> Array[Dictionary]:
return resource_candidates
func get_activity_target(_action_id: StringName) -> Dictionary:
func get_activity_target(_action_id: StringName, _origin: Vector3 = Vector3.ZERO) -> Dictionary:
return {"target_id": "", "position": activity_position}
@@ -69,6 +69,7 @@ func _test_selection_and_execution_are_separate() -> void:
func _test_target_resolution_and_travel_are_separate() -> void:
var adapter := FakeActiveWorld.new()
adapter.resource_candidates = [
{"target_id": "boundary_risky_bush", "position": Vector3(1.0, 0.0, 0.0)},
{"target_id": "boundary_bush", "position": Vector3(3.0, 0.0, 2.0)}
]
root.add_child(adapter)
@@ -91,20 +92,42 @@ func _test_target_resolution_and_travel_are_separate() -> void:
"reserved_by": -1,
"enabled": true,
"can_npcs_use": true,
"can_player_use": true
"can_player_use": true,
"safety_risk": 0.0,
"comfort_distance": 18.0,
"discovery_priority": 0.0
}
)
manager.resource_states[resource_state.get_node_id()] = resource_state
var risky_resource_state := ResourceStateRecord.from_dictionary(
{
"schema_version": ResourceStateRecord.SCHEMA_VERSION,
"node_id": "boundary_risky_bush",
"action_id": String(SimulationIds.ACTION_GATHER_FOOD),
"resource_id": String(SimulationIds.RESOURCE_FOOD),
"amount_remaining": 5.0,
"yield_per_action": 1.0,
"reserved_by": -1,
"enabled": true,
"can_npcs_use": true,
"can_player_use": true,
"safety_risk": 1.0,
"comfort_distance": 18.0,
"discovery_priority": 0.0
}
)
manager.resource_states[risky_resource_state.get_node_id()] = risky_resource_state
var npc: SimNPC = manager.npcs[0]
npc.energy = 40.0
npc.set_task(SimulationIds.ACTION_GATHER_FOOD)
_check(
manager.resolve_npc_target(npc.id, Vector3.ZERO),
"Target resolver should resolve a resource from adapter facts"
"Target resolver should resolve a scored resource from adapter facts"
)
_check(
npc.target_id == &"boundary_bush" and resource_state.get_reserved_by() == npc.id,
"Target resolver should authoritatively assign and reserve the target"
"Target resolver should prefer the safer comfortable resource over risky proximity"
)
_check(
travel_requests == [Vector3(3.0, 0.0, 2.0)],
+4 -1
View File
@@ -27,7 +27,10 @@ func _run() -> void:
"reserved_by": -1,
"enabled": true,
"can_npcs_use": true,
"can_player_use": true
"can_player_use": true,
"safety_risk": 0.0,
"comfort_distance": 18.0,
"discovery_priority": 0.0
}
)
manager.resource_states[resource.get_node_id()] = resource
+10
View File
@@ -116,6 +116,16 @@ func _run() -> void:
ids == ["berry_bush_01", "berry_bush_02", "tree_01", "tree_02"],
"Jajce resources should preserve all stable IDs"
)
var metadata_valid := true
for resource in resources:
var node := resource as ResourceNode
metadata_valid = (
metadata_valid
and node.comfort_distance > 0.0
and node.safety_risk >= 0.0
and node.safety_risk <= 1.0
)
_check(metadata_valid, "Jajce resources should define discovery metadata")
_check(world.has_node("WorldObjects/StorageSites"), "JajceWorld should expose StorageSites")
var pantry := world.get_node("WorldObjects/StorageSites/VillagePantry") as StorageNode
_check(pantry != null, "JajceWorld should include a typed VillagePantry StorageNode")
@@ -156,6 +156,11 @@ func _test_legacy_resource_migration() -> void:
is_equal_approx(migrated.get_amount_remaining(), 6.0) and migrated.get_reserved_by() == 12,
"Resource migration should preserve mutable authority"
)
_check(
is_equal_approx(migrated.get_safety_risk(), 0.0)
and is_equal_approx(migrated.get_comfort_distance(), 18.0),
"Resource migration should default discovery metadata"
)
func _test_legacy_npc_migration() -> void: