extends SceneTree const PlayerView = preload("res://ui/player_view.gd") var failures: Array[String] = [] var ghosts_checked := 0 func _initialize() -> void: call_deferred("run") func check(ok: bool, message: String) -> void: if not ok: failures.append(message) printerr("FAIL: ", message) func run() -> void: OS.set_environment("MTGODOT_GPUSKIN", "1") var cam := Camera3D.new() root.add_child(cam) cam.make_current() for race in 8: cam.position = Vector3(0, 1, 3) var pv := PlayerView.new() root.add_child(pv) if not pv.build(AssetRoot.path(), race): check(false, "race %d build failed" % race) pv.queue_free() continue pv.anim.set_process(false) pv.anim.set("blend_time", 0.0) pv.anim.set("time", 0.5) var mesh_node: MeshInstance3D = pv.model.get_node("MeshInstance3D") var reference_hair: PackedVector3Array = mesh_node.mesh.surface_get_arrays(mesh_node.mesh.get_surface_count() - 1)[Mesh.ARRAY_VERTEX] for level in [0, 1, 2, 3, 2, 1, 0]: cam.position = Vector3(0, 1, [3.0, 25.0, 60.0, 110.0][level]) for frame in 3: await process_frame check(int(pv.model.call("get_lod_level")) == level, "race %d did not reach LOD %d" % [race, level]) var mesh := mesh_node.mesh var last := mesh.get_surface_count() - 1 check(String(mesh.surface_get_name(last)).begins_with("hair_"), "race %d LOD %d lost hair" % [race, level]) var vertices: PackedVector3Array = mesh.surface_get_arrays(last)[Mesh.ARRAY_VERTEX] check(vertices == reference_hair, "race %d LOD %d changed hair geometry" % [race, level]) var mat: ShaderMaterial = mesh_node.get_active_material(last) check(int(mat.get_shader_parameter("mode")) == 2, "race %d LOD %d lost hair cutout" % [race, level]) check(mat.get_shader_parameter("bones_tex") != null, "race %d LOD %d lost skin matrices" % [race, level]) var ghost: MeshInstance3D = pv.model.get_node_or_null("LodGhost") if ghost != null: ghosts_checked += 1 var old_mat: ShaderMaterial = ghost.get_active_material(0) var frozen: Texture2D = old_mat.get_shader_parameter("bones_tex") check(frozen != mat.get_shader_parameter("bones_tex"), "LOD ghost shares live bone texture") var before := frozen.get_image().get_data() pv.anim.set("time", 1.0) check(before == frozen.get_image().get_data(), "LOD ghost pose changed after animation sample") check(ghost.custom_aabb.size.length_squared() > 0, "LOD ghost lost posed culling bounds") for surface in ghost.mesh.get_surface_count(): check(ghost.get_active_material(surface) != null, "LOD ghost lost surface material") pv.anim.set("time", 0.5) pv.queue_free() await process_frame check(ghosts_checked > 0, "No LOD fade snapshots were exercised") print("gpu_lod_attachment_test: failures=", failures.size(), " ghosts=", ghosts_checked) quit(0 if failures.is_empty() else 1)