extends SceneTree ## Does every surface of every shipping skin get classified, and does the ## classification survive the trip from Blender into Godot? ## ## The surface table is written by tools/surface_map.py against the Blender ## scene and read by SkinSurfaces against the loaded glTF. Nothing forces the ## two to agree on what anything is called, so this checks that they do — by ## counting how many surfaces resolve from the TABLE versus how many fell ## through to the heuristic. A model whose names stopped matching still renders, ## because the fallback catches it; it just quietly loses its per-class art ## direction. That is exactly the kind of silent regression this catches. ## ## godot --headless --path . -s res://debug/surface_class_check.gd var _failures: int = 0 func _init() -> void: var data = JSON.parse_string(FileAccess.get_file_as_string( "res://assets/characters/skins/skins.json")) for entry in data["skins"]: _check_skin(entry["id"], entry.get("model", "")) print("\n%s" % ("FAILED (%d)" % _failures if _failures > 0 else "All skins classified")) quit(1 if _failures > 0 else 0) func _check_skin(id: String, path: String) -> void: if path == "" or not ResourceLoader.exists(path): return var side := path.get_basename() + ".rig.json" var rig = JSON.parse_string(FileAccess.get_file_as_string(side)) if typeof(rig) != TYPE_DICTIONARY: print("%-10s NO SIDECAR" % id) _failures += 1 return var surfaces := SkinSurfaces.from_rig_info(rig) var scene: Node = load(path).instantiate() var tally := {} var from_table := 0 var from_guess := 0 var unmatched: PackedStringArray = [] for mi in scene.find_children("*", "MeshInstance3D", true, false): if mi.mesh == null: continue for s in mi.mesh.get_surface_count(): var src: BaseMaterial3D = mi.mesh.surface_get_material(s) as BaseMaterial3D var mat_name := "" if src == null else src.resource_name if surfaces.lookup(mi.name, s, mat_name).is_empty(): from_guess += 1 unmatched.append("%s|%d|%s" % [mi.name, s, mat_name]) else: from_table += 1 var cls: String = surfaces.resolve(mi.name, s, src)[0] tally[cls] = tally.get(cls, 0) + 1 scene.free() var parts: PackedStringArray = [] var keys: Array = tally.keys() keys.sort() for k in keys: parts.append("%s x%d" % [k, tally[k]]) print("%-10s %d surfaces — %s (table %d, fallback %d)" % [id, from_table + from_guess, ", ".join(parts), from_table, from_guess]) if from_guess > 0: print(" unmatched: %s" % ", ".join(unmatched)) _failures += 1 if surfaces.is_empty(): print(" sidecar has no surface table") _failures += 1