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)