feat: validate expanded resource discovery
This commit is contained in:
@@ -652,11 +652,12 @@ Completed after the architecture gate:
|
|||||||
|
|
||||||
The practical next sequence is:
|
The practical next sequence is:
|
||||||
|
|
||||||
1. Expand resource discovery with many finite `ResourceNode` instances placed
|
1. Continue growing resource discovery with finite `ResourceNode` instances
|
||||||
in meaningful foliage, animal-camp, berry, and village-stockpile contexts.
|
placed in meaningful foliage, animal-camp, berry, and village-stockpile
|
||||||
The first distance/safety/comfort scoring pass and small authored resource
|
contexts. The first distance/safety/comfort scoring pass, twelve authored
|
||||||
spread exist; next, validate placement rules and grow the authored set
|
resources, and placement validation exist; next additions should increase
|
||||||
carefully rather than reintroducing abstract resource zones.
|
authored variety only when they preserve reachability, stable IDs, scoring
|
||||||
|
metadata, and clear player interaction ranges.
|
||||||
2. Expand persistence only when schedules, player state, or a real save menu
|
2. Expand persistence only when schedules, player state, or a real save menu
|
||||||
creates a concrete requirement.
|
creates a concrete requirement.
|
||||||
|
|
||||||
|
|||||||
@@ -550,6 +550,12 @@ profession, and NPC comfort range. The first scoring pass stores
|
|||||||
`safety_risk`, `comfort_distance`, and `discovery_priority` on resource state
|
`safety_risk`, `comfort_distance`, and `discovery_priority` on resource state
|
||||||
and uses them when selecting NPC resource targets.
|
and uses them when selecting NPC resource targets.
|
||||||
|
|
||||||
|
The first validated spread contains twelve finite resources: berry bushes and
|
||||||
|
patches, animal camps, trees, and one village woodpile. Placement is guarded by
|
||||||
|
headless tests for stable IDs, food/wood coverage, semantic contexts,
|
||||||
|
discovery metadata, navigation reachability, and non-overlapping pantry/player
|
||||||
|
interaction range.
|
||||||
|
|
||||||
## Test scenarios
|
## Test scenarios
|
||||||
|
|
||||||
At minimum, exercise:
|
At minimum, exercise:
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ func _run() -> void:
|
|||||||
"Runtime should not retain duplicate flat-world objects"
|
"Runtime should not retain duplicate flat-world objects"
|
||||||
)
|
)
|
||||||
var resource_root := main_scene.get_node("JajceWorld/WorldObjects/ResourceNodes")
|
var resource_root := main_scene.get_node("JajceWorld/WorldObjects/ResourceNodes")
|
||||||
_check(resource_root.get_child_count() == 8, "Runtime should contain eight Jajce ResourceNodes")
|
_check(resource_root.get_child_count() == 12, "Runtime should contain twelve Jajce ResourceNodes")
|
||||||
_check(
|
_check(
|
||||||
simulation_manager.resource_states.size() == 8,
|
simulation_manager.resource_states.size() == 12,
|
||||||
"Simulation authority should bind all eight Jajce resources"
|
"Simulation authority should bind all twelve Jajce resources"
|
||||||
)
|
)
|
||||||
|
|
||||||
var navigation_map: RID = main_scene.get_world_3d().navigation_map
|
var navigation_map: RID = main_scene.get_world_3d().navigation_map
|
||||||
@@ -102,7 +102,7 @@ func _run() -> void:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if failures.is_empty():
|
if failures.is_empty():
|
||||||
print("[TEST] Jajce runtime passed: 6 NPCs, 8 resources, typed sites")
|
print("[TEST] Jajce runtime passed: 6 NPCs, 12 resources, typed sites")
|
||||||
quit(0)
|
quit(0)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -116,19 +116,42 @@ func _run() -> void:
|
|||||||
ids
|
ids
|
||||||
== [
|
== [
|
||||||
"animal_camp_01",
|
"animal_camp_01",
|
||||||
|
"animal_camp_river_01",
|
||||||
"berry_bush_01",
|
"berry_bush_01",
|
||||||
"berry_bush_02",
|
"berry_bush_02",
|
||||||
"berry_patch_river_01",
|
"berry_patch_river_01",
|
||||||
|
"berry_patch_south_01",
|
||||||
"tree_01",
|
"tree_01",
|
||||||
"tree_02",
|
"tree_02",
|
||||||
"tree_north_outskirts_01",
|
"tree_north_outskirts_01",
|
||||||
"tree_south_edge_01"
|
"tree_river_resource_01",
|
||||||
|
"tree_south_edge_01",
|
||||||
|
"wood_pile_village_01"
|
||||||
],
|
],
|
||||||
"Jajce resources should preserve all stable and discoverable IDs"
|
"Jajce resources should preserve all stable and discoverable IDs"
|
||||||
)
|
)
|
||||||
var metadata_valid := true
|
var metadata_valid := true
|
||||||
|
var food_count := 0
|
||||||
|
var wood_count := 0
|
||||||
|
var animal_context_count := 0
|
||||||
|
var berry_context_count := 0
|
||||||
|
var village_context_count := 0
|
||||||
|
var outskirt_context_count := 0
|
||||||
for resource in resources:
|
for resource in resources:
|
||||||
var node := resource as ResourceNode
|
var node := resource as ResourceNode
|
||||||
|
var id := String(node.node_id)
|
||||||
|
if node.resource_id == SimulationIds.RESOURCE_FOOD:
|
||||||
|
food_count += 1
|
||||||
|
if node.resource_id == SimulationIds.RESOURCE_WOOD:
|
||||||
|
wood_count += 1
|
||||||
|
if id.begins_with("animal_camp"):
|
||||||
|
animal_context_count += 1
|
||||||
|
if id.begins_with("berry_"):
|
||||||
|
berry_context_count += 1
|
||||||
|
if id.contains("_village_"):
|
||||||
|
village_context_count += 1
|
||||||
|
if id.contains("_outskirts_") or id.contains("_edge_") or id.contains("_river_"):
|
||||||
|
outskirt_context_count += 1
|
||||||
metadata_valid = (
|
metadata_valid = (
|
||||||
metadata_valid
|
metadata_valid
|
||||||
and node.comfort_distance > 0.0
|
and node.comfort_distance > 0.0
|
||||||
@@ -136,6 +159,12 @@ func _run() -> void:
|
|||||||
and node.safety_risk <= 1.0
|
and node.safety_risk <= 1.0
|
||||||
)
|
)
|
||||||
_check(metadata_valid, "Jajce resources should define discovery metadata")
|
_check(metadata_valid, "Jajce resources should define discovery metadata")
|
||||||
|
_check(food_count >= 6, "Jajce should expose at least six finite food resources")
|
||||||
|
_check(wood_count >= 6, "Jajce should expose at least six finite wood resources")
|
||||||
|
_check(animal_context_count >= 2, "Jajce food discovery should include animal-camp contexts")
|
||||||
|
_check(berry_context_count >= 4, "Jajce food discovery should include berry contexts")
|
||||||
|
_check(village_context_count >= 1, "Jajce discovery should include at least one village stockpile")
|
||||||
|
_check(outskirt_context_count >= 4, "Jajce discovery should include outskirts/river contexts")
|
||||||
_check(world.has_node("WorldObjects/StorageSites"), "JajceWorld should expose StorageSites")
|
_check(world.has_node("WorldObjects/StorageSites"), "JajceWorld should expose StorageSites")
|
||||||
var pantry := world.get_node("WorldObjects/StorageSites/VillagePantry") as StorageNode
|
var pantry := world.get_node("WorldObjects/StorageSites/VillagePantry") as StorageNode
|
||||||
_check(pantry != null, "JajceWorld should include a typed VillagePantry StorageNode")
|
_check(pantry != null, "JajceWorld should include a typed VillagePantry StorageNode")
|
||||||
@@ -190,6 +219,24 @@ func _run() -> void:
|
|||||||
var adapter := ActiveWorldAdapter.new()
|
var adapter := ActiveWorldAdapter.new()
|
||||||
adapter.pantry_storage = pantry
|
adapter.pantry_storage = pantry
|
||||||
root.add_child(adapter)
|
root.add_child(adapter)
|
||||||
|
var food_candidates := adapter.get_resource_candidates(SimulationIds.ACTION_GATHER_FOOD)
|
||||||
|
_check(food_candidates.size() >= 6, "Adapter should expose all authored food candidates")
|
||||||
|
var candidate_metadata_valid := true
|
||||||
|
for candidate in food_candidates:
|
||||||
|
candidate_metadata_valid = (
|
||||||
|
candidate_metadata_valid
|
||||||
|
and candidate.has_all(
|
||||||
|
[
|
||||||
|
"target_id",
|
||||||
|
"position",
|
||||||
|
"resource_id",
|
||||||
|
"safety_risk",
|
||||||
|
"comfort_distance",
|
||||||
|
"discovery_priority"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_check(candidate_metadata_valid, "Adapter resource facts should include discovery metadata")
|
||||||
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||||
manager.debug_logs = false
|
manager.debug_logs = false
|
||||||
manager.active_world_adapter = adapter
|
manager.active_world_adapter = adapter
|
||||||
@@ -202,6 +249,10 @@ func _run() -> void:
|
|||||||
manager.resolve_npc_target(npc.id, Vector3.ZERO),
|
manager.resolve_npc_target(npc.id, Vector3.ZERO),
|
||||||
"Target resolver should find Jajce resources without a parent path"
|
"Target resolver should find Jajce resources without a parent path"
|
||||||
)
|
)
|
||||||
|
_check(
|
||||||
|
npc.target_id != &"animal_camp_river_01",
|
||||||
|
"Target resolver should not choose the far risky animal camp while safer food exists"
|
||||||
|
)
|
||||||
var selected := ResourceNode.get_by_id(npc.target_id)
|
var selected := ResourceNode.get_by_id(npc.target_id)
|
||||||
if selected != null:
|
if selected != null:
|
||||||
var selected_state: ResourceStateRecord = manager.get_resource_state(selected.node_id)
|
var selected_state: ResourceStateRecord = manager.get_resource_state(selected.node_id)
|
||||||
|
|||||||
@@ -10,7 +10,14 @@ func get_resource_candidates(action_id: StringName) -> Array[Dictionary]:
|
|||||||
if node.action_id != action_id or node.interaction_point == null:
|
if node.action_id != action_id or node.interaction_point == null:
|
||||||
continue
|
continue
|
||||||
candidates.append(
|
candidates.append(
|
||||||
{"target_id": String(node.node_id), "position": node.interaction_point.global_position}
|
{
|
||||||
|
"target_id": String(node.node_id),
|
||||||
|
"position": node.interaction_point.global_position,
|
||||||
|
"resource_id": String(node.resource_id),
|
||||||
|
"safety_risk": node.safety_risk,
|
||||||
|
"comfort_distance": node.comfort_distance,
|
||||||
|
"discovery_priority": node.discovery_priority
|
||||||
|
}
|
||||||
)
|
)
|
||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
|
|||||||
@@ -255,6 +255,24 @@ safety_risk = 0.35
|
|||||||
comfort_distance = 24.0
|
comfort_distance = 24.0
|
||||||
discovery_priority = -0.5
|
discovery_priority = -0.5
|
||||||
|
|
||||||
|
[node name="BerryPatch_South_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
|
position = Vector3(-18, 0, -6)
|
||||||
|
node_id = &"berry_patch_south_01"
|
||||||
|
initial_amount = 7.0
|
||||||
|
yield_per_action = 1.5
|
||||||
|
safety_risk = 0.12
|
||||||
|
comfort_distance = 24.0
|
||||||
|
discovery_priority = 0.1
|
||||||
|
|
||||||
|
[node name="AnimalCamp_River_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
|
position = Vector3(20, 0, 16)
|
||||||
|
node_id = &"animal_camp_river_01"
|
||||||
|
initial_amount = 4.0
|
||||||
|
yield_per_action = 1.0
|
||||||
|
safety_risk = 0.45
|
||||||
|
comfort_distance = 30.0
|
||||||
|
discovery_priority = -0.75
|
||||||
|
|
||||||
[node name="Tree_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
[node name="Tree_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
position = Vector3(6, 0, -8)
|
position = Vector3(6, 0, -8)
|
||||||
node_id = &"tree_01"
|
node_id = &"tree_01"
|
||||||
@@ -296,6 +314,28 @@ yield_per_action = 2.0
|
|||||||
safety_risk = 0.24
|
safety_risk = 0.24
|
||||||
comfort_distance = 26.0
|
comfort_distance = 26.0
|
||||||
|
|
||||||
|
[node name="Tree_River_Resource_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
|
position = Vector3(20, 0, 18)
|
||||||
|
node_id = &"tree_river_resource_01"
|
||||||
|
action_id = &"gather_wood"
|
||||||
|
resource_id = &"wood"
|
||||||
|
initial_amount = 8.0
|
||||||
|
yield_per_action = 2.0
|
||||||
|
safety_risk = 0.22
|
||||||
|
comfort_distance = 30.0
|
||||||
|
discovery_priority = 0.1
|
||||||
|
|
||||||
|
[node name="WoodPile_Village_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||||
|
position = Vector3(5, 0, -13)
|
||||||
|
node_id = &"wood_pile_village_01"
|
||||||
|
action_id = &"gather_wood"
|
||||||
|
resource_id = &"wood"
|
||||||
|
initial_amount = 6.0
|
||||||
|
yield_per_action = 1.5
|
||||||
|
safety_risk = 0.02
|
||||||
|
comfort_distance = 16.0
|
||||||
|
discovery_priority = 0.75
|
||||||
|
|
||||||
[node name="StorageSites" type="Node3D" parent="WorldObjects"]
|
[node name="StorageSites" type="Node3D" parent="WorldObjects"]
|
||||||
|
|
||||||
[node name="VillagePantry" parent="WorldObjects/StorageSites" instance=ExtResource("14_storage")]
|
[node name="VillagePantry" parent="WorldObjects/StorageSites" instance=ExtResource("14_storage")]
|
||||||
|
|||||||
Reference in New Issue
Block a user