Files
Ransom-s-wake/tests/integration/run_sunlit_shallows_environment.gd
T
OlivierandClaude Fable 5.1 50b5aac069
Quality Gate / Quality Gate (pull_request) Successful in 6m49s
Add Hebat Allah wreck to the sunlit shallows site
First wreck in the production world layer. A WreckActor (StaticBody3D)
instantiates a two-LOD glTF visual, builds a trimesh collision from a
separate lighter glTF, and carries the sonar metadata (metal, 0.9
reflectivity) so the bathymetry rays echo it. The sunlit shallows site
places the MV Hebat Allah 62 m ahead of the submarine on the floor.

The assets derive from a CC BY 4.0 photogrammetry scan by Holger Buss
(Sketchfab). The raw 274 MB scan stays outside the repo; the committed
GLBs come from a reproducible Blender pipeline under scripts/assets/:
skirt cut, 44 m rescale, merge by distance (the scan is a triangle soup),
fragment removal, 0.4 m voxel remesh, decimation to 58k/14.5k/2.9k
triangles, smart UVs and a 2K albedo rebaked from a luminance-rebuilt
texture (the scan's red channel is dead).

docs/ASSET_LICENSES.md starts the licence registry required by the design
plan and records the attribution owed in the credits. The site suite
gains twelve assertions covering sonar metadata, collision, LOD handover
and triangle budgets. Godot extracts the embedded albedo next to the glb
on import; those PNGs are ignored like the cockpit ones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 07:30:21 +02:00

314 lines
11 KiB
GDScript

extends SceneTree
## Checks the sunlit shallows visual site keeps its three promises:
## a flat floor, a calm shadow-only water surface, and a daytime sun whose
## hour never changes.
const SCENE_PATH := "res://game/world/sites/sunlit_shallows_environment.tscn"
const WORLD_SCENE_PATH := (
"res://game/composition/vertical_slice/sunlit_shallows_world.tscn"
)
var _failures: Array[String] = []
var _checks := 0
func _init() -> void:
_run.call_deferred()
func _run() -> void:
var scene := load(SCENE_PATH) as PackedScene
_expect(scene != null, "scene loads")
if scene == null:
_finish()
return
var site := scene.instantiate() as SunlitShallowsEnvironment
_expect(site != null, "scene root is SunlitShallowsEnvironment")
if site == null:
_finish()
return
root.add_child(site)
await process_frame
_test_fixed_daytime_sun(site)
_test_flat_floor(site)
_test_calm_surface_occluder(site)
_test_scattering_environment(site)
_test_air_fog_cut(site)
_test_surface_ceiling(site)
_test_wreck(site)
site.queue_free()
await process_frame
_test_composed_world()
_finish()
func _test_composed_world() -> void:
var world_scene := load(WORLD_SCENE_PATH) as PackedScene
_expect(world_scene != null, "composed world scene loads")
if world_scene == null:
return
var world := world_scene.instantiate() as Node3D
_expect(
world.get_node_or_null("Environment") is SunlitShallowsEnvironment,
"composed world uses the sunlit shallows environment"
)
var submarine := world.get_node_or_null("SubmarineActor") as Node3D
_expect(submarine != null, "composed world has a submarine")
if submarine != null:
_expect(
submarine.position.y < SunlitShallowsEnvironment.SURFACE_LEVEL_M
and submarine.position.y > SunlitShallowsEnvironment.FLOOR_DEPTH_M,
"submarine starts between surface and floor"
)
_expect(
submarine.get_node_or_null("CockpitInterior") != null
and submarine.get_node_or_null("CockpitCameraRig") != null,
"submarine carries cockpit and camera rig"
)
for projector_name: String in ["LeftProjector", "RightProjector"]:
var projector := submarine.get_node_or_null(projector_name) as SpotLight3D
_expect(projector != null, "%s exists" % projector_name)
if projector != null:
_expect(
projector.light_volumetric_fog_energy < 1.0,
"%s does not wash out the sun shafts in the fog" % projector_name
)
world.free()
func _test_fixed_daytime_sun(site: SunlitShallowsEnvironment) -> void:
var sun := site.get_node_or_null("Sun") as DirectionalLight3D
_expect(sun != null, "sun is a DirectionalLight3D")
if sun == null:
return
_expect(
sun.rotation_degrees.is_equal_approx(
SunlitShallowsEnvironment.SUN_ROTATION_DEGREES
),
"sun hour is the fixed constant"
)
var sun_direction := -sun.global_transform.basis.z
_expect(sun_direction.y < -0.6, "sun shines downward from above the porthole (daytime)")
_expect(
sun_direction.z > 0.15,
"sun shines toward the observer (rays come from ahead, world -Z)"
)
_expect(sun.shadow_enabled, "sun casts shadows (needed for light shafts)")
_expect(
sun.light_volumetric_fog_energy >= 1.0,
"sun injects energy into the volumetric fog"
)
func _test_flat_floor(site: SunlitShallowsEnvironment) -> void:
var floor_mesh := site.get_node_or_null("FlatFloor/Mesh") as MeshInstance3D
_expect(floor_mesh != null, "floor mesh exists")
if floor_mesh == null:
return
var vertices := floor_mesh.mesh.get_faces()
var flat := true
for vertex: Vector3 in vertices:
if not is_zero_approx(vertex.y):
flat = false
break
_expect(flat, "floor mesh is flat")
_expect(
is_equal_approx(
floor_mesh.global_position.y,
SunlitShallowsEnvironment.FLOOR_DEPTH_M
),
"floor sits at the site depth"
)
_expect(
floor_mesh.material_override is ShaderMaterial,
"floor uses the caustic shader material"
)
var collision := site.get_node_or_null("FlatFloor/Collision") as CollisionShape3D
_expect(collision != null and collision.shape is BoxShape3D, "floor has a box collider")
func _test_calm_surface_occluder(site: SunlitShallowsEnvironment) -> void:
var occluder := site.get_node_or_null("CalmSurfaceOccluder") as MeshInstance3D
_expect(occluder != null, "surface occluder exists")
if occluder == null:
return
_expect(
occluder.cast_shadow == GeometryInstance3D.SHADOW_CASTING_SETTING_SHADOWS_ONLY,
"surface occluder is shadow-only"
)
_expect(
is_equal_approx(
occluder.global_position.y,
SunlitShallowsEnvironment.SURFACE_LEVEL_M
),
"surface occluder sits at water level"
)
var material := occluder.material_override as ShaderMaterial
_expect(material != null, "surface occluder uses a shader material")
if material == null:
return
var drift: Variant = material.get_shader_parameter("drift_speed")
_expect(
drift is float and drift > 0.0 and drift < 0.05,
"surface drift is slow (calm water)"
)
_expect(
material.get_shader_parameter("ripple_noise") is NoiseTexture2D,
"surface ripple noise is assigned"
)
var pool_center: Variant = material.get_shader_parameter("pool_center_xz")
_expect(
pool_center is Vector2 and pool_center.y < -20.0 and pool_center.y > -120.0,
"light pool sits a few tens of metres ahead (world -Z)"
)
var pool_radius: Variant = material.get_shader_parameter("pool_radius")
_expect(
pool_radius is float and pool_radius > 5.0 and pool_radius < 60.0,
"light pool radius is local, not the whole surface"
)
func _test_scattering_environment(site: SunlitShallowsEnvironment) -> void:
var world_environment := site.get_node_or_null("WorldEnvironment") as WorldEnvironment
_expect(world_environment != null, "world environment exists")
if world_environment == null:
return
var environment := world_environment.environment
_expect(environment.volumetric_fog_enabled, "volumetric fog is enabled")
_expect(environment.volumetric_fog_anisotropy > 0.5, "fog scatters forward")
_expect(environment.fog_enabled, "depth fog is enabled")
func _test_surface_ceiling(site: SunlitShallowsEnvironment) -> void:
var ceiling := site.get_node_or_null("SurfaceCeiling") as MeshInstance3D
_expect(ceiling != null, "visible surface ceiling exists")
if ceiling == null:
return
_expect(
is_equal_approx(ceiling.global_position.y, SunlitShallowsEnvironment.SURFACE_LEVEL_M),
"surface ceiling sits at water level"
)
_expect(
ceiling.cast_shadow == GeometryInstance3D.SHADOW_CASTING_SETTING_OFF,
"surface ceiling leaves shadowing to the occluder"
)
var ceiling_material := ceiling.material_override as ShaderMaterial
var occluder := site.get_node_or_null("CalmSurfaceOccluder") as MeshInstance3D
var occluder_material: ShaderMaterial = null if occluder == null else occluder.material_override
_expect(
ceiling_material != null
and occluder_material != null
and ceiling_material.get_shader_parameter("pool_center_xz")
== occluder_material.get_shader_parameter("pool_center_xz")
and ceiling_material.get_shader_parameter("pool_radius")
== occluder_material.get_shader_parameter("pool_radius"),
"surface ceiling lights the same pool the occluder opens"
)
func _test_air_fog_cut(site: SunlitShallowsEnvironment) -> void:
var air := site.get_node_or_null("AirFogCut") as FogVolume
_expect(air != null, "air fog cut exists")
if air == null:
return
var bottom := air.global_position.y - air.size.y * 0.5
_expect(
is_equal_approx(bottom, SunlitShallowsEnvironment.SURFACE_LEVEL_M),
"air fog cut starts at the water surface"
)
var material := air.material as FogMaterial
_expect(material != null and material.density < 0.0, "air fog cut subtracts fog")
var world_environment := site.get_node("WorldEnvironment") as WorldEnvironment
_expect(
material != null and is_equal_approx(
-material.density,
world_environment.environment.volumetric_fog_density
),
"air fog cut cancels the global volumetric density exactly"
)
func _test_wreck(site: SunlitShallowsEnvironment) -> void:
var wreck := site.get_node_or_null("HebatAllahWreck") as WreckActor
_expect(wreck != null, "site places the Hebat Allah wreck")
if wreck == null:
return
_expect(
int(wreck.get_meta("sonar_material", SonarMaterial.Value.UNKNOWN))
== SonarMaterial.Value.METAL,
"wreck echoes as metal on the sonar"
)
_expect((wreck.collision_layer & 1) == 1, "wreck sits on the sonar collision layer")
_expect(
wreck.position.y < SunlitShallowsEnvironment.SURFACE_LEVEL_M
and absf(wreck.position.y - SunlitShallowsEnvironment.FLOOR_DEPTH_M) < 3.0,
"wreck rests on the site floor"
)
var collision := wreck.get_node_or_null(WreckActor.COLLISION_SHAPE_NAME) as CollisionShape3D
_expect(
collision != null and collision.shape is ConcavePolygonShape3D,
"wreck has a trimesh collision separate from its visuals"
)
if collision != null and collision.shape is ConcavePolygonShape3D:
var faces := (collision.shape as ConcavePolygonShape3D).get_faces()
_expect(faces.size() >= 3 * 500, "wreck collision mesh is not empty")
_expect(faces.size() <= 3 * 6000, "wreck collision mesh stays light")
var lod0 := wreck.find_child(WreckActor.LOD0_NODE_NAME, true, false) as MeshInstance3D
var lod1 := wreck.find_child(WreckActor.LOD1_NODE_NAME, true, false) as MeshInstance3D
_expect(lod0 != null and lod1 != null, "wreck carries two visual LODs")
if lod0 == null or lod1 == null:
return
_expect(
lod0.visibility_range_end > 0.0
and is_equal_approx(lod1.visibility_range_begin, lod0.visibility_range_end),
"wreck LODs hand over at one distance"
)
var lod0_triangles := _triangle_count(lod0.mesh)
var lod1_triangles := _triangle_count(lod1.mesh)
_expect(
lod0_triangles > 0 and lod0_triangles <= 120000,
"wreck LOD0 stays within the triangle budget"
)
_expect(lod1_triangles > 0 and lod1_triangles < lod0_triangles / 2, "wreck LOD1 is far lighter")
_expect(
lod0.mesh.surface_get_material(0) != null
or lod0.get_surface_override_material(0) != null,
"wreck LOD0 carries a material"
)
func _triangle_count(mesh: Mesh) -> int:
if mesh == null:
return 0
var triangles := 0
for surface in mesh.get_surface_count():
var arrays := mesh.surface_get_arrays(surface)
var indices: PackedInt32Array = arrays[Mesh.ARRAY_INDEX]
if indices.is_empty():
var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
triangles += vertices.size() / 3
else:
triangles += indices.size() / 3
return triangles
func _expect(condition: bool, label: String) -> void:
_checks += 1
if not condition:
_failures.append(label)
func _finish() -> void:
if _failures.is_empty():
print("Sunlit shallows environment: %d passed, 0 failed." % _checks)
quit(0)
else:
for failure: String in _failures:
push_error("sunlit shallows environment: %s" % failure)
print("Sunlit shallows environment: %d failed." % _failures.size())
quit(1)