extends Node ## ## SelfTest - headless verification of every core subsystem. ## Run: godot --headless res://tests/self_test.tscn ## ## Exits with code 0 if all checks pass, 1 otherwise. This is the regression ## gate for the modules; it does not need a display or GPU. ## var _failures := 0 var _host: Node3D func _ready() -> void: _host = Node3D.new() _host.name = "TestHost" var geom := Node3D.new() geom.name = "Geometry" _host.add_child(geom) var rig := Node3D.new() rig.name = "Rig" _host.add_child(rig) add_child(_host) _test_memory_manager() _test_mesh_generators() _test_sculpt() _test_rigging() _test_simulation() _test_timeline() print("") if _failures == 0: print("SELFTEST: ALL PASS") get_tree().quit(0) else: print("SELFTEST: %d FAILURE(S)" % _failures) get_tree().quit(1) func _check(name: String, cond: bool) -> void: if cond: print(" [PASS] ", name) else: _failures += 1 print(" [FAIL] ", name) func _test_memory_manager() -> void: print("[Test] MemoryManager") _check("budget set", MemoryManager.asset_budget_mb > 0.0) _check("feature count >= 20", FeatureRegistry.feature_count() >= 20) var snap := MemoryManager.debug_snapshot() _check("snapshot has budget", snap.has("budget_mb")) func _load_module(path: String) -> Node: var script: GDScript = load(path) var n: Node = script.new() _host.add_child(n) if n.has_method("_activate"): n.call("_activate", _host) return n func _test_mesh_generators() -> void: print("[Test] Mesh generators") var mg := _load_module("res://scripts/modules/mesh_gen.gd") for kind in ["cube", "sphere", "cylinder", "plane", "torus", "ico", "suzanne"]: var data: Dictionary = mg.call("generate", kind, {}) var vcount: int = (data["verts"] as PackedVector3Array).size() var icount: int = (data["indices"] as PackedInt32Array).size() _check("%s has verts & tris (v=%d,i=%d)" % [kind, vcount, icount], vcount > 0 and icount > 0) var mi: MeshInstance3D = mg.call("spawn", "sphere", {"radius": 1.0}) _check("spawn returns MeshInstance3D", mi != null and mi.mesh != null and mi.mesh.get_surface_count() == 1) var cost := VertexBufferManager.estimate_cost_mb(1000) _check("cost estimate > 0", cost > 0.0) mg.queue_free() func _test_sculpt() -> void: print("[Test] Sculpting") var mg := _load_module("res://scripts/modules/mesh_gen.gd") var sc := _load_module("res://scripts/modules/sculpt.gd") var mi: MeshInstance3D = mg.call("spawn", "sphere", {"radius": 1.0}) sc.call("set_target", mi) var before: PackedVector3Array = (mi.mesh.surface_get_arrays(0)[Mesh.ARRAY_VERTEX] as PackedVector3Array).duplicate() for brush in ["draw", "grab", "pinch", "smooth", "flatten"]: sc.set("_brush_id", brush) sc.call("begin", Vector3(0, 1, 0)) sc.call("apply", Vector3(0, 1, 0), Vector3(0, 0.1, 0)) sc.call("apply", Vector3(0, 1, 0), Vector3(0.05, 0.1, 0)) sc.call("end") var after: PackedVector3Array = mi.mesh.surface_get_arrays(0)[Mesh.ARRAY_VERTEX] var changed := false for i in range(mini(before.size(), after.size())): if before[i].distance_to(after[i]) > 0.0001: changed = true break _check("sculpt modified vertices", changed) _check("mesh still valid after sculpt", mi.mesh.get_surface_count() == 1) mg.queue_free() sc.queue_free() func _test_rigging() -> void: print("[Test] Rigging") var mg := _load_module("res://scripts/modules/mesh_gen.gd") var rg := _load_module("res://scripts/modules/rigging.gd") var mi: MeshInstance3D = mg.call("spawn", "cube", {"size": 1.0}) rg.call("generate_default_armature", mi) _check("bones created", int(rg.call("bone_count")) >= 9) _check("mesh has skin", mi.skin != null) _check("mesh has skeleton path", mi.skeleton != NodePath()) if mi.mesh.get_surface_count() > 0: var arr: Array = mi.mesh.surface_get_arrays(0) _check("bones channel present", arr[Mesh.ARRAY_BONES] != null) _check("weights channel present", arr[Mesh.ARRAY_WEIGHTS] != null) mg.queue_free() rg.queue_free() func _test_simulation() -> void: print("[Test] Simulation") var sim := _load_module("res://scripts/modules/simulation.gd") sim.call("create_cloth", "sim.cloth", 6, 6, 1.0) var before: PackedVector3Array = (sim.get("_sims")["sim.cloth"]["particles"] as PackedVector3Array).duplicate() for i in range(30): sim.call("_step", sim.get("_sims")["sim.cloth"]) sim.call("_upload", sim.get("_sims")["sim.cloth"]) var after: PackedVector3Array = sim.get("_sims")["sim.cloth"]["particles"] var moved := false for i in range(after.size()): if before[i].distance_to(after[i]) > 0.0001: moved = true break _check("cloth fell under gravity", moved) var mesh: ArrayMesh = sim.get("_sims")["sim.cloth"]["mesh"] _check("cloth mesh uploaded", mesh.get_surface_count() == 1) sim.call("create_hair", "sim.hair", 4, 5) _check("hair sim created", int(sim.call("active_sim_count")) >= 2) sim.queue_free() func _test_timeline() -> void: print("[Test] Timeline") var mg := _load_module("res://scripts/modules/mesh_gen.gd") var tl := _load_module("res://scripts/modules/timeline.gd") var mi: MeshInstance3D = mg.call("spawn", "cube", {"size": 1.0}) tl.call("add_keyframe", mi, "position", 0.0, Vector3.ZERO) tl.call("add_keyframe", mi, "position", 1.0, Vector3(0, 2, 0)) _check("track created", int(tl.call("track_count")) >= 1) _check("key count == 2", int(tl.call("key_count")) == 2) tl.call("scrub", 0.5) _check("scrub clamps/works", absf(float(tl.call("playhead")) - 0.5) < 0.001) mg.queue_free() tl.queue_free()