232 lines
13 KiB
Markdown
232 lines
13 KiB
Markdown
# NPC visual baseline and acceptance criteria
|
||
|
||
Status: baseline captured 2026-11-02. The P1 fixes (tourist photo-prop
|
||
visibility, backpack straps), the neck/shoulder and torso/hip transitions, the
|
||
face-depth pass and the first hand pass are applied. Hand-item grip alignment
|
||
and clothing work remain open; the baseline remains the reference for 0.9.4.
|
||
Comparison sheets:
|
||
[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg),
|
||
[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
|
||
the latest [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg),
|
||
the face-detail pass [`sheet_npc_baseline_compare-face-detail.jpg`](blender_previews/sheet_npc_baseline_compare-face-detail.jpg),
|
||
the conversation-distance hand pass
|
||
[`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg), and the
|
||
forward-grip pass [`sheet_npc_hands_grip_compare.jpg`](blender_previews/sheet_npc_hands_grip_compare.jpg).
|
||
|
||
## What was captured
|
||
|
||
`src/test/capture_npc_baseline.gd` stages all eight library characters in a
|
||
neutral studio and captures six views per character:
|
||
|
||
| View | Camera | Purpose |
|
||
| --- | --- | --- |
|
||
| `front` | 1.85 m, 32° FOV, head/upper body | face, hair, collar, shoulders |
|
||
| `three_quarter` | 1.85 m, yaw 35° | cheek/jaw, ear, profile transition |
|
||
| `profile` | 1.85 m, yaw 90° | head depth, nose, ear, chest silhouette |
|
||
| `back` | 1.85 m, yaw 180° | hair cap, neck, coat/backpack fit |
|
||
| `full_body` | 4.2 m, 38° FOV | proportions, hands, shoes, hem |
|
||
| `crowd` | 8.0 m, 45° FOV | silhouette read at normal crowd distance |
|
||
|
||
The stage is deliberately separate from the venue review harness: a mid-grey
|
||
solid background, ground plane, one key light and one cool fill light, no HUD,
|
||
no crowd, no flashlight, no phase lighting and no animation. Front is Godot +Z,
|
||
matching `NpcAvatar.yaw_towards()`. The camera orbits at the recorded yaw rather
|
||
than rotating the avatar, so the pose and foot contact stay fixed between views.
|
||
The full command is:
|
||
|
||
```bash
|
||
godot --path . --script res://src/test/capture_npc_baseline.gd -- \
|
||
--out=res://tmp/npc-baseline
|
||
python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline
|
||
```
|
||
|
||
The 48 raw PNGs are local QA under `tmp/` (gitignored). The committed contact
|
||
sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg).
|
||
For the P2 hand/grip check there is an optional eighth view, captured separately
|
||
so the 48-frame baseline stays stable:
|
||
|
||
```bash
|
||
godot --path . --script res://src/test/capture_npc_baseline.gd -- \
|
||
--out=res://tmp/npc-hands --view=hand
|
||
```
|
||
|
||
That writes eight `*__hand.png` frames at 0.80 m from the right hand; the
|
||
before/after review image is
|
||
[`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg).
|
||
The capture is deterministic apart from engine rasterisation; re-running into
|
||
the same directory keeps the earlier files and writes `-vN` copies rather than
|
||
overwriting them.
|
||
|
||
## Acceptance criteria
|
||
|
||
The criteria below are for the stylized target, not photorealism. A change must
|
||
pass every applicable item before a library rebuild is accepted.
|
||
|
||
1. **Asset contract:** `npc_base` mesh node, the required bones, five tint slots,
|
||
front +Z, feet within 3 cm of y=0, 1.6–2.1 m height, at most 8,000 triangles
|
||
and 8,000 glTF vertices per library character. The frozen `npc_base` fallback
|
||
keeps 6,000 triangles / 4,000 vertices. Gear remains at most 1,500 triangles.
|
||
2. **Proportion and silhouette:** head, neck, shoulders, torso, hips, limbs and
|
||
feet read as one body; front/three-quarter/profile silhouettes agree with the
|
||
rest of the eight-character library; broad/slim/average build variation is
|
||
preserved.
|
||
3. **Face:** at 1.85 m the eyes, iris, brows, nose, mouth and ears are distinct;
|
||
the head volume has a readable jaw/cheek/chin transition; no face-plane seams,
|
||
inverted normals or z-fighting. The face direction must remain unambiguous.
|
||
4. **Hair and headgear:** hair covers the scalp without gaps or scalp-through
|
||
seams; fringes, buns and beards keep their silhouette. Caps, beanies, hoods,
|
||
sunglasses and the firefighter helmet clear the brow, eyes and ears. Headgear
|
||
may sit outside the 1.55–1.81 m face band, but its brim must not cover the
|
||
eyes.
|
||
5. **Hands and footwear:** at conversation distance the fingers and thumb read
|
||
as a hand, not a mitten; hand items contact the palm rather than floating;
|
||
shoes and boots are grounded with a readable toe/heel silhouette and preserve
|
||
the responder/firefighter footwear language.
|
||
6. **Clothing:** collars, cuffs, plackets, waistbands, pockets, badges and
|
||
reflective bands transition cleanly; no z-fighting or floating detail; coat,
|
||
dress and skirt hems do not show legs through the fabric during idle, walk,
|
||
block, sit or turn; the five tint slots and fixed detail colours remain
|
||
unchanged.
|
||
7. **Gear:** head gear clears the refined face/hair; the lanyard badge remains
|
||
readable in front of the jacket; backpack, shoulder bag and SCBA tank straps
|
||
lie outside the torso and do not read as a second bag; hand items use the
|
||
authored grip points.
|
||
8. **Crowd and performance:** the silhouette remains readable at the 8 m crowd
|
||
frame and the LOD/impostor tiers match the full mesh; the 24-NPC budget stays
|
||
within the existing 6.93 ms baseline (no regression beyond the measured
|
||
variance), and no new per-frame material or mesh churn is introduced.
|
||
|
||
Asset-space `fit` values are measurements of the untouched GLBs, not a pass/fail
|
||
result. A negative `x_clearance` means the gear's narrowest face is inside the
|
||
body reference; a negative `radial_clearance` means it extends beyond the body
|
||
silhouette (normal for a backpack, suspicious when it is a strap). Runtime
|
||
`NpcAvatar.GEAR_OFFSETS` is applied after attachment and is not part of the GLB
|
||
measurement.
|
||
|
||
## Proportion audit
|
||
|
||
The separate [`npc-proportion-audit.md`](npc-proportion-audit.md) measures
|
||
height, head size, shoulder/hip width, torso, leg, arm and foot ratios for every
|
||
variant with `tools/npc_proportions.py`. All eight pass the stylized guide; the
|
||
head and broad-build shoulders sit nearest their upper bounds. No skeleton or
|
||
mesh proportion change was needed in this pass.
|
||
|
||
## Baseline measurements
|
||
|
||
From `python3 tools/blender_assets.py fit` and the manifest:
|
||
|
||
| Character | Tris | Verts | Head width | Clothing radius | Notable gear clearances |
|
||
| --- | ---: | ---: | ---: | ---: | --- |
|
||
| `npc_casual` | 4,482 | 4,260 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
|
||
| `npc_jacket` | 4,872 | 4,661 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
|
||
| `npc_coat` | 5,474 | 5,264 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
|
||
| `npc_vest` | 4,980 | 4,879 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
|
||
| `npc_dress` | 4,900 | 4,719 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
|
||
| `npc_hoodie` | 5,304 | 4,950 | 0.315 m | 0.190 m (hip) | — |
|
||
| `npc_firefighter` | 5,344 | 5,494 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
|
||
| `npc_guard` | 5,046 | 5,096 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
|
||
|
||
`npc_coat` is widest at 5,474 triangles (2,526 below the 8,000 cap);
|
||
`npc_firefighter` is closest on vertices at 5,494 (2,506 below the 8,000 cap).
|
||
The refit backpack measures 380 triangles / 406 vertices.
|
||
|
||
## Baseline findings, ranked
|
||
|
||
### P1 — fix in the first model pass
|
||
|
||
1. **The tourist carried two bag silhouettes. Fixed 2026-11-02.** `_add_props()`
|
||
created a 0.30 × 0.36 × 0.14 box at `(-0.34, 1.15, -0.18)` for the tourist
|
||
photo pose while `_add_accessory("backpack")` also attached the library
|
||
backpack. From the three-quarter/back view the two read as separate bags. The
|
||
photo prop is now hidden outside `photo`/`film` poses
|
||
(`photo_prop_visible_for_pose()`), so it only appears when the character is
|
||
actually taking a picture; the after frame confirms one bag at rest.
|
||
2. **Face depth and detail. Fixed 2026-11-02.** The sclera was recessed into the
|
||
socket and wrapped with a thin upper lid; the separate jaw/cheek spheres were
|
||
replaced by a chin plate and cheekbones sculpted into the head mesh. The nose
|
||
now has ala wings and a smaller bridge/tip, the mouth is one rounded slot
|
||
instead of a flat bar, and the ears have a darker concha. That removes the
|
||
dark intersection rings and the moustache-like read at portrait distance; the
|
||
low-poly brows remain stylized.
|
||
3. **Backpack straps read as rigid black bars. Refit 2026-11-02.** The straps
|
||
were thin round tubes whose chest/waist points sat inside the torso, so only
|
||
fragments showed from the front. They are now flattened bands whose front path
|
||
sits outside the chest; the after sheet shows a continuous strap instead of
|
||
two detached rods. The backpack asset is now 380 triangles / 406 vertices
|
||
(still far below the 1,500-triangle gear budget).
|
||
|
||
### P2 — fix after the first P1 pass
|
||
|
||
4. **Hands: geometry and forward offset fixed 2026-11-02; finger curl still
|
||
open.** The palm is wider and shallower, and each finger is now a
|
||
two-segment bent tube with a rounded tip and a separate bent thumb. Hand gear
|
||
is authored around the wrist proxy, so the runtime now moves every hand item
|
||
`HAND_ITEM_FORWARD` (4 cm) into the open palm; the phone, flashlight, camera,
|
||
cup and clipboard are no longer buried inside the hand. The fingers are still
|
||
open, so a true wrap/thumb contact and the umbrella/sign grips remain to be
|
||
judged.
|
||
5. **Clothing detail is tile-driven and transitional seams are visible.** The
|
||
fabric pattern repeats strongly on the casual, coat and dress variants, and
|
||
collar/cuff/waist edges are abrupt. This is more visible than the shoe/hem
|
||
details in the current portraits.
|
||
6. **Shoes: fixed 2026-11-02.** Casual shoes gained rounded toe and heel caps;
|
||
responder/firefighter boots gained matching toe/heel volumes while keeping the
|
||
taller black rubber boot language. Both stay above the sole contact plane, and
|
||
the full-body comparison is `sheet_npc_baseline_compare-shoes.jpg`.
|
||
7. **Headgear scale and clearance. Checked 2026-11-02.** The cap and helmet
|
||
width ratios are 1.067 and 1.651; those are expected for headgear. Final-context
|
||
portraits confirm the cap, firefighter helmet, hood and sunglasses clear the
|
||
refined brow/eyes, and the helmet brim still reads as deliberate turnout gear.
|
||
No gear rebuild was needed.
|
||
8. **Performer props dominate the silhouette.** The red cone hat and the
|
||
primitive sign are intentionally large stage props, but they should be
|
||
checked during the pose pass so they do not read as head/body geometry.
|
||
|
||
### Accepted or deferred for this baseline
|
||
|
||
- The hooded variants intentionally cover part of the face; face readability is
|
||
judged on the visible lower face and silhouette, not on hidden eyes.
|
||
- The low-poly style is the target. The acceptance bar is readability and clean
|
||
construction, not photoreal skin or hair.
|
||
- Hem-through-leg clipping during a walk cannot be judged from a rest pose; it
|
||
stays in the body/clothing workstream.
|
||
- The 8 m `crowd` view is still LOD 0. The separate LOD/impostor check remains
|
||
required; this baseline does not claim to validate it.
|
||
|
||
## Before/after workflow
|
||
|
||
Keep the baseline directory, rebuild the same capture into a new directory after
|
||
the model changes, then produce a comparison sheet. Do not overwrite the
|
||
committed baseline image:
|
||
|
||
```bash
|
||
godot --path . --script res://src/test/capture_npc_baseline.gd -- \
|
||
--out=res://tmp/npc-baseline-after
|
||
python3 tools/npc_baseline.py compare \
|
||
--before tmp/npc-baseline --after tmp/npc-baseline-after \
|
||
--out tmp/npc-baseline-compare.jpg
|
||
```
|
||
|
||
The comparison sheet places the before/after pair for each view side by side.
|
||
The committed runs are
|
||
[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg),
|
||
[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
|
||
and [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg).
|
||
The transition pass is [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg).
|
||
The face-detail pass is [`sheet_npc_baseline_compare-face-detail.jpg`](blender_previews/sheet_npc_baseline_compare-face-detail.jpg).
|
||
The hand pass is [`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg);
|
||
the forward-grip pass is
|
||
[`sheet_npc_hands_grip_compare.jpg`](blender_previews/sheet_npc_hands_grip_compare.jpg).
|
||
Keep the baseline sheet as the before artifact and add a new comparison sheet
|
||
rather than replacing it.
|
||
|
||
## Next implementation order
|
||
|
||
1. Add finger curl/thumb contact for the hand items and judge the umbrella/sign
|
||
grips; the forward offset is already in place.
|
||
2. Add the remaining body-shape variation between variants and re-measure the
|
||
LOD/impostor silhouettes.
|
||
3. Audit hems in walk/sit/block poses; footwear silhouette is now in the baseline.
|
||
4. Re-run the 48-frame baseline, the 96-frame final-context review, the 24-NPC
|
||
budget and `blender_assets.py audit/fit`; publish the next comparison sheet.
|