Audit -> normalize (16 GLBs + thumbs) -> web/assets/manifest.json (the contract Lanes B/C/D read) -> facade gap-fill. Step 4 facades generated locally on the M3 Ultra via MODELBEAST flux_local (FLUX.2-klein-4B / MLX on MPS): 23 skins, free, ~5.7s each, blank signboard, eyeballed, harvested, mapped to shop types. Every registry type now has >=3 facades (stall 2->4, closing the Lane A gap). Corner-lot -side walls tagged face:"side" with empty types so no shop front ever selects a windowless wall. manifest validates green. Still gated on John (both optional -- the game runs asset-free): publish the 16 GLBs to 3GOD (needs GOD3_PW), MeshGod hero props (~$2.70). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
151 lines
11 KiB
Markdown
151 lines
11 KiB
Markdown
# 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` acceptance. Nothing in other lanes' files was touched.*
|
||
|
||
## 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 | `MESHGOD_BATCH.md` + `meshgod_batch.json` (9 solids) | ✅ spec'd; ⛔ **not run (John's go, ~$2.70)** |
|
||
| — | `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.
|