PROCITY/pipeline
m3ultra 91a606a875 Fable (integrator): remove the five retired ped bodies from the repo
Executes steps 1-3 of Lane F's documented removal (F-progress.md §42.6). The five bodies
D retired in §42.4 are gone from web/models/peds/ (which is NOT gitignored, and which
deploy_digalot_procity.sh rsyncs wholesale — so these bytes were shipping), dj_phrtt_01's
publish-staging copy is deleted, and its row is out of _published.json so the depot drift
check stays honest.

The one that mattered: dj_phrtt_01 is an unclothed body whose only texture is a bare-skin
atlas. It was in the DEFAULT crowd and it was the only one of the five ever published.
E-cast found it by rendering all 24 peds and looking at them — a name-scoped deny-list
could never have caught an innocent name from a different pipeline.

Verified after removal: r42_roster.mjs PASS (period law + roster licence, both controls
demonstrated), validate_manifest.py --depot 0 errors 0 warnings, 32 ped GLBs remain
(24 shipped bodies + 8 base clips).

STILL OUTSTANDING, for John: the depot copy at digalot.fyi/3god is live and there is NO
delete verb — publish.py only uploads, and CITY_SPEC's depot contract documents upload,
fetch and list and nothing else. Removing it is a deliberate act on the depot host. Filed
to R43 with the deny-list follow-up, which goes green the moment the bytes are gone and
red forever after if one returns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 23:51:31 +10:00
..
_godverse Lane G R26 (v5.0-beta): the mint baker, the crate menu, the atlas manifest (ledgers #2, #6) 2026-07-17 10:33:40 +10:00
packs Lane E round 8: REAL stock packs (record 350 / book 311 / toy 273) live on depot 2026-07-15 04:09:14 +10:00
_audit_stats.json Lane E (assets): pipeline, manifest + 23 on-device facade skins 2026-07-14 13:48:25 +10:00
_bird_b.json Lane E R39 (1/n): THE MAGPIE, SETTLED ON A PICTURE — B's own birdGeometry() dumped vertex-for-vertex (182 tris, not 154), rendered against E's tinted GLB at 64/48/32 px, still and under motion blur 2026-08-03 20:35:00 +10:00
_gig_results.json Lane E R13 (v3.0-beta): gigKey rename + grunge/covers beds + instrument decimation 2026-07-15 23:03:14 +10:00
_normalize_results.json Lane E R22 (v4.0): the venue_night tri diet — B's #1a driver was mine, fixed (BLOCKING #1b) 2026-07-16 22:04:56 +10:00
_peds_decim.json Lane E round 4 (E1): decimate 19 ped rigs 924k→46k tris (≤3k each) for gate 3 2026-07-14 18:29:50 +10:00
_peds_merge.json Lane E round 7 (E1): merge ped rigs to ONE draw each (gates roster flip) 2026-07-15 00:33:03 +10:00
_props_results.json Lane E R22 (v4.0): the venue_night tri diet — B's #1a driver was mine, fixed (BLOCKING #1b) 2026-07-16 22:04:56 +10:00
_published.json Fable (integrator): remove the five retired ped bodies from the repo 2026-08-07 23:51:31 +10:00
_r38_results.json Lane E R38 s2: the three GLBs PUBLISHED + sha1-verified off the public depot; the magpie is a magpie 2026-08-03 19:02:06 +10:00
_r39_results.json Lane E R39 (6/n): THREE GLBs shipped as GLASS, not one — transmissionFactor 1 stripped, published, sha1-verified, and now GATED. Plus the arcade's three meshes. 2026-08-03 21:11:34 +10:00
_r41_3dstore_results.json Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
_r41_djgear_results.json Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
_r41_pub_results.json Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
_r41_publish_list.txt Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
_r41_sha1.json Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
_r42_publish_list.txt R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
_r42_sha1.json R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
arcade_measure.mjs Lane E R39 (5/n): two corrections to my own R39 arithmetic, both measured 2026-08-03 20:58:26 +10:00
AUDIT.md Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
bake_lowpoly.py Lane E round 5 (E3): fal Hunyuan glass_case + bus_shelter + magazine_rack (~$0.64) 2026-07-14 21:20:44 +10:00
batch_bake_r39.json Lane E R39 (3/n): the notes + the audit — the magpie recommendation with its arithmetic, the arcade's measured kit, and the sizing law promoted to a standing instruction 2026-08-03 20:53:55 +10:00
batch_bake.json Lane E round 5 (E1): bake 6 hero props to <=8k tris (288k->48k), live on depot 2026-07-14 20:56:07 +10:00
batch_counter.json Lane E round 4 (E2): fix record_crate + ship counter-with-till, both live on depot 2026-07-14 19:07:15 +10:00
batch_e2.json Lane E round 5 (E2): depot growth — street bin + record-crate stack (consumer-driven) 2026-07-14 21:27:13 +10:00
batch_fal.json Lane E round 5 (E3): fal Hunyuan glass_case + bus_shelter + magazine_rack (~$0.64) 2026-07-14 21:20:44 +10:00
batch_peds.json Lane E round 7 (E1): merge ped rigs to ONE draw each (gates roster flip) 2026-07-15 00:33:03 +10:00
batch_props.json Lane E round 3 (landed by Fable after review): 5 hero props gen'd local + published, local-depot mode, manifest 14 fittings + 8 furniture 2026-07-14 18:03:03 +10:00
batch_r38.json Lane E R38 (v8 wave 1): the asset wave — trellis2_mlx swap verified 1.92x, six sounds, four grounds, and the thin-structure answer 2026-08-03 18:30:33 +10:00
batch_r39.json Lane E R39 (3/n): the notes + the audit — the magpie recommendation with its arithmetic, the arcade's measured kit, and the sizing law promoted to a standing instruction 2026-08-03 20:53:55 +10:00
batch.json Lane E (assets): pipeline, manifest + 23 on-device facade skins 2026-07-14 13:48:25 +10:00
bird_to_glb.py Lane E R39 (1/n): THE MAGPIE, SETTLED ON A PICTURE — B's own birdGeometry() dumped vertex-for-vertex (182 tris, not 154), rendered against E's tinted GLB at 64/48/32 px, still and under motion blur 2026-08-03 20:35:00 +10:00
build_manifest.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
build_stock_pack.py Lane E round 8: REAL stock packs (record 350 / book 311 / toy 273) live on depot 2026-07-15 04:09:14 +10:00
build_towns.py Lane E R24 (v5.0-alpha): the crate's real street, the honest gate, and Ballarat retired 2026-07-17 01:06:00 +10:00
check_contract.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
clips_export.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
clips_pack.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
clips_r41.json Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
clips_verify.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
contact_sheet.py R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
decimate_props.py Lane E R13 (v3.0-beta): gigKey rename + grunge/covers beds + instrument decimation 2026-07-15 23:03:14 +10:00
dump_bird.mjs Lane E R39 (1/n): THE MAGPIE, SETTLED ON A PICTURE — B's own birdGeometry() dumped vertex-for-vertex (182 tris, not 154), rendered against E's tinted GLB at 64/48/32 px, still and under motion blur 2026-08-03 20:35:00 +10:00
fbx_to_clip.py Lane E R16 (v3.1): the drummer's sit clip — sit.glb produced + published 2026-07-16 12:37:27 +10:00
fix_glb_textures.py Lane E round 4 (E2): fix record_crate + ship counter-with-till, both live on depot 2026-07-14 19:07:15 +10:00
gen_audio.py Lane E R38 (v8 wave 1): the asset wave — trellis2_mlx swap verified 1.92x, six sounds, four grounds, and the thin-structure answer 2026-08-03 18:30:33 +10:00
gen_props_fal.py Lane E round 5 (E3): fal Hunyuan glass_case + bus_shelter + magazine_rack (~$0.64) 2026-07-14 21:20:44 +10:00
gen_props.py Lane E R38 (v8 wave 1): the asset wave — trellis2_mlx swap verified 1.92x, six sounds, four grounds, and the thin-structure answer 2026-08-03 18:30:33 +10:00
gen_skins.py Lane E R39 (2/n): THE ARCADE, MEASURED THEN DRESSED — 3 skins live ($0, on-device, green), and the instance count taken BEFORE generating 2026-08-03 20:46:43 +10:00
gig_instruments.json Lane E R12 (v3.0-alpha gig layer): 5 instruments + 4 posters + 3 gig audio 2026-07-15 19:44:48 +10:00
gig_norm_batch.json Lane E R12 (v3.0-alpha gig layer): 5 instruments + 4 posters + 3 gig audio 2026-07-15 19:44:48 +10:00
glb_stat.py Lane E R39 (6/n): THREE GLBs shipped as GLASS, not one — transmissionFactor 1 stripped, published, sha1-verified, and now GATED. Plus the arcade's three meshes. 2026-08-03 21:11:34 +10:00
godverse_server.py Lane G R27 (v5.0): tier 2 — the two read endpoints, the three fences, and the gate's missing subject 2026-07-17 13:56:02 +10:00
godverse_stock.py Lane G R26 (v5.0-beta): the mint baker, the crate menu, the atlas manifest (ledgers #2, #6) 2026-07-17 10:33:40 +10:00
godverse_town.py Lane G R24 (v5.0-alpha): the crate's real street — redhill_godverse, the keyed re-emit, and two contract conflicts 2026-07-17 01:31:07 +10:00
isolate.py R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
merge_ped.py Lane E round 7 (E1): merge ped rigs to ONE draw each (gates roster flip) 2026-07-15 00:33:03 +10:00
merge_prims.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
meshgod_batch_r38.json Lane E R38 (v8 wave 1): the asset wave — trellis2_mlx swap verified 1.92x, six sounds, four grounds, and the thin-structure answer 2026-08-03 18:30:33 +10:00
meshgod_batch_r39.json Lane E R39 (2/n): THE ARCADE, MEASURED THEN DRESSED — 3 skins live ($0, on-device, green), and the instance count taken BEFORE generating 2026-08-03 20:46:43 +10:00
meshgod_batch.json Lane E round 5 (E3): fal Hunyuan glass_case + bus_shelter + magazine_rack (~$0.64) 2026-07-14 21:20:44 +10:00
MESHGOD_BATCH.md Fable: correct stale fal.ai budget — Hunyuan v2 ~14-17¢/gen, remainder is 2-3 props (~$0.30-0.50, ~$1 ceiling), not $2.70 2026-07-14 20:36:12 +10:00
montage.py R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
normalize.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
palette.py R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
ped_from_fbx.py R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
ped_sheet.py R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
publish.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
r41_3dstore.json Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
r41_djgear_batch.json Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
r41_pub_batch.json Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
r41_stage_3dstore.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
r42_palette.json R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
README.md R42 THE LOOK: five lane-runs — the pub is dressed, the decks read, and the town is cast 2026-08-07 23:49:49 +10:00
render_views.py Lane E R39 (1/n): THE MAGPIE, SETTLED ON A PICTURE — B's own birdGeometry() dumped vertex-for-vertex (182 tris, not 154), rendered against E's tinted GLB at 64/48/32 px, still and under motion blur 2026-08-03 20:35:00 +10:00
rig_join.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
seamless_tile.py Lane E R39 (2/n): THE ARCADE, MEASURED THEN DRESSED — 3 skins live ($0, on-device, green), and the instance count taken BEFORE generating 2026-08-03 20:46:43 +10:00
SKIN_PROMPTS.md Lane E (assets): pipeline, manifest + 23 on-device facade skins 2026-07-14 13:48:25 +10:00
skin_sheet.py Lane E R39 (3/n): the notes + the audit — the magpie recommendation with its arithmetic, the arcade's measured kit, and the sizing law promoted to a standing instruction 2026-08-03 20:53:55 +10:00
stage_local_depot.py Lane E round 3 (landed by Fable after review): 5 hero props gen'd local + published, local-depot mode, manifest 14 fittings + 8 furniture 2026-07-14 18:03:03 +10:00
strip_transmission.py Lane E R39 (6/n): THREE GLBs shipped as GLASS, not one — transmissionFactor 1 stripped, published, sha1-verified, and now GATED. Plus the arcade's three meshes. 2026-08-03 21:11:34 +10:00
thumbs.py Lane E R41 (§41.1+§41.2): 46 clips and 110 fittings — and every handover number was the wrong unit 2026-08-07 18:10:12 +10:00
tint_atlas.py Lane E R38 s2: the three GLBs PUBLISHED + sha1-verified off the public depot; the magpie is a magpie 2026-08-03 19:02:06 +10:00
validate_atlas.py Lane E R26 w4: the mint-id fence — my rule was the stale thing (wave 4, E's one line) 2026-07-17 13:08:40 +10:00
validate_manifest.py Lane E R40 §40.1+§40.4: the gate now watches every door the glass could walk through 2026-08-07 12:28:43 +10:00
validate_pack.py Lane E round 8 (E1+E2): book/toy stock packs + pack-QA gate 2026-07-15 02:27:50 +10:00
view_sheet.py Lane E R39 (1/n): THE MAGPIE, SETTLED ON A PICTURE — B's own birdGeometry() dumped vertex-for-vertex (182 tris, not 154), rendered against E's tinted GLB at 64/48/32 px, still and under motion blur 2026-08-03 20:35:00 +10:00

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/3godGET /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 cachedtrellis_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)

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)

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)

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)

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:

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:

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 (2577k 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.41.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):

