Remove the obsolete graphics plan
Delete docs/plan-graphics-v0.6.md and update README, architecture, asset tooling, Blender docs and MEMORY references. Completed work stays summarized in ADR-0013, the asset-pipeline architecture and the changelog; the v1.0.0 roadmap owns future graphics work.
This commit is contained in:
1 parent
6c367e8c7e
commit
7e084192ce
8 files changed
+14
-316
No files matched your search
@@ -23,13 +23,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
head width uses skin vertices weighted to Head in y 1.55–1.81; clothing radius is the cloth
|
head width uses skin vertices weighted to Head in y 1.55–1.81; clothing radius is the cloth
|
||||||
skirt in y 0.30–0.52 with a pants hip fallback. The command reports per-character gear boxes,
|
skirt in y 0.30–0.52 with a pants hip fallback. The command reports per-character gear boxes,
|
||||||
head-band status and x/radial skirt clearance; `test_blender_assets` covers its shape.
|
head-band status and x/radial skirt clearance; `test_blender_assets` covers its shape.
|
||||||
- Post-realism work is split by theme, not by date: the finished lighting/flashlight/NPC items
|
- Post-realism work is split by theme, not by date: finished items are archived in `DONE.md`, and
|
||||||
are archived in `DONE.md`, and `TODO.md` carries three planned workstreams (NPC gear fit,
|
`TODO.md` carries the v1.0.0 gap register and milestone roadmap. New work belongs there, not in
|
||||||
venue geometry + committed gallery, Forward+ desktop preset). New visual work should be added
|
a revived historical plan document.
|
||||||
as concrete steps under one of those headings instead of a new "realism" bucket.
|
|
||||||
|
|
||||||
- Graphics v0.6.0 plan (2026-11-01): NPCs move to Blender-authored skinned meshes
|
- Graphics v0.6.0 plan (completed 2026-11-01; ADR-0013): NPCs move to Blender-authored skinned meshes. `tools/npc_pipeline.py` builds
|
||||||
(`docs/plan-graphics-v0.6.md`, ADR-0013). `tools/npc_pipeline.py` builds
|
|
||||||
`assets/models/generated/npc_base.glb`; `NpcAvatar` keeps the procedural `_build_body()`
|
`assets/models/generated/npc_base.glb`; `NpcAvatar` keeps the procedural `_build_body()`
|
||||||
fallback. Preserve `Rig/Head`, the limb node names, and the seed-deterministic
|
fallback. Preserve `Rig/Head`, the limb node names, and the seed-deterministic
|
||||||
`outfit_for`/`layer_for`/`hand_item_for` helpers. Blender = flatpak 5.2.0 LTS, sandbox cwd
|
`outfit_for`/`layer_for`/`hand_item_for` helpers. Blender = flatpak 5.2.0 LTS, sandbox cwd
|
||||||
@@ -44,7 +42,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
- Shift length is the scenario's `shift_duration_s` (900 s in all three venues). Tests and the screenshot tool override elapsed time; they do not change the default.
|
- Shift length is the scenario's `shift_duration_s` (900 s in all three venues). Tests and the screenshot tool override elapsed time; they do not change the default.
|
||||||
- RNG: NPC reactions and event scheduling are seedable for deterministic tests.
|
- RNG: NPC reactions and event scheduling are seedable for deterministic tests.
|
||||||
|
|
||||||
- **Documentation ownership (2026-11-01):** `docs/architecture.md` §12 now defines the source-of-truth map and consolidation rules. `docs/plan-graphics-v0.6.md` is historical; current work belongs in `TODO.md`. README/development-status/Blender AO/Forward+ stale claims were corrected. The v1.0.0 gap register and 0.8/0.9/0.9.5/1.0 roadmap live in `TODO.md`.
|
- **Documentation ownership (2026-11-01):** `docs/architecture.md` §12 defines the source-of-truth map and consolidation rules. Obsolete/historical plan documents are removed rather than kept; current work belongs in `TODO.md`. README/development-status/Blender AO/Forward+ stale claims were corrected. The v1.0.0 gap register and 0.8/0.9/0.9.5/1.0 roadmap live in `TODO.md`.
|
||||||
- **Vision judge (v0.7.2):** `tools/openrouter_judge.py` reads `/api/v1/models`, filters image-input models with `response_format`, and ranks by advertised prompt/image/completion prices. `--model auto` (default) uses the cheapest (`z-ai/glm-5.3-flash` on 2026-11-01) with fallbacks; explicit `--model`/`GIG_LINE_JUDGE_MODEL` wins. Auth errors abort, provider/model errors fall through. Full screen judge 13/13 match, 0 diff; A/B judge ~$0.006.
|
- **Vision judge (v0.7.2):** `tools/openrouter_judge.py` reads `/api/v1/models`, filters image-input models with `response_format`, and ranks by advertised prompt/image/completion prices. `--model auto` (default) uses the cheapest (`z-ai/glm-5.3-flash` on 2026-11-01) with fallbacks; explicit `--model`/`GIG_LINE_JUDGE_MODEL` wins. Auth errors abort, provider/model errors fall through. Full screen judge 13/13 match, 0 diff; A/B judge ~$0.006.
|
||||||
- **Desktop HQ preset (v0.7.1):** `packaging/desktop_hq/override.cfg` switches a
|
- **Desktop HQ preset (v0.7.1):** `packaging/desktop_hq/override.cfg` switches a
|
||||||
copied executable to Forward+; `Materials.forward_plus_active()` gates
|
copied executable to Forward+; `Materials.forward_plus_active()` gates
|
||||||
|
|||||||
@@ -115,7 +115,6 @@ Full setup, every run/test/smoke command, and the controls table are in
|
|||||||
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
|
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
|
||||||
- [`docs/adr/`](docs/adr/) — Architecture Decision Records.
|
- [`docs/adr/`](docs/adr/) — Architecture Decision Records.
|
||||||
- [`TODO.md`](TODO.md) — Active tasks plus the v1.0.0 gap analysis and milestone roadmap.
|
- [`TODO.md`](TODO.md) — Active tasks plus the v1.0.0 gap analysis and milestone roadmap.
|
||||||
- [`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6.md) — Historical v0.6 graphics program (completed).
|
|
||||||
|
|
||||||
## Development Status
|
## Development Status
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ strong vertical slice, not yet a 1.0 product.
|
|||||||
- [x] Keep command-heavy operational pages separate but subordinate: `docs/asset-tooling.md`,
|
- [x] Keep command-heavy operational pages separate but subordinate: `docs/asset-tooling.md`,
|
||||||
`docs/blender-assets.md`, `docs/getting-started.md`, `docs/RELEASING.md` and
|
`docs/blender-assets.md`, `docs/getting-started.md`, `docs/RELEASING.md` and
|
||||||
`docs/smoke-checklist.md` now link to architecture rather than acting as parallel sources.
|
`docs/smoke-checklist.md` now link to architecture rather than acting as parallel sources.
|
||||||
- [x] Mark `docs/plan-graphics-v0.6.md` as completed/historical; current graphics work belongs in
|
- [x] Remove the obsolete `docs/plan-graphics-v0.6.md` and its links; completed graphics work is
|
||||||
this roadmap.
|
represented by ADR-0013, the asset pipeline section and the changelog.
|
||||||
- [x] Resolve stale claims: README release version/download row, development status
|
- [x] Resolve stale claims: README release version/download row, development status
|
||||||
"Forward+ not implemented", Blender props "not AO-baked", and historical plan status.
|
"Forward+ not implemented", and Blender props "not AO-baked".
|
||||||
- [x] Record the documentation ownership rule in `docs/architecture.md` §12: design intent
|
- [x] Record the documentation ownership rule in `docs/architecture.md` §12: design intent
|
||||||
(`PLAN.md`), mechanics (`gameplay.md`), system architecture/contracts (`architecture.md`),
|
(`PLAN.md`), mechanics (`gameplay.md`), system architecture/contracts (`architecture.md`),
|
||||||
decisions (ADRs), status (`development-status.md`), roadmap (`TODO.md`), operations
|
decisions (ADRs), status (`development-status.md`), roadmap (`TODO.md`), operations
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ Architecture decisions are recorded as ADRs:
|
|||||||
- The optional judge and image-generation paths depend on an external key, model availability and budget; CI cannot make them a release gate, and keys must never be committed.
|
- The optional judge and image-generation paths depend on an external key, model availability and budget; CI cannot make them a release gate, and keys must never be committed.
|
||||||
- Accessibility and localization coverage is partial; a 1.0 release needs subtitles/captions, colour-independent cues, remapping, text expansion tests and at least a documented localization workflow.
|
- Accessibility and localization coverage is partial; a 1.0 release needs subtitles/captions, colour-independent cues, remapping, text expansion tests and at least a documented localization workflow.
|
||||||
- Unsigned macOS builds, AI asset provenance and any storefront/legal requirements are release blockers for a public 1.0.
|
- Unsigned macOS builds, AI asset provenance and any storefront/legal requirements are release blockers for a public 1.0.
|
||||||
- Documentation drift has already occurred (release version, implemented status, historical graphics plan); the ownership map and roadmap must be kept current in the same change as the implementation.
|
- Documentation drift has already occurred (release version, implemented status and obsolete plan docs); the ownership map and roadmap must be kept current in the same change as the implementation.
|
||||||
|
|
||||||
## 12. Documentation Consolidation and Ownership
|
## 12. Documentation Consolidation and Ownership
|
||||||
|
|
||||||
@@ -288,14 +288,13 @@ architecture document.
|
|||||||
| [`docs/smoke-checklist.md`](smoke-checklist.md) | Manual verification script | QA appendix to §10 |
|
| [`docs/smoke-checklist.md`](smoke-checklist.md) | Manual verification script | QA appendix to §10 |
|
||||||
| [`docs/development-status.md`](development-status.md) | Implemented features and current gaps | Status companion to the roadmap |
|
| [`docs/development-status.md`](development-status.md) | Implemented features and current gaps | Status companion to the roadmap |
|
||||||
| [`TODO.md`](../TODO.md) | Active tasks and v1.0.0 roadmap | Planning source; no architecture duplication |
|
| [`TODO.md`](../TODO.md) | Active tasks and v1.0.0 roadmap | Planning source; no architecture duplication |
|
||||||
| [`docs/plan-graphics-v0.6.md`](plan-graphics-v0.6.md) | Historical v0.6 graphics program (completed) | Historical context; superseded by ADR-0013 and this document |
|
|
||||||
| [`CHANGELOG.md`](../CHANGELOG.md) | User-facing release history | Release/version companion |
|
| [`CHANGELOG.md`](../CHANGELOG.md) | User-facing release history | Release/version companion |
|
||||||
|
|
||||||
Consolidation decisions:
|
Consolidation decisions:
|
||||||
|
|
||||||
- **Architecture owns the "how"** (components, contracts, data flow, constraints, quality gates); **PLAN/gameplay own the "what"**; **ADRs own the "why"**. Links replace duplicated prose.
|
- **Architecture owns the "how"** (components, contracts, data flow, constraints, quality gates); **PLAN/gameplay own the "what"**; **ADRs own the "why"**. Links replace duplicated prose.
|
||||||
- **Asset and release documents remain separate** because they are command-heavy operational procedures; their architecture-relevant contracts and failure modes are summarized in §5 and §7.
|
- **Asset and release documents remain separate** because they are command-heavy operational procedures; their architecture-relevant contracts and failure modes are summarized in §5 and §7.
|
||||||
- **`plan-graphics-v0.6.md` is historical, not a current plan.** The completed work is represented by ADR-0013, the asset pipeline section and the v0.6.0 changelog; new graphics work belongs in TODO, not a revived phase plan.
|
- **Completed plans are removed, not kept as competing roadmaps.** Historical graphics work is summarized by ADR-0013, the asset pipeline section and the v0.6.0 changelog; new work belongs in TODO.
|
||||||
- **Status and roadmap are separate from architecture.** `docs/development-status.md` records what exists; `TODO.md` records what remains, including the v1.0.0 gap analysis and milestone exit criteria.
|
- **Status and roadmap are separate from architecture.** `docs/development-status.md` records what exists; `TODO.md` records what remains, including the v1.0.0 gap analysis and milestone exit criteria.
|
||||||
- **Documentation drift is a tracked risk:** release/version, test counts, renderer status, and stale plans must be corrected in the same commit as the change that invalidates them.
|
- **Documentation drift is a tracked risk:** release/version, test counts, renderer status, and stale plans must be corrected in the same commit as the change that invalidates them.
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ character authored in Blender ([ADR-0013](adr/0013-blender-authored-npc-characte
|
|||||||
per-slot material maps. `NpcAvatar` instantiates that skinned model, tints its
|
per-slot material maps. `NpcAvatar` instantiates that skinned model, tints its
|
||||||
five material slots from the seed-driven outfit, and drives the bones from the
|
five material slots from the seed-driven outfit, and drives the bones from the
|
||||||
existing animation code; the procedural rig remains the offline fallback (see
|
existing animation code; the procedural rig remains the offline fallback (see
|
||||||
the [graphics plan](plan-graphics-v0.6.md)).
|
[ADR-0013](adr/0013-blender-authored-npc-characters.md) and
|
||||||
|
[`blender-assets.md`](blender-assets.md)).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Print the frozen contract (slots, bone names, triangle budget)
|
# Print the frozen contract (slots, bone names, triangle budget)
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ Architecture context: the asset contracts and fallback path are summarized in
|
|||||||
|
|
||||||
Status: built, audited, and integrated (2026-11-01). `NpcAvatar` selects the
|
Status: built, audited, and integrated (2026-11-01). `NpcAvatar` selects the
|
||||||
character variants and attaches the gear; `WorldBuilder` loads the prop library
|
character variants and attaches the gear; `WorldBuilder` loads the prop library
|
||||||
with colliders and phase-driven emissives. See the Phase 3.5 integration in
|
with colliders and phase-driven emissives.
|
||||||
[`plan-graphics-v0.6.md`](plan-graphics-v0.6.md).
|
|
||||||
|
|
||||||
Generator: [`tools/blender_assets.py`](../tools/blender_assets.py). Manifest (the
|
Generator: [`tools/blender_assets.py`](../tools/blender_assets.py). Manifest (the
|
||||||
source of truth for integration):
|
source of truth for integration):
|
||||||
|
|||||||
@@ -1,298 +0,0 @@
|
|||||||
# Development Plan — Graphics Quality v0.6.0 (Blender-authored NPCs)
|
|
||||||
|
|
||||||
> Status: **Completed / historical** · Target version: **v0.6.0** (MINOR), followed by
|
|
||||||
> v0.7.1's optional Forward+ Desktop HQ build
|
|
||||||
> Companion decision: [ADR-0013](adr/0013-blender-authored-npc-characters.md)
|
|
||||||
>
|
|
||||||
> This is an implementation-history document. The current architecture is in
|
|
||||||
> [`architecture.md`](architecture.md); remaining graphics work is tracked in
|
|
||||||
> [`TODO.md`](../TODO.md), not as a revived phase plan.
|
|
||||||
|
|
||||||
## Why this exists
|
|
||||||
|
|
||||||
The game is functionally complete through v0.5.0, but NPCs are still the
|
|
||||||
procedural capsule-and-box rig from [ADR-0010](adr/0010-procedural-npc-presentation.md).
|
|
||||||
They read as people at conversation distance but not at cinematic distance, and
|
|
||||||
every surface on a body is a flat color. ADR-0010 recorded that "a later
|
|
||||||
skinned-mesh pass will replace this rig and must keep the `Head` contract" —
|
|
||||||
this plan is that pass.
|
|
||||||
|
|
||||||
The goal is **better NPCs first**, then a modest environment lift, using the
|
|
||||||
existing AI tooling for art direction and quality gates, and **Blender** for the
|
|
||||||
geometry, UVs, skeleton, and baked material maps.
|
|
||||||
|
|
||||||
## Constraints that shape the plan
|
|
||||||
|
|
||||||
- **Renderer is GL Compatibility** (`project.godot` `config/features`). SSAO,
|
|
||||||
SSIL, and volumetrics are unavailable. Quality therefore comes from better
|
|
||||||
meshes, baked maps (AO/normal/roughness), and lighting — not from a renderer
|
|
||||||
switch.
|
|
||||||
- **CI builds offline.** `.gitea/workflows/build.yml` runs the Godot editor only
|
|
||||||
(no Blender, no network). Everything the game loads must be committed; the
|
|
||||||
Blender step is a developer tool, and the game keeps a procedural fallback so a
|
|
||||||
fresh checkout still boots and the headless suite still passes.
|
|
||||||
- **Determinism.** Seed-driven variety (`outfit_for`, `layer_for`,
|
|
||||||
`hand_item_for`, `idle_style_for`) must keep working and keep its tests green.
|
|
||||||
AI generation is a dev-time input, not a runtime dependency; the committed
|
|
||||||
assets are the source of truth.
|
|
||||||
- **Frame budget.** v0.5.0 measured 16 avatars = 534 meshes ≈ 6.9 ms headless.
|
|
||||||
A skinned rig replaces ~33 draw calls per avatar with one skin, but skinning
|
|
||||||
costs CPU. Re-measure with `src/test/smoke_npc_budget.gd` before raising
|
|
||||||
`MAX_LIVE_NPCS`.
|
|
||||||
|
|
||||||
## The NPC asset contract (what must not break)
|
|
||||||
|
|
||||||
From `src/game/npc_avatar.gd` and `src/game/guard_controller.gd`:
|
|
||||||
|
|
||||||
- A scene-level node named **`Rig/Head`** — `GuardController` raycasts for it
|
|
||||||
(`get_node_or_null("Rig/Head")`), and the focus circle + name tag + reaction
|
|
||||||
tag attach under it.
|
|
||||||
- Limb pivots currently named `HipL`/`HipR` → `Knee`, `ShoulderL`/`ShoulderR` →
|
|
||||||
`Elbow` → `Hand`. `_animate()` and `_idle_layer()` drive them by `rotation.x`.
|
|
||||||
- Pure static helpers must stay pure and seed-deterministic: `outfit_for`,
|
|
||||||
`layer_for`, `hand_item_for`, `idle_style_for`, `queue_slot_for`,
|
|
||||||
`lod_tier_for_distance`, `reaction_label`, `crouch_for_pose`,
|
|
||||||
`prop_lift_for_pose`, `limb_swing`, `sway_for_pose`.
|
|
||||||
- LOD tiers (`apply_lod`, 0 / 1 / 2 with a billboard impostor at tier 2) must
|
|
||||||
keep working; `_detail_nodes` are hidden at tier 1.
|
|
||||||
- `set_focus_visual`, `_focus_circle`, `_name_tag`, `_reaction_tag`, the
|
|
||||||
theater poses, and the crowd reaction (`start_rubberneck`, `start_flee`) all
|
|
||||||
operate on the rig and must survive the swap.
|
|
||||||
|
|
||||||
## Workstreams
|
|
||||||
|
|
||||||
### Phase 0 — Toolchain spike and contract freeze (small, throwaway)
|
|
||||||
|
|
||||||
1. Confirm headless Blender works in this environment. Blender **5.2.0 LTS**
|
|
||||||
flatpak is installed; the sandbox cwd is `/app/blender`, so `--python` and
|
|
||||||
all input/output paths must be absolute:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
flatpak run org.blender.Blender --background \
|
|
||||||
--python /var/home/zwitschi/Documents/06-games/security-game/tools/npc_pipeline.py -- --build
|
|
||||||
```
|
|
||||||
|
|
||||||
Record the working invocation in `MEMORY.md` (the Im-Dornäcker notes already
|
|
||||||
warn about the cwd gotcha).
|
|
||||||
2. Freeze the asset contract in writing (this document + ADR-0013). No
|
|
||||||
animation clips are exported: `_animate()` and `_idle_layer()` drive the
|
|
||||||
armature bones directly at runtime, so the GLB only needs the skinned rest
|
|
||||||
mesh, the armature, and the material maps.
|
|
||||||
- `assets/models/generated/npc_base.glb` — one skinned mesh (or a small fixed
|
|
||||||
set of sub-meshes: body / head / hands) with a **named armature** and UVs.
|
|
||||||
- Material slots rather than one atlas-per-prop: `skin`, `cloth`,
|
|
||||||
`pants`, `accessory`, `hair` each carry a neutral albedo + normal + ORM
|
|
||||||
(occlusion/roughness/metallic) map. `outfit_for` colors tint the slot, so
|
|
||||||
multi-color outfits and seed variety survive.
|
|
||||||
- Bone names mirror the current node names (`Head`, `HipL/R`, `KneeL/R`,
|
|
||||||
`ShoulderL/R`, `ElbowL/R`, `HandL/R`) so `_animate()` can target them.
|
|
||||||
- Budget: tier-0 base body ≤ ~6,000 triangles, ≤ ~4,000 vertices (revisit
|
|
||||||
after the first `smoke_npc_budget.gd` run).
|
|
||||||
|
|
||||||
### Phase 1 — `tools/npc_pipeline.py`: author + export + audit
|
|
||||||
|
|
||||||
New tool, run through the Blender flatpak, mirroring the `model_pipeline.py`
|
|
||||||
shape (`list` / `build` / `audit`):
|
|
||||||
|
|
||||||
1. **Author** a low-poly base humanoid in Blender: torso, articulated arms/legs,
|
|
||||||
head with a face front (so the focus ring and the "noticed the guard" cue
|
|
||||||
keep a direction), hands, shoes. Keep the current silhouette language
|
|
||||||
(heads, caps, hoods, backpacks, lanyards) so existing `outfit_for`
|
|
||||||
accessories still attach.
|
|
||||||
2. **Rig** it with an armature whose bones are named to match the node contract.
|
|
||||||
Godot 4.3 imports glTF skeletons as `Skeleton3D`; keep the bone rest pose
|
|
||||||
matching the current `_animate()` assumptions (arms hang, legs straight,
|
|
||||||
+Y up, origin at the feet on the floor).
|
|
||||||
3. **UV-unwrap** into a power-of-two layout and **bake** maps in Blender from
|
|
||||||
node materials or a higher-detail source:
|
|
||||||
- neutral albedo (white fabric pattern) per material slot,
|
|
||||||
- normal map (fabric weave, denim, knit),
|
|
||||||
- ORM map (occlusion in R, roughness in G, metallic in B — same packing
|
|
||||||
`model_pipeline.atlas_for` already uses),
|
|
||||||
- ambient occlusion baked into the albedo or the ORM R channel (this is the
|
|
||||||
Compatibility renderer's only AO source).
|
|
||||||
4. **Export** `npc_base.glb` with materials referenced as Godot-importable
|
|
||||||
textures (or export textures alongside and wire them in GDScript — decide in
|
|
||||||
the spike).
|
|
||||||
5. **Audit** (`npc_pipeline.py audit`) enforcing: UVs in [0,1], expected bone
|
|
||||||
names present, triangle/vertex budget, feet at y ≈ 0, no missing material
|
|
||||||
slots. Reuse the checks and the colour-space discipline from
|
|
||||||
`model_pipeline.py` (glTF `baseColorFactor` is linear; albedo textures are
|
|
||||||
sRGB — do not mix them up).
|
|
||||||
6. **Commit** the `.blend` source (`assets/models/npc/npc_base.blend`) so the
|
|
||||||
build is reproducible and editable, plus the generated `.glb` + textures so
|
|
||||||
CI never needs Blender.
|
|
||||||
|
|
||||||
### Phase 2 — AI-assisted texturing (existing tools only)
|
|
||||||
|
|
||||||
Use the tools already in `tools/`, not new services:
|
|
||||||
|
|
||||||
1. `tools/asset_pipeline.py generate` (OpenRouter,
|
|
||||||
`openai/gpt-5-image-mini`) for seamless clothing albedos — shirt, trousers,
|
|
||||||
jacket, hi-vis vest, coat, dress — at the role sizes its `audit` validates.
|
|
||||||
Run `seamless` for tile-edge blending and `audit` to reject wrong sizes.
|
|
||||||
2. `tools/model_pipeline.py`'s palette helpers (`dominant_colors`,
|
|
||||||
`assign_roles`) or a small shared import, to derive per-role outfit palettes
|
|
||||||
from AI concept sheets (`concept`). `outfit_for` keeps determinism: the AI
|
|
||||||
supplies palettes and textures, never per-frame randomness.
|
|
||||||
3. Normal/roughness detail (fabric weave, denim) baked procedurally in Blender,
|
|
||||||
with the AI albedos layered on top.
|
|
||||||
4. Close the loop with `tools/screen_designs.py judge`: regenerate the staged
|
|
||||||
captures and confirm the new NPCs realise the same screen intents (art style
|
|
||||||
is ignored by the judge; layout and readability are not).
|
|
||||||
|
|
||||||
Outputs land under `assets/textures/ai/game_ready/` (or a new
|
|
||||||
`assets/textures/npc/`) and are **committed**.
|
|
||||||
|
|
||||||
### Phase 3 — Godot integration (`npc_avatar.gd`, `materials.gd`)
|
|
||||||
|
|
||||||
1. Add a `_load_model()` path in `NpcAvatar` that instantiates the imported
|
|
||||||
`npc_base.glb` `PackedScene`, exactly like `WorldBuilder._add_prop_model()`
|
|
||||||
loads props. Keep `_build_body()` as the offline/procedural fallback; choose
|
|
||||||
per-avatar with the same "asset or fallback" pattern (record the choice in a
|
|
||||||
`loaded_model` / `fallback_avatar` field for tests).
|
|
||||||
2. Map `outfit_for()` onto the model's material slots (skin/cloth/pants/
|
|
||||||
accessory/hair) so seed variety, `layer_for`, `hand_item_for`, and the
|
|
||||||
archetype color read stay intact. Accessories (cap/hood/backpack/
|
|
||||||
sunglasses/lanyard) keep attaching under `Rig`/`Head` as they do today.
|
|
||||||
3. Add `SurfaceMaterials` recipes for the baked maps — e.g. `npc()` /
|
|
||||||
`skin()` / `cloth()` — so `_material()` gains a textured path and the body
|
|
||||||
stops calling `Materials.flat()`.
|
|
||||||
4. Abstract the animation targets so `_animate()` / `_idle_layer()` write to
|
|
||||||
either the procedural `Node3D` pivots (fallback) or `Skeleton3D` bones
|
|
||||||
(skinned). Keep the existing `rotation.x` math; the abstraction is a thin
|
|
||||||
`_set_limb_rotation(part, axis, radians)` wrapper.
|
|
||||||
5. Keep `Rig/Head` as a real scene node (a marker following the head bone, or
|
|
||||||
the imported bone's Godot node) so focus, nametag, and head-turn still find
|
|
||||||
it. Re-verify the focus cone smoke and `test_npc_character` /
|
|
||||||
`test_npc_theater` / `test_target_cycle`.
|
|
||||||
6. Re-measure `smoke_npc_budget.gd`; adjust `LOD_TIERS` if the skinned rig is
|
|
||||||
heavier up close. The impostor path is unchanged.
|
|
||||||
|
|
||||||
### Phase 3.5 — Blender asset library (characters, gear, props)
|
|
||||||
|
|
||||||
The library generator is `tools/blender_assets.py` (flatpak Blender, headless).
|
|
||||||
**Status: integrated** — `NpcAvatar` selects characters/gear and `WorldBuilder`
|
|
||||||
loads the replacement/new props plus phase-driven emissives; the procedural rig
|
|
||||||
and primitive props remain fallbacks. Integration notes:
|
|
||||||
[`blender-assets.md`](blender-assets.md).
|
|
||||||
|
|
||||||
**Outputs and contract**
|
|
||||||
|
|
||||||
| Kind | Path | Notes |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Characters | `assets/models/blender/npc/<name>.glb` | 8 variants: `npc_casual`, `npc_jacket`, `npc_coat`, `npc_vest`, `npc_dress`, `npc_hoodie`, `npc_firefighter`, `npc_guard` |
|
|
||||||
| Gear | `assets/models/blender/npc_gear/<name>.glb` | 18 pieces; authored in attach-point space (`head`, `hand`, `back`, `chest`) |
|
|
||||||
| Props | `assets/models/blender/props/<name>.glb` | 20 pieces; the first nine replace current generated/primitive street furniture, the rest are new venue dressing |
|
|
||||||
| Manifest | `assets/models/blender/manifest.json` | path, category, triangles, vertices, bounds, materials, roles, tint slots, attach points |
|
|
||||||
| Sources | `assets/models/blender_src/<name>.blend` | committed, editable source |
|
|
||||||
| Previews | `docs/blender_previews/sheet_<category>.jpg` (committed) + per-asset `*.png` (local) | `render` / `sheet` for visual review |
|
|
||||||
|
|
||||||
All characters reuse the `npc_base` contract: mesh node `npc_base`, bones
|
|
||||||
`Head/HipL/R/KneeL/R/ShoulderL/R/ElbowL/R/HandL/R` (+ Hips/Spine/Neck),
|
|
||||||
material slots `skin/cloth/pants/accessory/hair`, front toward Blender `-Y`
|
|
||||||
(Godot `+Z`), feet at `y ~= 0`. Gear origins are the attach points (head at
|
|
||||||
`Rig/Head`, hand at the hand proxy, back at the spine, chest at the sternum).
|
|
||||||
Props are grounded except `wall_lantern` and `marquee`; both may be emissive.
|
|
||||||
|
|
||||||
**Integration steps**
|
|
||||||
|
|
||||||
1. **Audit the finished build.** `python3 tools/blender_assets.py audit` and
|
|
||||||
`sheet`; fix assets or reconcile the budgets. **Done 2026-11-01:** the
|
|
||||||
characters were simplified to fit the unchanged 6,000 / 4,000 contract
|
|
||||||
(3,788–4,640 tris), and all 46 assets audit clean.
|
|
||||||
2. **Commit the library and sources.** Add `assets/models/blender/**`,
|
|
||||||
`assets/models/blender_src/**`, the generator, manifest, previews, and
|
|
||||||
`docs/blender-assets.md`; generated folders that CI can rebuild offline may
|
|
||||||
stay ignored. `blender_assets.py audit` must work without Blender.
|
|
||||||
3. **Select characters by role.** Extend `NpcAvatar` to pick a variant from the
|
|
||||||
manifest using the profile's role and `outfit_for`/`layer_for` (casual,
|
|
||||||
jacket, coat, vest, dress, hoodie, firefighter, guard). Fall back to
|
|
||||||
`npc_base.glb`, then to the procedural rig, and record the chosen path.
|
|
||||||
4. **Tint and anchor.** Apply `outfit_for` to every variant's five slots via
|
|
||||||
`SurfaceMaterials.npc_slot()`; keep `Rig/Head` for the focus circle, name tag,
|
|
||||||
reaction tag, and accessories. Keep the walk/idle/escalation bone-driving
|
|
||||||
wrapper.
|
|
||||||
5. **Replace procedural gear.** Use the `npc_gear` catalogue for caps, hoods,
|
|
||||||
helmets, backpacks, lanyards, hand items (phone, cup, clipboard, umbrella,
|
|
||||||
camera, sign, flashlight, wand, radio) and the SCBA tank; attach at the
|
|
||||||
documented points and fall back to the current primitives when an asset is
|
|
||||||
missing.
|
|
||||||
6. **Replace and extend props.** Load the replacement set through
|
|
||||||
`WorldBuilder` (`bollard`, `bench`, `trash_bin`, `planter_box`,
|
|
||||||
`newspaper_box`, `fire_hydrant`, `street_lamp`, `street_tree`,
|
|
||||||
`traffic_cone`), then place the new dressing (`palm_tree`, `round_planter`,
|
|
||||||
`a_frame_sign`, `wall_lantern`, `crowd_barrier`, `stanchion`, `handrail`,
|
|
||||||
`security_podium`, `double_door`, `marquee`, `sedan`) using the existing
|
|
||||||
placement table/collider/avoidance contract. No prop may enter the
|
|
||||||
door/steps/guest lane.
|
|
||||||
7. **Wire emissives to the phase.** Street-lamp bulbs, wall lanterns, the door
|
|
||||||
lock LED, marquee bulbs/sign face, and sedan headlights should follow the
|
|
||||||
existing day/night lighting rather than staying always on.
|
|
||||||
8. **Measure and gate.** Re-run `smoke_npc_budget.gd` with the new characters
|
|
||||||
before raising `MAX_LIVE_NPCS`; add a prop triangle/draw budget check. Then
|
|
||||||
recapture `docs/screenshots/` and run `screen_designs.py compare` + `judge`
|
|
||||||
plus a close-up target shot for character readability.
|
|
||||||
9. **Fallbacks stay offline.** A fresh checkout with no Blender and no network
|
|
||||||
must boot on the committed library (or `npc_base.glb`/procedural rig when an
|
|
||||||
asset is absent); CI still runs the Godot import and headless suite.
|
|
||||||
|
|
||||||
|
|
||||||
### Phase 4 — Environment and lighting lift (after NPCs land) ✅
|
|
||||||
|
|
||||||
1. **Baked AO on props:** use the Phase 3.5 Blender prop replacements
|
|
||||||
(`street_lamp`, `street_tree`, `bench`, …) and bake AO into their
|
|
||||||
albedo/ORM maps so contact shadows read without SSAO. Keep the same
|
|
||||||
`WorldBuilder` load/fallback contract (ADR-0012's follow-up).
|
|
||||||
2. **Higher-resolution environment textures:** regenerate brick/concrete/asphalt
|
|
||||||
through `asset_pipeline.py` with normal maps, keeping the current UV scales;
|
|
||||||
use the same sRGB/albedo + packed ORM discipline as the library assets.
|
|
||||||
3. **Evaluate a desktop-only Forward+ preset** — completed in v0.7.1 as the optional
|
|
||||||
Desktop HQ zip. The default renderer stays GL Compatibility for Web parity; the
|
|
||||||
override is applied only to the copied HQ binaries.
|
|
||||||
4. Regenerate `docs/screenshots/` with `capture_screenshots.gd`, then run
|
|
||||||
`screen_designs.py compare` and `judge` as the regression gate, plus
|
|
||||||
`blender_assets.py sheet` for the character/gear/prop library.
|
|
||||||
|
|
||||||
### Phase 5 — Verification and release
|
|
||||||
|
|
||||||
- Headless: fallback build, `npc_pipeline.py audit` (via `test_model_pipeline`
|
|
||||||
style Python tests if Blender is present, otherwise skip with reason), outfit
|
|
||||||
determinism, `Rig/Head` presence, material recipe assertions.
|
|
||||||
- Windowed smokes: `smoke_npc_budget.gd`, `smoke_movement.gd`,
|
|
||||||
`smoke_focus_radial.gd`, `smoke_props.gd`; screenshot comparison.
|
|
||||||
- Bump `config/version` to `0.6.0`, update `README.md`, `docs/architecture.md`
|
|
||||||
(Level 2 gains `tools/npc_pipeline.py`), and publish per
|
|
||||||
[`docs/RELEASING.md`](RELEASING.md).
|
|
||||||
|
|
||||||
## Risks and mitigations
|
|
||||||
|
|
||||||
| Risk | Mitigation |
|
|
||||||
| --- | --- |
|
|
||||||
| Blender flatpak sandbox paths (cwd `/app/blender`) | Absolute paths everywhere; record the invocation in `MEMORY.md`. |
|
|
||||||
| glTF colour space (linear factors vs sRGB textures) | Reuse `model_pipeline.srgb_to_linear`; bake textures as sRGB, keep factors linear; assert in `audit`. |
|
|
||||||
| Skeleton retargeting vs current node math | Keep the bone names + a thin limb-rotation wrapper; fallback rig keeps old math running. |
|
|
||||||
| Frame budget with skinned avatars | Measure first (`smoke_npc_budget.gd`); tune `LOD_TIERS`; impostor path unchanged. |
|
|
||||||
| No SSAO in Compatibility | Bake AO into albedo/ORM; contact-shadow tune in Phase 4. |
|
|
||||||
| AI output nondeterminism | Generation is dev-time only; commit outputs and gate with `audit`/`judge`/`compare`. |
|
|
||||||
| Blender unavailable in CI | Committed `.glb` + textures; procedural fallback; `audit` skipped with reason when Blender is absent. |
|
|
||||||
|
|
||||||
## Acceptance criteria (definition of done)
|
|
||||||
|
|
||||||
1. NPCs and street furniture are Blender-authored meshes with baked maps; the
|
|
||||||
character variants, gear, and props read as people/objects at conversation
|
|
||||||
distance and survive a cinematic close-up.
|
|
||||||
2. `Rig/Head`, the focus cone, nametag, theater poses, crowd reactions, and LOD
|
|
||||||
all still work; all existing NPC tests pass unchanged or are updated with
|
|
||||||
clear reasons.
|
|
||||||
3. The game boots and plays from a fresh checkout with **no** Blender and no
|
|
||||||
network (committed assets + procedural fallback).
|
|
||||||
4. `npc_pipeline.py build` is deterministic and `audit` enforces the contract.
|
|
||||||
5. 212 headless + 60 Python tests stay green (Blender-dependent Python tests
|
|
||||||
skip with a reason when Blender is absent); `npc_pipeline.py audit` and
|
|
||||||
`blender_assets.py audit` pass; NPC-budget and movement smokes pass;
|
|
||||||
screenshot `compare`/`judge` and the library contact sheets show no
|
|
||||||
regressions.
|
|
||||||
6. The library is committed with its generator/manifest/previews, runtime
|
|
||||||
selection and fallbacks are covered by tests, version bumped to v0.6.0,
|
|
||||||
docs and ADR-0013 updated, release published.
|
|
||||||
@@ -16,7 +16,7 @@ path must be absolute):
|
|||||||
python3 tools/npc_pipeline.py palette # derive outfit colours -> palette.json
|
python3 tools/npc_pipeline.py palette # derive outfit colours -> palette.json
|
||||||
python3 tools/npc_pipeline.py audit
|
python3 tools/npc_pipeline.py audit
|
||||||
|
|
||||||
Contract (frozen in docs/plan-graphics-v0.6.md + ADR-0013):
|
Contract (frozen in ADR-0013 and docs/blender-assets.md):
|
||||||
|
|
||||||
* scene root "Rig" with a skinned mesh and an armature
|
* scene root "Rig" with a skinned mesh and an armature
|
||||||
* bones: Head, HipL/R, KneeL/R, ShoulderL/R, ElbowL/R, HandL/R
|
* bones: Head, HipL/R, KneeL/R, ShoulderL/R, ElbowL/R, HandL/R
|
||||||
|
|||||||
Reference in new issue
Block a user