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,
|
||||
`npc_crowd_b` bald/broad/beard/shoulder bag, `npc_crowd_c` coat/umbrella, `npc_crowd_d`
|
||||
hood/cup; prototype sheet committed.)
|
||||
- [ ] Re-derive the four approved specs against the re-evaluated character baseline (face, hands,
|
||||
proportions, gear offsets) so the new variants do not reintroduce the gaps the review found.
|
||||
- [ ] Generate the four variants through the assisted pipeline (procedural generator → `inspect` →
|
||||
`audit`/`fit`) and capture the result before committing any of them.
|
||||
- [ ] Add the four approved specs to `CHARACTERS`, build and commit their GLBs/AO maps, and
|
||||
update the manifest with roles and tint data. Build only the new assets, never a no-op full
|
||||
rebuild, and remember that `export_filter="all_resources"` packs committed GLBs into the
|
||||
next release build: keep this off `main` until after the 0.10.0 tag or accept the size.
|
||||
- [x] Re-derive the four specs against the new baseline: the four crowd entries inherit the
|
||||
realistic face (`seg=24/rings=16`) and the reviewed hand/gear offsets automatically because
|
||||
they share `_face`/`_hands_and_shoes`/`GEAR_ATTACH`.
|
||||
- [x] Generate the four variants: built via `blender_assets.py build`, AO re-baked, `audit` clean
|
||||
(4,734–5,814 tris), `inspect` clean, proportions pass.
|
||||
- [x] Add the four specs to `CHARACTERS`, commit their GLBs/AO maps and manifest roles/tint data.
|
||||
(Note: this adds ~2 MB to the next release build via `export_filter=all_resources` — accepted
|
||||
for the crowd-variety roster.)
|
||||
- [ ] 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).
|
||||
- [ ] Rebuild and verify gear fit for every new variant (`blender_assets.py fit` plus the runtime
|
||||
|
||||
@@ -510,6 +510,166 @@
|
||||
"triangles": 6030,
|
||||
"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": {
|
||||
"bounds_godot": {
|
||||
"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_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_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 |
|
||||
|
||||
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_firefighter` | 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)
|
||||
|
||||
|
||||
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_firefighter", "type": Rules.NpcType.RESPONDER},
|
||||
{"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
|
||||
## 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",
|
||||
"gear": ["cap", "wand"],
|
||||
"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.
|
||||
|
||||
@@ -21,6 +21,10 @@ VARIANTS = [
|
||||
"npc_hoodie",
|
||||
"npc_firefighter",
|
||||
"npc_guard",
|
||||
"npc_crowd_a",
|
||||
"npc_crowd_b",
|
||||
"npc_crowd_c",
|
||||
"npc_crowd_d",
|
||||
]
|
||||
VIEWS = ["front", "three_quarter", "profile", "back", "full_body", "crowd"]
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import npc_baseline
|
||||
class NpcBaselineTests(unittest.TestCase):
|
||||
def test_expected_files_cover_all_variants_and_views(self):
|
||||
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[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):
|
||||
from PIL import Image
|
||||
|
||||
@@ -6,8 +6,8 @@ import npc_proportions
|
||||
class NpcProportionTests(unittest.TestCase):
|
||||
def test_every_library_character_is_measured(self):
|
||||
rows = npc_proportions.report()
|
||||
self.assertEqual(len(rows), 8)
|
||||
self.assertEqual(len({row["character"] for row in rows}), 8)
|
||||
self.assertEqual(len(rows), 12)
|
||||
self.assertEqual(len({row["character"] for row in rows}), 12)
|
||||
for row in rows:
|
||||
self.assertGreater(row["measurements"]["height"], 1.6)
|
||||
|
||||
|
||||
Reference in new issue
Block a user