npx @gltf-transform/cli optimize IN.glb OUT.glb \
  --texture-compress webp --texture-size 1024 --simplify-error 0.01     # NO --compress draco

Files land ~23× 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:

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).

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:

# 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.pyclips_pack.pyclips_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.jsonevery asset by name, no wildcard.

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.

11. Palette — palette.py + isolate.py + montage.py (round-42 §42.1/§42.2a: DONE, free, on-device)

The R41 fittings shipped geometrically correct and visually unfinished. They were not untextured — they carry COLOR_0 and three.js renders it — they were bleached (15 of 37 pub props below 0.05 saturation). The fix is a palette imposed on colour that is already there, which costs zero draws: the COLOR_0 accessor is rewritten in place, so primitive count, triangle count and every POSITION/NORMAL/index byte are untouched. Adding baseColour maps instead would have re-imported the cost merge_prims.py bought back in R41 (14,140 → 117 interior draws).

python3 pipeline/palette.py --report web/assets/models/procity_fit_jukebox_01.glb   # what colour IS it?
python3 pipeline/palette.py --spec pipeline/r42_palette.json \
        --src pipeline/.props_orig/r42 --out pipeline/_normalized          # repaint the named set
python3 pipeline/strip_transmission.py --dry-run pipeline/_normalized/procity_fit_*.glb
python3 pipeline/check_contract.py --list pipeline/_normalized/procity_fit_deck_1200_rigged_01.glb
BL=/Applications/Blender.app/Contents/MacOS/Blender
"$BL" --background --python pipeline/thumbs.py -- web/assets/thumbs pipeline/_normalized/<set>.glb
GOD3_DEPOT=... GOD3_PW=... python3 pipeline/publish.py --files pipeline/_r42_publish_list.txt

⚠️ palette.py is NOT idempotent. ramp and tint normalise against the source's own luminance spread, so a second pass re-stretches an already-stretched file. Every output is stamped asset.extras.procity_palette; a stamped input is skipped unless --force. Point --src at the pristine copies in pipeline/.props_orig/r42/ (git-ignored) when re-running. Routes, per-asset stops/rules and the reason for each are in pipeline/r42_palette.json — every asset by name.

Proof tools: isolate.py renders ONE staged scene from a perspective camera at eye height on a bench of a stated colour (the only rig that can answer "does this read from where the player stands" — it is what diagnosed the DJ booth); montage.py builds labelled before/after grids from finished PNGs; contact_sheet.py --pitch M spaces tiles when the set mixes a 2.68 m umbrella with a 0.2 m ashtray.