195 lines
6.2 KiB
GDScript
195 lines
6.2 KiB
GDScript
extends SceneTree
|
|
|
|
## Synthetic main-thread scene, ownership, boundary and timing regression for
|
|
## ADT water finalization.
|
|
|
|
const FINALIZER_SCRIPT := preload("res://src/render/liquid/adt_water_scene_finalizer.gd")
|
|
const FINALIZER_PATH := "res://src/render/liquid/adt_water_scene_finalizer.gd"
|
|
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
|
|
|
|
|
|
func _initialize() -> void:
|
|
var failures: Array[String] = []
|
|
_verify_invalid_and_dry_input(failures)
|
|
_verify_build_attach_geometry_and_owner(failures)
|
|
_verify_ownership_boundaries(failures)
|
|
var elapsed_milliseconds := _verify_bounded_timing(failures)
|
|
if not failures.is_empty():
|
|
for failure in failures:
|
|
push_error("ADT_WATER_SCENE_FINALIZER: %s" % failure)
|
|
quit(1)
|
|
return
|
|
print(
|
|
"ADT_WATER_SCENE_FINALIZER PASS cases=8 iterations=100 elapsed_ms=%.3f"
|
|
% elapsed_milliseconds
|
|
)
|
|
quit(0)
|
|
|
|
|
|
func _verify_invalid_and_dry_input(failures: Array[String]) -> void:
|
|
var finalizer: RefCounted = FINALIZER_SCRIPT.new()
|
|
var tile_root := Node3D.new()
|
|
_expect_null(
|
|
finalizer.call("attach_water_scene", {}, Vector3.ZERO, tile_root, null),
|
|
"empty water data returns null",
|
|
failures
|
|
)
|
|
_expect_equal(tile_root.get_child_count(), 0, "empty data does not mutate tile", failures)
|
|
_expect_null(
|
|
finalizer.call("attach_water_scene", _synthetic_water_data(), Vector3.ZERO, null, null),
|
|
"missing tile root returns null",
|
|
failures
|
|
)
|
|
_expect_null(
|
|
finalizer.call(
|
|
"attach_water_scene",
|
|
{"chunks": [{"origin": Vector3.ZERO, "liquids": []}]},
|
|
Vector3.ZERO,
|
|
tile_root,
|
|
null
|
|
),
|
|
"dry parsed ADT returns null",
|
|
failures
|
|
)
|
|
_expect_equal(tile_root.get_child_count(), 0, "dry data does not mutate tile", failures)
|
|
tile_root.free()
|
|
|
|
|
|
func _verify_build_attach_geometry_and_owner(failures: Array[String]) -> void:
|
|
var finalizer: RefCounted = FINALIZER_SCRIPT.new()
|
|
var editor_scene_root := Node3D.new()
|
|
var tile_root := Node3D.new()
|
|
get_root().add_child(editor_scene_root)
|
|
editor_scene_root.add_child(tile_root)
|
|
|
|
var tile_origin := Vector3(10.0, 20.0, 30.0)
|
|
var water_root: Node3D = finalizer.call(
|
|
"attach_water_scene",
|
|
_synthetic_water_data(tile_origin),
|
|
tile_origin,
|
|
tile_root,
|
|
editor_scene_root
|
|
)
|
|
_expect_not_null(water_root, "synthetic liquid builds a water root", failures)
|
|
if water_root != null:
|
|
_expect_true(water_root.get_parent() == tile_root, "water root attached once", failures)
|
|
_expect_equal(tile_root.get_child_count(), 1, "tile owns one water root", failures)
|
|
_expect_true(water_root.owner == editor_scene_root, "water root editor owner", failures)
|
|
_expect_equal(water_root.get_child_count(), 1, "one liquid surface grouped", failures)
|
|
if water_root.get_child_count() == 1:
|
|
var liquid_mesh_instance := water_root.get_child(0) as MeshInstance3D
|
|
_expect_not_null(liquid_mesh_instance, "liquid child is MeshInstance3D", failures)
|
|
if liquid_mesh_instance != null:
|
|
_expect_true(
|
|
liquid_mesh_instance.owner == editor_scene_root,
|
|
"liquid child editor owner",
|
|
failures
|
|
)
|
|
var liquid_mesh := liquid_mesh_instance.mesh as ArrayMesh
|
|
_expect_not_null(liquid_mesh, "liquid surface has ArrayMesh", failures)
|
|
if liquid_mesh != null:
|
|
_expect_equal(
|
|
liquid_mesh.surface_get_array_len(0),
|
|
4,
|
|
"one enabled liquid cell has four vertices",
|
|
failures
|
|
)
|
|
_expect_equal(
|
|
liquid_mesh.surface_get_array_index_len(0),
|
|
6,
|
|
"one enabled liquid cell has six indices",
|
|
failures
|
|
)
|
|
editor_scene_root.free()
|
|
|
|
|
|
func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
|
var finalizer_source := FileAccess.get_file_as_string(FINALIZER_PATH)
|
|
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
|
|
_expect_true(
|
|
finalizer_source.contains("ADT_BUILDER_SCRIPT.build_tile_water_scene"),
|
|
"finalizer retains existing ADTBuilder implementation",
|
|
failures
|
|
)
|
|
_expect_true(
|
|
finalizer_source.contains("tile_root.add_child(water_root)"),
|
|
"finalizer owns water subtree attachment",
|
|
failures
|
|
)
|
|
_expect_equal(
|
|
loader_source.count("_adt_water_scene_finalizer.attach_water_scene("),
|
|
2,
|
|
"both loader water paths delegate finalization",
|
|
failures
|
|
)
|
|
_expect_false(
|
|
loader_source.contains("_builder.build_tile_water_scene"),
|
|
"loader no longer builds water scenes directly",
|
|
failures
|
|
)
|
|
_expect_true(
|
|
loader_source.contains("state[\"water_loaded\"] = true"),
|
|
"loader retains water-loaded timing",
|
|
failures
|
|
)
|
|
_expect_true(
|
|
loader_source.contains("WATER_FINALIZE"),
|
|
"loader retains finalization permit ownership",
|
|
failures
|
|
)
|
|
|
|
|
|
func _verify_bounded_timing(failures: Array[String]) -> float:
|
|
var finalizer: RefCounted = FINALIZER_SCRIPT.new()
|
|
var tile_root := Node3D.new()
|
|
var started_microseconds := Time.get_ticks_usec()
|
|
for _iteration in range(100):
|
|
finalizer.call("attach_water_scene", {}, Vector3.ZERO, tile_root, null)
|
|
var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
|
|
_expect_true(elapsed_milliseconds < 1000.0, "100 empty attempts under 1 second", failures)
|
|
_expect_equal(tile_root.get_child_count(), 0, "timing loop creates no nodes", failures)
|
|
tile_root.free()
|
|
return elapsed_milliseconds
|
|
|
|
|
|
func _synthetic_water_data(tile_origin: Vector3 = Vector3.ZERO) -> Dictionary:
|
|
var liquid_mask := PackedByteArray()
|
|
liquid_mask.resize(64)
|
|
liquid_mask.fill(0)
|
|
liquid_mask[0] = 1
|
|
var liquid_heights := PackedFloat32Array()
|
|
liquid_heights.resize(81)
|
|
liquid_heights.fill(tile_origin.y + 5.0)
|
|
return {
|
|
"chunks": [{
|
|
"origin": tile_origin,
|
|
"liquids": [{
|
|
"liquid_id": 7,
|
|
"mask": liquid_mask,
|
|
"heights": liquid_heights,
|
|
}],
|
|
}],
|
|
}
|
|
|
|
|
|
func _expect_true(condition: bool, label: String, failures: Array[String]) -> void:
|
|
if not condition:
|
|
failures.append(label)
|
|
|
|
|
|
func _expect_false(condition: bool, label: String, failures: Array[String]) -> void:
|
|
_expect_true(not condition, label, failures)
|
|
|
|
|
|
func _expect_null(value: Variant, label: String, failures: Array[String]) -> void:
|
|
_expect_true(value == null, label, failures)
|
|
|
|
|
|
func _expect_not_null(value: Variant, label: String, failures: Array[String]) -> void:
|
|
_expect_true(value != null, label, failures)
|
|
|
|
|
|
func _expect_equal(actual: int, expected: int, label: String, failures: Array[String]) -> void:
|
|
if actual != expected:
|
|
failures.append("%s expected=%d actual=%d" % [label, expected, actual])
|