Lane C measured the bookshelf's transmission halving interior draws (opshop/hall 191->104, book/hall
101->51, dept/hall 116->116 control). The cause, with vendored line numbers: three.module.js:16209
enters renderTransmissionPass(opaqueObjects, ...) whenever currentRenderList.transmissive is
non-empty, and :16433 re-renders the WHOLE OPAQUE LIST into a render target — every opaque draw
issued twice. Trigger is material.transmission > 0 (:6890), set from the glTF at GLTFLoader:1202.
And at 1.0 it is not 'a bit glassy': :494 does totalDiffuse = mix(totalDiffuse, transmitted.rgb,
material.transmission) — the diffuse term is REPLACED by the backdrop.
I SCANNED ALL 103 GLBs. Three carried it:
procity_fit_bookshelf_01 mtl_10218_Bookshelves_v1 shipped and hurting (C's)
procity_street_longbench_01 lambert3SG NOT WIRED — the 5.86 m arcade bench
the v9 charter names, on my R38
recommend list for Lane B
procity_street_streetlight_01 Streetlight_HighResSG3 NOT WIRED — one per ~18 m of street
The two unwired ones are the worse finding: the street budget is 292/300 with EIGHT draws of margin,
and a pre-pass there costs another ~292, not eight. Armed, not fired.
FIXED SURGICALLY. pipeline/strip_transmission.py rewrites only the JSON chunk and copies BIN through,
printing sha1(BIN) before and after: BIN IDENTICAL on all three, tris/dims/mtl/img/tex unchanged.
KHR_materials_ior deliberately left (ior does not trigger the pre-pass).
PUBLISHED + VERIFIED under John's R22 standing authorization via the passwordless tailnet ingress:
6/6 HTTP 200 off the PUBLIC depot, byte counts exact, sha1 identical, depot copies re-scanned clean.
_published.json 56 -> 59.
GATED, which matters more than the fix: validate_manifest.py now HARD FAILS on transmissionFactor>0
with the three.js line numbers and the fix command in the message; glb_stat.py grew a transmission
column and a loud footer. Nobody looked for three epochs because a bookshelf is obviously not glass.
ALSO: the arcade's three meshes, published and catalogued the same round they were generated —
aframe_board 366 tris (<=16 inst), blade_sign 299 (17 inst), keycutter_sign 498 (1 inst). manifest
furniture 13->16, GLBs 36->39, validate 0 errors 0 warnings. blade_sign ships FLAGGED: its back face
carries TRELLIS-hallucinated art where the concept had no data, which is one more reason the
+0-draw perpendicular-quad answer is the recommendation and this GLB is the fallback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1103 lines
81 KiB
Markdown
1103 lines
81 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 was
|
||
**PENDING John's OK** at R12 → **DONE in R13** (see the R13 section below; the 30–96k figures in
|
||
the table above are the R12 originals, decimated to 14k each in R13).
|
||
|
||
## Round 13 — gig-layer finalization (v3.0-beta, `?gigs=1`)
|
||
|
||
Supersedes the R12 instrument table + pending-publish note above. Three closes, all 🟢 provenance
|
||
unchanged (100% on-device synthetic / procedural — decimation and renaming don't touch the licence zone):
|
||
|
||
**1. Instrument decimation (`pipeline/decimate_props.py`, Blender collapse-decimate — NOT voxel-remesh,
|
||
so thin features survive).** The R12 instruments were the TRELLIS shell-soup tri-weight outlier; a
|
||
district means multiple concurrent stages, so all 5 were taken to a uniform **14 000 tris** (fleet
|
||
**236k → 70k, −70%**). Collapse (keeps original UVs/textures, no re-bake) held the mic boom pole + cable,
|
||
drum cymbals/stands and guitar necks — verified by re-rendered thumbs. The high-poly originals are kept
|
||
as `_hi` variants (published to the depot as archival source, not manifest-referenced).
|
||
|
||
| instrument | R12 tris → R13 | `_hi` original | footprint (m) | h (m) |
|
||
|---|---|---|---|---|
|
||
| electric_guitar | 43 451 → **14 000** | 43 451 (`_hi`) | 0.38 × 0.08 | 1.00 |
|
||
| bass_guitar | 36 031 → **14 000** | 36 031 (`_hi`) | 0.34 × 0.12 | 1.15 |
|
||
| mic_stand | 34 079 → **14 000** | 34 079 (`_hi`) | 0.57 × 0.57 | 1.70 |
|
||
| drum_kit | 26 238 → **14 000** | 26 238 (`_hi`) | 1.50 × 1.24 | 1.30 |
|
||
| guitar_amp | 96 583 → **14 000** | 96 583 (`_hi`) | 0.51 × 0.29 | 0.61 |
|
||
|
||
**2. gigKey rename + two new beds (debt #1).** The manifest music key IS the gigKey, always
|
||
`gig-<genreKey>`. `music-pubrock-live` → **`gig-pubrock`**; added **`gig-grunge`** (band_room) and
|
||
**`gig-covers`** (rsl, organ-led) — all procedural numpy synth, $0, seamless, parody-safe (new synth
|
||
helpers `organ()` + `grungechord()`). Audio pack **26 → 28 assets**. Old `pubrock-live` files deleted.
|
||
|
||
**3. Depot publish — DONE (John OK'd direct publish, R13).** Pushed via the passwordless tailnet
|
||
ingress (this box is allowlisted): the 5 base names now serve **14k bytes** + the 5 `_hi` originals are
|
||
live. Verified on the public `digalot.fyi` depot (amp base **5.9 MB → 1.31 MB**, `_hi` 5.9 MB live).
|
||
`_published.json` **33 → 38**. `validate_manifest --depot` GREEN; Gate-3 provenance flag **cleared**.
|
||
|
||
## Round 36 wave 1 — the 2 a.m. ped assets, swept (provenance + depot regularization)
|
||
|
||
`046668e` (4 dance clips) + `b339402` (**5** trellis DJ bodies — the R36 brief said 3; the diffstat
|
||
says 5) landed July 20 outside the round system. R36 wave 1 swept all **9** into the pipeline; the
|
||
files themselves are untouched (byte-identical to both commits and to their 90sDJsim originals).
|
||
|
||
**Dance clips — zone corrected by measurement: ROKOKO, not Mixamo.** Source = Rokoko's free 263-asset
|
||
mocap library (`~/Documents/Rokoko_Free_Mocap_FBX_263`, `Rokoko Studio (Mocap)/Dancing/*_mixamo.fbx`,
|
||
Rokoko's own Mixamo-skeleton export), baked mesh-free by 90sDJsim `tools/bake_clips.py` (the
|
||
`fbx_to_clip.py` port). Licence per the pack's rokoko.com page: royalty-free including commercial use.
|
||
Structure (all 4): 0 meshes · 68 nodes · 198 tracks · 52-joint dangling skin with IBM · 0 NaN/Inf ·
|
||
binds 65/66 canonical targets on a shipped ped skeleton (only `mixamorigReference` unbound — dropped
|
||
silently by three.js; matches 90sDJsim's in-game 64/65). Durations 30.58/30.50/44.04/52.42 s.
|
||
|
||
| clip | bytes | sha1 |
|
||
|---|---|---|
|
||
| dance_party.glb | 688 220 | 38587f7b0cf4aaf199f55677a139db087acdd80d |
|
||
| dance_medium.glb | 1 028 164 | 4a30fb62163b6e5137c8fb513dfc70d5f6f322b4 |
|
||
| dance_drink.glb | 962 464 | d818ddb0868e19c87a435b26c8b6ffab14832be4 |
|
||
| dance_sway.glb | 1 132 560 | 8af9112538e5219fc6a7b6cb36bf1e87e5ac24ff |
|
||
|
||
**DJ bodies — on-device TRELLIS.2 mesh (CC0-equiv, the instrument zone) + Adobe Mixamo auto-rig,
|
||
converted/decimated by MESHGOD `scripts/fbx_to_ped.py`.** GLB law: no Draco, 1024² webp
|
||
(`EXT_texture_webp` — roster-standard), 1 mtl/1 img, 65-joint mixamorig skin, IBM 0 NaN. Ped law ≤5k:
|
||
PASS. ⚠ bind-pose skeleton spans ~0.95–0.99 units vs the existing roster's ~173–180 (cm convention) —
|
||
absorbed at spawn (`rigs.js:163` height/span normalize), flagged to F's R36-w2 figure-scale gate.
|
||
|
||
| body | tris | bytes | sha1 |
|
||
|---|---|---|---|
|
||
| dj_phrtt_01.glb | 4 000 | 712 792 | 21cf0db5a291f88f7fd9b9cfdb0eac8413c7cf2e |
|
||
| dj_techno_01.glb | 4 000 | 702 760 | 4cb6bf44a752f921e14d1355f9944b7859c92386 |
|
||
| woman_dj_01.glb | 3 999 | 731 440 | 7d364dfc7e7659c0b15bead82a5d8bbd285e5286 |
|
||
| woman_dj_02.glb | 3 999 | 715 116 | 2413ededff1a7684999d6fd6fd58417b77f25c10 |
|
||
| woman_dj_03.glb | 3 998 | 718 128 | ec39692fa8019645bd200aab9eb6d1078535189c |
|
||
|
||
**Depot:** staged to `_normalized/` (the sit/look practice) and published via the passwordless tailnet
|
||
ingress under John's R22 standing authorization — `_published.json` **44 → 53**, drift check green, and
|
||
all 9 fetched back from the public depot (`digalot.fyi/3god/a/`) sha1-identical to local. Gates after
|
||
the sweep: `validate_manifest` 0 errors / 0 warnings · `publish.py --verify` ✓ 53/53 live.
|
||
|
||
## Round 38 — v8 wave 1: the asset wave (pipeline swap · audio · grounds · the mesh probe)
|
||
|
||
*Everything below is measured on this box today. Where the R38 review's costed figures differ from
|
||
what the hardware actually did, the measured number is given and the difference is named.*
|
||
|
||
### 1. THE PIPELINE FIX — `trellis_mac` → `trellis2_mlx`, and the output-contract trap it hides
|
||
|
||
`gen_props.py` hardcoded **`trellis_mac`** (the review's farm telemetry: p50 245.4 s, 33 jobs/7 d).
|
||
Swapped to **`trellis2_mlx`** (384 jobs/7 d, p50 134.6 s, better PBR bake). Operator contract read
|
||
from its `manifest.json`, not assumed — `pipeline_type` / `seed` / `steps` / `baker` /
|
||
`texture_size` / `max_bake_faces` / `alpha_mode`; we pass `1024_cascade`, `baker:metal`,
|
||
`max_bake_faces:500000` (fal parity, M3-Ultra-verified), `alpha_mode:opaque`.
|
||
|
||
**The two operators are NOT drop-in on output, and the naive swap would have shipped untextured
|
||
meshes.** `trellis_mac` writes one `*.glb`; **`trellis2_mlx` writes BOTH `candidate.glb` (raw) and
|
||
`candidate_pbr.glb` (baked)** into the same tree, and the old code took `sorted(rglob('*.glb'))[0]`
|
||
— which is alphabetically the **raw** one. Fixed with `_pick_glb()`: trust the operator's own
|
||
`result.json` first, then `candidate_pbr.glb` by name, then anything. `--legacy-trellis` keeps the
|
||
old operator for an A/B.
|
||
|
||
Also added the **`bg_remove_local` (RMBG-2.0) cutout stage** the review's path calls for
|
||
(flux → bg_remove → trellis2 → normalize), fail-soft: if RMBG errors the raw concept is fed through
|
||
and the mesh operator segments internally as before.
|
||
|
||
**Measured on the 5-prop R38 batch (wall clock, mesh stage only):** 122 s · 83 s · ~108 s · ~123 s
|
||
→ **mean ≈ 109 s**, i.e. *faster* than the farm's 134.6 s p50 and **~2.2× the old operator's
|
||
245.4 s**. The cutout stage costs **5–6 s** per prop. **The review's ~2× throughput claim is
|
||
confirmed with a real number.** Retry budget went unused: **5/5 first-attempt success, 0 failures,
|
||
0 OOM-kills.**
|
||
|
||
### 2. AUDIO — the inhabitation layer (item 1.1). 6 new PACK entries, $0, 🟢 original
|
||
|
||
Same doctrine as R11/R12 and for the same reasons: **100% procedural numpy synthesis**, no neural
|
||
model, no sample library, no third-party recording — **CC0-equivalent original work, parody-safe by
|
||
construction** (every waveform is an oscillator or a noise render, so there is nothing to imitate).
|
||
Deterministic through `crc32(name)` → the same dog is the same dog on every boot.
|
||
|
||
| asset | cat | dur | ogg | m4a | synthesis |
|
||
|---|---|---|---|---|---|
|
||
| `sfx-dog-bark-fence` | sfx | 3.2 s | 16 KB | 37 KB | 8 barks: pitch-dropping saw through 2 vocal-tract formants + attack noise; 2.2 kHz lid + a 28 ms paling slap-back + yard reverb 🟢 |
|
||
| `sfx-magpie-carol` | sfx | 2.6 s | 28 KB | 42 KB | fluted glides under a 28–52 Hz quaver, **every note doubled a fifth up** (the syrinx has two sides — that is why a carol sounds like two birds) 🟢 |
|
||
| `sfx-roller-door` | sfx | 2.8 s | 24 KB | 45 KB | slow-fast-slow clatter train + sheet-metal drum + panel modes **amplitude-followed** off the clatter + the stop 🟢 |
|
||
| `ambience-mower-distant` | ambience | 12.0 s | 78 KB | 190 KB | firing pulse train w/ load wander, blade hiss, 900 Hz lid. Loop **genuinely folds**: every LFO is a harmonic of 1/12 s and the firing rate is a whole number of cycles per loop 🟢 |
|
||
| `ambience-radio-through-window` | ambience | 8.6 s | 82 KB | 136 KB | an **original** 4-bar turnaround (D A G E) → 380–2600 Hz little-speaker band → 1.5 kHz glass lid 🟢 |
|
||
| `ambience-sprinkler` | ambience | 8.0 s | 53 KB | 127 KB | impulse arm ticking across a 62% arc then swinging back silent, under a sweeping spray hiss 🟢 |
|
||
|
||
**Budget: +281 KB ogg / +577 KB m4a = +0.86 MB.** Pack 10 MB → **10.9 MB against the 25 MB cap**,
|
||
which matches the charter's ~11 MB estimate. **Zero draws, zero tris** — as chartered, this item
|
||
cannot touch the render budget under any circumstance.
|
||
|
||
**Verified** (I synthesise; I cannot subjectively listen): spectrogram sheet
|
||
`docs/shots/laneE/inhabitation_spectrograms.png` + a numeric pass — peaks **0.33–0.71** (no clip),
|
||
loop wrap-jump **≤ 0.18× the 95th-percentile internal step** on all three beds (radio 0.00×,
|
||
mower 0.08×), and band-energy fractions that match the intent: mower **99% below 1 kHz**, radio
|
||
**87%/13%/0%** across <1k/1–4k/>4k, carol **86% in 1–4 kHz**, bark **89% below 1 kHz**.
|
||
**One defect found and fixed by looking**: at spray gain 0.5 the sprinkler's hiss masked its own
|
||
ticks and it read as a hose — the tick *is* the sprinkler. Rebalanced to 0.28 and re-rendered; the
|
||
ticks and the silent swing-back are now visible in the sheet. **Final subjective ear-check is
|
||
John's / F's** — flagged honestly, same as R11.
|
||
|
||
### 3. GROUND SKINS — the per-town palette (item 1.2). 4 new, `flux_local`, $0, 🟢
|
||
|
||
`gen_skins.py` had **no GROUND template at all** — the ten shipped grounds predate it. Added one,
|
||
plus a `--grounds` mode and a dedicated `GROUND_STYLE` prefix: the house `STYLE` bible says *"warm
|
||
fluorescent lighting"*, which is an **interior** cue and wrong for an outdoor overhead surface.
|
||
|
||
| skin | use | px | bytes | note |
|
||
|---|---|---|---|---|
|
||
| `ground-grass-dry` | yard | 512² | 157 801 | straw-blond patchy summer lawn, bare soil through it 🟢 |
|
||
| `ground-grass-temperate` | yard | 512² | 183 917 | dense damp deep-green lawn + clover 🟢 |
|
||
| `ground-coastal-sand` | yard | 512² | 131 231 | pale wind-rippled sand + shell grit 🟢 |
|
||
| `ground-bitumen-reddust` | road | — | — | **re-rolled, see below** |
|
||
|
||
**Cost correction.** The review costs `gen_skins.py --local` at **5.7 s**; measured here at
|
||
**~9.7 s each**. That is not a regression — the review's figure is for the 1024×576 facade
|
||
geometry and these are **1024² square tiles, 1.78× the pixels**. Per-pixel the two agree. Square is
|
||
the deliberate choice: grounds tile through `RepeatWrapping`, and the two `djsim-*` grounds (the
|
||
only 512×512 ones) are the correct precedent — the eight flux-era grounds are **512×279** and
|
||
stretch under a square UV tile. That is a pre-existing defect, recorded not fixed.
|
||
|
||
### 4. MESHES — the proof batch (items 1.3 + 1.4), `flux_local` → `bg_remove_local` → `trellis2_mlx`
|
||
|
||
**Licence: 🟢 CC0-equivalent** under `AUDIT.md:101`'s standing doctrine — a `flux_local` concept on
|
||
an Apache-2.0 ungated model (FLUX.2-klein-4B), reconstructed by TRELLIS.2 into original geometry.
|
||
Nothing downloaded, nothing pooled. **The 3GOD depot was not read as a pool** (971 live, 53
|
||
recorded, known trademarked items inside — it stays a read-only archive) and **the hardyards GLBs
|
||
are REJECTED on provenance**, as the round ruled: 39 GLBs with provenance documented for one file.
|
||
Every one of these five is ours.
|
||
|
||
**Operator timings, all five, wall clock (includes the 5–6 s RMBG cutout):**
|
||
|
||
| prop | trellis2_mlx | raw tris | outcome |
|
||
|---|---|---|---|
|
||
| magpie | **129.0 s** | 473 513 | ✅ shipped 894 tris |
|
||
| hills-hoist | **88.1 s** | 503 716 | ❌ **thin-structure failure** |
|
||
| paling-fence-panel | **112.3 s** | 479 611 | ✅ shipped 1 121 tris |
|
||
| tv-aerial | **128.0 s** | 494 204 | ❌ **thin-structure failure** |
|
||
| water-tank | **489.0 s** | 475 047 | ✅ shipped 500 tris |
|
||
|
||
**median 128.0 s** — inside the farm's 134.6 s p50 and **1.92× faster than `trellis_mac`'s 245.4 s**.
|
||
Mean is 189.3 s, dragged by one 489 s outlier (3.8× the median, same params, same box, no error) —
|
||
worth naming: **the operator's tail is long even when nothing fails.** 5/5 first-attempt success,
|
||
**0 retries used** against the 4–5% budget, 0 OOM-kills.
|
||
|
||
#### 4a. SHIPPED — three assets, all GLB-law clean
|
||
|
||
House law verified on every one: metres · +Y up · base origin · **1 material / 1 image / 512² WebP**
|
||
· no Draco.
|
||
|
||
| id | tris | footprint (m) | height (m) | notes |
|
||
|---|---|---|---|---|
|
||
| **magpie** | **894** | 0.15 × 0.40 | 0.358 | the 1.3 asset. Silhouette excellent at 894 — beak, legs, folded wing and tail all read. **Albedo flagged, see 4c.** 🟢 |
|
||
| **paling_fence** | 1 121 | 0.28 × 3.59 | 1.788 | weathered grey palings with gaps + two rails. Overshot its 400 target (see 4b) 🟢 |
|
||
| **water_tank** | **500** | 1.73 × 1.64 | 2.186 | corrugated galvanised tank, domed lid, outlet tap. **Cleanest asset in the batch** 🟢 |
|
||
|
||
Contact sheet (4 angles each): `docs/shots/laneE/r38_props_shipped.png`.
|
||
|
||
**⚠ NOT PUBLISHED — one step outstanding.** All three are normalized, baked to budget and staged in
|
||
`pipeline/_normalized/`, but they are **not on the 3GOD depot** and therefore deliberately **not in
|
||
`manifest.json`**: pushing to a public depot is an outward-facing action and this lane had no
|
||
in-session authorization for it. `validate_manifest.py` correctly fails any manifest GLB with no
|
||
`_published.json` record, so the results file is held at **`pipeline/_r38_results.PENDING_PUBLISH.json`**.
|
||
`build_manifest.py` is already wired to `_r38_results.json`: **publish, rename, re-run** and the three
|
||
land. The gate is green as committed (0 errors, 0 warnings).
|
||
|
||
#### 4b. THE DECIMATION FLOOR — a real finding, not a tuning miss
|
||
|
||
**`normalize.py`'s collapse decimator cannot reach small budgets on TRELLIS output**, and nobody had
|
||
ever asked it for ≤900 before. Measured, all five: 470 442→**13 849** (target 900) · 478 236→**250 068**
|
||
(600) · 476 974→**18 791** (400) · 485 565→**89 306** (400) · 475 047→**70 893** (500). The cause is
|
||
the documented shell-soup floor — collapse cannot merge across disconnected shells, so the floor *is*
|
||
the shell count. `bake_lowpoly.py` (R5's voxel-remesh → decimate → Cycles bake) is the correct tool
|
||
and clears it: magpie **894**, water_tank **500**, tv_aerial 299, hills_hoist 496. paling_fence floored
|
||
at **1 121** against a 400 target — a flat panel of separate palings has an irreducible shell count at
|
||
voxel 0.012; it is shipped at 1 121 rather than remeshed coarser, because coarser merges the palings
|
||
into a slab and the gaps are the whole point of a paling fence.
|
||
|
||
#### 4c. THE TWO FAILURES — thin structures, exactly as the review predicted
|
||
|
||
**`hills_hoist` and `tv_aerial` FAIL and are NOT shipped.** Both were deleted from `_normalized/`
|
||
rather than left where a later manifest build could catalogue them. Evidence:
|
||
`docs/shots/laneE/r38_thin_structure_failure.png`.
|
||
|
||
TRELLIS reconstructs the *solid* parts well — the hoist's pole, concrete footing and four arms are all
|
||
there at 250 k tris, and it is recognisably a Hills Hoist. **The wire is what dies**: it comes back as
|
||
thousands of tiny disconnected fragments, which is simultaneously why the mesh reads as frayed *and*
|
||
why it will not decimate. And **both escape routes fail in opposite directions**: collapse floors at
|
||
250 k / 89 k with the fraying intact, and a voxel remesh coarse enough to reach 600 tris **erases the
|
||
pole entirely** — the 496-tri hoist is a floating horizontal smear with no upright, and the 299-tri
|
||
aerial is scattered debris. There is no voxel size that keeps a 30 mm pole and a 2 mm wire at 600 tris.
|
||
|
||
**This is a DECISIVE capability answer, and it is the one the round asked for.** Thin-structure props
|
||
are not a TRELLIS class. **→ Lane B: use primitives for the hoist and the aerial**, which is the
|
||
`boxMats` path anyway and costs **+0 draws** — strictly better than the GLB would have been.
|
||
|
||
#### 4d. THE ALBEDO PROBE — TRELLIS crushes high-contrast markings, and the input is exonerated
|
||
|
||
The magpie came back as an **all-black bird — a crow, not a magpie**. Traced properly rather than
|
||
guessed at, because the fix differs completely depending on where it happened:
|
||
|
||
1. **The input is provably perfect.** The `bg_remove_local` cutout fed to TRELLIS has a bright white
|
||
nape, white wing patches, a white tail tip and a pale blue-grey beak. Not the concept's fault, not
|
||
RMBG's.
|
||
2. **The raw 500 k TRELLIS mesh has already lost it** (`docs/shots/laneE/r38_magpie_albedo_probe.png`):
|
||
the white nape survives only as a pale blue-grey wash and the wing bar and tail tip are gone. Its
|
||
1024² albedo atlas is near-uniformly black with sparse pale slivers. **So the loss is the
|
||
operator's PBR bake — our normalize/decimate/re-bake chain is exonerated.**
|
||
3. **The documented workaround does not work through our normalizer.** The manifest advertises
|
||
`baker: "vertex"` as "the 1s clean-albedo bake, cleanest colors". Re-ran the magpie with it
|
||
(159 s): the GLB comes back with **0 materials and 0 images** — colour lives only in vertex
|
||
attributes, and `normalize.py` (join → decimate → re-export) drops them. The result renders
|
||
**pure white**. `baker: vertex` is unusable here without normalizer work.
|
||
|
||
**Shipped anyway, flagged honestly**, because the silhouette is what a swooping bird reads as at
|
||
street distance and 1.3 cannot ship without a mesh. **Fix path for a later round** (stated, not done):
|
||
bake the LP's albedo from the *concept image* rather than from the reconstructed mesh, or hand-tint
|
||
the nape/wing/tail regions of the 512² baked atlas. **Both are cheap; neither is this round's job.**
|
||
|
||
### 5. REUSE BEFORE GENERATING — the review's nine, re-measured, and its "one line each" corrected
|
||
|
||
All nine confirmed idle and re-measured off the bytes. **But "one line each" is wrong for four of
|
||
them**, and the reason is in `furniture.js`'s own merge contract: `extractGLB` merges every mesh into
|
||
ONE geometry and **keeps only the FIRST material**. So a multi-material GLB does not fail — it renders
|
||
**entirely in material #1**, silently.
|
||
|
||
| id | tris | mtl | verdict |
|
||
|---|---|---|---|
|
||
| `novelty_record` | **8 000** | 1 | ✅ true one-liner. `furniture.js:275` already calls `glb('novelty_record',…)`; `GLB_FILES` has no key, so the call can never fire. **The blocking comment at `furniture.js:30` says "26.5k tris" and is STALE** — Lane E baked it to 8 000 in R5. |
|
||
| `streetlight` | **4 999** | 1 | ⚠️ **decimate first.** It is placed every ~18 m — at ~50 instances that is ~250 k tris on a 200 k budget. The review is right that it needs `decimate_props.py` to ~800 before it is wired. |
|
||
| `bench_modern` | **640** | 1 | ✅ true one-liner |
|
||
| `longbench` | **1 172** | 1 | ✅ true one-liner. 5.86 m — the arcade/market feature bench |
|
||
| `park_bench` | **5 000** | **2** | ⚠️ renders in material #1 only |
|
||
| `bench_wood` | **3 999** | **0** | ⚠️ **zero materials** → falls to `extractGLB`'s grey `0x888888` default |
|
||
| `coffee_table` | **495** | **2** | ⚠️ material #1 only |
|
||
| `box_crate` | **78** | **3** | ⚠️ material #1 only |
|
||
| `counter` (balcao) | **276** | 1 | ✅ 1.06 × 4.02 m, 1.10 m — exactly the `bar_counter` fitting still primitive-only in all three venue types. Different loader (`interiors/glb.js`), not `furniture.js`. |
|
||
|
||
**Nothing was re-generated that already existed.** No duplicate of any of these was made this round.
|
||
|
||
### 6. LICENCE — every R38 asset lands 🟢
|
||
|
||
| class | count | zone | method |
|
||
|---|---|---|---|
|
||
| audio | 6 | **🟢 original by construction** | pure numpy synthesis, `gen_audio.py`, deterministic via `crc32(name)` |
|
||
| ground skins | 4 | **🟢 original** | `flux_local` / FLUX.2-klein-4B (Apache-2.0, ungated), on-device |
|
||
| meshes | 3 shipped | **🟢 CC0-equivalent** (`AUDIT.md:101` doctrine) | `flux_local` concept → `bg_remove_local` (RMBG-2.0) → `trellis2_mlx` (TRELLIS.2-4B) → `normalize.py` → `bake_lowpoly.py` |
|
||
|
||
**The 3GOD depot was NOT used as a pool** — it stays a read-only archive (971 live, 53 recorded,
|
||
known trademarked items inside). **The hardyards set stays REJECTED on provenance** (39 GLBs,
|
||
provenance documented for one file); every mesh here is ours. **$0 spent — no cloud key touched.**
|
||
|
||
### 7. SESSION 2 — the depot step, the magpie tint, and the sizing law
|
||
|
||
*Everything in §7 is measured on this box after `1568c9b`; nothing in §1–§6 is retracted.*
|
||
|
||
#### 7a. PUBLISHED — three GLBs, verified by fetching the bytes back off the PUBLIC depot
|
||
|
||
Pushed under **John's R22 standing authorization for Lane E assets** (the same authority as
|
||
`sit.glb` in R28 and the nine R36 sweep assets) through the passwordless tailnet ingress
|
||
(`GOD3_DEPOT=http://100.94.195.115:8788`, decision #4 — no secret read, no cookie, `publish.py`
|
||
never logs one). `_published.json` **53 → 56**; post-publish drift check `✓ all 56 recorded assets
|
||
live`. Thumbs uploaded with each (`glb:200 thumb:200`).
|
||
|
||
Then verified the way this lane always verifies: **fetch it back off the public host and compare
|
||
hashes**, not "the upload said 200".
|
||
|
||
| file | HTTP | local B | depot B | sha1 (identical both sides) |
|
||
|---|---|---|---|---|
|
||
| `procity_street_magpie_01.glb` | 200 | 132 592 | 132 592 | `b5f02b26d15f7a58f8761f54fdb98b14fa65ee10` |
|
||
| `procity_street_paling_fence_01.glb` | 200 | 165 752 | 165 752 | `a580a8833f02bccb2433776072bca2d4fbee8050` |
|
||
| `procity_street_water_tank_01.glb` | 200 | 77 152 | 77 152 | `9b08fb9774e892f04d5c36a09ab2100383f4e360` |
|
||
|
||
`_r38_results.PENDING_PUBLISH.json` → `_r38_results.json`, `build_manifest.py` re-run — manifest
|
||
GLBs **33 → 36**, all three under `furniture`. **`validate_manifest.py`: 0 errors, 0 warnings.**
|
||
|
||
#### 7b. THE MAGPIE TINT — a 3-D repaint of a 2-D atlas, because the atlas is unreadable by eye
|
||
|
||
§4d left the bird an all-black crow with the loss traced to `trellis2_mlx`'s PBR bake. The stated
|
||
fix path ("hand-tint the 512² atlas") is **not a paint job**: the atlas is a smart-UV bake, ~100
|
||
scattered islands, and no island announces itself as a nape. New tool **`pipeline/tint_atlas.py`**:
|
||
|
||
1. rasterize every UV triangle into the atlas grid → **texel → (position, normal)** maps
|
||
(barycentric, glTF top-left UV origin, 1-texel conservative pad) — 101 086 / 262 144 texels
|
||
covered, 38.6 %;
|
||
2. evaluate **soft anatomical windows on the POSITIONS** in the mesh's own metric frame
|
||
(x ±0.0777 lateral · y 0→0.3583 up · z −0.2021 tail → +0.2021 beak): white **nape** behind a
|
||
black crown, white **shoulder/wing bar** on the flanks only so the dorsal midline stays black,
|
||
white **rump + tail base** stopping short of the black terminal band — Australian magpie
|
||
(*Gymnorhina tibicen*), adult, black-backed form;
|
||
3. paint with the bake's own luminance kept as shading (`0.88 + 0.12·smoothstep(luma)`), so it is
|
||
white with the bake's light in it, not a flat decal;
|
||
4. **dilate 5 texels into the bake's UV padding**, or bilinear/mip at an island edge would pull the
|
||
old black through;
|
||
5. re-embed as WebP and rewrite the GLB. The image bufferView is verified last in the buffer, so
|
||
**geometry is untouched byte for byte**.
|
||
|
||
Because the masks live in 3-D, the soft edge is consistent across every island simultaneously — a
|
||
2-D blur cannot do this at all (it would bleed island-to-island through the black background).
|
||
|
||
**Result, measured:** painted texels' median luma **0.843** (p90 0.949) vs the bake's 0.133; whole
|
||
atlas 0.075 → 0.207. **GLB law re-checked after the rewrite: 894 tris · 1 mesh · 1 material · 1
|
||
image · 512² WebP · no Draco · 0.155 × 0.358 × 0.404 m — every number unchanged.**
|
||
|
||
**Verified in the game's own renderer, not just in Blender.** The *published* file was loaded off
|
||
`https://digalot.fyi/3god` through the vendored `GLTFLoader` + three.js (`MeshStandardMaterial`,
|
||
hemisphere + sun, sky background): **1 mesh, 894 tris, 512×512 texture decoded** — which also proves
|
||
the PIL-written WebP decodes in a browser. Sheet: `docs/shots/laneE/r38_magpie_tint.png` —
|
||
before/after × 5 angles (EEVEE, the GLB's own material incl. the 0.28 emissive) + a **64 / 48 / 32 px
|
||
distance strip** (a 0.36 m bird at 3–10 m on a 1080-high screen is 50–100 px) + the three.js frame.
|
||
|
||
**VERDICT: it reads at the size and speed the bird is seen.** Pied and unmistakable at 64 px and
|
||
48 px from flank and rear-quarter; still clearly black-and-white at 32 px. **Honest caveat:** from
|
||
directly below-and-in-front it stays dark — correct behaviour, because a real magpie's bib and belly
|
||
ARE black; the white is nape, shoulders and rump, i.e. what you see as it comes over and as it
|
||
leaves. Thumbnail re-rendered from the **rear quarter** of the same `bake_lowpoly` camera rig
|
||
(`render_views.py --thumb --rear`): the roster's standard front-quarter looks straight at a magpie's
|
||
black face and would have recorded none of the plumage.
|
||
|
||
**Standing after Lane B's `4514cb9`: the tinted GLB is an UPGRADE PATH, not a dependency.** B shipped
|
||
a 182-tri procedural bird with the marks as vertex colours, fetching nothing — 4.9× lighter, and the
|
||
right call. The GLB is on the depot and in the manifest; **nothing probes for it and nothing should**
|
||
(R26's blind-probe ruling). Cost if ever swapped in at `birdGeometry()` / `birdMaterial()`: +1 draw,
|
||
+894 tris at one instance.
|
||
|
||
#### 7c. THE SIZING LAW — `paling_fence` is a good asset and a rejected one
|
||
|
||
Lane B's measurement: **5 runs × 181 lots = 905 instances × 1 121 tris = 1.01 M triangles** against
|
||
~75 k spare (~47× over), and a GLB cannot ride the `boxMats` InstancedMesh that makes the other yard
|
||
props +0 draws. **REJECTED; it ships as a primitive.** `water_tank` (500 tris, one per yard, low
|
||
count, replacing a 24-tri pair) is **ACCEPTED**.
|
||
|
||
**The law for every later asset round: a mesh's triangle budget is `tris × the instance count the
|
||
placement rule implies`, not "≤5 k like a prop".** A fence panel repeated on every lot boundary is
|
||
in a different budget class from a one-per-town water tank, and the number to beat is set by the
|
||
placement *rule*, not by the asset. **Get the instance count from the consuming lane BEFORE
|
||
generating anything that tiles, edges, or repeats per-lot.** The published fence stays on the depot
|
||
and in the manifest — harmless, provenance-recorded, available if a low-count use ever appears.
|
||
|
||
#### 7d. New pipeline tools (all $0, on-device)
|
||
|
||
| tool | what it does |
|
||
|---|---|
|
||
| `tint_atlas.py` | the 3-D-region atlas repaint above; `--debug` renders the region masks as flat colours so the anatomy can be checked before any paint lands |
|
||
| `render_views.py` | Blender view sheet from the angles an asset is *seen* from (flank / rear-quarter / swoop / top / rear), `--eevee` for the GLB's own material under sky, `--thumb [--rear]` to re-render a roster-consistent 256 px thumbnail |
|
||
| `view_sheet.py` | composites those into one labelled sheet + the **distance strip** — the check no 256 px thumbnail can make |
|
||
|
||
**Licence unchanged: 🟢.** The tint is our own numeric edit of our own baked atlas; no asset, no
|
||
model and no third-party image entered the pipeline for it. **$0 — no cloud key touched.**
|
||
|
||
## Round 39 — the magpie A/B, and the arcade's kit (v9 39.4)
|
||
|
||
*Every number below was measured on this box today. `$0` — no cloud key touched, no third-party
|
||
asset or image entered the pipeline, and the 3GOD depot was **not** read as a pool (it stays the
|
||
read-only archive R38 declared it).*
|
||
|
||
### 0. STANDING INSTRUCTION — the sizing law, promoted out of the round notes
|
||
|
||
**Before generating anything that tiles, edges, repeats per lot, or is placed by a rule rather than
|
||
by hand: get the instance count from the CONSUMING LANE. The budget is `tris × that count`, not
|
||
"≤5 k like a prop".** R38 paid for this with `paling_fence` — a good 1,121-tri panel that became a
|
||
**rejected asset** at 5 runs × 181 lots = 905 instances = 1.01 M triangles. Two clauses were added
|
||
in R39 and both are now part of the law:
|
||
|
||
1. **Draws are counted per CLASS, and where the class lives decides everything.** A GLB cannot ride
|
||
`boxMats` (BoxGeometry, one shared untextured shell material), so every distinct GLB geometry is
|
||
+1 draw. As ONE town-wide `InstancedMesh` (`magpie.js`'s pattern) that is +1 **total**; as a
|
||
per-chunk furniture class it is +1 × up to **25 live chunks**, which against 8 draws of margin is
|
||
a breach, not a cost. Ask which one before generating.
|
||
2. **For a SKIN the budget is not bytes, it is the SLOT.** `skins.js:facadeAtlasUV` has **36 slots
|
||
(6×6)**; past 36 it `console.warn`s and **reuses one**, i.e. a 37th facade skin silently
|
||
repaints an existing shop. Measured: the synthetic uses **22 distinct facade skins** (21 at seed
|
||
123456) ⇒ **14 free**. That is the real budget line for a facade skin.
|
||
|
||
The law is now runnable rather than conversational: **`node pipeline/arcade_measure.mjs`** reads
|
||
Lane A's own `generatePlan` and prints the counts. It changed this round's deliverable — one of the
|
||
four commissioned assets stopped being an asset (§3, item 4).
|
||
|
||
### 1. THE MAGPIE A/B — rendering Lane B's own geometry, not a re-implementation of it
|
||
|
||
The R38 debt was *"two magpies exist and nobody has seen them side by side."* The trap in settling
|
||
it is that a hand-ported strawman proves nothing, so B's bird is not ported:
|
||
|
||
- **`pipeline/dump_bird.mjs`** imports `web/js/world/magpie.js` **unmodified** and calls the real
|
||
`createMagpie()`. The bare `three` / `three/addons/` specifiers its imports use are resolved to
|
||
the repo's **own vendored build** (`web/vendor/three.module.js`) through a `node:module`
|
||
`registerHooks` resolver — the importmap from `index.html:22-25`, as a node resolver. The vertices
|
||
dumped are the vertices the browser gets.
|
||
**Measured: 182 triangles, 216 verts, bbox 0.680 × 0.166 × 0.670 m.** The arithmetic agrees
|
||
independently: sphere(8,6) = 8×(2·6−2) = 80 · sphere(6,5) = 48 · cone(5) = 5 side + 5 cap = 10 ·
|
||
3 boxes = 36 · 4 planes = 8 ⇒ **182**. `magpie.js:9` and `:123` say **154** and are stale.
|
||
- **`pipeline/bird_to_glb.py`** wraps the dump as a GLB **in E's frame** (glTF +Y up, head at +Z;
|
||
three's frame is +Y up, nose at −Z, and Blender's `export_yup` writes `glTF = (Bx, Bz, −By)`, so
|
||
the Blender vertex is `(−xt, zt, yt)` — a 180° yaw, det +1, winding preserved). Without that the
|
||
two rows of the sheet would be shot from opposite ends of the bird. `--fold` applies the perch
|
||
pose (`place(..., folded)` scales the instance X by 0.42). `glb_stat.py` re-measures **182**.
|
||
- **`pipeline/render_views.py --noemit`** strips emission from **both** candidates, and this is the
|
||
correction that makes the picture usable at all. `normalize.py` gives every baked GLB
|
||
`emissiveFactor 0.28` **with an `emissiveTexture`** — 0.28 × its own albedo, so blacks stay black
|
||
and whites brighten. A vertex-coloured mesh **cannot express that in glTF** (COLOR_0 multiplies
|
||
base colour only), and Blender's exporter silently writes a **flat 0.28**, which lifted B's black
|
||
bird to grey and produced a first sheet that was worthless in E's favour. Off both sides. The
|
||
consequence, stated on the sheet: **a baked GLB reads slightly better in game than it does here** —
|
||
the safe direction to be wrong in.
|
||
- **`pipeline/view_sheet.py --ab`** lays the distance strip out as a **grid** (one line per
|
||
candidate, same view and same pixel size stacked) and repeats it under **linear motion blur** —
|
||
a true N-position average, not a gaussian, with the smear length scaled to the SUBJECT's on-screen
|
||
width. Two levels, both derived: **25 %** (the approach) and **100 %** (the pass — the swoop
|
||
covers 34 m in 1.25 s = 27.3 m/s = 0.45 m per 60 fps frame ≈ one body length).
|
||
|
||
**The measurement that decided it** (`dump_bird.mjs --sim`, magpie.js's own clock: DEFEND_R 34 m,
|
||
drawn out to 74.8 m, COOLDOWN 5.5 s, SWOOP_T 1.25 s, RETURN_T 1.9 s; a 60 s walk at WALK 4.6 m/s
|
||
passing 4 m off the perch):
|
||
|
||
> the bird is drawn in **1,949 of 3,600 frames (54.1 %)** — and of those it is
|
||
> **perched 1,569 (80.5 %) · swooping 150 (7.7 %) · returning 230 (11.8 %)**.
|
||
|
||
**And the mechanism, which is why no re-tint could have saved B's bird.** Three of its four white
|
||
marks cannot be seen from where the player stands:
|
||
|
||
| mark | `magpie.js` | why it does not read |
|
||
|---|---|---|
|
||
| wing bars ×2 | `:145-148`, bar plane at **y 0.048**, wing plane at **y 0.045** | both horizontal (`rotateX(-π/2)`), both DoubleSide, and the bar's footprint (x 0.174–0.316, z 0.005–0.055) lies **entirely inside** the wing's (x 0–0.34, z −0.085–0.065) ⇒ **from underneath the black wing occludes its own white bar**, and the player is always underneath (perch 4.32 m vs EYE 1.62 m) |
|
||
| nape | `:140`, box `0.052×0.014×0.055` at y 0.06, z −0.10 | at z −0.10 the head sphere's surface is **y 0.0728**; the box tops out at **0.067** ⇒ buried, and only its rear ~12 mm (z > −0.085) ever clears both head and body |
|
||
| nape + rump, from the flank | `:140-141` | horizontal slabs **14 mm and 20 mm** thick on a **166 mm** tall bird. At 64 px for a 0.67 m bird one pixel is ~10 mm ⇒ **1.3 px and 2 px of white** — and the flank is 80 % of the frames |
|
||
|
||
**VERDICT — Lane E recommends TAKING THE GLB**, with the cost stated: **+712 tris at one instance**,
|
||
draws a **wash** (both are one town-wide `InstancedMesh` at count 0 or 1), **+1 fetch / +1 material /
|
||
+1 512² texture**, and it is a **use-if-ready second path**, not a substitution — `?noassets` and
|
||
`?classic` still need the procedural bird. No blind probe is implied: `magpie` has been in
|
||
`manifest.json` and on the public depot since R38 §7a, so this is `furniture.js`'s existing shape.
|
||
Sheet: **`docs/shots/laneE/r39_magpie_ab.png`**. Fable rules.
|
||
|
||
### 2. THE ARCADE, MEASURED (`pipeline/arcade_measure.mjs`, 5 seeds, off Lane A's `generatePlan`)
|
||
|
||
| | measured |
|
||
|---|---|
|
||
| lane | ONE edge `kind:'arcade'`, **42.00 m × 5 m**, 2 blocks |
|
||
| shops | **16–17** (17 at 20261990 / 20260101; 16 at 7 / 123456 / 999) |
|
||
| frontage | 3.00–4.94 m, mean **3.87–4.05 m**; depth 5.10–7.99 m; 4–11 two-storey |
|
||
| walkable | lot faces at ±2.50 m ⇒ **5.00 m** |
|
||
| roof | `AWNING_DEPTH 2.2` from each face ⇒ slabs reach ±0.30 m ⇒ **roofed except a 0.60 m centre slot**, ceiling **y 2.95 m** |
|
||
| floor | `ground.js`'s arcade branch: ONE quad **42.00 × 12.00 m = 504 m²**, wearing `footSkin` |
|
||
| ⚠ posts | `buildings.js:643` at `d/2 + 2.2 − 0.2` ⇒ **±0.50 m from the centreline: two post rows 1.00 m apart down a 5.00 m lane.** No collider, so it walks — it just reads as a colonnade in its own doorway. → Lane B |
|
||
|
||
**Synthetic-only as a code rule:** `plan_osm.js:305` is one unconditional `addDistrict('mainstreet')`
|
||
⇒ no cache town has an arcade DISTRICT, while the corpus has **1,102 arcade-KIND edges = 39 km of
|
||
ordinary suburban footpath**. Key on `district.kind`, never `edge.kind`.
|
||
|
||
### 3. THE KIT — ranked by value per draw, because the margin is eight
|
||
|
||
| # | asset | form | instances | cost | licence |
|
||
|---|---|---|---|---|---|
|
||
| 1 | `facade-shuttered` | facade skin **768×595** | 1–2 shops | **+0 draws, +0 tris**, 1 of 14 free atlas slots | 🟢 |
|
||
| 2 | `ground-arcade-floor` | ground skin **512²** | 1 class | **+1 draw town-wide**, **+0 tris** (the 504 m² quad *moves* class) | 🟢 |
|
||
| 3 | `ground-arcade-roof` | ground skin **512²** | 1 quad | **+1 draw in the 1–2 arcade chunks**, **+2 tris** | 🟢 |
|
||
| 4 | hanging blade signage | **no asset — a refusal with a better answer** | 17 | **+0 draws, +0 assets, +4 tris/shop = 68** | — |
|
||
|
||
**Item 4 is the most useful line in the table.** `buildings.js` already draws each shop's sign into
|
||
a per-chunk 512×168 canvas atlas cell and pushes ONE quad into the merged `signQuads` mesh. Push a
|
||
**second, perpendicular quad with the same `signUV.get(shop.id)` rect** and the arcade gets 17
|
||
projecting signs **carrying each shop's real name**, riding a mesh that already exists: **+0 draws,
|
||
+2 tris single-sided / +4 back-to-back = 68 triangles for all 17.** The bracket is two `boxMats`
|
||
boxes, also +0 draws. A GLB blade sign at 17 instances cannot beat that; it can only look different.
|
||
|
||
**Aspect is not decoration.** `skins.js` paints every facade into a **square** 341 px atlas slot, and
|
||
arcade lots are 3.87 × 3.0 m ⇒ **1.29:1**, against the main-street pool's 1.78:1 — a 38 % horizontal
|
||
stretch if shot to the pool. `facade-shuttered` is therefore shot at 1.29:1 and lands nearly
|
||
undistorted (1024×768 → square → 1.29:1 quad ≈ 1.03 net). Recorded; not retro-fitted to the other 48.
|
||
|
||
**`use` values that name no slot — this time on purpose.** R38 recorded `gravel`/`reddust` as
|
||
selected by nothing *by accident*. `arcade-floor` and `arcade-ceiling` are selected by nothing *with
|
||
the price printed*: the arcade cannot ride `footpath` (one town-wide merged mesh — re-using it lays
|
||
terrazzo over every footpath in town), so each needs its own class. Measured for the ceiling:
|
||
**arcade lots span exactly 2 chunks on all five seeds** (`0,0` and `0,1`, `CHUNK` 64 m).
|
||
|
||
**⚠ THE CLASSIC ARGUMENT IS INVERTED HERE relative to R38, and reusing R38's would move a golden.**
|
||
R38's ground palette is classic-safe **by absence** — `character.js:83-87` returns frozen v1 literals
|
||
for a null town key and classic has none, so that path contains no `if (classic)` at all. **The
|
||
arcade district exists ONLY on the synthetic, and the synthetic IS the classic boot**, so every piece
|
||
of this kit lands on the covenanted town and needs an **explicit** flag test: `ground.js:41-47`'s
|
||
`VERGE` shape, where `?classic=1` forces it off.
|
||
|
||
### 4. `pipeline/seamless_tile.py` — and the guard it needed
|
||
|
||
A tiled floor is the one skin class where the repeat seam is a *mistake* rather than a texture:
|
||
grass hides a joint, terrazzo does not, and the arcade floor is 504 m². The tool finds the pattern's
|
||
pitch by autocorrelating the mean-absolute column/row gradient (a grout line is a gradient spike) and
|
||
crops to a whole number of periods — arithmetic instead of feathering, which would smear the grout.
|
||
|
||
**It needed a guard, because the naive version made things worse and said nothing.** Measured:
|
||
|
||
| skin | pitch found (corr) | seam before → after | kept |
|
||
|---|---|---|---|
|
||
| `ground-arcade-roof` | x 212 px (r 0.25) | **x 26.7 → 8.3** | ✅ x cropped to 848 |
|
||
| `ground-arcade-floor` | x 196 (r 0.26) / y 108 (r 0.30) | **x 27.9 → 31.9**, y 29.6 → 33.4 | ❌ rejected, passed through |
|
||
|
||
So the crop is now a **proposal that has to beat the number it is trying to improve, per axis**, and
|
||
the tool prints `NO IMPROVEMENT AVAILABLE, passed through` when it cannot. **The finding underneath
|
||
it:** a diffusion-drawn tiled floor is **not periodic enough to cut on** — the model draws tiles with
|
||
perspective and irregular spacing, so the autocorrelation peaks weakly (r ≈ 0.26) and at the wrong
|
||
lag. **Fix path for a later round, stated not done:** synthesise the terrazzo procedurally in numpy
|
||
(the `gen_audio.py` doctrine — original by construction, exactly periodic, 🟢), instead of
|
||
photographing it with a diffusion model. The shipped floor is the flux one, consistent with the ten
|
||
photographic grounds already in the pool, seam measured and recorded rather than hidden.
|
||
|
||
### 5. LICENCE — every R39 asset lands 🟢
|
||
|
||
| class | count | zone | method |
|
||
|---|---|---|---|
|
||
| skins | 3 | **🟢 original** | `flux_local` / FLUX.2-klein-4B (Apache-2.0, ungated), on-device |
|
||
| meshes | see §6 | **🟢 CC0-equivalent** (`AUDIT.md:101` doctrine) | `flux_local` concept → `bg_remove_local` (RMBG-2.0) → `trellis2_mlx` (TRELLIS.2-4B) → `normalize.py` → `bake_lowpoly.py` |
|
||
| tooling | 4 files | — | `dump_bird.mjs`, `bird_to_glb.py`, `arcade_measure.mjs`, `seamless_tile.py` — all ours |
|
||
|
||
**$0 spent.** No cloud key touched. The hardyards set stays REJECTED on provenance (R38), and the
|
||
3GOD depot stays a read-only archive.
|
||
|
||
### 6. THE THREE MESHES — measured, published, byte-verified
|
||
|
||
`flux_local` → `bg_remove_local` (RMBG-2.0) → `trellis2_mlx` (1024_cascade, metal baker, 500 k bake
|
||
cap) → `normalize.py` → `bake_lowpoly.py`. **3/3 first attempt, 0 retries, 0 OOM-kills.**
|
||
|
||
| id | tris | footprint (m) | height (m) | mtl/img/tex | instances the rule implies | sha1 (local == depot) |
|
||
|---|---|---|---|---|---|---|
|
||
| `aframe_board` | **366** | 0.68 × 0.64 | 0.890 | 1 / 1 / 512² | **≤16/town** (arcade ~4 + v9's unmarked cue ≤12) ⇒ ≤5,856 tris | `2aa9732d23c7…` |
|
||
| `blade_sign` | **299** | 0.66 × 0.10 | 0.549 | 1 / 1 / 512² | **17** (one per arcade shop) ⇒ 5,083 tris | `210e270c22ec…` |
|
||
| `keycutter_sign` | **498** | 0.29 × 0.06 | 0.599 | 1 / 1 / 512² | **1** | `6f22995d6402…` |
|
||
|
||
House GLB law verified on all three: metres · +Y up · base origin · **1 material / 1 image / 512²
|
||
WebP** · no Draco. Decimation floors, again confirming R38's finding that collapse cannot reach
|
||
these budgets and the bake must: 443,014 → 104,997 (normalize) → **366** (bake) · 490,612 → 94,202 →
|
||
**299** · 493,916 → 28,940 → **498**.
|
||
|
||
**⚠ `blade_sign` ships with a defect, flagged not hidden.** Its FRONT face is the blank white the
|
||
house law requires, but its BACK face carries **TRELLIS-hallucinated artwork** (a plant, a globe,
|
||
scattered shapes) invented where the single concept photograph had no data. A blade sign in a 5 m
|
||
arcade is read from both sides, so this matters — and it is one more reason the **+0-draw
|
||
perpendicular-quad answer (§3 item 4) is the recommendation and this GLB is the fallback.** Fix
|
||
path if it is ever wanted, stated not done: the R38 `tint_atlas.py` machinery already rasterizes
|
||
UV→position maps, so flat-whiting every texel with `z < 0` is the same pass that painted the magpie.
|
||
|
||
**⚠ AND THE OPERATOR'S TAIL IS NOT AN OUTLIER — it looks like memory pressure.** Wall clock:
|
||
**516.3 s · 538.7 s · 176.8 s.** R38 reported a single 489 s job against a 128 s median and called
|
||
it an unexplained tail. Here the **first two** jobs — the two that followed a `flux_local` skin
|
||
batch and a `flux_local` concept batch in the same session — took ~3× the third, which ran after
|
||
everything else had exited. Same operator, same params, same box, no error, monotonically improving.
|
||
**Hypothesis, not proof: the tail is resident MPS memory from the image model, not the mesh model.**
|
||
Worth one controlled A/B in a later round; until then, plan a mesh batch as a *cold* run.
|
||
|
||
### 7. THE TRANSMISSION PRE-PASS — three GLBs shipped as glass (R39, from Lane C's measurement)
|
||
|
||
Lane C measured that zeroing `bookshelf`'s transmission halves interior draws. The cause is not
|
||
subtle once named: **three.js runs a transmission PRE-PASS.**
|
||
|
||
> `three.module.js:16209` — `if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, … )`
|
||
> `three.module.js:16433` — `renderTransmissionPass` renders the **whole opaque list** into a
|
||
> `WebGLRenderTarget`. **Every opaque draw in that scene is issued twice.**
|
||
> `three.module.js:6890` — the trigger is `material.transmission > 0`, nothing else.
|
||
> `GLTFLoader.js:1202-1204` — `materialParams.transmission = extension.transmissionFactor`.
|
||
> `three.module.js:494` (`transmission_fragment`) — `totalDiffuse = mix( totalDiffuse, transmitted.rgb,
|
||
> material.transmission )`, so at **1.0 the diffuse is replaced entirely by the refracted backdrop**:
|
||
> the asset is not "slightly glassy", it *is* glass.
|
||
|
||
**Lane C's numbers:** `opshop`/hall **191 → 104 (−46%)** · `opshop`/wide 142 → 80 · `book`/hall
|
||
**101 → 51 (−50%)** · `dept`/hall 116 → 116 (control — no bookshelf, no change).
|
||
|
||
**I scanned all 103 GLBs** in `pipeline/_normalized`, `web/assets/models`, `web/assets` and
|
||
`pipeline/packs` for `KHR_materials_transmission` / `KHR_materials_volume`. **Three carried it —
|
||
not one:**
|
||
|
||
| file | material | state before R39 |
|
||
|---|---|---|
|
||
| `procity_fit_bookshelf_01.glb` | `mtl_10218_Bookshelves_v1` | **SHIPPED and hurting** — the one C measured |
|
||
| `procity_street_longbench_01.glb` | `lambert3SG` (+ ior 1) | **NOT WIRED** — the 5.86 m arcade/market bench the v9 charter names, and LANE_E_NOTES R38 lists as a "true one-liner" for Lane B |
|
||
| `procity_street_streetlight_01.glb` | `Streetlight_HighResSG3` (+ ior 1) | **NOT WIRED** — one per ~18 m of street, also on R38's recommend list |
|
||
|
||
**The two unwired ones are the worse finding.** They are STREET assets, both recommended to Lane B
|
||
by this lane's own notes, and the street budget is **292/300 with eight draws of margin**. A
|
||
transmission pre-pass on the street does not cost eight draws — it costs **another ~292**. They were
|
||
armed, not fired. No other GLB in the repo carries either extension.
|
||
|
||
**The fix: `pipeline/strip_transmission.py`.** Dependency-free GLB surgery — rewrite the JSON chunk,
|
||
copy the BIN chunk through untouched, and **print the sha1 of the BIN chunk before and after** so
|
||
"geometry unchanged" is a checked claim. All three: `BIN IDENTICAL`. Tris, dims, material and image
|
||
counts and max texture size are unchanged (5000 / 1172 / 4999). `KHR_materials_ior` is deliberately
|
||
**left alone** — ior does not trigger the pre-pass, and removing more than the defect widens the
|
||
diff for no measured gain.
|
||
|
||
**Appearance:** before/after through the same rig, `docs/shots/laneE/r39_transmission_fix.png`. In
|
||
Blender/EEVEE the difference is small; in three.js it is the whole diffuse term (see the shader line
|
||
above). Either way the direction is not a trade: **a bookshelf, a park bench and a streetlight are
|
||
not glass.** This was an exporter default that rode in with the source meshes and has been doubling
|
||
interior draws for however many epochs it has shipped.
|
||
|
||
**Published and verified**, under John's R22 standing authorization, through the passwordless tailnet
|
||
ingress (`GOD3_DEPOT=http://100.94.195.115:8788`; no secret read, no cookie). Then fetched back off
|
||
the **public** host and compared: **6/6 HTTP 200, byte counts exact, sha1 identical**, and the depot
|
||
copies re-scanned — **`clean, nothing to do` on all six.**
|
||
|
||
| file | HTTP | bytes (local == depot) | sha1 |
|
||
|---|---|---|---|
|
||
| `procity_fit_bookshelf_01.glb` | 200 | 356 636 | `1769af492fbf9338…` |
|
||
| `procity_street_longbench_01.glb` | 200 | 56 600 | `2813e81c6bb1feb5…` |
|
||
| `procity_street_streetlight_01.glb` | 200 | 156 328 | `9c5ec45d4d861db5…` |
|
||
| `procity_street_aframe_board_01.glb` | 200 | 88 572 | `2aa9732d23c72c2e…` |
|
||
| `procity_street_blade_sign_01.glb` | 200 | 64 196 | `210e270c22ec4dd6…` |
|
||
| `procity_street_keycutter_sign_01.glb` | 200 | 79 708 | `6f22995d6402a509…` |
|
||
|
||
`_published.json` **56 → 59**; drift check green. `build_manifest.py` re-run (furniture 13 → 16,
|
||
GLBs 36 → 39); **`validate_manifest.py`: 0 errors, 0 warnings.**
|
||
|
||
**AND IT IS NOW UNGATEABLE-PAST, which matters more than the fix.**
|
||
- `glb_stat.py` grew a **`transmission` column** and prints a loud block when any file has it.
|
||
- `validate_manifest.py` **HARD FAILS** on `transmissionFactor > 0` for every manifest GLB, with the
|
||
three.js line numbers in the message and the fix command. Nobody looked for three epochs because a
|
||
bookshelf is obviously not glass; a gate is the only thing that makes "nobody looked" impossible.
|
||
|
||
**Licence unchanged 🟢, $0.** No geometry, no texture and no third-party asset was added or altered
|
||
— one extension object was deleted from three material blocks.
|