Files
security-game/docs/npc-visual-review.md
T
zwitschi 12e70357c5
Full build / Import + headless tests (push) Successful in 35s
Full build / Full desktop build + optional release (push) Skipped
Refine the NPC nose, mouth and ears
2026-11-02 19:29:19 +01:00

225 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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),
and the conversation-distance hand pass
[`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_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,226 | 4,119 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
| `npc_jacket` | 4,616 | 4,447 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
| `npc_coat` | 5,218 | 4,975 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
| `npc_vest` | 4,724 | 4,713 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
| `npc_dress` | 4,644 | 4,439 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
| `npc_hoodie` | 5,048 | 4,712 | 0.315 m | 0.190 m (hip) | — |
| `npc_firefighter` | 5,088 | 5,240 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
| `npc_guard` | 4,790 | 4,865 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
`npc_coat` is widest at 5,218 triangles (2,782 below the 8,000 cap);
`npc_firefighter` is closest on vertices at 5,240 (2,760 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: first pass fixed 2026-11-02; grip alignment 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. The hand capture shows a readable
four-finger hand instead of flat bars. The camera, phone, cup, clipboard and
sign still need their attach offsets checked: several props occlude the palm
or read as floating beside it.
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 are simple blocks.** They are grounded, but the toe/heel silhouette
is minimal; the responder and firefighter footwear should remain distinct
after any refinement.
7. **Headgear scale needs a visual, not just numeric, check.** The cap and
helmet width ratios are 1.067 and 1.651; those are expected for headgear, but
the brim/ear clearance and the helmet's relationship to the head still need a
face-on check.
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).
Keep the baseline sheet as the before artifact and add a new comparison sheet
rather than replacing it.
## Next implementation order
1. Fix hand-item grip offsets (phone, cup, clipboard, camera, sign, flashlight,
wand and radio) against the new palm/finger geometry.
2. Add the remaining body-shape variation between variants and re-measure the
LOD/impostor silhouettes.
3. Audit hems and footwear in walk/sit/block poses.
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.