PROCITY/E-progress.md
m3ultra bd40ec7e7a Lane E round 3 (landed by Fable after review): 5 hero props gen'd local + published, local-depot mode, manifest 14 fittings + 8 furniture
- gen_props.py: flux_local -> trellis_mac -> normalize pipeline; 5/8 props kept
  (arcade-cabinet, listening-booth, drinks-fridge, milkshake-mixer, novelty-record)
- magazine-rack -> primitive (thin-wire gotcha); glass-case + bus-shelter -> fal.ai fallback
- cash-register reused from depot; all 22 GLBs live, validate --depot 0/0
- loaders.js ?localdepot=1 + stage_local_depot.py for network-free GLB validation
- qa.sh --strict GREEN 4/4 at commit time

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 18:03:03 +10:00

176 lines
13 KiB
Markdown
Raw 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.

# LANE E — ASSETS · progress (PROCITY-E)
*2026-07-14. Owner: PROCITY-E. Scope: `pipeline/*`, `web/assets/*`, `web/assets/manifest.json`.
Reviewed against `docs/LANES/LANE_E_ASSETS.md` + `docs/LANES/ROUND3_INSTRUCTIONS.md`. Nothing in
other lanes' files was touched.*
## Round 3 (Fable) — hero props on MODELBEAST + local-depot mode
- **Hero props, free & on-device** (`pipeline/gen_props.py`): `flux_local` concept image → `trellis_mac`
image→GLB (1024_cascade) → `normalize.py` → thumbs → manifest. **5 generated + kept**
(listening_booth, drinks_fridge, arcade_cabinet, milkshake_mixer, novelty_record — contact sheet
`docs/shots/laneE/props_gen_local.png`), **cash_register reused** from the depot
(`vintage-cash-register.glb`). 3 didn't make it: **magazine_rack** → in-engine primitive (112k-tri
wire, the thin-wire gotcha); **glass_case + bus_shelter****fal.ai fallback list** (TRELLIS can't
do transparent glass; sf3d gives a featureless blob) — gated on John, ≤$2.70, both have primitive
fallbacks. Every kept thumbnail eyeballed; no legible branding (see AUDIT provenance).
- **Local-depot mode** (`core/loaders.js` `?localdepot=1` + `pipeline/stage_local_depot.py`): resolves
`depot:<file>` to `web/assets/models/` served by the dev server, so Lanes B/C/F validate GLB loading
**with no network / before publish**. Verified live in-browser (loads a `depot:` GLB, CDN path intact).
- **Manifest**: 14 fittings + 8 furniture (5 hero + cash_register added); `validate_manifest.py`
green (0 err). Provenance recorded in `AUDIT.md` (props are 100% synthetic/local — cleanest in the set).
- **Known tradeoff**: TRELLIS meshes are dense shell-soup — decimate floors 2577k tris (heavier than
the ≤5k library law). Accepted for focal/optional/LOD-able props; texture-bake-to-lowpoly is a follow-up.
- **Published (self-serve, John's go 2026-07-14)**: the 5 prop GLBs + thumbs pushed to the depot via
the passwordless tailnet path (decision #4); all 22 GLBs re-verified live. **`qa.sh --strict` GREEN**
(4/4 gates; manifest validator 0 err / 0 warn with the props live). Provenance was recorded first.
- **Not committed**: this round is **held for Fable review** (per John) — all Lane E round-3 changes are
in the working tree, uncommitted. GLB binaries stay git-ignored/staged; the depot has them.
## Status at a glance
| Step | Deliverable | State |
|---|---|---|
| 1 Audit | `pipeline/AUDIT.md` (+ `glb_stat.py`, `_audit_stats.json`) | ✅ done |
| 2 Normalize | `pipeline/normalize.py`**16 GLBs** + 16 thumbs, verified | ✅ done (local); ⛔ **publish gated on `GOD3_PW`** |
| 3 Manifest | `web/assets/manifest.json` + `validate_manifest.py` (+ `build_manifest.py`) | ✅ done, validates green |
| 4 Facade skins | `gen_skins.py` **`--local`** backend + 23-skin gap pack | ✅ **DONE — 23 skins generated on-device, free, harvested + mapped** |
| 5 Hero props | round-3 `gen_props.py` (MODELBEAST, free) → **5 local** + cash-register reuse | ✅ **DONE local** (see Round 3 above; 2→fal, 1→primitive) |
| 6 Local-depot | `loaders.js ?localdepot=1` + `stage_local_depot.py` | ✅ **DONE, verified in-browser** |
| — | `pipeline/README.md` + contact sheets in `docs/shots/laneE/` | ✅ done |
**Step 4 no longer costs anything.** The 23 facades were generated **locally on the M3 Ultra**
via MODELBEAST `flux_local` (FLUX.2-klein-4B on MPS) — free, ~5.7s each, weights already cached.
No OpenRouter key, no ~9¢ spend. See "Step 4 (done, local)" below.
**Two things still need John (both optional — the game runs asset-free):** (1) the depot password
`GOD3_PW` to publish the 16 GLBs live; (2) a go/no-go on the paid **hero-prop** step (5 ≈ $2.70).
## What landed
### Step 1 — Audit
- `pipeline/glb_stat.py`: dependency-free GLB parser (tris, bbox, texture px, Draco) — runs on
ultra's py3.9. Full library stats → `pipeline/_audit_stats.json`.
- `pipeline/AUDIT.md`: the library is a raw dump — almost nothing obeys the GLB law (only
`record_crate_plastic_01` was native-metres; scale/orientation arbitrary; tris 12→960k; textures
to 4096; several **branded/trademarked** assets quarantined). Candidate→PROCITY-id table with
status, coverage-vs-registry table, QUARANTINE list, depot cross-reference.
- Toolchain reality documented (Blender present on ultra but off PATH; no coreutils/timeout).
### Step 2 — Normalize & publish
- `pipeline/normalize.py`: headless Blender, extends `MESHGOD/finish_glb.py` (solidify/decimate/
bright/base-origin) + adds **height-normalize, outlier cleanup, texture→WebP≤1024, per-asset
up/yaw, 256px thumbnail**. Driven by `pipeline/batch.json`.
- **16 assets** normalized to house law (metres, +Y up, base origin, ≤5k tris, WebP ≤1024, no
Draco). Every one **eyeballed** — Y-up confirmed by re-importing exports through `render_glb.py`.
Footprints from Blender-space `_normalize_results.json`. Thumbs in `web/assets/thumbs/`.
- fittings (9): record_crate, wire_shelf, clothes_rack, bookshelf, cube_shelf_wide, counter,
coffee_table, work_table, box_crate
- furniture (7): bench, bench_modern, bench_wood, longbench, park_bench, streetlight, food_cart
- `pipeline/publish.py`: mirrors the meshgod→3GOD contract exactly (cookie `hmac(GOD3_PW,"3god-ok")`,
`POST /api/upload` + `/api/thumb`). Reads the secret from env — never contains/logs it. `--dry-run`
verified all 16 ready. **Not published yet: `GOD3_PW` isn't set here.** Run: `export GOD3_PW=… &&
python3 pipeline/publish.py` (idempotent, reversible).
### Step 3 — Manifest
- `web/assets/manifest.json` (v1): the contract Lanes B/C/D read. All **69 skins** catalogued
(25 facade / 10 sky / 10 ground / 8 wallpaper / 6 floor / 7 surface / 3 awning) + 16 fittings/
furniture with **footprints, heights, thumbs**. `conventions` block documents the GLB law,
blank-signboard rule, corner-lot policy, fallbacks, and depot load path.
- Every registry shop type has **≥2 facades** (verified in the validator). Facades tagged
`signboard: blank|baked` — 5 djsim/market/grimy skins carry baked sign text and must not get a
name overlay; the other 20 are clean overlay targets.
- `pipeline/validate_manifest.py`: Lane F gate — parses JSON, checks every skin exists, every GLB
is local-or-on-depot, thumbs exist, footprints/heights sane, ≥2 facades/type. **Green** (16
local-only warnings until publish; `--depot` enforces live).
- `pipeline/build_manifest.py`: reproducible generator (curated type-maps + derived skin/fitting
lists) so the manifest regenerates when assets change.
### Step 4 — Facade skin expansion (DONE, local)
- `pipeline/gen_skins.py`: ported from `thriftgod/gen_assets.py` — **same STYLE bible + blank-
signboard template** so new skins match. Resumable `.genraw/`, `--dry-run`/`--only`/`--harvest`.
- **`--local` backend (the one I used):** MODELBEAST `flux_local` → FLUX.2-klein-4B on the M3
Ultra's MPS GPU. Apache/**ungated**, weights already cached (15 GB), **free**, ~5.7 s/img, one
GPU job at a time. Direct subprocess to `~/Documents/MODELBEAST/server/operators/flux_local/run.py`
(`--outdir`/`--params` JSON) — no server, no cloud, no key. Cloud OpenRouter path kept as fallback.
- **All 23 generated → harvested → mapped.** 0 failures, 2:11 total. Eyeballed every one
(contact sheet `docs/shots/laneE/facades_gen_local.png`): every shopfront has a **genuinely
blank signboard** (Lane B name-overlay target), style indistinguishable from the 25 anchors.
The pack: toy (2), book (2), video (2), milkbar (3), wide dept anchors (2), stall/market (2),
warehouse (2), arcade portal (1), residential house fronts (4), corner-lot `-side` walls (3).
- **Coverage after mapping** (`build_manifest.py` `FACADE_TYPES`, mirrors registry.js + additive):
record 3 · opshop 3 · toy 5 · book 5 · video 4 · pawn 7 · milkbar 5 · dept 4 · **stall 4**
— every registry type ≥3. **`stall` went 2→4**, retiring the Lane A single-facade gap.
- **Corner-lot `-side` skins now exist** (brick/render/weatherboard): tagged `face:"side"` with
empty `types` in the manifest so no shop-front pool ever picks a windowless wall; Lane B maps
them to the exposed side face (`conventions.corner_lots` + new `conventions.facade_face`).
- `pipeline/SKIN_PROMPTS.md`: identical pack kept for the free manual flow (fallback if MODELBEAST
is ever down).
### Step 5 — Bespoke hero props
- `pipeline/meshgod_batch.json` + `MESHGOD_BATCH.md`: 9 solids (glass case, fridge, till, listening
booth, arcade cabinet, magazine rack, milkshake mixer, novelty record, bus shelter) in the
thriftgod_hero pattern. Documents the **MeshGod gotcha** (thin wires/flat discs → primitives),
reuse-first (cash-register/glass-case likely already on depot), and the primitive fallbacks.
**Not run** — gated on John (~$2.70). Nothing blocks: all have in-engine fallbacks.
## Adversarial review & fixes (self-run, 4-dimension × verify)
Ran a 19-agent adversarial review over all deliverables (correctness / contract-fit / doc-accuracy /
house-law), each finding independently verified. **15 confirmed** (1 blocker, 5 major, 9 minor).
All fixed and re-verified:
- **BLOCKER — off-origin exports.** `normalize.py` joined meshes but never flattened parent node
hierarchies, so assets under a parent empty shipped off-origin: `park_bench` was **~1 km off-origin,
floating 30 m**; `food_cart` 15 m off; `clothes_rack` sank 0.19 m. My earlier spot-check missed it
(the clean assets have no hierarchy). **Fixed:** `parent_clear(KEEP_TRANSFORM)` + drop non-mesh
nodes before join. Re-verified node-aware: all 16 now `center=[0, h/2, 0]`, **minY=0.0**.
- **Major — tri budget counted faces, not triangles** (latent for quad FBX/OBJ). Fixed: triangulate
in `clean_mesh` so the ≤5k gate + reported counts are true triangles.
- **Major — fitting-id namespace.** Added `fittingAliases` (registry semantic id → physical id) to
the manifest. (Lane C's `glb.js` already maps its kinds to my physical ids, so it worked — this
makes it explicit + future-proof.)
- **Major — facade map vs registry.js.** Aligned my facade→type map to be a consistent superset of
Lane A's `registry.js`; flagged that registry's `stall` pool has only 1 facade (Lane A gap).
- **Major — facing -Z not enforced.** Corrected the docs: symmetric props are orientation-agnostic;
no auto-facing (per-asset `yaw` available). Removed the false "enforced" claim.
- **Major — licence provenance.** Strengthened the publish-set caveat (record per-asset provenance
before pushing to the shared CDN) — a reason publish stays gated on John.
- **Minors:** hardened `glb_stat.py` (node-aware world bounds + WebP size sniff — the exact blind
spots that hid the blocker); `validate_manifest.py` liveness now uses a Range GET not HEAD;
Akai-MPC trademark flag; corrected audit counts (59 parsed `.glb`, rejects 8, food_cart tris).
## Acceptance check (LANE_E)
-`AUDIT.md` complete.
- ✅ ≥15 normalized GLBs (**16**) with thumbnails — ⛔ *live on 3GOD pending publish (needs cred)*.
-`manifest.json` validates green; fittings/furniture carry footprints for B/C to place untouched.
- ✅ Every registry shop type has ≥2 facades — now **≥3** (48 facades total). New skins
**generated** (not just planned) and eyeball-matched to the anchor style
(`facades_style_anchor.png` = 25 originals; `facades_gen_local.png` = 23 new local skins;
`fittings_normalized.png` = the 16 GLBs — all in `docs/shots/laneE/`).
- ✅ One-page `pipeline/README.md` (re-run each step, costs, where creds live).
## Notes for the other lanes
- **Lane B / C:** read `web/assets/manifest.json`. `fittings`/`furniture` → `depot:<file>` via
`core/loaders.js` (placeholder box on miss — the game runs asset-free). Footprints are `[w,d]`
metres on the ground; heights in metres; all base-origin, +Y up, facing Z.
- **Lane B:** overlay shop names only on facades with `signboard:"blank"`. Wide `dept-*` anchors
(when generated) want a wider UV plane. `awning`/`ground`/`sky` skins are yours; `wall`/`interior`
are Lane C's.
- **Lane F:** wire `python3 pipeline/validate_manifest.py` into the gate (add `--depot` post-publish).
## Open questions for Fable/John
1. **Publish now?** I have 16 verified GLBs staged in `pipeline/_normalized/` ready to push to the
shared depot. It's an outward-facing write to shared infra + needs `GOD3_PW`, so I left it for
your go rather than assume it.
2. ~~Spend the ~9¢ on facades?~~ **Resolved — done for free on-device** (MODELBEAST flux_local).
Only the **~$2.70 hero-prop** MeshGod batch (Step 5) still wants a go/no-go; all props have
in-engine primitive fallbacks, so it's a polish upgrade, not a blocker. *(Note: image→GLB is
also local on this box via `sf3d`/`trellis_mac`, but those weights are HF-gated — a setup
step before that path is free too.)*
3. A few library assets were near-misses (garage_shelves, radiogram, market_stall — see AUDIT
rejects). Worth a manual Blender re-origin pass later, or leave to primitives? I left them out.