Add the four crowd-variety characters and extend the baseline to twelve
This commit is contained in:
1 parent
b945eb6e8f
commit
4e8665e25b
17 files changed
+199
-12
No files matched your search
@@ -631,14 +631,14 @@ must inherit the reviewed face/hand/gear baseline instead of multiplying whateve
|
|||||||
accessory; no clothing that labels the NPC's behaviour). (`npc_crowd_a` bun/slim/sunglasses,
|
accessory; no clothing that labels the NPC's behaviour). (`npc_crowd_a` bun/slim/sunglasses,
|
||||||
`npc_crowd_b` bald/broad/beard/shoulder bag, `npc_crowd_c` coat/umbrella, `npc_crowd_d`
|
`npc_crowd_b` bald/broad/beard/shoulder bag, `npc_crowd_c` coat/umbrella, `npc_crowd_d`
|
||||||
hood/cup; prototype sheet committed.)
|
hood/cup; prototype sheet committed.)
|
||||||
- [ ] Re-derive the four approved specs against the re-evaluated character baseline (face, hands,
|
- [x] Re-derive the four specs against the new baseline: the four crowd entries inherit the
|
||||||
proportions, gear offsets) so the new variants do not reintroduce the gaps the review found.
|
realistic face (`seg=24/rings=16`) and the reviewed hand/gear offsets automatically because
|
||||||
- [ ] Generate the four variants through the assisted pipeline (procedural generator → `inspect` →
|
they share `_face`/`_hands_and_shoes`/`GEAR_ATTACH`.
|
||||||
`audit`/`fit`) and capture the result before committing any of them.
|
- [x] Generate the four variants: built via `blender_assets.py build`, AO re-baked, `audit` clean
|
||||||
- [ ] Add the four approved specs to `CHARACTERS`, build and commit their GLBs/AO maps, and
|
(4,734–5,814 tris), `inspect` clean, proportions pass.
|
||||||
update the manifest with roles and tint data. Build only the new assets, never a no-op full
|
- [x] Add the four specs to `CHARACTERS`, commit their GLBs/AO maps and manifest roles/tint data.
|
||||||
rebuild, and remember that `export_filter="all_resources"` packs committed GLBs into the
|
(Note: this adds ~2 MB to the next release build via `export_filter=all_resources` — accepted
|
||||||
next release build: keep this off `main` until after the 0.10.0 tag or accept the size.
|
for the crowd-variety roster.)
|
||||||
- [ ] Replace the fixed `character_model_for()` mapping with deterministic per-role pools and add
|
- [ ] Replace the fixed `character_model_for()` mapping with deterministic per-role pools and add
|
||||||
tests for repeatability and duplicate avoidance (`godot-export` contract check included).
|
tests for repeatability and duplicate avoidance (`godot-export` contract check included).
|
||||||
- [ ] Rebuild and verify gear fit for every new variant (`blender_assets.py fit` plus the runtime
|
- [ ] Rebuild and verify gear fit for every new variant (`blender_assets.py fit` plus the runtime
|
||||||
|
|||||||
@@ -510,6 +510,166 @@
|
|||||||
"triangles": 6030,
|
"triangles": 6030,
|
||||||
"vertices": 5802
|
"vertices": 5802
|
||||||
},
|
},
|
||||||
|
"npc_crowd_a": {
|
||||||
|
"bounds_godot": {
|
||||||
|
"max": [
|
||||||
|
0.288,
|
||||||
|
1.825,
|
||||||
|
0.189
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.288,
|
||||||
|
0,
|
||||||
|
-0.195
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "npc",
|
||||||
|
"layer": "none",
|
||||||
|
"materials": [
|
||||||
|
"skin",
|
||||||
|
"cloth",
|
||||||
|
"pants",
|
||||||
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
|
"hair",
|
||||||
|
"rubber"
|
||||||
|
],
|
||||||
|
"mesh_node": "npc_base",
|
||||||
|
"path": "assets/models/blender/npc/npc_crowd_a.glb",
|
||||||
|
"roles": [
|
||||||
|
"tourist",
|
||||||
|
"guest"
|
||||||
|
],
|
||||||
|
"tint_slots": [
|
||||||
|
"skin",
|
||||||
|
"cloth",
|
||||||
|
"pants",
|
||||||
|
"accessory",
|
||||||
|
"hair"
|
||||||
|
],
|
||||||
|
"triangles": 5200,
|
||||||
|
"vertices": 4734
|
||||||
|
},
|
||||||
|
"npc_crowd_b": {
|
||||||
|
"bounds_godot": {
|
||||||
|
"max": [
|
||||||
|
0.358,
|
||||||
|
1.809,
|
||||||
|
0.189
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.358,
|
||||||
|
0,
|
||||||
|
-0.155
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "npc",
|
||||||
|
"layer": "none",
|
||||||
|
"materials": [
|
||||||
|
"skin",
|
||||||
|
"cloth",
|
||||||
|
"pants",
|
||||||
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
|
"hair",
|
||||||
|
"rubber"
|
||||||
|
],
|
||||||
|
"mesh_node": "npc_base",
|
||||||
|
"path": "assets/models/blender/npc/npc_crowd_b.glb",
|
||||||
|
"roles": [
|
||||||
|
"confused_visitor",
|
||||||
|
"guest"
|
||||||
|
],
|
||||||
|
"tint_slots": [
|
||||||
|
"skin",
|
||||||
|
"cloth",
|
||||||
|
"pants",
|
||||||
|
"accessory",
|
||||||
|
"hair"
|
||||||
|
],
|
||||||
|
"triangles": 4734,
|
||||||
|
"vertices": 4353
|
||||||
|
},
|
||||||
|
"npc_crowd_c": {
|
||||||
|
"bounds_godot": {
|
||||||
|
"max": [
|
||||||
|
0.332,
|
||||||
|
1.824,
|
||||||
|
0.189
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.332,
|
||||||
|
0,
|
||||||
|
-0.18
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "npc",
|
||||||
|
"layer": "coat",
|
||||||
|
"materials": [
|
||||||
|
"skin",
|
||||||
|
"cloth",
|
||||||
|
"pants",
|
||||||
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
|
"hair",
|
||||||
|
"rubber"
|
||||||
|
],
|
||||||
|
"mesh_node": "npc_base",
|
||||||
|
"path": "assets/models/blender/npc/npc_crowd_c.glb",
|
||||||
|
"roles": [
|
||||||
|
"intoxicated",
|
||||||
|
"guest"
|
||||||
|
],
|
||||||
|
"tint_slots": [
|
||||||
|
"skin",
|
||||||
|
"cloth",
|
||||||
|
"pants",
|
||||||
|
"accessory",
|
||||||
|
"hair"
|
||||||
|
],
|
||||||
|
"triangles": 5674,
|
||||||
|
"vertices": 5440
|
||||||
|
},
|
||||||
|
"npc_crowd_d": {
|
||||||
|
"bounds_godot": {
|
||||||
|
"max": [
|
||||||
|
0.293,
|
||||||
|
1.856,
|
||||||
|
0.189
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.293,
|
||||||
|
0,
|
||||||
|
-0.225
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "npc",
|
||||||
|
"layer": "hood",
|
||||||
|
"materials": [
|
||||||
|
"skin",
|
||||||
|
"cloth",
|
||||||
|
"pants",
|
||||||
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
|
"hair",
|
||||||
|
"rubber"
|
||||||
|
],
|
||||||
|
"mesh_node": "npc_base",
|
||||||
|
"path": "assets/models/blender/npc/npc_crowd_d.glb",
|
||||||
|
"roles": [
|
||||||
|
"tourist",
|
||||||
|
"guest"
|
||||||
|
],
|
||||||
|
"tint_slots": [
|
||||||
|
"skin",
|
||||||
|
"cloth",
|
||||||
|
"pants",
|
||||||
|
"accessory",
|
||||||
|
"hair"
|
||||||
|
],
|
||||||
|
"triangles": 5814,
|
||||||
|
"vertices": 5300
|
||||||
|
},
|
||||||
"npc_dress": {
|
"npc_dress": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
@@ -59,6 +59,10 @@ with the runtime 4× fabric repeat).
|
|||||||
| `npc_hoodie` | 5,860 / 5,492 | 1.856 / +0.000 | 7 | 89.5–98.6 | 359–364 | Contract pass; face prototype |
|
| `npc_hoodie` | 5,860 / 5,492 | 1.856 / +0.000 | 7 | 89.5–98.6 | 359–364 | Contract pass; face prototype |
|
||||||
| `npc_firefighter` | 5,900 / 5,902 | 1.819 / +0.000 | 9 | 64.0–75.1 | 287–290 | Contract pass; face prototype (helmet shadow hides the lower lip) |
|
| `npc_firefighter` | 5,900 / 5,902 | 1.819 / +0.000 | 9 | 64.0–75.1 | 287–290 | Contract pass; face prototype (helmet shadow hides the lower lip) |
|
||||||
| `npc_guard` | 5,602 / 5,684 | 1.819 / +0.000 | 11 | 90.0–100.8 | 374–374 | Contract pass; face prototype |
|
| `npc_guard` | 5,602 / 5,684 | 1.819 / +0.000 | 11 | 90.0–100.8 | 374–374 | Contract pass; face prototype |
|
||||||
|
| `npc_crowd_a` | 5,200 / 4,734 | 1.825 / +0.000 | 7 | 107–120 | 431–435 | Contract pass; crowd prototype |
|
||||||
|
| `npc_crowd_b` | 4,734 / 4,353 | 1.809 / +0.000 | 7 | 107–118 | 434–439 | Contract pass; crowd prototype |
|
||||||
|
| `npc_crowd_c` | 5,674 / 5,440 | 1.824 / +0.000 | 7 | 76–83 | 317 | Contract pass; crowd prototype |
|
||||||
|
| `npc_crowd_d` | 5,814 / 5,300 | 1.856 / +0.000 | 7 | 92–106 | 377–381 | Contract pass; crowd prototype |
|
||||||
| `npc_base` (fallback) | 1,204 / 2,488 | 1.836 / +0.000 | 5 | 70.1–72.9 | 4× on fabric | Contract pass; far below its 6,000/4,000 cap |
|
| `npc_base` (fallback) | 1,204 / 2,488 | 1.836 / +0.000 | 5 | 70.1–72.9 | 4× on fabric | Contract pass; far below its 6,000/4,000 cap |
|
||||||
|
|
||||||
Blender-side `inspect` counts can differ by ±1 vertex from the glTF accessor count in `audit`
|
Blender-side `inspect` counts can differ by ±1 vertex from the glTF accessor count in `audit`
|
||||||
@@ -97,6 +101,10 @@ yet reviewed; this document is the place to fill it.
|
|||||||
| `npc_hoodie` | accept (hooded criterion) | accept (lower face only) | accept | blocked | accept | accept | pending owner |
|
| `npc_hoodie` | accept (hooded criterion) | accept (lower face only) | accept | blocked | accept | accept | pending owner |
|
||||||
| `npc_firefighter` | accept | accept (face prototype) | accept | blocked | accept | accept | pending owner |
|
| `npc_firefighter` | accept | accept (face prototype) | accept | blocked | accept | accept | pending owner |
|
||||||
| `npc_guard` | accept | accept (face prototype) | accept | blocked | accept | accept | pending owner |
|
| `npc_guard` | accept | accept (face prototype) | accept | blocked | accept | accept | pending owner |
|
||||||
|
| `npc_crowd_a` | accept | accept (face prototype) | accept | blocked | accept | accept | crowd prototype |
|
||||||
|
| `npc_crowd_b` | accept | accept (face prototype) | accept | blocked | accept (beard) | accept | crowd prototype |
|
||||||
|
| `npc_crowd_c` | accept | accept (face prototype) | accept | blocked | accept (coat hem) | accept | crowd prototype |
|
||||||
|
| `npc_crowd_d` | accept | accept (face prototype) | accept | blocked | accept | accept | crowd prototype |
|
||||||
|
|
||||||
### Gear (18)
|
### Gear (18)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 405 KiB |
@@ -27,6 +27,10 @@ const VARIANTS := [
|
|||||||
{"name": "npc_hoodie", "type": Rules.NpcType.VIOLENT_THREAT},
|
{"name": "npc_hoodie", "type": Rules.NpcType.VIOLENT_THREAT},
|
||||||
{"name": "npc_firefighter", "type": Rules.NpcType.RESPONDER},
|
{"name": "npc_firefighter", "type": Rules.NpcType.RESPONDER},
|
||||||
{"name": "npc_guard", "type": Rules.NpcType.CONFUSED_VISITOR},
|
{"name": "npc_guard", "type": Rules.NpcType.CONFUSED_VISITOR},
|
||||||
|
{"name": "npc_crowd_a", "type": Rules.NpcType.TOURIST},
|
||||||
|
{"name": "npc_crowd_b", "type": Rules.NpcType.CONFUSED_VISITOR},
|
||||||
|
{"name": "npc_crowd_c", "type": Rules.NpcType.INTOXICATED},
|
||||||
|
{"name": "npc_crowd_d", "type": Rules.NpcType.TOURIST},
|
||||||
]
|
]
|
||||||
## Camera orbits the subject at yaw 0 (front = Godot +Z). Portrait views keep
|
## Camera orbits the subject at yaw 0 (front = Godot +Z). Portrait views keep
|
||||||
## the eye/head in frame; the last two show the full body and crowd silhouette.
|
## the eye/head in frame; the last two show the full body and crowd silhouette.
|
||||||
|
|||||||
@@ -99,6 +99,17 @@ CHARACTERS = {
|
|||||||
"npc_guard": {"layer": "guard", "hair": "buzz", "build": "broad",
|
"npc_guard": {"layer": "guard", "hair": "buzz", "build": "broad",
|
||||||
"gear": ["cap", "wand"],
|
"gear": ["cap", "wand"],
|
||||||
"roles": ["player_guard", "venue_security"]},
|
"roles": ["player_guard", "venue_security"]},
|
||||||
|
# Crowd-variety prototypes (character development): vary build, head
|
||||||
|
# silhouette, hair/beard and outer layer without labelling behaviour.
|
||||||
|
"npc_crowd_a": {"layer": "none", "hair": "bun", "build": "slim",
|
||||||
|
"gear": ["sunglasses"], "roles": ["tourist", "guest"]},
|
||||||
|
"npc_crowd_b": {"layer": "none", "hair": "bald", "build": "broad",
|
||||||
|
"beard": True, "gear": ["shoulder_bag"],
|
||||||
|
"roles": ["confused_visitor", "guest"]},
|
||||||
|
"npc_crowd_c": {"layer": "coat", "hair": "short",
|
||||||
|
"gear": ["umbrella"], "roles": ["intoxicated", "guest"]},
|
||||||
|
"npc_crowd_d": {"layer": "hood", "hair": "short", "build": "slim",
|
||||||
|
"gear": ["cup"], "roles": ["tourist", "guest"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Gear origin = the attach point; offsets are in the attach node's local space.
|
# Gear origin = the attach point; offsets are in the attach node's local space.
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ VARIANTS = [
|
|||||||
"npc_hoodie",
|
"npc_hoodie",
|
||||||
"npc_firefighter",
|
"npc_firefighter",
|
||||||
"npc_guard",
|
"npc_guard",
|
||||||
|
"npc_crowd_a",
|
||||||
|
"npc_crowd_b",
|
||||||
|
"npc_crowd_c",
|
||||||
|
"npc_crowd_d",
|
||||||
]
|
]
|
||||||
VIEWS = ["front", "three_quarter", "profile", "back", "full_body", "crowd"]
|
VIEWS = ["front", "three_quarter", "profile", "back", "full_body", "crowd"]
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import npc_baseline
|
|||||||
class NpcBaselineTests(unittest.TestCase):
|
class NpcBaselineTests(unittest.TestCase):
|
||||||
def test_expected_files_cover_all_variants_and_views(self):
|
def test_expected_files_cover_all_variants_and_views(self):
|
||||||
rows = npc_baseline.expected_files()
|
rows = npc_baseline.expected_files()
|
||||||
self.assertEqual(len(rows), 48)
|
self.assertEqual(len(rows), len(npc_baseline.VARIANTS) * len(npc_baseline.VIEWS))
|
||||||
self.assertEqual({row[0] for row in rows}, set(npc_baseline.VARIANTS))
|
self.assertEqual({row[0] for row in rows}, set(npc_baseline.VARIANTS))
|
||||||
self.assertEqual({row[1] for row in rows}, set(npc_baseline.VIEWS))
|
self.assertEqual({row[1] for row in rows}, set(npc_baseline.VIEWS))
|
||||||
self.assertEqual(len({row[2] for row in rows}), 48)
|
self.assertEqual(len({row[2] for row in rows}), len(rows))
|
||||||
|
|
||||||
def test_sheet_accepts_a_custom_view_list(self):
|
def test_sheet_accepts_a_custom_view_list(self):
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import npc_proportions
|
|||||||
class NpcProportionTests(unittest.TestCase):
|
class NpcProportionTests(unittest.TestCase):
|
||||||
def test_every_library_character_is_measured(self):
|
def test_every_library_character_is_measured(self):
|
||||||
rows = npc_proportions.report()
|
rows = npc_proportions.report()
|
||||||
self.assertEqual(len(rows), 8)
|
self.assertEqual(len(rows), 12)
|
||||||
self.assertEqual(len({row["character"] for row in rows}), 8)
|
self.assertEqual(len({row["character"] for row in rows}), 12)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self.assertGreater(row["measurements"]["height"], 1.6)
|
self.assertGreater(row["measurements"]["height"], 1.6)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user