MOTION (§41.1). The clip library goes 8 -> 46: ten idles, eight browse, eight sit/lean, eight social, six locomotion, six venue, in SIX grouped GLBs (one fetch each), 3.35 MB — LESS than the 4.29 MB the old eight cost, via lossless dedup + int16 rotations (worst error 0.0034 deg). All six verified skeleton-only (tris 0, meshes 0, nodes 66): ZERO DRAW, which is what makes this round affordable. No retarget was run and none was wanted — the bank and the peds are the same mixamorig skeleton, so retargeting would add error AND bake the ped mesh into the clip, ending zero-draw. MIRPAMO 'make smoke' green to prove the tool, then deliberately unused. Three seeds in the brief were duds and got substituted: the whole *_Degree_Turn set is RIFLE-AIMING, and two 'examine' clips are 262 KB static poses, not motion. R16 flat-body trap re-checked on all 46 (spine tilt 24 samples/clip, 0 frames >75 deg); turn_in_place auto-demoted from loopable on a 36.7 deg seam. RULING 1 — the Bandai hazard, closed non-destructively and better than specified. 3,077 CC BY-NC clips sat unzoned in a neutrally-named path. Renamed PER FILE (3,077/3,077) not just the directory, because mirpamo names output <rig>@<clip>.glb — the _NC-research marker now PROPAGATES into any retargeted GLB automatically. Nothing deleted; ultra's red/bandai re-verified as canonical. Ledger corrected: the manifest claimed CC-BY-NC-ND, the bundled licence text says CC BY-NC, no ND. PROPS (§41.2). 110 assets from three libraries, published, sha1-verified, 0 validator errors both modes. THE FINDING: every handover number was a TRIANGLE count, and triangles were never the binding constraint — DRAW CALLS were. As handed over this cargo cost 14,140 draws against a 162-draw margin; it ships at 117. '3dstore passes as-is' was true on tris/metres/ Draco/textures and FALSE on draws — 30 of 46 files carried up to 16 materials on one mesh (one per record sleeve), so a 1,020-tri tub cost 16 draws; baked to COLOR_0, 288 -> 40, zero tri drift, A/B identical. dj-gear's own manifest claimed median 6,288 tris / 18 under budget; measured 51,528 and 12, with draws to 2,145. Pub props were NOT metre-correct (every source unit-normalised to max dim 1.00 m) and 40x decimation was impossible as specified (loungeChair is 94% non-manifold). Ruling 5 vs the draw budget was a real conflict (one mixer = 4.6x a whole room); resolved by joining SCENERY selectively while every control node keeps its object/name/pivot — verified by parsing the SHIPPED GLB, not the tool that wrote it: PASS 4 / STATIC 10 / N/A 17 / FAIL 0. deck_1200_rigged = 5 draws with Platter_SPIN, Arm_YAW, Fader_PITCH, Btn_STARTSTOP intact. The R40 four-surface transmission gate FIRED ON LIVE CARGO: 5 genuinely transmissive materials (cartridge dust-covers, a mixer meter window) that would each have doubled every opaque draw in the room. 9 assets rejected on eyeball after decimation rather than shipping mush. Two existing-tooling bugs fixed: normalize.py's yaw/up were silent no-ops (the jukebox exported 0.40 m instead of 0.97 m), and footprints now measure the shipped GLB. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
210 lines
15 KiB
Markdown
210 lines
15 KiB
Markdown
# PROCITY pipeline (Lane E) — how to re-run everything
|
||
|
||
The asset pipeline: audit ultra's library → normalize the good bits to house GLB law → publish
|
||
to the 3GOD depot → ship `web/assets/manifest.json` (the contract Lanes B/C/D read) → fill facade
|
||
+ prop gaps. **Everything is optional at runtime** — the game runs asset-free; this lane makes it
|
||
gorgeous, it doesn't make it work.
|
||
|
||
## Where things live
|
||
|
||
| what | where |
|
||
|---|---|
|
||
| ultra (library + Blender) | `ssh johnking@100.91.239.7` — key auth, **no** coreutils/timeout, py3.9, Blender at `/Applications/Blender.app/Contents/MacOS/Blender` (not on PATH) |
|
||
| model library | ultra `~/Documents/3D=models/{shop-fittings,street-furniture,furniture,props-scenes}` |
|
||
| 3GOD depot | `https://digalot.fyi/3god` — `GET /a/<f>`, `GET /api/list`, `POST /api/upload?name=<f>` |
|
||
| OpenRouter key (skins) | env `OPENROUTER_KEY` or `~/Documents/dealgod/lore/keys.env` (never committed) |
|
||
| depot password (publish) | env `GOD3_PW` (never committed; ask John) — cookie = `hmac(GOD3_PW,"3god-ok")` |
|
||
| Ollama (free LLM) | `http://100.89.131.57:11434` (qwen3) — for names/lore/cataloguing, unused so far |
|
||
| MODELBEAST (free local gen) | this box, `~/Documents/MODELBEAST` + its own queue on **`:8777`**. `flux_local` = FLUX.2-klein-4B text→image on MPS (**the facade skins** — ungated, weights cached). `sf3d`/`trellis_mac` = image→GLB, **local + PROVEN, weights already cached** — `trellis_mac` (TRELLIS.2 `1024_cascade`) went **15/15 on thriftgod's Tier-1 props** (Lane G/ultra, R1); `sf3d` = seconds-fast draft. Also live on the queue: `bg_remove_local`, `mflux_image_edit`, `seedvr2_upscale`. *(The old "HF-gated / setup needed" note here was stale — flagged by ultra, corrected R22.)* |
|
||
|
||
## The steps (each is idempotent / resumable)
|
||
|
||
### 1. Audit — `AUDIT.md` *(free)*
|
||
```bash
|
||
scp pipeline/glb_stat.py johnking@100.91.239.7:/tmp/
|
||
ssh johnking@100.91.239.7 'cd ~/Documents/3D=models && python3 /tmp/glb_stat.py --json shop-fittings street-furniture furniture props-scenes' > pipeline/_audit_stats.json
|
||
```
|
||
`glb_stat.py` is a dependency-free GLB parser (tris, bbox, texture sizes, Draco flag). Findings +
|
||
licensing triage are written up in `AUDIT.md`.
|
||
|
||
### 2. Normalize — `normalize.py` → 16 GLBs + thumbs *(free, runs on ultra)*
|
||
```bash
|
||
scp pipeline/normalize.py pipeline/batch.json johnking@100.91.239.7:/tmp/
|
||
BL=/Applications/Blender.app/Contents/MacOS/Blender
|
||
ssh johnking@100.91.239.7 "\$BL --background --python /tmp/normalize.py -- /tmp/batch.json /tmp/procity_norm"
|
||
scp johnking@100.91.239.7:/tmp/procity_norm/_results.json pipeline/_normalize_results.json
|
||
scp 'johnking@100.91.239.7:/tmp/procity_norm/thumbs/*.png' web/assets/thumbs/
|
||
scp 'johnking@100.91.239.7:/tmp/procity_norm/*.glb' pipeline/_normalized/
|
||
```
|
||
Enforces the house GLB law (metres, +Y up, base origin, ≤5k tris, WebP ≤1024, no Draco), renders a
|
||
256px thumbnail each. Extends `MESHGOD/scripts/finish_glb.py` (solidify/decimate/bright/base-origin)
|
||
+ adds texture-webp, height-normalize, outlier cleanup, per-asset up/yaw. Edit `batch.json` to add
|
||
assets; **always eyeball the thumbnails** — node-transform-distorted sources (see AUDIT rejects)
|
||
look fine in-frame but have wrong footprints.
|
||
|
||
### 3. Manifest — `build_manifest.py` + `validate_manifest.py` *(free)*
|
||
```bash
|
||
python3 pipeline/build_manifest.py # → web/assets/manifest.json
|
||
python3 pipeline/validate_manifest.py # Lane F gate: 0 errors = green
|
||
python3 pipeline/validate_manifest.py --depot # stricter: require GLBs live on the depot
|
||
```
|
||
`build_manifest.py` derives skins from `web/assets/gen/` + fittings from `_normalize_results.json`;
|
||
the curated facade→shop-type maps live at the top of that file (edit there when adding skins).
|
||
|
||
### 4. Publish — `publish.py` → 3GOD depot *(the one outward-facing step)*
|
||
```bash
|
||
python3 pipeline/publish.py --dry-run # free: list what would upload
|
||
export GOD3_PW=... # ask John
|
||
python3 pipeline/publish.py # POST each GLB + thumb to the depot (idempotent)
|
||
```
|
||
|
||
### 5. Facade skin gaps — `gen_skins.py --local` *(DONE, free, on-device)*
|
||
The 23-skin gap pack is **already generated + harvested + mapped**. To re-run or extend:
|
||
```bash
|
||
python3 pipeline/gen_skins.py --local --dry-run # free: list the pack, no GPU
|
||
python3 pipeline/gen_skins.py --local # MODELBEAST flux2-klein-4b on MPS → .genraw/ (~5.7s/img)
|
||
python3 pipeline/gen_skins.py --harvest # → web/assets/gen/facade-*.jpg, then re-run build_manifest.py
|
||
```
|
||
`--local` drives `~/Documents/MODELBEAST/server/operators/flux_local/run.py` directly (no server,
|
||
no key, weights cached). It runs one GPU job at a time. Add skins by editing `PROMPTS` in
|
||
`gen_skins.py` and their shop-type map in `build_manifest.py` (`FACADE_TYPES`). **Always eyeball
|
||
`.genraw/` and delete rejects before `--harvest`.** Cloud fallback (drop `--local`): OpenRouter
|
||
`google/gemini-3.1-flash-image`, ~$0.004/img, needs `OPENROUTER_KEY`. Manual flow: `SKIN_PROMPTS.md`.
|
||
|
||
### 6. Bespoke hero props — `gen_props.py` (MODELBEAST) *(round-3: DONE, free, on-device)*
|
||
The `meshgod_batch.json` gap list now goes through MODELBEAST instead of fal.ai — free, local:
|
||
```bash
|
||
python3 pipeline/gen_props.py --dry-run # list props + what's done
|
||
python3 pipeline/gen_props.py --concepts # flux_local concept images → .genprops/*.png (eyeball!)
|
||
python3 pipeline/gen_props.py --mesh --only glass-case # trellis_mac 1024_cascade image→GLB (~5-8 min)
|
||
python3 pipeline/gen_props.py --mesh # all props with a concept, no glb
|
||
# then normalize (uses local Blender), stage, catalogue:
|
||
BL=/Applications/Blender.app/Contents/MacOS/Blender
|
||
"$BL" --background --python pipeline/normalize.py -- pipeline/batch_props.json pipeline/_normalized web/assets/thumbs
|
||
cp pipeline/_normalized/_results.json pipeline/_props_results.json && python3 pipeline/build_manifest.py
|
||
```
|
||
**5 shipped** (arcade_cabinet, listening_booth, drinks_fridge, milkshake_mixer, novelty_record) + cash-register
|
||
reused from depot. `magazine-rack` → primitive (thin-wire gotcha); `glass-case`/`bus-shelter` → fal.ai
|
||
fallback (TRELLIS can't do transparent glass; `sf3d --draft` gives a featureless blob). TRELLIS meshes run
|
||
heavy (25–77k tris) — see AUDIT provenance. **Eyeball every thumbnail**; ≤2 local attempts then fal (John's go).
|
||
|
||
#### ⚠️ Finishing a `trellis_mac` mesh — ultra's post-mortem (Lane G, R1) — folded in verbatim
|
||
> ⚠️ **Do NOT run trellis_mac output through the MESHGOD finish farm** — its fill_holes+decimate
|
||
> shreds 200k-tri marching-cubes meshes (proven on ultra, post-mortem in dealgod memory). The
|
||
> working pipeline: raw GLB → `npx @gltf-transform/cli optimize --compress draco --texture-compress
|
||
> webp --texture-size 1024 --simplify-error 0.01` → 0.4–1.8 MB at raw-quality look.
|
||
|
||
*Credit: Lane G / ultra (`G-progress.md`), proven post-mortem — it cost them a shredded batch so it
|
||
shouldn't cost us one.* **One amendment, per Fable's Ruling 1 (`docs/LANES/FABLE_TO_LANE_G.md`):
|
||
NO-DRACO WINS — the depot speaks uncompressed GLB and PROCITY's loader stays decoder-free.** So drop
|
||
`--compress draco`; everything else in ultra's command stands (WebP ≤1024 is already our law):
|
||
```bash
|
||
npx @gltf-transform/cli optimize IN.glb OUT.glb \
|
||
--texture-compress webp --texture-size 1024 --simplify-error 0.01 # NO --compress draco
|
||
```
|
||
Files land ~2–3× bigger than the Draco'd version and that's fine — the depot holds ONE law.
|
||
Note our own in-house paths remain valid for their own cases and are NOT the finish farm:
|
||
`bake_lowpoly.py` (voxel-remesh + bake, for shell-soup that must hit a low budget) and
|
||
`decimate_props.py` (collapse, detail-preserving — see the R13 instrument diet).
|
||
|
||
### 7. Local-depot mode — `loaders.js ?localdepot=1` + `stage_local_depot.py` *(round-3: DONE)*
|
||
Lets Lanes B/C/F validate GLB loading with **no network / before publish**:
|
||
```bash
|
||
python3 pipeline/stage_local_depot.py # mirror _normalized/*.glb → web/assets/models/ (git-ignored)
|
||
# then load the game with ?localdepot=1 — depot:<file> resolves to assets/models/<file> instead of the CDN
|
||
```
|
||
|
||
### 8. Audio pack — `gen_audio.py` *(round-11: DONE, free, on-device)*
|
||
**Tool choice (Fable asked E to pick + document): pure procedural synthesis in numpy**, not a neural
|
||
model. MODELBEAST has no audio operator, and for this pack synthesis wins outright — $0, no model
|
||
download, deterministic/seeded (house law), **perfectly seamless loops** (neural gens don't loop),
|
||
**parody-safe by construction** (every sample is an original oscillator/noise render — nothing to
|
||
imitate), and precise ≤25 MB budget control. Pipeline: numpy synth → WAV → ffmpeg OGG/Opus (+ M4A/AAC).
|
||
```bash
|
||
PY=~/Documents/MODELBEAST/venvs/mflux/bin/python # numpy lives in the mflux venv
|
||
$PY pipeline/gen_audio.py --dry-run # list the 23-asset pack + budget
|
||
$PY pipeline/gen_audio.py # render all → web/assets/audio/*.ogg + *.m4a
|
||
$PY pipeline/gen_audio.py --only music # just the music beds
|
||
python3 pipeline/build_manifest.py # catalogue → manifest.json audio:{ambience,sfx,music}
|
||
```
|
||
23 assets, **8.1 MB** shipped (opus+aac): 6 ambience beds (street day/night, rain, 3 roomtones), 13
|
||
sfx (doorbell, door, till, riffle, toast, tram bell/rumble, 6 footsteps), 4 instrumental 90s music
|
||
loops. Loudness: beds/music `loudnorm` -16 LUFS, sfx peak-normalised. **Ear-ball** = spectrogram sheet
|
||
`docs/shots/laneE/audio_spectrograms.png` + numeric loop-seam/peak check (I can't subjectively listen —
|
||
final ear-check is John's/F's). Add assets by editing `PACK` in `gen_audio.py` + the `AUDIO_*` maps in
|
||
`build_manifest.py`. WAV scratch `pipeline/.genaudio/` is git-ignored; OGG/M4A ship committed.
|
||
|
||
### 9. Gig layer — round-12 (v3.0-alpha, `?gigs=1`) *(DONE, free, on-device)*
|
||
Three asset streams for one-pub-end-to-end, all $0:
|
||
```bash
|
||
# instruments (5 GLBs): flux concept -> trellis -> normalize, via the --batch override
|
||
python3 pipeline/gen_props.py --batch pipeline/gig_instruments.json --concepts # eyeball first
|
||
python3 pipeline/gen_props.py --batch pipeline/gig_instruments.json --mesh # ~3-4 min each
|
||
BL=/Applications/Blender.app/Contents/MacOS/Blender
|
||
"$BL" --background --python pipeline/normalize.py -- pipeline/gig_norm_batch.json pipeline/_normalized web/assets/thumbs
|
||
# posters (4 skins): portrait flyers with a blank name-band (B/C overprint like signboards)
|
||
python3 pipeline/gen_skins.py --posters && python3 pipeline/gen_skins.py --harvest
|
||
# gig audio (3): pub-rock live bed + crowd-walla + applause — already in gen_audio.py PACK
|
||
~/Documents/MODELBEAST/venvs/mflux/bin/python pipeline/gen_audio.py --only music # etc.
|
||
python3 pipeline/build_manifest.py # catalogues skins.poster + audio gig assets + fittings
|
||
```
|
||
Instruments: amp/guitar/bass reconstruct well; thin objects (mic-stand pole, drum hardware) may fall
|
||
to D's primitive (round-3 thin-wire lesson — spec allows "primitives when absent"). Posters carry
|
||
`nameZone` [x,y,w,h] in `skins.poster`. Gig audio is flagged `gig:true` in `manifest.audio`.
|
||
|
||
## Cost summary
|
||
|
||
| step | cost |
|
||
|---|---|
|
||
| audit, normalize, manifest, validate, publish | **free** (compute + bandwidth) |
|
||
| facade skin gap-fill (23 imgs) | **free** — done on-device (MODELBEAST flux_local); cloud fallback ~9¢ |
|
||
| hero props (round-3) | **free** — 5 via MODELBEAST trellis_mac; glass-case/bus-shelter on fal.ai list (≤$2.70, John's go) |
|
||
| audio pack (round-11, 23 assets) | **free** — 100% procedural synthesis (numpy → ffmpeg), $0, no model |
|
||
|
||
## Artifacts (not committed — regenerated from the steps)
|
||
|
||
`pipeline/_normalized/` (staging GLBs, live on depot), `pipeline/.genraw/` (raw skin gen).
|
||
Committed: the scripts, `*.md`, `batch.json`, `meshgod_batch.json`, `_audit_stats.json`,
|
||
`_normalize_results.json`, `web/assets/manifest.json`, `web/assets/thumbs/`, `docs/shots/laneE/`.
|
||
|
||
### 10. Motion library — `clips_export.py` → `clips_pack.py` → `clips_verify.py` *(round-41 §41.1: DONE, free, on-device)*
|
||
|
||
46 Mixamo clips → **6 grouped skeleton-only GLBs** in `web/models/clips/` (3 498 124 B, 0 tris) + a
|
||
**new** `web/assets/motion_manifest.json`. Source is ultra's `~/Documents/MOTIONLIB/green/mixamo_full/`
|
||
**only** (Mixamo = GREEN: usable in games, **no attribution**, never redistribute as clip packs).
|
||
The selection is checked in at `pipeline/clips_r41.json` — **every asset by name, no wildcard**.
|
||
|
||
```bash
|
||
python3 - <<'EOF' > /tmp/fbx_list.txt # 1. the 46 names
|
||
import json;print('\n'.join(c['file'] for g in json.load(open('pipeline/clips_r41.json'))['groups'].values() for c in g['clips']))
|
||
EOF
|
||
mkdir -p /tmp/fbx && ssh johnking@100.91.239.7 \
|
||
"cd ~/Documents/MOTIONLIB/green/mixamo_full && tar czf - $(tr '\n' ' ' </tmp/fbx_list.txt)" | tar xzf - -C /tmp/fbx
|
||
BL=/Applications/Blender.app/Contents/MacOS/Blender # 2. FBX -> per-clip skeleton-only GLB
|
||
"$BL" --background --python pipeline/clips_export.py -- --list pipeline/clips_r41.json --fbx /tmp/fbx --out /tmp/clip_glb
|
||
python3 pipeline/clips_pack.py --stage /tmp/clip_glb \ # 3. merge to <=6 groups + manifest
|
||
--out web/models/clips --manifest web/assets/motion_manifest.json
|
||
python3 pipeline/clips_verify.py --stage /tmp/clip_glb # 4. gate: 0 errors = green
|
||
```
|
||
|
||
**No retarget is run and none is wanted:** the Mixamo bank and PROCITY's peds are the *same*
|
||
mixamorig skeleton (`rigs.js` `_canon` folds `mixamorig1/4/…`). A mixamorig→mixamorig pass through
|
||
MIRPAMO would add error **and** bake the ped mesh into the clip file, destroying the zero-draw
|
||
property. MIRPAMO (`~/Documents/MIRPAMO`, `make smoke` = its ground truth, green on Blender 5.1.2)
|
||
is for foreign skeletons (CMU/BVH) and for rigging raw meshes. **Blender-5 gotcha avoided by
|
||
construction: FBX goes in, glTF comes out — nothing is ever exported through FBX.**
|
||
|
||
Packer flags (each measured in `AUDIT.md` R41): `--tracks all` restores translation+scale (the
|
||
engine's `_rotOnly` discards them, so they ship off by default) · `--no-quantize` keeps float32
|
||
rotations (int16-normalized is ~2× smaller at a worst measured error of **0.0034°**) ·
|
||
`--drop-fingers` removes the 40 finger channels = **49.7% of the payload**, taking the set to
|
||
1.71 MB. Accessor dedup is lossless and always on (saved 1 341 724 B).
|
||
|
||
⚠️ **Licence zoning, ruling 1 (closed R41):** MIRPAMO's `library/downloads/` also held 3,077
|
||
**CC BY-NC** (never-shippable) Bandai-Namco BVH, unzoned and unmarked. They now live at
|
||
`library/downloads/red/bandai_namco_1_NC-research/` with the `_NC-research` suffix on **every** file
|
||
(so `mirpamo batch`'s `<rig>@<clip>.glb` naming propagates the marker), and MIRPAMO's
|
||
`library/manifest.json` licence label is reconciled to ultra's `MOTIONLIB/SOURCES.md`.
|
||
**R41 ships one licence regime: Mixamo.** CMU/100STYLE/ACCAD are CC-BY — they wait on 41.5's credits
|
||
surface.
|