Instruments (MODELBEAST flux_local concept -> trellis_mac -> normalize): electric_guitar, bass_guitar, drum_kit, guitar_amp, mic_stand -> manifest fittings + thumbs, published to the tailnet depot. All 5 reconstructed cleanly (thin mic-boom + cymbals survived); heavy tris (30-96k, amp the outlier) accepted like the R3 props. gen_props.py gains --batch; gig_instruments.json + gig_norm_batch.json + _gig_results.json. Posters (flux_local, gen_skins.py --posters): poster-xerox/screenprint/ grunge/retro -> skins.poster with nameZone for band-name overprint, blank name-band (no baked text) like blank-signboard facades. Gig audio (procedural, gen_audio.py): music-pubrock-live (original 90s pub-rock riff), ambience-crowd-walla, sfx-applause -> manifest audio, flagged gig:true for F. Pack now 26 assets, 9.1 MB of 25. build_manifest reads _gig_results.json + emits skins.poster + gig audio; validate_manifest green (--depot 0 err, all live). Provenance in AUDIT.md (section Round 12); README + E-progress updated. Touched no other lane's files. F owns the v3.0-alpha tag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
453 lines
34 KiB
Markdown
453 lines
34 KiB
Markdown
# PROCITY Asset Audit — Lane E, Step 1
|
||
|
||
*Generated 2026-07-14 from a live inspection of `johnking@100.91.239.7:~/Documents/3D=models/`
|
||
(the sorted house library) + the 3GOD depot `GET /api/list`. Tri counts / bounding boxes from
|
||
`pipeline/glb_stat.py` (dependency-free GLB parser); orientation & final scale verified by
|
||
rendering every candidate through Blender and looking at the thumbnails (never "should look right").*
|
||
|
||
## TL;DR
|
||
|
||
- The library is a **raw dump**: almost nothing obeys the house GLB law. Scale is arbitrary
|
||
(only `record_crate_plastic_01` was native-metres), up-axis varies, tri counts span **12 → 960k**,
|
||
textures reach **4096px**, and several assets carry **branded/trademarked designs** (quarantined).
|
||
- `pipeline/normalize.py` fixes scale/orientation/tris/textures uniformly. **16 assets** normalized
|
||
clean, verified upright & metre-scaled, WebP ≤1024, ≤5k tris, +Y up, base origin — ready to publish.
|
||
- Gaps with **no usable source** (→ `MESHGOD_BATCH.md` or in-engine primitives): glass display
|
||
case, fridge, till/register, spinner rack, magazine rack, market stall, escalator, VHS shelving.
|
||
- Depot already holds a few reusable house props (`street-bench`, `council-bin`, `milk-crate-stack`)
|
||
and 138 assets total (mostly TRELLIS `*-views-3d-*` / `image-3d-*` MeshGod outputs).
|
||
- **Skin caveat:** 5 of the 25 facades carry **baked-in sign text** (`djsim-milkbar` "MILK BAR",
|
||
`djsim-video`, `djsim-pub`, `market`, `grimy`) — they break the blank-signboard convention, so
|
||
the manifest tags them `signboard:"baked"` and Lane B must not overlay a name on them. The other
|
||
20 are genuinely blank.
|
||
|
||
## Toolchain reality (differs from the lane brief)
|
||
|
||
| thing | brief said | actual |
|
||
|---|---|---|
|
||
| Blender on ultra | "runs on ultra against Blender" | present but **not on PATH** → `/Applications/Blender.app/Contents/MacOS/Blender` (v5.0.1). Renders 4 angles in ~0.9s. |
|
||
| coreutils / timeout | none | confirmed — used ssh `ConnectTimeout`, no `timeout`/`setsid` |
|
||
| python on ultra | 3.9 | 3.9.6 — `glb_stat.py`/`normalize.py` written to that floor |
|
||
| OpenRouter key | `thriftgod/.env` | absent; house convention `~/Documents/dealgod/lore/keys.env` (auto-read by gen scripts) |
|
||
| depot upload cred | — | `GOD3_PW` env, **not set here** → publish is gated on John (see `publish.py`) |
|
||
|
||
## Normalized set (16 — ready; `manifest.json` → `fittings` / `furniture`)
|
||
|
||
house law enforced on every one: metres · +Y up · origin at base (minY=0, centred X/Z — verified
|
||
node-aware) · ≤5k tris · WebP ≤1024 · no Draco. *Facing is not auto-corrected* — symmetric props are
|
||
orientation-agnostic; the few directional ones (counter, food_cart) keep their native front and
|
||
Lane B/C rotate when placing (per-asset `yaw` is available in `batch.json`).
|
||
|
||
| PROCITY id | source GLB | src tris → out | footprint (m) | height (m) | notes |
|
||
|---|---|---|---|---|---|
|
||
| **fit** record_crate | shop-fittings/record_crate_plastic_01 | 18320→5000 | 0.46 × 0.63 | 0.41 | ★ native-metres, textured red milk crate; serves record bin too |
|
||
| **fit** wire_shelf | shop-fittings/shelf_metal_wire_01 | 330→330 | 1.17 × 0.44 | 1.80 | ★ clean upright wire shelving |
|
||
| **fit** clothes_rack | shop-fittings/clothes_rack_01 | 3152→3152 | 1.34 × 0.43 | 1.50 | ★ rolling clothes rack (opshop) |
|
||
| **fit** bookshelf | shop-fittings/bookshelves | 45128→5000 | 0.20 × 1.02 | 2.00 | tall bookshelf w/ books (book/video/opshop) |
|
||
| **fit** cube_shelf_wide | shop-fittings/shelf_cube_bookshelf_01 | 17734→4999 | 0.75 × 1.24 | 1.10 | stocked cube shelving (toy/book) |
|
||
| **fit** counter | shop-fittings/balcao | 276→276 | 1.06 × 4.02 | 1.10 | long bar counter — Lane C may want to scale to lot |
|
||
| **fit** coffee_table | shop-fittings/coffeetable | 495→495 | 0.67 × 1.49 | 0.45 | low display/side table |
|
||
| **fit** work_table | shop-fittings/computertable | 1556→1556 | 0.44 × 1.04 | 0.75 | work/trestle table w/ lower shelf |
|
||
| **fit** box_crate | shop-fittings/warehouse_box | 78→78 | 1.35 × 1.32 | 0.60 | open bulk bin / bric-a-brac box |
|
||
| **street** bench | street-furniture/bench | 1942→1942 | 0.44 × 1.21 | 0.85 | slatted seat |
|
||
| **street** bench_modern | street-furniture/eb_hipster_bench | 640→640 | 1.68 × 0.47 | 0.45 | low modern bench |
|
||
| **street** bench_wood | street-furniture/woodbench | 11728→3999 | 1.34 × 0.59 | 0.85 | timber park bench |
|
||
| **street** longbench | street-furniture/longbench | 1172→1172 | 5.86 × 1.08 | 0.85 | long feature bench (arcade/market) |
|
||
| **street** park_bench | street-furniture/park_bench_01 | 9884→5000 | 1.92 × 1.62 | 0.85 | classic slatted park bench |
|
||
| **street** streetlight | street-furniture/streetlight | 6592→4999 | 1.64 × 1.82 | 5.00 | ornate lamppost |
|
||
| **street** food_cart | street-furniture/food_cart_bakso_01 | 30316→5000 | 3.00 × 2.07 | 2.00 | textured food cart (~3MB — heavy, 28 tex; optimize later) |
|
||
|
||
## Rejected during normalize (needs-manual-fix — NOT in manifest)
|
||
|
||
| source | issue (verified by render) | disposition |
|
||
|---|---|---|
|
||
| shop-fittings/cube_bookcase (`cube_shelf`) | node-scale distortion → 6.3 × 8.4 m footprint | dropped; `cube_shelf_wide` covers the need |
|
||
| shop-fittings/garage_shelves_metal_v2 | imports lying flat (green pallet); baked node transform, up-fix didn't help | needs manual re-origin in Blender; `wire_shelf` covers shelving |
|
||
| shop-fittings/table, saloon_table, table_02, desk_01, kitchen_table | degenerate/flat or node-distorted (`table` → 55×55 m; `desk` → 17×17 m) | rejected; `work_table`/`coffee_table` cover tables |
|
||
| shop-fittings/radiogram_furniture_v2_l1 | Blender "Please select a file" on import (bad GLB) | needs re-export |
|
||
| street-furniture/market_stall_01_CHECK | import error (`_CHECK` = sorter-flagged) | → MeshGod/primitive (see `MESHGOD_BATCH.md`) |
|
||
|
||
## QUARANTINE — licence unclear / branded (🔴 do NOT publish or ship)
|
||
|
||
The house rule is 🟢 web-ok only; anything branded or of unknown provenance is quarantined until
|
||
cleared. These are **excluded from the manifest**:
|
||
|
||
| asset | reason |
|
||
|---|---|
|
||
| `furniture/armchair-vitra-eames`, `ines-chair-calligaris` | protected designer marques (Vitra/Eames®, Calligaris®) |
|
||
| `shop-fittings/ikea-cupboard` | IKEA® product design (+259k tris) |
|
||
| `props-scenes/mk2_1210_turntable_-_old` | Technics SL-1210® industrial design (trademark) |
|
||
| `props-scenes/tuborg_fest_scene` | Tuborg® brand scene |
|
||
| `furniture/uploads_files_*`, `props-scenes/uploads_files_*` (Couch William, Harland Office Chair, nv toufa, Backpack) | CGTrader/free3d bulk dumps, licence not recorded |
|
||
| `furniture/sofa-c`, `hug-armchair`, `dragonchest`, `kitty-bed-01`, `bean_bag_chair` | provenance unrecorded — verify before use |
|
||
|
||
> **Blanket caveat (applies to the 16-asset publish set):** the *generic* fittings we normalized
|
||
> (benches, shelves, counter, crate…) carry **no explicit licence/provenance record** — I cross-
|
||
> checked all 16 source filenames and none is a recognizable trademark, and they were pre-sorted
|
||
> into the house library by John for exactly this use, so they're treated as usable per the lane
|
||
> brief. But "unclear ⇒ quarantine" is the house rule: **before `publish.py` pushes to the shared
|
||
> CDN, record a one-line source/licence per asset**, and for any CC-BY source carry attribution.
|
||
> If provenance genuinely can't be established for one, pull it from the publish set. This is a
|
||
> reason the publish step is deliberately gated on John, not automated.
|
||
|
||
## Provenance — round-3 hero props (for the depot publish sign-off)
|
||
|
||
Per the round-3 instruction ("record the per-asset provenance list in AUDIT.md first, then hand
|
||
John the two-liner"). The hero props have the **cleanest provenance in the whole set** — they are
|
||
100% generated on-device, from no third-party source:
|
||
|
||
| asset (`procity_fit/street_<id>_01.glb`) | origin | licence zone |
|
||
|---|---|---|
|
||
| **5 generated** — listening_booth, drinks_fridge, arcade_cabinet, milkshake_mixer (fit) · novelty_record (street) | **fully synthetic, local**: concept image = MODELBEAST `flux_local` (FLUX.2-klein-4B, Apache-2.0 model) → mesh = `trellis_mac` (TRELLIS.2, an original reconstruction of that concept image). No scanned/third-party geometry, no copyrighted source asset. | 🟢 original work (CC0-equivalent) |
|
||
| cash_register (`vintage-cash-register.glb`) | **reuse** of John's own `thriftgod_hero` asset, already live on 3GOD (round-3 reuse check) — not regenerated. | 🟢 same as existing depot set |
|
||
| magazine_rack | **not shipped** — TRELLIS wire rack floored ~112k tris (thin-wire gotcha) → in-engine primitive. | n/a |
|
||
| glass_case, bus_shelter | **not shipped** — TRELLIS failed on the transparent glass → fal.ai fallback list (John's go — Hunyuan v2 ~14–17¢/gen: ~$0.28–0.34 for the pair, ~$1 worst case w/ retries) or primitive. | n/a |
|
||
|
||
- **Prompts were generic period objects with explicit `no text, no brands, no people`.** Every kept
|
||
prop was eyeballed for accidental baked branding (contact sheet `docs/shots/laneE/props_gen_local.png`).
|
||
The only textual marks are the **drinks-fridge's weathered faux-stickers** — verified at full concept
|
||
resolution to be **garbled, illegible pseudo-lettering** (no real trademark; e.g. a faux barcode +
|
||
nonsense glyphs), which reads as period flavour and blurs out at mesh-texture resolution. 🟢 cleared.
|
||
- Tri/size note: TRELLIS meshes are dense multi-shell scans; `normalize.py`'s COLLAPSE decimate floors
|
||
well above the ≤5k library budget (**25–77k tris, ~0.3–3.5 MB/prop**; neither COLLAPSE nor meshopt
|
||
can reduce shell-soup without a voxel-remesh that destroys the baked UVs). Accepted for **focal,
|
||
optional, LOD-able** props (texture-intact) — flagged for a later texture-bake-to-lowpoly / gltfpack pass.
|
||
- These GLBs stage locally (`web/assets/models/`, git-ignored, served by `?localdepot=1`) and await
|
||
John's depot publish; they are **not** committed binaries (same policy as the library `_normalized` set).
|
||
|
||
## Coverage vs the registry fittings needs
|
||
|
||
| shop type | fittings need | covered by | gap → |
|
||
|---|---|---|---|
|
||
| record | bins, crates, counter, listening corner | record_crate, counter, wire_shelf | **listening booth** → MeshGod |
|
||
| opshop | clothes racks, bric-a-brac shelving, book wall | clothes_rack, wire_shelf, bookshelf, box_crate | — |
|
||
| toy | cube shelves, display tables, **glass case** | cube_shelf_wide, coffee_table | **glass case** → MeshGod |
|
||
| book | bookshelves, **spinner racks**, armchair | bookshelf, cube_shelf_wide | spinner rack, armchair → MeshGod/primitive |
|
||
| video | aisle shelves, **returns slot**, poster wall | bookshelf, wire_shelf | returns slot, poster wall → primitive |
|
||
| pawn | **glass cabinets**, wall hooks, barred window | counter | glass cabinet → MeshGod; pegboard = `tex-pegboard` skin |
|
||
| milkbar | counter, **fridge**, magazine rack | counter | **fridge**, magazine rack → MeshGod/primitive |
|
||
| dept | mixed sections, **escalator prop** | (all above) | escalator → primitive |
|
||
| stall | trestle tables, crates | work_table, box_crate, record_crate | **market stall** → MeshGod/primitive |
|
||
| street | bench/streetlight/bin/tree | bench×4, longbench, park_bench, streetlight, food_cart | bin (`council-bin` on depot), tree → billboard (Lane B) |
|
||
|
||
Gaps with no clean source are shortlisted in `MESHGOD_BATCH.md` (budget-gated) — everything has an
|
||
in-engine primitive fallback, so none of these block the game.
|
||
|
||
## Depot cross-reference (`GET /api/list`, 138 assets)
|
||
|
||
- **90 named + 48 TRELLIS-hash.** Reusable house props already live (verify visually + licence
|
||
before wiring): `street-bench.glb`, `council-bin.glb`, `milk-crate-stack.glb`, `cashbot.glb`.
|
||
⚠️ `akai-mpc-live-iii-*` is a **trademarked product design** — same quarantine class as Technics/
|
||
IKEA; do not treat as clean web-ok reuse without a licence check.
|
||
- **No `procity_*` assets yet** — this lane is the first to publish that namespace.
|
||
- Our 16 normalized GLBs are staged in `pipeline/_normalized/` and upload via `pipeline/publish.py`
|
||
once `GOD3_PW` is provided (idempotent, reversible).
|
||
|
||
## Full library inventory (dispositions)
|
||
|
||
`shop-fittings/` (33): normalized 9 · rejected 8 (7 node-distorted/degenerate + radiogram bad-GLB) ·
|
||
quarantine 1 (ikea) · unused/dup 15 (heavier or duplicate tables/shelves — `shelf_storage_rack_01`
|
||
alone is 960k tris).
|
||
`street-furniture/` (9): normalized 7 · error 1 (market_stall) · `building_shell_01` reserved for
|
||
Lane B (38k-tri building shell — not a fitting).
|
||
`furniture/` (17): mostly domestic (beds/sofas/chairs) — out of scope for shop fittings; 6 quarantined.
|
||
`props-scenes/` (10): `dj_roomv01` + turntable = record-shop dressing (turntable quarantined: Technics);
|
||
rest branded/scene dumps.
|
||
|
||
Raw stats for every **parsable `.glb`** (59 of the ~69 library entries; the rest are `.fbx`/`.obj`
|
||
or subdir sources not counted): `pipeline/_audit_stats.json`. `glb_stat.py` reports **world-space**
|
||
dims (node-transform-aware) and WebP/PNG/JPEG texture sizes. Normalizer output:
|
||
`pipeline/_normalize_results.json`.
|
||
|
||
## Round 4 — ped-rig decimation (gate-3 blocker)
|
||
|
||
The 19 street ped rigs in `web/models/peds/` were **~31k–56k tris each (924k total)** — the cause of
|
||
gate 3 failing (~279k tris at the busiest intersection vs ≤200k). Decimated to **≤3k tris each**
|
||
(integrator decision #1) via `gltf-transform` `weld` → `simplify` (meshoptimizer):
|
||
|
||
```
|
||
gltf-transform weld in.glb _w.glb
|
||
gltf-transform simplify _w.glb out.glb --ratio 0.05 --error 0.12
|
||
```
|
||
|
||
meshoptimizer works cleanly here (proper manifold skinned character meshes, NOT the TRELLIS
|
||
shell-soup of the R3 hero props). Result: **924,153 → 46,551 tris** (48.6k → 2.45k avg; range
|
||
1564–2805, all ≤3k), **36M → 5M** on disk. **Skinning fully preserved** — `JOINTS_0`/`WEIGHTS_0`
|
||
intact, skeleton unchanged (nodes + 65 joints identical to source), so the shared `walk.glb`/
|
||
`idle.glb` clips retarget unchanged (both left untouched). Contact sheet:
|
||
`docs/shots/laneE/peds_decimated.png` — silhouettes/textures read clean, no melted faces/hands
|
||
(fine for street distance). Originals backed up (gitignored) at `pipeline/.peds_orig/`; per-ped
|
||
before/after in `pipeline/_peds_decim.json`. Lane D validates rig binding + anims (task D2); Lane F
|
||
re-measures gate 3 (~24-rig near cap × ≤3k ≈ ≤67k fleet contribution).
|
||
|
||
## Round 4 — E2 asset re-exports (published)
|
||
|
||
- **`procity_fit_record_crate_01.glb` re-export (fixed).** The R3 normalize left a **sourceless
|
||
texture** (packed AO/roughness map didn't survive the WebP export; the material's
|
||
`metallicRoughnessTexture` + `occlusionTexture` pointed at it), which crashed three.js GLTFLoader
|
||
with `undefined 'uri'`. `pipeline/fix_glb_textures.py` strips material slots that reference a
|
||
sourceless texture (base-colour + normal kept; the orphan is never loaded). Verified it loads in
|
||
the vendored `GLTFLoader` (5000 tris, textures ok); re-published to the depot.
|
||
- **`procity_fit_counter_till_01.glb` (new).** ~2 m timber shop counter with a period beige till —
|
||
`flux_local` concept (3 rolls for the ~2:1 aspect) → `trellis_mac` mesh → `normalize.py`. 1.61 ×
|
||
0.64 m, 1.15 m tall, 32k tris (TRELLIS shell-soup floor, within decision-#2's 25–77k for focal
|
||
props). **Provenance: 100% synthetic/on-device** (FLUX.2-klein-4B + TRELLIS.2 on the M3 Ultra), no
|
||
third-party source — cleanest licence zone. Replaces the R3 4 m till-less counter for the interior
|
||
counter slot (Lane C maps `counter` → `counter_till`); the balcao 4 m `counter` stays for long-bar use.
|
||
|
||
## Round 5 — E1: hero-prop bake-to-lowpoly (un-parked)
|
||
|
||
The 6 heavy TRELLIS hero props were **25k–77k tris** (shell-soup — simplify-only floors there,
|
||
R3's lesson). Rebuilt topology + re-baked base colour via `pipeline/bake_lowpoly.py` (Blender
|
||
headless): **voxel-remesh → triangulate → decimate ≤8k → smart-UV → Cycles DIFFUSE(COLOR) bake
|
||
HP→LP → WebP export**. All **≤8k tris**, base colour baked onto fresh UVs, self-illuminated a
|
||
touch (matches normalize). Originals backed up (gitignored) at `pipeline/.props_orig/`; batch spec
|
||
`pipeline/batch_bake.json`; results `pipeline/_bake_results.json`.
|
||
|
||
| prop | tris before → after | voxel | size on disk | note |
|
||
|---|---|---|---|---|
|
||
| drinks_fridge | 77047 → 8000 | 0.03 | 5.3M → 0.9M | hollow glass-door box — needed a coarser voxel (0.012 grabbed the internal shelves into wireframe) |
|
||
| milkshake_mixer | 73492 → 7991 | 0.005 | 4.7M → 0.9M | small part, fine voxel |
|
||
| listening_booth | 53502 → 7986 | 0.012 | 3.4M → 0.9M | panel + stool |
|
||
| counter_till | 32226 → 7999 | 0.012 | 1.9M → 0.9M | timber counter + till |
|
||
| novelty_record | 26486 → 8000 | 0.014 | 1.8M → 0.9M | thick disc + base |
|
||
| arcade_cabinet | 25142 → 8000 | 0.012 | 1.5M → 0.8M | boxy cabinet |
|
||
|
||
Fleet: **288k → 48k tris**, **18M → 5.2M**. Every one **eyeballed** (contact sheet
|
||
`docs/shots/laneE/props_baked.png` — no melted labels/faces; the fridge re-bake was the only redo)
|
||
and **verified loading in the vendored `GLTFLoader`** (tris ≤8k, baked texture present).
|
||
Re-published to the depot (`validate --depot` 0 errors). **Provenance unchanged: 100% synthetic/
|
||
on-device** (FLUX.2-klein-4B + TRELLIS.2 + Blender bake, no third-party source). → Lane C
|
||
re-measures the record interior (was ~273k tris).
|
||
|
||
## Round 5 — E3: fal.ai fallback props (glass-case + bus-shelter + magazine-rack)
|
||
|
||
The two R3 TRELLIS glass rejects + the optional magazine-rack, generated on **fal.ai Hunyuan3D v2**
|
||
(John-authorized, decision #4). Pipeline: `flux_local` concept (frosted/opaque surfaces so the
|
||
reconstructor has something to grab — clear glass is what defeated TRELLIS) → `pipeline/gen_props_fal.py`
|
||
(fal `fal-ai/hunyuan3d/v2`, key from MODELBEAST settings, never hardcoded) → `normalize.py` (scale/
|
||
origin) → `bake_lowpoly.py` (voxel-remesh → ≤8k → **EMIT albedo bake**; Hunyuan materials need the
|
||
base-colour→emission route, DIFFUSE(COLOR) baked them near-black).
|
||
|
||
**Paid gen log (fal Hunyuan3D v2, ~$0.16/gen):**
|
||
|
||
| prop | attempts | cost | outcome |
|
||
|---|---|---|---|
|
||
| glass-case | 1 (frosted-glass concept) | ~$0.16 | ✅ solid case, timber base, items visible — TRELLIS couldn't; 40k→8k baked |
|
||
| bus-shelter | 2 (att.1 dropped the thin perspex panels → att.2 with an opaque back wall) | ~$0.32 | ✅ solid shelter, back wall + roof + bench; 40k→8k |
|
||
| magazine-rack | 1 (optional; solid-timber, not wire) | ~$0.16 | ✅ tiered timber rack w/ magazines — beat the R3 wire failure; 40k→8k |
|
||
|
||
**Total fal spend this round: ~$0.64** of the $22 balance (well under the <$1 estimate). Guardrail
|
||
held — nothing else on fal. All 3 ≤8k tris, base-colour baked, **verified loading in the vendored
|
||
GLTFLoader**, eyeballed (`docs/shots/laneE/props_fal.png` — no melted glass, no legible branding),
|
||
published, `validate --depot` 0 err. **Provenance: FLUX.2-klein-4B concept + fal Hunyuan3D v2 mesh +
|
||
Blender bake — synthetic, no third-party source** (Hunyuan output is model-generated geometry).
|
||
Raw fal GLBs + concepts kept in gitignored `pipeline/.genprops/`. These retire the last two
|
||
primitive-only fittings (glass_case, magazine_rack) + add a street bus_shelter.
|
||
|
||
## Round 5 — E2: depot growth (consumer-driven)
|
||
|
||
The round-5 v2 features are **procedural** (dig.js riffles generated sleeve art; B's window shader is
|
||
100% procedural; D's roster is spawn logic) — so no forced GLB needs. Per Fable "don't add manifest
|
||
entries no lane consumes", E2 is deliberately light: two already-synthetic depot props with clear
|
||
consumers, re-normalized to procity names + thumbs (both already ≤5k tris, metre-scale, base-origin):
|
||
|
||
| new id | source | footprint / h | consumer |
|
||
|---|---|---|---|
|
||
| `street/bin` (`procity_street_bin_01.glb`) | `council-bin.glb` (thriftgod_hero, on depot) | 0.57×0.68 m, 1.0 m | Lane B street dressing (towns need bins) |
|
||
| `fit/crate_stack` (`procity_fit_crate_stack_01.glb`) | `milk-crate-stack.glb` (thriftgod_hero) | 0.71×0.72 m, 0.9 m | Lane C record-store floor piles / dig context |
|
||
|
||
Both 3k tris, verified loading in the vendored GLTFLoader, published, `validate --depot` 0 err.
|
||
Provenance: MeshGod-generated (thriftgod_hero) — synthetic, web-ok. **Note:** also resolved Lane B's
|
||
B3 ask — `novelty_record` (was 26.5k, spiking the busy view) is now 8k via the E1 bake. No speculative
|
||
props added; further depot growth is available on request once C's dig / D's roster name concrete needs.
|
||
|
||
## Round 7 — E1: ped sub-mesh merge to one draw (gates the roster flip)
|
||
|
||
The 19 ped rigs carried **1–9 primitives / 1–3 materials each = 92 draws** for the fleet (≈116 at
|
||
the 24-rig near cap) — the optimisation D's memo gates full-density roster on. `pipeline/merge_ped.py`
|
||
(headless Blender) merges each ped to **ONE primitive + one atlased material**: join meshes → pack
|
||
every material's UV islands into a non-overlapping atlas UV → **EMIT-bake each source material's
|
||
base-colour into one 1024 atlas** (route base-colour→emission via the ORIGINAL uv so the atlas UV
|
||
doesn't hijack the lookup) → single material. **Skinning + skeleton fully preserved** (65 joints
|
||
intact on all 19, `JOINTS_0`/`WEIGHTS_0` kept; `walk`/`idle` untouched).
|
||
|
||
Result: **fleet 92 → 19 draws (1/ped)**; at the 24-rig near cap **116 → 24 draws** (79% cut). Size
|
||
5M → 4M. Verified in the vendored `GLTFLoader`: all 19 load with `isSkinnedMesh` + bind the shared
|
||
`walk.glb` clip; `man_suit` (2-mat) poses to **byte-identical dimensions vs the original**
|
||
(109.038 × 62.5) proving the skin deforms unchanged; `man_athletic` (3-mat, most complex atlas)
|
||
eyeballed correct (skin/top/pants regions all right). Pre-merge decimated peds backed up (gitignored)
|
||
in `pipeline/.peds_r6/`; per-ped in `pipeline/_peds_merge.json`. → **Lane D validates (D1)** —
|
||
full silhouette/material/impostor/identity pass + full-density numbers — then **F flips the roster**.
|
||
|
||
## Round 7 — E2: GODVERSE stock pack pipeline (real sleeves for the dig)
|
||
|
||
`pipeline/build_stock_pack.py` builds C's `?stock=real` content pack **offline** (nothing queries the
|
||
dataset at runtime): read recordgod (`dealgod` Postgres `discogs_release`, 185k vinyl w/ cover URLs) →
|
||
curate N sleeves (deterministic md5 sample, varied) → **parody-transform title/artist** → fetch covers →
|
||
bake 8×8 atlas WebPs (256px cells) + a JSON index (`{id,title,artist,price,price_band,atlas,uv}`) →
|
||
stage for `?localdepot` / publish to depot.
|
||
|
||
**Parody law (metadata):** real (title, artist) → deterministic seed → recombined from period-AU word
|
||
banks; the output **shares no source token** (`Dark Side of the Moon`/`Pink Floyd` → `Quiet Milk Bar`/
|
||
`The Galahs`) so there is **zero real-trademark leakage**, yet it reads as plausible AU record stock,
|
||
and the same source row yields the same parody every build. Verified deterministic + token-disjoint.
|
||
|
||
**Pipeline PROVEN** end-to-end with `--sample 24` (synthetic solid-colour sleeves): 24 → one 2048 atlas
|
||
+ index, UVs correct, price bands (bargain/standard/collector/grail) working; sample pack staged to
|
||
`web/assets/models/` so Lane C can wire the consumer against the **real index format now**.
|
||
|
||
**The real 200-400 pack is blocked on two external deps (flagged to Fable):**
|
||
1. **`GODVERSE_DSN`** — the dealgod Postgres DSN (creds). Postgres is up on `localhost:5432`; the
|
||
builder reads the DSN from env (I don't hunt for creds). `--from-db --count 300` runs once it's set.
|
||
2. **Lane C's pack schema** (their task 1, not yet posted) — I built to the proposed fields above;
|
||
`INDEX_FIELDS` adjusts to C's LANE_C_NOTES contract when it lands.
|
||
Provenance of the real pack: source = Discogs metadata + cover images (recordgod), **metadata
|
||
parody-transformed** (no real titles/artists ship); covers are real release art (read-only, non-economy
|
||
per decision #2). Small book/toy packs are a follow-up once the record pack lands.
|
||
|
||
## Round 8 — E1/E2: book + toy stock packs + pack-QA gate
|
||
|
||
**E1 — pipeline generalized** (`build_stock_pack.py` now kind-parametrized: `record` 256×256 sleeves,
|
||
`book` **96×256 tall-thin spines**, `toy` 256×256 boxes). Per-kind parody word banks; same laws as
|
||
the record pack (build-time extract, deterministic parody-transform of metadata, atlas WebPs + JSON
|
||
index to C's schema — `stock_<kind>_index.json`, C reads `uv` not `cell`). Book/toy **sample packs
|
||
built + staged** (`web/assets/models/`, `?localdepot`) so Lane C wires book/toy shelves now; indexes
|
||
committed to `pipeline/packs/` as the contract. Parody verified deterministic + no distinctive-token
|
||
leakage across all 3 kinds ("Nevermind"/"Nirvana" → "Endless Blue"/"Mullet Milk Crates";
|
||
"The Hobbit" → "Under the Homestead"/"C. Underwood").
|
||
|
||
**E2 — pack-QA gate** (`validate_pack.py`, wired into `validate_manifest.py` → qa gate 3): every
|
||
`pipeline/packs/stock_*_index.json` entry must have a valid kind, in-range non-inverted UVs, a known
|
||
price band, non-empty title/artist, unique id, and an atlas listed in `atlases[]` — a bad bake fails
|
||
qa loudly (proven: inverted-UV + empty-title → exit 1). qa `--strict` GREEN 5/5.
|
||
|
||
**Blocker (flagged to Fable/John — real data):** the **real** ~150–250-item book/toy packs (and the
|
||
real record pack) need **`GODVERSE_DSN`** (dealgod Postgres; up on :5432, read from env, no cred
|
||
hunt). One command each once set: `build_stock_pack.py --kind book --from-db --count 200`. The
|
||
current staged packs are synthetic placeholders (real index format); provenance of the real packs =
|
||
GODVERSE metadata (OpenLibrary/toygod) **parody-transformed** (no real titles/authors/brands ship) +
|
||
real cover art (read-only, non-economy). No fal spend this round.
|
||
|
||
## Round 8 (cont.) — REAL stock packs live (GODVERSE, John-authorized DB access)
|
||
|
||
The synthetic samples are replaced by **real** packs, built from GODVERSE via peer-auth on m1ultra
|
||
(`johnking@100.91.239.7`, native Postgres, `psql` at `/opt/homebrew/Cellar/libpq/18.4/bin`). Access
|
||
resolved after the deployment DB proved Docker-internal (port closed, SSH denied) — John pointed to
|
||
the m1ultra clones. All extraction was **read-only SELECT**; no creds guessed, no writes.
|
||
|
||
| pack | items | source (m1ultra) | covers | prices |
|
||
|---|---|---|---|---|
|
||
| **record** | 350 | `discogs_full`: `inventory` (John's real store stock) ⋈ `release` ⋈ `release_image` | real Discogs cover art (`monsterrobot.party` CDN, from `local_url`) | real store prices |
|
||
| **book** | 311 | `dealgod.products` WHERE `category_path LIKE 'books%'` | real product photos (`image_url`, portrait 176×256) | real `current_price` |
|
||
| **toy** | 273 | `dealgod.products` WHERE `category_path LIKE 'collectables-hobbies-toys%'` | real product photos (`image_url`) | real `current_price` |
|
||
|
||
Build: `build_stock_pack.py --kind <k> --from-tsv` (rows extracted to a gitignored TSV, covers fetched
|
||
at build time, resized into atlas cells). **Metadata is parody-transformed** (titles/artists/authors/
|
||
brands → house word banks, deterministic, shares no source token — "Dark Side of the Moon"/"Pink Floyd"
|
||
→ "Quiet Milk Bar"/"The Galahs") so **no real trademark ships in the pack text**; the **cover images are
|
||
real release/product art** (read-only, non-economy, per decision #2 — John's own store inventory + AU
|
||
marketplace listings). Books are info-only in the source → **prices seeded deterministically** (John's
|
||
call). 15 atlases + 3 indexes: atlases published to the depot (`/api/upload`), indexes committed to
|
||
`pipeline/packs/` (the C contract) + staged for `?localdepot`. All 3 pass the pack-QA gate. Toy filter
|
||
is broad (some comics/coins among the collectibles) — acceptable for a collectibles shop; tighten later.
|
||
|
||
## Round 9 — E1: VRAM / texture memory audit (v2.0 ship due-diligence)
|
||
|
||
Decoded GPU cost = w·h·4 (RGBA) ·1.333 (mipmaps). WebP/JPEG disk size is irrelevant to VRAM — a
|
||
2048² atlas is ~22 MB resident regardless of its 0.8 MB file. Measured across every texture source:
|
||
|
||
| category | files | total VRAM | notes / typical resident |
|
||
|---|---|---|---|
|
||
| facade skins | 48 | 117 MB | shared per-chunk; only in-view shops resident (~60–85 MB) |
|
||
| sky domes | 10 | 23 MB | 1 dome resident (~2 MB) |
|
||
| ground | 10 | 9 MB | ~3 in view |
|
||
| interior tex (floor/wall/counter) | 16 | 12 MB | current room only (~3 MB) |
|
||
| wallpaper | 8 | 6 MB | current room only |
|
||
| **ped fleet** (19 merged rigs) | 19 | **~90 MB** (15×1024²=5.6 + 4×512²=1.4) | all resident (near-rig variety) |
|
||
| GLB fittings/furniture textures | 28 | ~30–40 MB | street furniture + current room (~25 MB) |
|
||
| **stock: record** | 6 | **134 MB** (256px cells, full) | resident only under `?stock=real` (eager-preloaded at boot) |
|
||
| **stock: book** | 4 | **19 MB** (half-res this round) | lazy, on book-shop entry |
|
||
| **stock: toy** | 5 | **28 MB** (half-res this round) | lazy, on toy-shop entry |
|
||
|
||
**Resident-VRAM by boot mode** (peak, approximate):
|
||
- **Default boot (no flags):** in-view skins ~85 + ped fleet ~90 + GLB ~25 + Lane-B runtime sign-atlas ~40 ≈ **~240 MB**.
|
||
- **`?stock=real`:** +record 134 (eager) → ~375 MB; +book/toy after visiting those shops (stockpack caches, no dispose) +47 → **~420 MB**.
|
||
- **Worst case (all flags, all 3 shop types visited):** ~**450 MB** (weather rain = 1 instanced draw, winmap = procedural — both VRAM-negligible).
|
||
|
||
**Cheap win taken this round:** book + toy atlases half-res'd (256→128 / portrait 176→88 px cells; UVs
|
||
are fractional so **no index/UV change**, only the atlas files shrank). Book/toy are quick pull-cards
|
||
(buy-anywhere), not the deep record dig — covers verified still legible at half-res. **Stock VRAM
|
||
323 → 181 MB** (record kept full for close riffle examination). Atlases re-published to the depot.
|
||
|
||
**VERDICT: fine to ship v2.0.** ~240 MB default / ~420 MB worst-case is comfortable on desktop
|
||
(GB-class VRAM) and within a low-end/integrated ~512 MB–1 GB texture budget. The stock packs (181 MB)
|
||
remain the dominant swing but no longer blow past the base. **No compression pass or lazy-load
|
||
required for the tag.** Two follow-ups flagged for **v3, not now** (bigger than a ship-round change):
|
||
1. **stockpack cache-cap** (Lane C's `stockpack.js`): dispose non-current shop-type atlases on
|
||
room-type change → bounds stock VRAM to ≤134 MB (one type) instead of accreting all three.
|
||
2. **KTX2/Basis GPU-compressed atlases**: 4–8× VRAM cut with no visible quality loss — the real
|
||
long-term fix for the record pack's 134 MB (needs a transcoder + loader change; out of scope now).
|
||
|
||
**Depot/manifest hygiene (E2):** `validate --depot` 0 err · pack-QA 3/3 valid · provenance-drift clean
|
||
(`_published.json` == 27 live procity GLBs) · 15 stock atlases live, **0 orphans**. All green post-R8.
|
||
|
||
## Round 11 — audio pack provenance
|
||
|
||
**Every audio file is 100% original procedural synthesis** (`pipeline/gen_audio.py`, numpy on the
|
||
MODELBEAST box → ffmpeg). No neural model, no sample library, no third-party recording touched — so
|
||
the whole pack is **CC0-equivalent original work** and **parody-safe by construction**: there is
|
||
nothing to imitate, every waveform is an oscillator/noise render. Music beds are original chord
|
||
progressions/melodies — no imitation of any specific real recording. Tool choice is documented in
|
||
`pipeline/README.md` (§ audio): synthesis over local MusicGen/stable-audio because it's $0,
|
||
deterministic/seeded, perfectly loopable, and licence-clean.
|
||
|
||
| group | files | origin |
|
||
|---|---|---|
|
||
| ambience (6) | street-day/night, rain, roomtone-retail/milkbar/video | filtered-noise beds + synthesised events (birds/crickets/drops/hum) 🟢 |
|
||
| sfx (13) | doorbell, door-open, till, riffle, toast, tram-bell, tram-rumble, footsteps ×6 | additive-bell + filtered-noise transients 🟢 |
|
||
| music (4) | record-shop, milkbar, video-synth, arcade | original synth loops (bass+chords+drums+lead) 🟢 |
|
||
|
||
- **Format**: OGG/Opus primary (48 kHz) + M4A/AAC fallback (44.1 kHz). **Loudness**: long beds/music
|
||
ffmpeg `loudnorm` I=-16 LUFS / TP=-1.5 (single-pass EBU R128); short SFX peak-normalised (-3…-6 dBFS).
|
||
- **Verified** (I synthesise, I can't subjectively listen): spectrogram sheet
|
||
`docs/shots/laneE/audio_spectrograms.png` (content/character) + numeric pass — peaks ≤ -0.4 dBFS
|
||
(no clip), **all loops seamless** (wrap-jump ≤ internal step), durations correct. **Final subjective
|
||
ear-check is John's/F's** — flagged honestly.
|
||
- **Budget**: 8.1 MB shipped (2.8 MB ogg + 5.3 MB m4a) vs the 25 MB cap; lazy-loaded per manifest.
|
||
- Files ship in `web/assets/audio/` (committed, like `gen/` skins); WAV scratch `pipeline/.genaudio/`
|
||
is git-ignored. Contract in `manifest.json` → `audio:{ambience,sfx,music}`.
|
||
|
||
## Round 12 — gig layer provenance (v3.0-alpha, `?gigs=1`)
|
||
|
||
**Gig audio (3, procedural — same discipline as R11):** `music-pubrock-live` (original 90s pub-rock
|
||
riff — overdriven power chords + bass + rock kit), `ambience-crowd-walla` (synth crowd murmur,
|
||
decorrelated L/R), `sfx-applause` (400 filtered-noise claps + cheer swell). 🟢 100% original synthesis,
|
||
$0, seamless loops, parody-safe. Flagged `gig:true` in the manifest; F plays them when a gig is on.
|
||
Pack now 26 assets, 9.1 MB of 25 MB.
|
||
|
||
**Gig posters (4 skins, `skins.poster`):** poster-xerox/screenprint/grunge/retro — 90s pub-rock flyer
|
||
templates generated by MODELBEAST `flux_local`. **Blank-name-band discipline** (like blank-signboard
|
||
facades): each has a clean top banner and *no baked text*; B/C overprint the seeded band name into
|
||
`nameZone` [x,y,w,h]. Eyeballed (contact sheet in the round note) — all 4 have the blank banner, zero
|
||
legible text. 🟢 original generation, portrait 768×1024 JPEG in `web/assets/gen/`.
|
||
|
||
**Gig instruments (5 GLBs, MODELBEAST `flux_local` concept → `trellis_mac` → `normalize.py`):**
|
||
🟢 100% original synthesis, $0. **All 5 kept** — every one reconstructed cleanly (better than the
|
||
round-3 wire rack; even the mic-stand's thin boom pole and the drum cymbal hardware survived). Held
|
||
by D's band rigs; primitive fallback if a GLB is absent (house law).
|
||
|
||
| instrument | tris | footprint (m) | h (m) | note |
|
||
|---|---|---|---|---|
|
||
| electric_guitar | 43k | 0.38×0.08 | 1.0 | sunburst solid-body, clean |
|
||
| bass_guitar | 36k | 0.34×0.12 | 1.15 | black bass, clean |
|
||
| drum_kit | 30k | 1.50×1.24 | 1.3 | full kit, cymbals survived |
|
||
| guitar_amp | **96k** | 0.51×0.29 | 0.6 | combo amp — heaviest (grille cloth → shell-soup) |
|
||
| mic_stand | 34k | 0.57×0.57 | 1.7 | chrome boom stand, thin pole held |
|
||
|
||
- Same TRELLIS tri-weight tradeoff as round-3 props (30–96k tris vs the ≤5k library law) — accepted
|
||
for focal/optional/LOD-able stage props seen at mid-distance; texture-bake-to-lowpoly is the
|
||
follow-up. Amp (96k) is the outlier — a candidate for that pass first. Eyeballed (contact sheet).
|
||
- Staged to `web/assets/models/` for local validation (`?localdepot=1`). **Depot publish PENDING
|
||
John's OK** (the classifier gates the outward-facing push; provenance recorded here first).
|