extends SceneTree const MobView = preload("res://ui/mob_view.gd") var failures := 0 func _init() -> void: call_deferred("run") func run() -> void: for mode in ["0", "1"]: OS.set_environment("MTGODOT_GPUSKIN", mode) for race in [101, 110, 20001]: var view := MobView.new() if not view.build(AssetRoot.path(), null, race): failures += 1 view.free() continue root.add_child(view) view.anim.set_process(false) view.anim.set_time(0.3) var signed_area := 0.0 var total_area := 0.0 for instance in view.model.find_children("*", "MeshInstance3D", true, false): for surface in instance.mesh.get_surface_count(): var arrays: Array = instance.mesh.surface_get_arrays(surface) var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX] var normals: PackedVector3Array = arrays[Mesh.ARRAY_NORMAL] var indices: PackedInt32Array = arrays[Mesh.ARRAY_INDEX] for i in range(0, indices.size(), 3): var a := indices[i] var b := indices[i + 1] var c := indices[i + 2] var cross := (vertices[b] - vertices[a]).cross(vertices[c] - vertices[a]) signed_area += cross.dot((normals[a] + normals[b] + normals[c]).normalized()) total_area += cross.length() var ratio := signed_area / maxf(total_area, 0.00001) print("MOB winding race=%d GPU=%s normal_alignment=%f" % [race, mode, ratio]) # Godot's outward front face is clockwise; geometric cross is -normal. if ratio >= -0.5: failures += 1 printerr("FAIL: NPC/monster faces disagree with outward normals") view.free() await process_frame print("mob_winding_test: failures=%d" % failures) quit(1 if failures else 0)