feat: add witnessed knowledge and wind ambience
This commit is contained in:
@@ -67,6 +67,94 @@ func _run() -> void:
|
||||
world.get_node("FoliageRoot").get_child_count() >= 10,
|
||||
"JajceWorld should include restrained authored foliage clusters"
|
||||
)
|
||||
_check(
|
||||
(
|
||||
not world.has_node("WindStrokes_Valley")
|
||||
and not world.has_node("WindStrokes_Ridge")
|
||||
and not world.has_node("WindSpecks_Valley")
|
||||
and not world.has_node("WindSpecks_Ridge")
|
||||
),
|
||||
"Legacy overlapping valley and ridge wind fields should remain removed"
|
||||
)
|
||||
var wind_gust_field := world.get_node("AtmosphereRoot/WindGustField") as WindGustField
|
||||
_check(wind_gust_field != null, "Jajce should expose one bounded wind-gust controller")
|
||||
if wind_gust_field != null:
|
||||
_check(
|
||||
(
|
||||
wind_gust_field.interval_min >= 5.0
|
||||
and wind_gust_field.interval_max <= 12.0
|
||||
and wind_gust_field.duration_max <= 2.5
|
||||
and wind_gust_field.stroke_count_max <= 3
|
||||
),
|
||||
"Calligraphic wind should remain sporadic, brief, and sparse"
|
||||
)
|
||||
_check(
|
||||
wind_gust_field.trigger_gust_at(Vector3.ZERO, true, 0.5),
|
||||
"Wind controller should support a deterministic staged gust"
|
||||
)
|
||||
var active_stroke_count := wind_gust_field.get_active_stroke_count()
|
||||
_check(
|
||||
active_stroke_count in [2, 3],
|
||||
"One gust should contain only two or three related brush strokes"
|
||||
)
|
||||
var gust_burst := wind_gust_field.get_node_or_null("WindGustBurst") as Node3D
|
||||
var gust_geometry_valid := gust_burst != null
|
||||
if gust_burst != null:
|
||||
for stroke in gust_burst.get_children():
|
||||
var mesh_instance := stroke as MeshInstance3D
|
||||
if mesh_instance == null:
|
||||
gust_geometry_valid = false
|
||||
continue
|
||||
var material := mesh_instance.material_override as ShaderMaterial
|
||||
var tint: Color = (
|
||||
material.get_shader_parameter("tint") if material != null else Color.WHITE
|
||||
)
|
||||
var progress := (
|
||||
float(material.get_shader_parameter("progress")) if material != null else 0.0
|
||||
)
|
||||
gust_geometry_valid = (
|
||||
gust_geometry_valid
|
||||
and mesh_instance.mesh is ArrayMesh
|
||||
and material != null
|
||||
and material.shader == load("res://world/jajce/materials/wind_gust.gdshader")
|
||||
and tint.a >= 0.18
|
||||
and tint.a <= 0.3
|
||||
and progress >= 0.4
|
||||
and progress <= 0.6
|
||||
and (mesh_instance.cast_shadow == GeometryInstance3D.SHADOW_CASTING_SETTING_OFF)
|
||||
)
|
||||
_check(
|
||||
gust_geometry_valid,
|
||||
"Gust strokes should use soft tapered meshes without bright alpha or shadows"
|
||||
)
|
||||
_check(
|
||||
(
|
||||
not wind_gust_field.trigger_gust_at(Vector3.ZERO)
|
||||
and wind_gust_field.get_active_stroke_count() == active_stroke_count
|
||||
),
|
||||
"Repeated gust triggers should not stack visual fields"
|
||||
)
|
||||
var tree_phases: Array[float] = []
|
||||
var tree_wind_is_bounded := true
|
||||
for tree in world.get_node("FoliageRoot").get_children():
|
||||
if not tree.has_node("Canopy"):
|
||||
continue
|
||||
var canopy := tree.get_node("Canopy") as MeshInstance3D
|
||||
var material := canopy.mesh.material as ShaderMaterial
|
||||
tree_wind_is_bounded = (
|
||||
tree_wind_is_bounded
|
||||
and material != null
|
||||
and material.shader == load("res://world/jajce/materials/wind.gdshader")
|
||||
and float(material.get_shader_parameter("wind_strength")) <= 0.08
|
||||
and float(material.get_shader_parameter("wind_speed")) <= 0.65
|
||||
)
|
||||
tree_phases.append(float(material.get_shader_parameter("wind_phase")))
|
||||
_check(tree_phases.size() >= 2, "Decorative trees should expose named wind-reactive canopies")
|
||||
_check(
|
||||
tree_phases.size() >= 2 and not is_equal_approx(tree_phases[0], tree_phases[1]),
|
||||
"Tree wind should use stable per-tree phase instead of moving every canopy in lockstep"
|
||||
)
|
||||
_check(tree_wind_is_bounded, "Tree wind should stay within the restrained cozy-motion budget")
|
||||
_check(world.has_node("FortressBlockout"), "JajceWorld should include the fortress landmark")
|
||||
_check(world.has_node("WaterRoot/WaterfallBody"), "WaterRoot should include a waterfall drop")
|
||||
_check(
|
||||
@@ -91,9 +179,11 @@ func _run() -> void:
|
||||
"Jajce environment should provide sky and restrained valley fog"
|
||||
)
|
||||
_check(
|
||||
environment.glow_enabled
|
||||
and environment.volumetric_fog_enabled
|
||||
and environment.adjustment_enabled,
|
||||
(
|
||||
environment.glow_enabled
|
||||
and environment.volumetric_fog_enabled
|
||||
and environment.adjustment_enabled
|
||||
),
|
||||
"WorldEnvironment should provide bounded glow, depth haze, and color grading"
|
||||
)
|
||||
_check(
|
||||
@@ -102,24 +192,47 @@ func _run() -> void:
|
||||
)
|
||||
var smoke_count := 0
|
||||
var smoke_is_translucent := true
|
||||
var smoke_follows_wind := true
|
||||
var smoke_direction := Vector2.ZERO
|
||||
for suffix in ["House_01", "House_02", "House_03"]:
|
||||
if world.has_node("VillageRoot/%s/Smoke" % suffix):
|
||||
var smoke: GPUParticles3D = world.get_node("VillageRoot/%s/Smoke" % suffix)
|
||||
if smoke.draw_pass_1 == null:
|
||||
continue
|
||||
var smoke_material := (smoke.draw_pass_1 as QuadMesh).material as StandardMaterial3D
|
||||
var smoke_process := smoke.process_material as ParticleProcessMaterial
|
||||
if smoke_process != null and smoke_direction.is_zero_approx():
|
||||
smoke_direction = (
|
||||
Vector2(smoke_process.gravity.x, smoke_process.gravity.z).normalized()
|
||||
)
|
||||
smoke_is_translucent = (
|
||||
smoke_is_translucent
|
||||
and smoke_material != null
|
||||
and smoke_material.transparency != BaseMaterial3D.TRANSPARENCY_DISABLED
|
||||
and smoke_material.albedo_color.a < 0.5
|
||||
)
|
||||
smoke_follows_wind = (
|
||||
smoke_follows_wind
|
||||
and smoke_process != null
|
||||
and smoke_process.gravity.x > 0.0
|
||||
and smoke_process.gravity.z > 0.0
|
||||
)
|
||||
smoke_count += 1
|
||||
_check(
|
||||
smoke_count >= 2,
|
||||
"At least two houses should have chimney smoke particles (found %s)" % smoke_count
|
||||
)
|
||||
_check(smoke_is_translucent, "Chimney smoke should not render as opaque white quads")
|
||||
_check(smoke_follows_wind, "Chimney smoke should lean with the prevailing canopy wind")
|
||||
var gust_direction := Vector2.ZERO
|
||||
if wind_gust_field != null:
|
||||
gust_direction = (
|
||||
Vector2(wind_gust_field.wind_direction.x, wind_gust_field.wind_direction.z).normalized()
|
||||
)
|
||||
_check(
|
||||
gust_direction.dot(smoke_direction) >= 0.98,
|
||||
"Calligraphic gusts, canopy bend, and chimney smoke should share one wind direction"
|
||||
)
|
||||
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")
|
||||
@@ -131,27 +244,29 @@ func _run() -> void:
|
||||
ids.append(String((resource as ResourceNode).node_id))
|
||||
ids.sort()
|
||||
_check(
|
||||
ids
|
||||
== [
|
||||
"animal_camp_01",
|
||||
"animal_camp_river_01",
|
||||
"berry_bush_01",
|
||||
"berry_bush_02",
|
||||
"berry_bush_river_02",
|
||||
"berry_patch_mill_01",
|
||||
"berry_patch_river_01",
|
||||
"berry_patch_south_01",
|
||||
"farm_crop_01",
|
||||
"tree_01",
|
||||
"tree_02",
|
||||
"tree_forest_grove_01",
|
||||
"tree_forest_grove_02",
|
||||
"tree_north_outskirts_01",
|
||||
"tree_river_resource_01",
|
||||
"tree_south_edge_01",
|
||||
"wood_pile_mill_01",
|
||||
"wood_pile_village_01"
|
||||
],
|
||||
(
|
||||
ids
|
||||
== [
|
||||
"animal_camp_01",
|
||||
"animal_camp_river_01",
|
||||
"berry_bush_01",
|
||||
"berry_bush_02",
|
||||
"berry_bush_river_02",
|
||||
"berry_patch_mill_01",
|
||||
"berry_patch_river_01",
|
||||
"berry_patch_south_01",
|
||||
"farm_crop_01",
|
||||
"tree_01",
|
||||
"tree_02",
|
||||
"tree_forest_grove_01",
|
||||
"tree_forest_grove_02",
|
||||
"tree_north_outskirts_01",
|
||||
"tree_river_resource_01",
|
||||
"tree_south_edge_01",
|
||||
"wood_pile_mill_01",
|
||||
"wood_pile_village_01"
|
||||
]
|
||||
),
|
||||
"Jajce resources should preserve all stable and discoverable IDs"
|
||||
)
|
||||
var metadata_valid := true
|
||||
@@ -190,7 +305,9 @@ func _run() -> void:
|
||||
_check(wood_count >= 9, "Jajce should expose at least nine finite wood resources")
|
||||
_check(animal_context_count >= 2, "Jajce food discovery should include animal-camp contexts")
|
||||
_check(berry_context_count >= 6, "Jajce food discovery should include berry contexts")
|
||||
_check(village_context_count >= 1, "Jajce discovery should include at least one village stockpile")
|
||||
_check(
|
||||
village_context_count >= 1, "Jajce discovery should include at least one village stockpile"
|
||||
)
|
||||
_check(outskirt_context_count >= 4, "Jajce discovery should include outskirts/river contexts")
|
||||
_check(farming_context_count >= 1, "Jajce food discovery should include farming contexts")
|
||||
_check(world.has_node("WorldObjects/StorageSites"), "JajceWorld should expose StorageSites")
|
||||
@@ -232,7 +349,10 @@ func _run() -> void:
|
||||
"Jajce navigation mesh should contain baked polygons"
|
||||
)
|
||||
_check(
|
||||
navigation_region.navigation_mesh.resource_path == "res://world/jajce/JajceNavigationMesh.tres",
|
||||
(
|
||||
navigation_region.navigation_mesh.resource_path
|
||||
== "res://world/jajce/JajceNavigationMesh.tres"
|
||||
),
|
||||
"Jajce navigation should use the Terrain3D-derived baked mesh resource"
|
||||
)
|
||||
await _wait_for_navigation_map(navigation_map)
|
||||
@@ -329,9 +449,7 @@ func _run() -> void:
|
||||
)
|
||||
|
||||
if failures.is_empty():
|
||||
print(
|
||||
"[TEST] Jajce scaffold passed: Terrain3D, scattered resources, typed sites"
|
||||
)
|
||||
print("[TEST] Jajce scaffold passed: Terrain3D, scattered resources, typed sites")
|
||||
quit(0)
|
||||
return
|
||||
|
||||
@@ -357,7 +475,9 @@ func _check(condition: bool, message: String) -> void:
|
||||
failures.append(message)
|
||||
|
||||
|
||||
func _check_path_tracks_terrain(terrain: Terrain3D, path: PackedVector3Array, message: String) -> void:
|
||||
func _check_path_tracks_terrain(
|
||||
terrain: Terrain3D, path: PackedVector3Array, message: String
|
||||
) -> void:
|
||||
if path.is_empty():
|
||||
return
|
||||
|
||||
@@ -368,13 +488,17 @@ func _check_path_tracks_terrain(terrain: Terrain3D, path: PackedVector3Array, me
|
||||
return
|
||||
if absf(point.y - terrain_height) > 0.85:
|
||||
failures.append(
|
||||
"%s: path point %s is too far from terrain height %.2f"
|
||||
% [message, point, terrain_height]
|
||||
(
|
||||
"%s: path point %s is too far from terrain height %.2f"
|
||||
% [message, point, terrain_height]
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
func _check_path_reaches_destination(path: PackedVector3Array, destination: Vector3, message: String) -> void:
|
||||
func _check_path_reaches_destination(
|
||||
path: PackedVector3Array, destination: Vector3, message: String
|
||||
) -> void:
|
||||
if path.is_empty():
|
||||
return
|
||||
|
||||
@@ -383,6 +507,5 @@ func _check_path_reaches_destination(path: PackedVector3Array, destination: Vect
|
||||
var destination_xz := Vector2(destination.x, destination.z)
|
||||
if final_xz.distance_to(destination_xz) > 1.5:
|
||||
failures.append(
|
||||
"%s: final path point %s is too far from %s"
|
||||
% [message, final_point, destination]
|
||||
"%s: final path point %s is too far from %s" % [message, final_point, destination]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user