|
|
|
@@ -0,0 +1,184 @@
|
|
|
|
|
extends SceneTree
|
|
|
|
|
|
|
|
|
|
const FAR_BERRY_ID := &"berry_bush_river_02"
|
|
|
|
|
const CLUSTER_TREE_ID := &"tree_river_resource_01"
|
|
|
|
|
|
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _initialize() -> void:
|
|
|
|
|
call_deferred("_run")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _run() -> void:
|
|
|
|
|
var camera := Camera3D.new()
|
|
|
|
|
camera.current = true
|
|
|
|
|
root.add_child(camera)
|
|
|
|
|
var world: Node3D = load("res://world/jajce/JajceWorld.tscn").instantiate()
|
|
|
|
|
root.add_child(world)
|
|
|
|
|
await process_frame
|
|
|
|
|
for _frame in 5:
|
|
|
|
|
await physics_frame
|
|
|
|
|
|
|
|
|
|
var terrain := world.get_node("TerrainRoot/Terrain3D") as Terrain3D
|
|
|
|
|
var cluster := (
|
|
|
|
|
world.get_node("WorldObjects/ResourceClusters/RiverbankResourceCluster")
|
|
|
|
|
as RiverbankResourceCluster
|
|
|
|
|
)
|
|
|
|
|
_check(cluster != null, "Jajce should expose the authored riverbank resource cluster")
|
|
|
|
|
if cluster == null:
|
|
|
|
|
_finish()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var stats := cluster.get_presentation_stats()
|
|
|
|
|
_check(
|
|
|
|
|
int(stats["understory_instance_count"]) == 28,
|
|
|
|
|
"Riverbank understory should keep its reviewed 28-instance budget"
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
int(stats["multimesh_batch_count"]) == 3,
|
|
|
|
|
"Riverbank understory should use exactly three intentional MultiMesh batches"
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
int(stats["decorative_tree_count"]) == 2,
|
|
|
|
|
"Riverbank cluster should keep two non-authoritative silhouette trees"
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
int(stats["resource_anchor_count"]) == 2,
|
|
|
|
|
"Riverbank cluster should pair density with only two finite resource anchors"
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
_collect_resource_nodes(cluster.get_node("DecorativeInstances")).is_empty(),
|
|
|
|
|
"Decorative riverbank instances must not become simulation resources"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var anchors := cluster.get_resource_anchors()
|
|
|
|
|
var anchor_ids: Array[StringName] = []
|
|
|
|
|
for anchor in anchors:
|
|
|
|
|
anchor_ids.append(anchor.node_id)
|
|
|
|
|
var terrain_height := terrain.data.get_height(anchor.global_position)
|
|
|
|
|
_check(
|
|
|
|
|
absf(anchor.global_position.y - terrain_height) < 0.05,
|
|
|
|
|
"Resource anchor %s should snap to Terrain3D" % anchor.node_id
|
|
|
|
|
)
|
|
|
|
|
var channel_distance := absf(
|
|
|
|
|
(
|
|
|
|
|
anchor.global_position.x
|
|
|
|
|
- JajceWatercourse.downstream_center_x(anchor.global_position.z)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
(
|
|
|
|
|
channel_distance
|
|
|
|
|
> JajceWatercourse.downstream_half_width(anchor.global_position.z) + 1.5
|
|
|
|
|
),
|
|
|
|
|
"Resource anchor %s should sit on the riverbank rather than in water" % anchor.node_id
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
anchor_ids.size() == 2 and anchor_ids.has(FAR_BERRY_ID) and anchor_ids.has(CLUSTER_TREE_ID),
|
|
|
|
|
"Authored cluster should preserve the existing river berry/tree stable IDs"
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
(
|
|
|
|
|
cluster.has_node("ResourceAnchors/BerryBush_River_02/Visual/BerryPatchPresentation")
|
|
|
|
|
and cluster.has_node("ResourceAnchors/Tree_River_Resource_01/Visual/TreePresentation")
|
|
|
|
|
),
|
|
|
|
|
"Finite cluster anchors should have resource-specific visuals instead of boxes"
|
|
|
|
|
)
|
|
|
|
|
var berry_presentation := (
|
|
|
|
|
cluster.get_node("ResourceAnchors/BerryBush_River_02/Visual/BerryPatchPresentation")
|
|
|
|
|
as StylizedBerryPatch
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
berry_presentation.get_presentation_instance_count() == 13,
|
|
|
|
|
"Berry resource visual should keep its bounded leaf-and-fruit instance count"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var resources := _collect_resource_nodes(world.get_node("WorldObjects"))
|
|
|
|
|
_check(resources.size() == 18, "Clustering should preserve all eighteen finite resources")
|
|
|
|
|
var pantry := world.get_node("WorldObjects/StorageSites/VillagePantry") as StorageNode
|
|
|
|
|
var woodpile := world.get_node("WorldObjects/StorageSites/VillageWoodpile") as StorageNode
|
|
|
|
|
var adapter := ActiveWorldAdapter.new()
|
|
|
|
|
adapter.pantry_storage = pantry
|
|
|
|
|
adapter.woodpile_storage = woodpile
|
|
|
|
|
root.add_child(adapter)
|
|
|
|
|
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
|
|
|
|
manager.debug_logs = false
|
|
|
|
|
manager.active_world_adapter = adapter
|
|
|
|
|
root.add_child(manager)
|
|
|
|
|
manager.set_process(false)
|
|
|
|
|
await process_frame
|
|
|
|
|
|
|
|
|
|
_check(
|
|
|
|
|
int(adapter.get_resource_index_stats()["candidate_count"]) == 18,
|
|
|
|
|
"ActiveWorldAdapter should index direct and nested resource anchors equally"
|
|
|
|
|
)
|
|
|
|
|
var nearby_food := adapter.get_resource_candidates_in_radius(
|
|
|
|
|
SimulationIds.ACTION_GATHER_FOOD, Vector3.ZERO, 24.0
|
|
|
|
|
)
|
|
|
|
|
var farther_food := adapter.get_resource_candidates_in_radius(
|
|
|
|
|
SimulationIds.ACTION_GATHER_FOOD, Vector3.ZERO, 64.0, 24.0
|
|
|
|
|
)
|
|
|
|
|
_check(not nearby_food.is_empty(), "The authored world should retain nearby food choices")
|
|
|
|
|
_check(
|
|
|
|
|
_candidate_ids(farther_food).has(FAR_BERRY_ID),
|
|
|
|
|
"The nested riverbank berry should occupy a discoverable farther range"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for resource in resources:
|
|
|
|
|
if resource.resource_id != SimulationIds.RESOURCE_FOOD:
|
|
|
|
|
continue
|
|
|
|
|
var state: ResourceStateRecord = manager.get_resource_state(resource.node_id)
|
|
|
|
|
state.set_enabled(resource.node_id == FAR_BERRY_ID)
|
|
|
|
|
var npc: SimNPC = manager.npcs[0]
|
|
|
|
|
npc.set_task(SimulationIds.ACTION_GATHER_FOOD)
|
|
|
|
|
_check(
|
|
|
|
|
manager.resolve_npc_target(npc.id, Vector3.ZERO),
|
|
|
|
|
"Resolver should expand from the village to the farther authored berry"
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
npc.target_id == FAR_BERRY_ID,
|
|
|
|
|
"Farther discovery should return the nested stable-ID berry anchor"
|
|
|
|
|
)
|
|
|
|
|
_check(
|
|
|
|
|
int(manager.target_resolver.last_resource_query_stats["range_pass_count"]) > 1,
|
|
|
|
|
"Farther authored discovery should expand beyond the initial 24 m range"
|
|
|
|
|
)
|
|
|
|
|
manager.release_resource(FAR_BERRY_ID, npc.id)
|
|
|
|
|
for resource in resources:
|
|
|
|
|
if resource.resource_id == SimulationIds.RESOURCE_FOOD:
|
|
|
|
|
manager.get_resource_state(resource.node_id).set_enabled(resource.initial_enabled)
|
|
|
|
|
|
|
|
|
|
_finish()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _candidate_ids(candidates: Array[Dictionary]) -> Array[StringName]:
|
|
|
|
|
var ids: Array[StringName] = []
|
|
|
|
|
for candidate in candidates:
|
|
|
|
|
ids.append(StringName(candidate["target_id"]))
|
|
|
|
|
return ids
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _collect_resource_nodes(parent: Node) -> Array[ResourceNode]:
|
|
|
|
|
var resources: Array[ResourceNode] = []
|
|
|
|
|
for child in parent.get_children():
|
|
|
|
|
if child is ResourceNode:
|
|
|
|
|
resources.append(child)
|
|
|
|
|
else:
|
|
|
|
|
resources.append_array(_collect_resource_nodes(child))
|
|
|
|
|
return resources
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _check(condition: bool, message: String) -> void:
|
|
|
|
|
if not condition:
|
|
|
|
|
failures.append(message)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _finish() -> void:
|
|
|
|
|
if failures.is_empty():
|
|
|
|
|
print("[TEST] Jajce resource cluster passed: bounded density -> far stable-ID discovery")
|
|
|
|
|
quit(0)
|
|
|
|
|
return
|
|
|
|
|
for failure in failures:
|
|
|
|
|
push_error("[TEST] " + failure)
|
|
|
|
|
quit(1)
|