Stage the Blender character/gear/prop library
Commit the finished generator output from the parallel Blender pass: 8 skinned NPC variants, 18 gear pieces, and 20 props under assets/models/blender/, editable sources (gdignored), manifest, review sheets, and the integration notes in docs/blender-assets.md. - audit: all 46 assets clean (characters 3,788-4,640 tris, under 6000/4000) - tests: Python manifest/audit contract and Godot import/bone/slot/ground checks - docs: README library section, architecture/plan references, TODO Phase 3.5 status, MEMORY integration rules - .gitignore: per-asset preview PNGs, extracted GLB textures, blend1 backups Nothing in the game loads the library yet; integration follows." && git log --oneline -1
This commit is contained in:
1 parent
7efb5024ec
commit
d513e0b82c
109 files changed
+1916
-114
No files matched your search
@@ -0,0 +1,62 @@
|
||||
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_sources_are_hidden_from_godot(self):
|
||||
self.assertTrue((ba.SRC_DIR / ".gdignore").is_file())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user