import json import unittest import blender_assets as ba import npc_pipeline class CatalogueTests(unittest.TestCase): def test_contract_matches_npc_pipeline(self): self.assertEqual(ba.SLOTS, npc_pipeline.SLOTS) self.assertEqual(sorted(ba.BONES), sorted(npc_pipeline.BONES)) self.assertLessEqual(ba.CHAR_MAX_TRIS, npc_pipeline.MAX_TRIANGLES) self.assertLessEqual(ba.CHAR_MAX_VERTS, npc_pipeline.MAX_VERTICES) def test_gear_uses_known_attach_points(self): for name, attach in ba.GEAR.items(): self.assertIn(attach, ba.GEAR_ATTACH, name) self.assertIn(attach, ba.ATTACH_BLENDER, name) def test_character_gear_exists(self): layers = set() for name, spec in ba.CHARACTERS.items(): layers.add(spec["layer"]) for gear in spec.get("gear", []): self.assertIn(gear, ba.GEAR, f"{name} -> {gear}") # Every NpcAvatar.layer_for() value has a character. for layer in ("none", "jacket", "coat", "vest", "dress"): self.assertIn(layer, layers) def test_emissive_props_use_emissive_palette_entries(self): for name, spec in ba.PROPS.items(): for mat in spec.get("emissive", []): self.assertGreater(ba.PALETTE[mat][3], 0.0, f"{name}:{mat}") @unittest.skipUnless(ba.MANIFEST.is_file(), "Blender assets not built") class BuiltAssetTests(unittest.TestCase): def test_every_asset_audits_clean(self): bad = {} for category, name in ba.catalogue(): result = ba.audit_asset(category, name) if not result["ok"]: bad[name] = result["issues"] self.assertEqual(bad, {}) def test_manifest_covers_catalogue(self): manifest = json.loads(ba.MANIFEST.read_text(encoding="utf-8")) self.assertEqual(set(manifest["assets"]), {n for _, n in ba.catalogue()}) def test_manifest_paths_stay_in_the_library(self): manifest = json.loads(ba.MANIFEST.read_text(encoding="utf-8")) for name, entry in manifest["assets"].items(): self.assertTrue(entry["path"].startswith("assets/models/blender/"), name) if entry["category"] == "npc_gear": self.assertIn(entry["attach"], ba.GEAR_ATTACH, name) def test_baked_ao_maps_exist_for_every_asset(self): from PIL import Image for name, entry in json.loads( ba.MANIFEST.read_text(encoding="utf-8"))["assets"].items(): path = ba.OUT_DIR / entry["category"] / f"{name}_ao.png" self.assertTrue(path.is_file(), path) with Image.open(path) as image: self.assertEqual(image.size, (256, 256), path) def test_sources_are_hidden_from_godot(self): self.assertTrue((ba.SRC_DIR / ".gdignore").is_file()) if __name__ == "__main__": unittest.main()