feat: add Jajce world scaffold

This commit is contained in:
2026-07-04 21:06:57 +02:00
parent 83fc258afc
commit 556d0fdc26
19 changed files with 545 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
extends SceneTree
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
await physics_frame
var simulation_manager: Node = main_scene.get_node("SimulationManager")
simulation_manager.set_process(false)
_check(simulation_manager.npcs.size() == 3, "Baseline should create three NPCs")
_check(main_scene.get_node("ResourceNodes").get_child_count() == 4, "Baseline should contain four ResourceNodes")
var navigation_map: RID = main_scene.get_world_3d().navigation_map
await _wait_for_navigation_map(navigation_map)
NavigationServer3D.map_force_update(navigation_map)
var fixed_origins := [
Vector3(-6.0, 0.0, -4.0),
Vector3(0.0, 0.0, 0.0),
Vector3(6.0, 0.0, 4.0)
]
var destinations: Array[Vector3] = []
for marker_path in [
"TaskZone/FoodZone",
"TaskZone/GuardZone",
"TaskZone/StudyZone",
"TaskZone/RestZone"
]:
destinations.append(main_scene.get_node(marker_path).global_position)
for resource in main_scene.get_node("ResourceNodes").get_children():
destinations.append((resource as ResourceNode).interaction_point.global_position)
for origin in fixed_origins:
for destination in destinations:
var path := NavigationServer3D.map_get_path(
navigation_map,
origin,
destination,
true
)
_check(
not path.is_empty(),
"Navigation path should exist from %s to %s" % [origin, destination]
)
var village := SimVillage.new()
var worker := SimNPC.new(100, "BaselineWorker", "scholar", 5.0, 5.0)
worker.set_task("study", 2.0)
worker.start_working()
worker.simulate_tick(village)
worker.simulate_tick(village)
_check(worker.task_complete, "A two-tick working task should complete")
var starving := SimNPC.new(101, "BaselineStarving", "wanderer", 5.0, 5.0)
starving.hunger = 100.0
starving.starvation_death_threshold = 1
starving.simulate_tick(village)
_check(starving.is_dead, "Starvation threshold should still kill an NPC")
if failures.is_empty():
print("[TEST] Flat-map baseline passed: 3 NPCs, 4 resources, 24 navigation routes")
quit(0)
return
for failure in failures:
push_error("[TEST] " + failure)
quit(1)
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, "Navigation map did not synchronize within 30 physics frames")
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+1
View File
@@ -0,0 +1 @@
uid://cpach52mwjq67
+118
View File
@@ -0,0 +1,118 @@
extends SceneTree
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 10:
await physics_frame
var terrain = world.get_node("TerrainRoot/Terrain3D")
_check(terrain is Terrain3D, "JajceWorld should own a Terrain3D node")
_check(
terrain.data_directory == "res://terrain/jajce/data",
"Terrain3D should use dedicated Jajce data"
)
_check(world.has_node("WaterRoot"), "JajceWorld should expose WaterRoot")
_check(world.has_node("VillageRoot"), "JajceWorld should expose VillageRoot")
_check(world.has_node("FoliageRoot"), "JajceWorld should expose FoliageRoot")
_check(
world.has_node("GreyboxLandmarks/FortressBlockout"),
"Greybox should include the fortress landmark"
)
_check(
world.has_node("WaterRoot/WaterfallGreybox"),
"Greybox should include the waterfall drop"
)
_check(
world.has_node("VillageRoot/MillBlockout")
and world.has_node("VillageRoot/BridgeBlockout"),
"Greybox should include the mill and bridge"
)
var lookdev: Node3D = load("res://world/jajce/JajceLookdev.tscn").instantiate()
_check(lookdev.has_node("JajceWorld"), "Lookdev should instance JajceWorld")
_check(lookdev.has_node("BeautyCamera"), "Lookdev should provide a beauty camera")
lookdev.free()
var resources := world.get_node("WorldObjects/ResourceNodes").get_children()
var ids: Array[String] = []
for resource in resources:
ids.append(String((resource as ResourceNode).node_id))
ids.sort()
_check(
ids == ["berry_bush_01", "berry_bush_02", "tree_01", "tree_02"],
"Jajce resources should preserve all stable IDs"
)
var navigation_region := world.get_node("NavigationRegion3D") as NavigationRegion3D
var navigation_map: RID = NavigationServer3D.region_get_map(navigation_region.get_rid())
_check(navigation_map.is_valid(), "Jajce navigation region should be assigned to a map")
_check(
navigation_region.navigation_mesh.get_polygon_count() > 0,
"Jajce navigation mesh should contain baked polygons"
)
await _wait_for_navigation_map(navigation_map)
NavigationServer3D.map_force_update(navigation_map)
var origins := [
Vector3(-6, 0, -4),
Vector3(0, 0, 0),
Vector3(6, 0, 4)
]
var destinations: Array[Vector3] = []
for resource in resources:
destinations.append((resource as ResourceNode).interaction_point.global_position)
for marker in world.get_node("LegacyActivityMarkers").get_children():
destinations.append((marker as Marker3D).global_position)
for origin in origins:
for destination in destinations:
var path := NavigationServer3D.map_get_path(
navigation_map,
origin,
destination,
true
)
_check(
not path.is_empty(),
"Jajce navigation path should exist from %s to %s" % [origin, destination]
)
var selected := ResourceNode.find_available(&"gather_food", 777, Vector3.ZERO)
_check(selected != null, "Target discovery should find Jajce resources without a parent path")
if selected != null:
_check(selected.reserve(777), "Selected Jajce resource should be reservable")
selected.release(777)
if failures.is_empty():
print("[TEST] Jajce scaffold passed: Terrain3D, stable IDs, 24 navigation routes")
quit(0)
return
for failure in failures:
push_error("[TEST] " + failure)
quit(1)
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, "Jajce navigation map did not synchronize within 30 physics frames")
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
+1
View File
@@ -0,0 +1 @@
uid://bhujwfm70p60v