22613a445e
- Add 3 missing Terrain3D textures: Dirt Path (id=3), Riverbank Stone (id=4), Compacted Ground (id=5) with procedural albedo PNGs + generate tool - Replace greybox river BoxMesh with animated vertex-displacement water shader - Replace greybox waterfall BoxMesh with scrolling-UV waterfall shader - Add WaterfallMist GPUParticles3D with upward drift and alpha fade - Add wind vertex shader for tree canopies (sin-based, height-weighted sway) - Replace StylizedTree.tscn with script-driven procedural tree supporting 3 canopy color variants and position-based randomization - Replace greybox house boxes with StylizedHouse (walls + pitched roof + door) - Replace mill box with StylizedMill (body + roof + water wheel cylinder) - Replace fortress box with FortressBlockout (keep + parapet + corner turret) - Replace bridge box with BridgeBlockout (deck + pillar supports) - Add ChimneySmoke GPUParticles3D on each house roof - Height-snap buildings (fortress + village) to terrain surface in _ready() - Update JajceWorld.tscn: remove all greybox landmark sub-resources, instance new scenes, keep terrain/navigation/sky/fog as-is - Update scaffold test for new scene structure and beauty elements
178 lines
5.6 KiB
GDScript
178 lines
5.6 KiB
GDScript
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(
|
|
terrain.assets.get_texture_count() >= 3,
|
|
"Jajce terrain should expose at least three materials (found %s)"
|
|
% terrain.assets.get_texture_count()
|
|
)
|
|
_check(
|
|
absf(terrain.data.get_height(Vector3.ZERO)) < 0.1,
|
|
"Central gameplay area should remain level for current navigation"
|
|
)
|
|
_check(
|
|
terrain.data.get_height(Vector3(-72, 0, 18)) > 10.0,
|
|
"Authored terrain should raise the western fortress ridge"
|
|
)
|
|
_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.get_node("FoliageRoot").get_child_count() >= 10,
|
|
"JajceWorld should include restrained authored foliage clusters"
|
|
)
|
|
_check(
|
|
world.has_node("FortressBlockout"),
|
|
"JajceWorld should include the fortress landmark"
|
|
)
|
|
_check(
|
|
world.has_node("WaterRoot/WaterfallBody"),
|
|
"WaterRoot should include a waterfall drop"
|
|
)
|
|
_check(
|
|
world.has_node("WaterRoot/WaterfallMist"),
|
|
"WaterRoot should include waterfall mist particles"
|
|
)
|
|
_check(
|
|
world.has_node("WaterRoot/RiverSurface"),
|
|
"WaterRoot should include a river surface"
|
|
)
|
|
var environment: Environment = (
|
|
world.get_node("WorldEnvironment") as WorldEnvironment
|
|
).environment
|
|
_check(
|
|
environment.fog_enabled and environment.sky != null,
|
|
"Jajce environment should provide sky and restrained valley fog"
|
|
)
|
|
_check(
|
|
world.has_node("VillageRoot/Mill")
|
|
and world.has_node("VillageRoot/Bridge"),
|
|
"VillageRoot should include the mill and bridge blockouts"
|
|
)
|
|
var smoke_count := 0
|
|
for suffix in ["House_01", "House_02", "House_03"]:
|
|
if world.has_node("VillageRoot/%s/Smoke" % suffix):
|
|
smoke_count += 1
|
|
_check(
|
|
smoke_count >= 2,
|
|
"At least two houses should have chimney smoke particles (found %s)" % smoke_count
|
|
)
|
|
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 adapter := ActiveWorldAdapter.new()
|
|
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
|
|
var npc: SimNPC = manager.npcs[0]
|
|
npc.set_task(SimulationIds.ACTION_GATHER_FOOD)
|
|
_check(
|
|
manager.resolve_npc_target(npc.id, Vector3.ZERO),
|
|
"Target resolver should find Jajce resources without a parent path"
|
|
)
|
|
var selected := ResourceNode.get_by_id(npc.target_id)
|
|
if selected != null:
|
|
var selected_state: ResourceStateRecord = manager.get_resource_state(
|
|
selected.node_id
|
|
)
|
|
_check(
|
|
selected_state.get_reserved_by() == npc.id,
|
|
"Selected Jajce resource should be authoritatively reserved"
|
|
)
|
|
manager.release_resource(selected.node_id, npc.id)
|
|
|
|
if failures.is_empty():
|
|
print("[TEST] Jajce scaffold passed: Terrain3D, 6 textures, shader water, building blockouts, chimney smoke")
|
|
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)
|