Break the README into focused docs pages
This commit is contained in:
1 parent
26d64baa01
commit
a7ca698f73
6 files changed
+557
-536
No files matched your search
@@ -0,0 +1,268 @@
|
||||
# Asset Pipeline and Tooling
|
||||
|
||||
Everything the game renders is produced or gated by the scripts below. Development-only outputs (AI concept sheets, Blender sources, raw AI fabrics) are gitignored; the audited game assets are committed.
|
||||
|
||||
## Image Asset Tools
|
||||
|
||||
Install the Pillow dependency used by the existing texture generator and the asset tools:
|
||||
|
||||
```bash
|
||||
python3 -m pip install -r requirements-tools.txt
|
||||
```
|
||||
|
||||
Generate a role-sized image (generation requires `OPENROUTER_API_KEY`):
|
||||
|
||||
```bash
|
||||
python3 tools/asset_pipeline.py generate --prompt "weathered red brick" --role tile --output assets/textures/brick_extra.png
|
||||
```
|
||||
|
||||
Generated images are resized to the selected role profile. Non-square requests use
|
||||
the validated `openai/gpt-5-image-mini` source ratio and crop to the final role dimensions.
|
||||
Transparent glyphs can be recolored while preserving their alpha mask with
|
||||
`--foreground '#ffffff'`.
|
||||
|
||||
Crop uses the image center by default; `--x` and `--y` select an explicit crop origin. Resize defaults to aspect-preserving `contain`; use `--fit cover` to crop or `--fit stretch` to distort to the exact dimensions.
|
||||
|
||||
```bash
|
||||
python3 tools/asset_pipeline.py crop assets/textures/source.png assets/textures/crop.png --width 256 --height 128
|
||||
python3 tools/asset_pipeline.py resize assets/textures/source.png assets/images/scenes/scene.png --width 1920 --height 1080 --fit cover
|
||||
python3 tools/asset_pipeline.py seamless assets/textures/rough_tile.png assets/textures/tile.png
|
||||
```
|
||||
|
||||
Audit accepts image files or directories. `--role auto` infers common roles from paths and filenames; explicit roles include `tile`, `texture`, `icon`, `card`, `banner`, `backdrop`, `background`, `overlay`, `app_icon`, and `screenshot`.
|
||||
|
||||
```bash
|
||||
python3 tools/asset_pipeline.py audit assets/textures
|
||||
python3 tools/asset_pipeline.py audit assets/images/scenes --role background --json
|
||||
python3 -m unittest discover -s tools -p 'test_*.py'
|
||||
```
|
||||
|
||||
The audit classifies measurable fit issues as `good` or `bad` based on role-specific dimensions, aspect ratio, alpha requirements, and tile-edge continuity. It lists perspective/projection as a visual review item because image semantics cannot be judged reliably by these deterministic checks.
|
||||
|
||||
## Prop models
|
||||
|
||||
Street furniture (bollards, benches, bins, planters, newspaper boxes, a hydrant)
|
||||
is real 3D geometry generated by `tools/model_pipeline.py` and written to
|
||||
`assets/models/generated/*.glb`. Godot imports each file as a `PackedScene`, and
|
||||
`WorldBuilder` instantiates it with a procedural fallback when the model or its
|
||||
import is missing, so the world still builds on a fresh checkout.
|
||||
|
||||
```bash
|
||||
# Print the catalogue (parts, triangle count, whether a model/concept exists)
|
||||
python3 tools/model_pipeline.py list
|
||||
|
||||
# Rebuild the models (deterministic, offline) and validate them
|
||||
python3 tools/model_pipeline.py build
|
||||
python3 tools/model_pipeline.py audit
|
||||
|
||||
# Optional: an image model supplies the colour palette per prop
|
||||
# (requires OPENROUTER_API_KEY; the repo .env is read automatically)
|
||||
python3 tools/model_pipeline.py concept
|
||||
python3 tools/model_pipeline.py concept --only bench --force
|
||||
```
|
||||
|
||||
Each prop is a small list of boxes and cylinders, so `build` needs no network and
|
||||
the output is byte-identical between runs. `concept` asks an image model for a
|
||||
studio-style reference; `dominant_colors` and `assign_roles` then map that image's
|
||||
colours onto material roles (darkest → metal, warm saturated → wood, light grey →
|
||||
stone, green → foliage). glTF stores colours in linear space, so the tool converts
|
||||
the sRGB palettes before writing them. Re-run `build` and `godot --headless
|
||||
--path . --import` after changing a prop. See
|
||||
[ADR-0012](adr/0012-surface-materials-and-generated-props.md).
|
||||
|
||||
Surface materials share one definition in `src/game/materials.gd`: named
|
||||
roughness/metallic profiles per surface, plus the environment tune-up (ACES
|
||||
tonemapping, glow, fog, colour grading) that the phase lighting applies.
|
||||
|
||||
## NPC character model
|
||||
|
||||
NPCs are moving from the procedural capsule rig to a skinned, UV-mapped base
|
||||
character authored in Blender ([ADR-0013](adr/0013-blender-authored-npc-characters.md)).
|
||||
`tools/npc_pipeline.py` builds `assets/models/generated/npc_base.glb` plus the
|
||||
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
|
||||
existing animation code; the procedural rig remains the offline fallback (see
|
||||
the [graphics plan](plan-graphics-v0.6.md)).
|
||||
|
||||
```bash
|
||||
# Print the frozen contract (slots, bone names, triangle budget)
|
||||
python3 tools/npc_pipeline.py list
|
||||
|
||||
# Rebuild the skinned base mesh + neutral maps in Blender and validate it
|
||||
flatpak run org.blender.Blender --background --python "$PWD/tools/npc_pipeline.py" -- build
|
||||
python3 tools/npc_pipeline.py audit
|
||||
|
||||
# Layer the AI clothing albedos (shirt/trousers) over the procedural weave, and
|
||||
# derive per-fabric outfit colours for the avatar palette
|
||||
python3 tools/npc_pipeline.py prepare
|
||||
python3 tools/npc_pipeline.py palette
|
||||
```
|
||||
|
||||
`prepare` resizes the AI fabric tiles (kept locally in `assets/textures/npc/ai/`,
|
||||
which is gitignored) onto the `cloth`/`pants` albedos; `build` bakes them into the
|
||||
`.glb` and regenerates the procedural normal + packed ORM maps. `palette` writes
|
||||
`assets/textures/npc/palette.json` using `model_pipeline.dominant_colors`.
|
||||
|
||||
## Blender asset library (loaded by the game)
|
||||
|
||||
`tools/blender_assets.py` models a larger library procedurally in Blender for the
|
||||
v0.6.0 integration: **8 skinned NPC characters** (casual, jacket, coat, hi-vis vest,
|
||||
dress, hoodie, firefighter, guard), each a drop-in for `npc_base.glb` (same bones,
|
||||
joint positions and tint slots); **18 gear pieces** authored at head/hand/back/chest
|
||||
attach points (caps, hood, helmet, backpack, SCBA tank, lanyard, phone, cup,
|
||||
clipboard, umbrella, camera, sign, flashlight, wand, radio, …); and **20 props**
|
||||
(octagonal bollard and bin, slatted bench, hydrant, news box, planters, street lamp,
|
||||
street and palm trees, A-frame sign, wall lantern, cone, crowd barrier, stanchion,
|
||||
handrail, podium, double door, marquee, sedan).
|
||||
|
||||
`NpcAvatar` selects a character by role/seed, tints the five contract slots, and
|
||||
attaches gear at the documented points; `WorldBuilder` loads the library props
|
||||
with colliders/avoidance and drives their emissive parts from the day/night
|
||||
cycle. The single `npc_base.glb` and primitive props remain offline fallbacks.
|
||||
Contract and integration notes: [`blender-assets.md`](blender-assets.md).
|
||||
|
||||
| Characters | Props |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
```bash
|
||||
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build
|
||||
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render
|
||||
python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg
|
||||
python3 tools/blender_assets.py audit # no Blender needed
|
||||
godot --headless --path . --import
|
||||
```
|
||||
|
||||
## Confirmed game assets
|
||||
|
||||
Everything below is audited and shipped in the game; development-only outputs
|
||||
(AI concept sheets, Blender sources, raw AI fabrics) are gitignored.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
- **Surface textures:** the v2 picks above (brick, sidewalk pavers, dry and wet asphalt, awning canvas) plus their normal maps, loaded by `src/game/materials.gd` and `WorldBuilder`; the three skyline panoramas are switched per shift phase. The first-generation tiles and `assets/textures/skyline.png` are no longer loaded.
|
||||
- **UI art:** `assets/textures/ai/game_ready/icons/` (flashlight, door lock, wand, REC) and `assets/textures/ai/game_ready/cards/` (ID, wristband, ticket, credential, guest list), loaded by `src/game/ui/hud.gd`.
|
||||
- **Generated models:** `assets/models/generated/*.glb` — bollard, bench, trash bin, planter, newspaper box, hydrant, and the NPC base — plus their colour atlases.
|
||||
- **NPC material maps:** `assets/textures/npc/*_{albedo,normal,orm}.png` and `palette.json`.
|
||||
|
||||
Audit them all:
|
||||
|
||||
```bash
|
||||
python3 tools/asset_pipeline.py audit assets/textures/ai/game_ready
|
||||
python3 tools/model_pipeline.py audit
|
||||
python3 tools/npc_pipeline.py audit
|
||||
```
|
||||
|
||||
## Realism pass: scene concepts, skylines and textures
|
||||
|
||||
The skyline, surface tiles and phase lighting were rebuilt against AI concept frames. The
|
||||
concepts live in [`docs/screens/concepts/`](../docs/screens/concepts/README.md); the validated
|
||||
picks the game loads are in `assets/textures/ai/game_ready/v2/picks/` (day/dusk/night
|
||||
panoramas, brick, sidewalk pavers, dry and wet asphalt, awning canvas, each with a normal
|
||||
map).
|
||||
|
||||
`tools/scene_concepts.py` generates and processes new candidates, promotes the validated ones
|
||||
to stable names, audits them, and scores captures against the concepts. It never overwrites an
|
||||
image: new files get a `-vN` suffix, and `process` skips existing candidates unless `--new`.
|
||||
|
||||
```bash
|
||||
python3 tools/scene_concepts.py generate --group scene # or backdrop / texture, --variants N
|
||||
python3 tools/scene_concepts.py process && python3 tools/scene_concepts.py audit
|
||||
python3 tools/scene_concepts.py promote # validated picks -> v2/picks/
|
||||
godot --headless --path . --import
|
||||
|
||||
# A/B a candidate without touching game code: the same cameras, textures swapped at runtime.
|
||||
godot --path . --script res://src/test/capture_realism.gd -- --out=res://docs/screens/realism/<run>/after
|
||||
godot --path . --script res://src/test/capture_realism.gd -- --out=res://docs/screens/realism/<run>/try \
|
||||
--swap=res://assets/textures/ai/game_ready/v2/picks/brick_facade.png=res://assets/textures/ai/game_ready/v2/brick_facade-v2.png
|
||||
|
||||
# Score before/after against the concepts (Pillow detail metrics + vision judge).
|
||||
python3 tools/scene_concepts.py realism --run docs/screens/realism/<run> --judge \
|
||||
--model google/gemini-2.5-flash --repeats 3
|
||||
```
|
||||
|
||||
`capture_realism.gd` runs six stages (day, portrait, evening, late, Joke Shop, studio);
|
||||
`--stages=day,late` limits it while tuning. The `portrait` shot freezes one NPC on open sidewalk
|
||||
and frames the face, which is the only capture that can judge avatar detail
|
||||
(`--portrait-type=N` picks a role). The texture-adoption run,
|
||||
[`docs/screens/realism/2026-11-01-adopt/`](../docs/screens/realism/2026-11-01-adopt/report.md),
|
||||
compares the pre-adoption captures with the current build: on the shots both sets captured the
|
||||
judge's like-for-like means rose on every sub-score (overall 0.8 → 1.0, backdrop 0.6 → 1.6) and
|
||||
the Pillow metric gap fell 0.444 → 0.348. The character pass
|
||||
([`2026-11-01-npc/`](../docs/screens/realism/2026-11-01-npc/report.md)) adds the portrait shot and
|
||||
records the new faces, hands and sleeve cuffs.
|
||||
|
||||

