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>
81 KiB
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_01was native-metres), up-axis varies, tri counts span 12 → 960k, textures reach 4096px, and several assets carry branded/trademarked designs (quarantined). pipeline/normalize.pyfixes 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.mdor 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 themsignboard:"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.pypushes 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 sheetdocs/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_normalizedset).
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 viapipeline/publish.pyonceGOD3_PWis 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.glbre-export (fixed). The R3 normalize left a sourceless texture (packed AO/roughness map didn't survive the WebP export; the material'smetallicRoughnessTexture+occlusionTexturepointed at it), which crashed three.js GLTFLoader withundefined 'uri'.pipeline/fix_glb_textures.pystrips material slots that reference a sourceless texture (base-colour + normal kept; the orphan is never loaded). Verified it loads in the vendoredGLTFLoader(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_localconcept (3 rolls for the ~2:1 aspect) →trellis_macmesh →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 mapscounter→counter_till); the balcao 4 mcounterstays 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):
GODVERSE_DSN— the dealgod Postgres DSN (creds). Postgres is up onlocalhost:5432; the builder reads the DSN from env (I don't hunt for creds).--from-db --count 300runs once it's set.- Lane C's pack schema (their task 1, not yet posted) — I built to the proposed fields above;
INDEX_FIELDSadjusts 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):
- 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. - 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
loudnormI=-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, likegen/skins); WAV scratchpipeline/.genaudio/is git-ignored. Contract inmanifest.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:
- The input is provably perfect. The
bg_remove_localcutout 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. - 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. - 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, andnormalize.py(join → decimate → re-export) drops them. The result renders pure white.baker: vertexis 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:
- 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 %;
- 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;
- 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; - dilate 5 texels into the bake's UV padding, or bilinear/mip at an island edge would pull the old black through;
- 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:
- 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-wideInstancedMesh(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. - For a SKIN the budget is not bytes, it is the SLOT.
skins.js:facadeAtlasUVhas 36 slots (6×6); past 36 itconsole.warns 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.mjsimportsweb/js/world/magpie.jsunmodified and calls the realcreateMagpie(). The barethree/three/addons/specifiers its imports use are resolved to the repo's own vendored build (web/vendor/three.module.js) through anode:moduleregisterHooksresolver — the importmap fromindex.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:9and:123say 154 and are stale.pipeline/bird_to_glb.pywraps 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'sexport_yupwritesglTF = (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.--foldapplies the perch pose (place(..., folded)scales the instance X by 0.42).glb_stat.pyre-measures 182.pipeline/render_views.py --noemitstrips emission from both candidates, and this is the correction that makes the picture usable at all.normalize.pygives every baked GLBemissiveFactor 0.28with anemissiveTexture— 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 --ablays 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—renderTransmissionPassrenders the whole opaque list into aWebGLRenderTarget. Every opaque draw in that scene is issued twice.three.module.js:6890— the trigger ismaterial.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.pygrew atransmissioncolumn and prints a loud block when any file has it.validate_manifest.pyHARD FAILS ontransmissionFactor > 0for 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.