97 lines
4.5 KiB
GDScript
97 lines
4.5 KiB
GDScript
extends SceneTree
|
|
|
|
## Asset-free formula, dependency and bounded timing regression for chunk LOD.
|
|
|
|
const PLANNER_SCRIPT := preload("res://src/render/terrain/terrain_chunk_lod_planner.gd")
|
|
const POLICY_SCRIPT := preload("res://src/render/terrain/terrain_chunk_lod_policy.gd")
|
|
const POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd")
|
|
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
|
|
|
|
|
|
func _initialize() -> void:
|
|
var failures: Array[String] = []
|
|
_verify_formula(failures)
|
|
_verify_disabled_and_custom_policy(failures)
|
|
_verify_loader_boundary(failures)
|
|
var elapsed_milliseconds := _verify_bounded_timing(failures)
|
|
if not failures.is_empty():
|
|
for failure in failures:
|
|
push_error("TERRAIN_CHUNK_LOD_PLANNER: %s" % failure)
|
|
quit(1)
|
|
return
|
|
print("TERRAIN_CHUNK_LOD_PLANNER PASS cases=7 iterations=250 elapsed_ms=%.3f" % elapsed_milliseconds)
|
|
quit(0)
|
|
|
|
|
|
func _verify_formula(failures: Array[String]) -> void:
|
|
var planner: RefCounted = PLANNER_SCRIPT.new()
|
|
var policy: RefCounted = POLICY_SCRIPT.new(true, 1, 2, 3, 10.0)
|
|
var focus: RefCounted = POSITION_SCRIPT.new(0.0, 9999.0, 0.0)
|
|
var chunks: Array = [
|
|
{"origin": Vector3(-5.0, 2000.0, -5.0)},
|
|
{"origin": Vector3(5.0, -3000.0, -5.0)},
|
|
{"origin": Vector3(5.001, 0.0, -5.0)},
|
|
{"origin": Vector3(15.0, 0.0, -5.0)},
|
|
{"origin": Vector3(20.0, 0.0, -5.0)},
|
|
{"origin": Vector3(25.0, 0.0, -5.0)},
|
|
{"origin": Vector3(25.001, 0.0, -5.0)},
|
|
{},
|
|
]
|
|
var desired: Dictionary = planner.call("plan", chunks, focus, policy)
|
|
_expect_equal(int(desired.get(0, -1)), 0, "center ignores vertical distance", failures)
|
|
_expect_equal(int(desired.get(1, -1)), 0, "lod0 inclusive boundary", failures)
|
|
_expect_equal(int(desired.get(2, -1)), 1, "just outside lod0", failures)
|
|
_expect_equal(int(desired.get(3, -1)), 1, "lod1 inclusive boundary", failures)
|
|
_expect_equal(int(desired.get(4, -1)), 2, "between lod1 and lod2", failures)
|
|
_expect_equal(int(desired.get(5, -1)), 2, "lod2 inclusive boundary", failures)
|
|
_expect_true(not desired.has(6) and not desired.has(7), "outside and sparse chunks absent", failures)
|
|
|
|
|
|
func _verify_disabled_and_custom_policy(failures: Array[String]) -> void:
|
|
var planner: RefCounted = PLANNER_SCRIPT.new()
|
|
var focus: RefCounted = POSITION_SCRIPT.new(0.0, 0.0, 0.0)
|
|
var chunks: Array = [{"origin": Vector3(-5.0, 0.0, -5.0)}]
|
|
_expect_true((planner.call("plan", chunks, focus, POLICY_SCRIPT.new(false, 1, 2, 3, 10.0)) as Dictionary).is_empty(), "disabled policy empty", failures)
|
|
var negative_policy: RefCounted = POLICY_SCRIPT.new(true, -1, -2, -3, 10.0)
|
|
_expect_equal(int((planner.call("plan", chunks, focus, negative_policy) as Dictionary).get(0, -1)), 0, "negative radius keeps squared semantics", failures)
|
|
|
|
|
|
func _verify_loader_boundary(failures: Array[String]) -> void:
|
|
var source := _read_text(LOADER_PATH, failures)
|
|
_expect_true(source.contains("TERRAIN_CHUNK_LOD_PLANNER_SCRIPT.new()"), "loader composes planner", failures)
|
|
_expect_true(source.contains("_terrain_chunk_lod_planner.plan(chunks, typed_focus_position, terrain_chunk_lod_policy)"), "loader delegates plan", failures)
|
|
_expect_true(not source.contains("dist_sq <= lod0_sq"), "loader omits chunk LOD formula", failures)
|
|
|
|
|
|
func _verify_bounded_timing(failures: Array[String]) -> float:
|
|
var planner: RefCounted = PLANNER_SCRIPT.new()
|
|
var policy: RefCounted = POLICY_SCRIPT.new(true, 10, 20, 40, 33.333333)
|
|
var focus: RefCounted = POSITION_SCRIPT.new(1000.0, 0.0, 1000.0)
|
|
var chunks: Array = []
|
|
for chunk_index in range(256):
|
|
chunks.append({"origin": Vector3(float(chunk_index % 16) * 33.333333, 0.0, float(chunk_index / 16) * 33.333333)})
|
|
var started_microseconds := Time.get_ticks_usec()
|
|
for _iteration in range(250):
|
|
planner.call("plan", chunks, focus, policy)
|
|
var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
|
|
_expect_true(elapsed_milliseconds < 1000.0, "250 plans remain bounded", failures)
|
|
return elapsed_milliseconds
|
|
|
|
|
|
func _read_text(path: String, failures: Array[String]) -> String:
|
|
var file := FileAccess.open(path, FileAccess.READ)
|
|
if file == null:
|
|
failures.append("cannot open %s" % path)
|
|
return ""
|
|
return file.get_as_text()
|
|
|
|
|
|
func _expect_equal(actual_value: int, expected_value: int, label: String, failures: Array[String]) -> void:
|
|
if actual_value != expected_value:
|
|
failures.append("%s expected %d, got %d" % [label, expected_value, actual_value])
|
|
|
|
|
|
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
|
|
if not actual_value:
|
|
failures.append("%s expected true" % label)
|