import os import unittest from pathlib import Path import npc_pipeline class NpcContractTests(unittest.TestCase): def test_contract_constants(self): self.assertEqual(npc_pipeline.SLOTS, ["skin", "cloth", "pants", "accessory", "hair"]) for bone in ["Head", "HipL", "HipR", "KneeL", "KneeR", "ShoulderL", "ShoulderR", "ElbowL", "ElbowR", "HandL", "HandR"]: self.assertIn(bone, npc_pipeline.BONES) self.assertLessEqual(npc_pipeline.MAX_TRIANGLES, 16000) self.assertLessEqual(npc_pipeline.MAX_VERTICES, 16000) def test_textures_exist_for_every_slot(self): for slot in npc_pipeline.SLOTS: for suffix in ("albedo", "normal", "orm"): path = npc_pipeline.TEX_DIR / f"{slot}_{suffix}.png" self.assertTrue(path.is_file(), path) self.assertGreater(path.stat().st_size, 100, path) @unittest.skipUnless(npc_pipeline.GLB_PATH.is_file(), "npc_base.glb not built") def test_committed_model_audits_clean(self): result = npc_pipeline.audit_glb() self.assertTrue(result["ok"], result["issues"]) self.assertLessEqual(result["triangles"], npc_pipeline.MAX_TRIANGLES) self.assertLessEqual(result["vertices"], npc_pipeline.MAX_VERTICES) for bone in npc_pipeline.BONES: self.assertIn(bone, result["bones"]) @unittest.skipUnless(npc_pipeline.GLB_PATH.is_file(), "npc_base.glb not built") def test_blend_source_is_committed(self): self.assertTrue(npc_pipeline.BLEND_PATH.is_file()) # Godot must not try to import the .blend (it hangs without Blender). gdignore = npc_pipeline.BLEND_PATH.parent / ".gdignore" self.assertTrue(gdignore.is_file(), "assets/models/npc/.gdignore is required") if __name__ == "__main__": unittest.main()