PROCITY/pipeline/README.md
m3ultra a7289d0550 Lane E R22 (v4.0): MODELBEAST notes refresh + provenance v4-final + G2a atlas review
Ledger #3 (E's G item), #6 (provenance prep), + the C/E contract review.

- MODELBEAST notes (#3): pipeline/README.md's toolchain row claimed sf3d/trellis_mac were 'HF-gated
  (setup needed)' -- ultra was right, that was stale (weights cached; trellis_mac 15/15 on thriftgod
  Tier-1; the :8777 queue also runs bg_remove_local/mflux_image_edit/seedvr2_upscale). Corrected.
  Ultra's finish-farm post-mortem folded into the props section VERBATIM + credited, with Fable's
  Ruling 1 amendment (NO-DRACO: drop --compress draco, rest of the command stands). Noted that our
  bake_lowpoly.py / decimate_props.py are NOT the finish farm, so nobody conflates them.
- Provenance v4-FINAL (#6): the frozen table carries the pack (22 towns, all 8 states/territories,
  1192 shops + roads[], ODbL, raw snapshots) + a row for Lane G's newtown_godverse attributed to G.
  The licence spine is complete -- nothing shipped is unaccounted.
- BUG: index.json still listed 22 -- generated before the dd4489c merge, so newtown_godverse was
  invisible to B's selector while R22 verifies at 23-town scale. Regenerated -> 23 towns (selfcheck
  ALL GREEN 161300/161300). Index is now self-describing: state reads from the cache first, then my
  config, so any lane's town rosters itself.
- G2a atlas contract review (E's half; C answers interior ceilings in LANE_C_PUB): Fable's priors all
  hold. Atlas-per-shop is an UPGRADE if the loader disposes on room exit -- today stockpack.js never
  disposes (R9: 181MB resident, accreting). Without dispose, per-shop strands ~16MB/shop (~190MB per
  town visited, unbounded across 23 towns). With dispose: ~4MB resident at 1024², ~45x better than
  today. Also: size the atlas to the shop (<=1024², not 2048² for a 6-item shop); reuse my index
  schema verbatim so stockpack.js/fail-soft-to-parody/?noassets/validate_pack all work unchanged;
  the R8 parody law (parodied metadata, real cover art) is unchanged.

-> Lane G: add "state":"NSW" to newtown_godverse.json and the index picks it up automatically.
-> Lane C: the R9 dispose-on-exit cache-cap is now load-bearing for G2a (my VRAM numbers above).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 21:54:49 +10:00

169 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (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):
```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 ~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**:
```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/`.