|
||||
|
||||
## Screen design references
|
||||
|
||||
Screen mockups and screenshot comparison use `tools/screen_designs.py`. It turns the
|
||||
screen descriptions in `docs/` (README captions, ADRs 0007–0010, `PLAN.md`, and
|
||||
`architecture.md`) into runtime reference art under `assets/images/ui/` (with a
|
||||
reference-only snapshot in `docs/screens/designs/`), then uses those images as
|
||||
the expected state to check the game's captures.
|
||||
|
||||
Each screen is tagged `scene` (a realistic first-person 3D world view) or `ui`
|
||||
(an in-game panel overlaid on a softened scene), and the generator appends a
|
||||
shared art-direction suffix so the frames read as one coherent game. Requests use
|
||||
a 3:2 landscape source and crop to the 16:9 capture dimensions.
|
||||
|
||||
```bash
|
||||
# Export the built-in scenarios to editor-authorable .tres resources
|
||||
godot --headless --path . --script res://tools/export_venues.gd
|
||||
|
||||
# List the catalogue (name, kind, whether a reference design exists)
|
||||
python3 tools/screen_designs.py list
|
||||
|
||||
# Deterministic wireframes (offline, no API key); existing designs are kept
|
||||
# unless --force is passed, so an offline run never replaces an AI mockup
|
||||
python3 tools/screen_designs.py generate --draft
|
||||
python3 tools/screen_designs.py generate --draft --only wand --force
|
||||
|
||||
# Realistic 3D scenes and in-game screen designs via OpenRouter
|
||||
# (requires OPENROUTER_API_KEY)
|
||||
python3 tools/screen_designs.py generate
|
||||
|
||||
# Prop gallery (studio line-up) for close-range prop checks
|
||||
godot --path . --script res://src/test/capture_prop_gallery.gd
|
||||
|
||||
# Compare docs/screenshots/*.png against the designs
|
||||
python3 tools/screen_designs.py compare
|
||||
python3 tools/screen_designs.py compare --json
|
||||
|
||||
# Ask a vision model whether each capture realises its design's screen and layout
|
||||
# (requires OPENROUTER_API_KEY; the repo .env is read automatically)
|
||||
python3 tools/screen_designs.py judge
|
||||
python3 tools/screen_designs.py judge --model openai/gpt-4o-mini --json
|
||||
python3 tools/screen_designs.py judge --only 03- # one capture
|
||||
python3 tools/screen_designs.py judge --dry-run # build requests, no network
|
||||
```
|
||||
|
||||
The catalogue covers 21 screens: the eleven README captures plus the start menu,
|
||||
pause overlay, settings, shift review, radial menu, emergency radio, wand
|
||||
screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival,
|
||||
body-camera timeline, and the escalation HUD. All 21 have an AI reference in
|
||||
`assets/images/ui/` (snapshot in `docs/screens/designs/`); the eleven screens with
|
||||
staged captures are also compared or judged against the running game.
|
||||
|
||||
`compare` flags gross drift: wrong aspect ratio, undersized frame, near-blank
|
||||
frame, and large layout/palette/detail distance. It is a regression gate, not a
|
||||
semantic judge — pixel and perceptual metrics cannot decide whether a frame's
|
||||
composition is *right*.
|
||||
|
||||
`judge` covers that gap: it sends each capture together with its concept mockup to
|
||||
a vision model and asks whether the capture realises the same screen and layout
|
||||
intent. Art style, fidelity, fonts, and placeholder text are explicitly ignored.
|
||||
Verdicts are classed as `match`, `review` (a mismatch below `--fail-confidence`,
|
||||
default 0.6 — expected when a concept mockup is more stylised than the game), or
|
||||
`diff` (a confident mismatch, which exits non-zero). Regenerate the designs after changing screen layout, then re-run
|
||||
`compare` after regenerating the game screenshots with
|
||||
`src/test/capture_screenshots.gd`.
|
||||
|
||||
For a one-command drift check, run `tools/check_screenshots.sh`. It renders the
|
||||
staged captures and prop gallery into `docs/screenshots/` (using `xvfb-run` when
|
||||
no display is available) and then runs `screen_designs.py compare`.
|
||||
Reference in new issue
Block a user