extends SceneTree const GROVE_TREE_01 := &"tree_forest_grove_01" const GROVE_TREE_02 := &"tree_forest_grove_02" const GROVE_IDS := [GROVE_TREE_01, GROVE_TREE_02] const CLUSTER_PATH := "JajceWorld/WorldObjects/ResourceClusters/ForestEdgeResourceCluster" var failures: Array[String] = [] func _initialize() -> void: call_deferred("_run") func _run() -> void: var main_scene: Node = load("res://main.tscn").instantiate() root.add_child(main_scene) await process_frame for _frame in 10: await physics_frame var manager = main_scene.get_node("SimulationManager") manager.set_process(false) var terrain := main_scene.get_node("JajceWorld/TerrainRoot/Terrain3D") as Terrain3D var cluster := main_scene.get_node(CLUSTER_PATH) as ForestEdgeResourceCluster _check(cluster != null, "Jajce should expose the authored forest-edge resource cluster") if cluster == null: _finish() return var stats := cluster.get_presentation_stats() _check( int(stats["understory_instance_count"]) == 24, "Forest understory should keep its reviewed 24-instance budget" ) _check( int(stats["multimesh_batch_count"]) == 3, "Forest understory should use exactly three intentional MultiMesh batches" ) _check( int(stats["decorative_tree_count"]) == 2, "Forest cluster should reuse two non-authoritative silhouette trees" ) _check( int(stats["resource_anchor_count"]) == 2, "Forest density should contain only the two existing finite tree anchors" ) _check( _collect_resource_nodes(cluster.get_node("DecorativeInstances")).is_empty(), "Decorative forest foliage 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 anchor_height := terrain.data.get_height(anchor.global_position) var interaction_position := anchor.interaction_point.global_position var interaction_height := terrain.data.get_height(interaction_position) _check( absf(anchor.global_position.y - anchor_height) < 0.05, "Forest anchor %s should snap to Terrain3D" % anchor.node_id ) _check( absf(interaction_position.y - interaction_height) < 0.1, "Forest interaction %s should follow Terrain3D" % anchor.node_id ) _check( interaction_position.distance_to(Vector3.ZERO) > 24.0, "Forest interaction %s should require expanded discovery" % anchor.node_id ) var visual := anchor.get_node("Visual/TreePresentation") as HarvestableTreePresentation _check( ( visual != null and visual.get_amount_tier() == ResourceAmountVisual.AmountTier.FULL and visual.get_visible_canopy_count() == 4 ), "Forest anchor %s should reuse the full harvestable-tree visual" % anchor.node_id ) _check( anchor_ids.size() == 2 and anchor_ids.has(GROVE_TREE_01) and anchor_ids.has(GROVE_TREE_02), "Forest cluster should preserve both existing stable tree IDs" ) var resources := _collect_resource_nodes(main_scene.get_node("JajceWorld/WorldObjects")) _check(resources.size() == 18, "Forest clustering should preserve all eighteen resources") _check( manager.resource_states.size() == 18, "Simulation authority should still bind eighteen resource records" ) var adapter := main_scene.get_node("ActiveWorldAdapter") as ActiveWorldAdapter var farther_wood := adapter.get_resource_candidates_in_radius( SimulationIds.ACTION_GATHER_WOOD, Vector3.ZERO, 64.0, 24.0 ) var farther_ids := _candidate_ids(farther_wood) _check( farther_ids.has(GROVE_TREE_01) and farther_ids.has(GROVE_TREE_02), "Both nested forest trees should occupy the farther discovery range" ) await _check_navigation(main_scene, terrain, anchors) _check_expanding_resolution(manager, resources) _check_depletion_restore(manager, cluster) _finish() func _check_expanding_resolution(manager, resources: Array[ResourceNode]) -> void: for resource in resources: if resource.resource_id != SimulationIds.RESOURCE_WOOD: continue var state: ResourceStateRecord = manager.get_resource_state(resource.node_id) state.set_enabled(resource.node_id == GROVE_TREE_01) var npc: SimNPC = manager.npcs[0] npc.set_task(SimulationIds.ACTION_GATHER_WOOD) _check( manager.resolve_npc_target(npc.id, Vector3.ZERO), "Resolver should expand from the village to the authored forest edge" ) _check( npc.target_id == GROVE_TREE_01, "Expanded discovery should return the forced nested forest tree by stable ID" ) _check( int(manager.target_resolver.last_resource_query_stats["range_pass_count"]) > 1, "Forest discovery should expand beyond the initial 24 m range" ) manager.release_npc_reservation(npc.id) for resource in resources: if resource.resource_id == SimulationIds.RESOURCE_WOOD: manager.get_resource_state(resource.node_id).set_enabled(resource.initial_enabled) func _check_depletion_restore(manager, cluster: ForestEdgeResourceCluster) -> void: var tree := cluster.get_node("ResourceAnchors/Tree_Forest_Grove_01") as ResourceNode var visual := tree.get_node("Visual/TreePresentation") as HarvestableTreePresentation var state: ResourceStateRecord = manager.get_resource_state(GROVE_TREE_01) state.set_amount_remaining(tree.yield_per_action) _check( ( visual.get_amount_tier() == ResourceAmountVisual.AmountTier.LOW and visual.get_visible_canopy_count() == 2 ), "A nearly spent forest tree should derive the shared low crown from amount" ) var low_snapshot: String = manager.serialize_state() var npc: SimNPC = manager.npcs[0] var inventory_before := npc.get_inventory_amount(SimulationIds.RESOURCE_WOOD) npc.target_id = GROVE_TREE_01 _check( manager.reserve_resource(GROVE_TREE_01, npc.id), "NPC should reserve the nested forest tree before gathering" ) manager.call("_complete_resource_gather", npc, SimulationIds.ACTION_GATHER_WOOD) _check( ( state.is_depleted() and is_equal_approx( npc.get_inventory_amount(SimulationIds.RESOURCE_WOOD), inventory_before + tree.yield_per_action ) ), "Real NPC extraction should conserve the final forest-tree yield" ) _check( ( visual.get_amount_tier() == ResourceAmountVisual.AmountTier.DEPLETED and visual.is_stump_visible() and not visual.is_standing_visible() ), "Real NPC depletion should leave the shared readable stump" ) _check( manager.restore_state_from_json(low_snapshot), "Low forest-tree authority should restore" ) state = manager.get_resource_state(GROVE_TREE_01) _check( ( is_equal_approx(state.get_amount_remaining(), tree.yield_per_action) and visual.get_amount_tier() == ResourceAmountVisual.AmountTier.LOW and visual.is_standing_visible() and not visual.is_stump_visible() and visual.get_visible_canopy_count() == 2 ), "Restore should rebuild the sparse standing tree from authoritative amount alone" ) func _check_navigation(main_scene: Node, terrain: Terrain3D, anchors: Array[ResourceNode]) -> void: var navigation_map: RID = main_scene.get_world_3d().navigation_map await _wait_for_navigation_map(navigation_map) NavigationServer3D.map_force_update(navigation_map) for anchor in anchors: var destination := anchor.interaction_point.global_position var path := NavigationServer3D.map_get_path(navigation_map, Vector3.ZERO, destination, true) _check( not path.is_empty(), "Navigation should reach forest interaction %s" % anchor.node_id ) _check_path_tracks_terrain(terrain, path, anchor.node_id) if not path.is_empty(): var final_point := path[path.size() - 1] _check( ( Vector2(final_point.x, final_point.z).distance_to( Vector2(destination.x, destination.z) ) <= 1.5 ), "Navigation should finish beside forest interaction %s" % anchor.node_id ) func _wait_for_navigation_map(navigation_map: RID) -> void: for _attempt in 30: if ( NavigationServer3D.map_get_iteration_id(navigation_map) > 0 and not NavigationServer3D.map_get_regions(navigation_map).is_empty() ): await physics_frame return await physics_frame _check(false, "Forest-edge navigation map did not synchronize within 30 physics frames") func _check_path_tracks_terrain( terrain: Terrain3D, path: PackedVector3Array, node_id: StringName ) -> void: for point in path: var terrain_height := terrain.data.get_height(point) if is_nan(terrain_height) or absf(point.y - terrain_height) > 0.85: _check(false, "Forest route to %s should track Terrain3D height" % node_id) return 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 forest edge passed: bounded grove -> far discovery -> restore") quit(0) return for failure in failures: push_error("[TEST] " + failure) quit(1)