§40.1 — the R39 blind spot, closed with both arms fired. Before: check_transmission()
parsed pipeline/_normalized/<file> only and returned SILENTLY when absent — so a
depot-only transmissive GLB (the longbench/streetlight shape) or a stale glass copy in
web/assets/models/ (what ?localdepot=1 actually serves) shipped unseen. After: all four
surfaces scanned — _normalized/ always, web/assets/models/ always, the DEPOT copy via
ranged GET of the GLB JSON chunk whenever no local file exists (206-aware, slices the
tailnet's 200-full), plus a sweep of every non-manifest .glb in both dirs (ped rigs and
dance clips load by literal path). A depot fetch failure is a LOUD warn, never a skip.
Controls demonstrated, not asserted:
arm 1 (served path): glass box_crate planted in web/assets/models/ only →
R39 gate rc 0 (the blind spot, live) · R40 gate rc 1 · fixture removed → rc 0
arm 2 (depot path): both local copies held aside, local mock depot serving glass →
rc 1 (depot copy) · clean bytes → rc 0 · REAL tailnet depot, local absent →
rc 0 / 0 warnings (production fetch path proven)
Nothing was ever published to the real depot namespace; sha1 6c327d86 restored both
copies; final validator on the real tree rc 0, 0 errors, 0 warnings, 39 manifest GLBs
+ 40 swept.
§40.4 — the arcade is a PLACEMENT rule, not an asset. Measured: one 42.00 × 5m arcade
edge, posts at d/2+2.2-0.2 = ±0.50m from the centreline — two rows 1.00m apart, 34
colliderless posts, 0.60m open slot. The kit designed as one thing: the roof spans
(depth 2.5 = half-lane, slabs meet flush) and a spanned roof has no posts. Proved
without touching B's tree — rule applied in-flight by a rewriting no-store server,
shot from inside the lane: draws 124→124 (+0), tris unchanged. Rule handed to B with
file:line; B applied it this round.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
33 KiB
LANE E — ASSETS · progress (PROCITY-E)
Owner: PROCITY-E. Scope: pipeline/*, web/assets/*. Nothing in other lanes' files was touched.
⚠ THIS FILE STOPS AT ROUND 12 AND HAS SINCE R13. IT IS NOT THE LIVE RECORD. Everything from Round 13 on lives in
docs/LANES/LANE_E_NOTES.md(what other lanes need from this one, newest first) andpipeline/AUDIT.md(provenance, licence zone, measurements and method, per round). Recorded in R39 rather than quietly resurrected: a progress file whose last entry is 27 rounds old is a trap for the next reader, and the honest fix is a signpost, not a backfill. (One exception below: the R40 tasking explicitly asked for its control-arm transcripts in THIS file, so §40 lives here AND is cross-referenced from the notes.)
Round 40 (§40.1+40.4) · 2026-08-04 — (entry here by explicit R40 tasking; cross-lane story in docs/LANES/LANE_E_NOTES.md §40)
§40.1 — transmission gate blind spot CLOSED, both control arms demonstrated
pipeline/validate_manifest.py check_transmission() now covers every path the game can load a
GLB from: pipeline/_normalized/ (as before), web/assets/models/ (the ?localdepot=1 served
mirror — never scanned before), the depot copy for any manifest GLB with no local file in either
dir (ranged GET of the GLB JSON chunk; 206-aware, slices a 200 — the tailnet depot returns 200-full),
and a sweep of every non-manifest .glb in both dirs (ped rigs, dance clips — rigs.js loads
them by literal path). A depot fetch failure is a loud WARN (depot copy UNSCANNED), never a skip.
Fixture: procity_fit_box_crate_01.glb with transmissionFactor: 1 injected into material[0]
(the exact defect strip_transmission.py removes; injector kept in the session scratchpad, BIN
untouched). Clean sha1 6c327d8624bc90203275a341128abb546f5d69e0 (staging = served, verified after
cleanup). Depot arm used a LOCAL MOCK depot (python3 -m http.server serving a/<file>) — no
fixture ever touched the real depot namespace.
RED/GREEN arm 1 — the web/assets/models/ path (transmissive copy planted at the served path
ONLY; staging copy clean — the R39 gate is structurally blind here):
$ python3 $SCRATCH/make_glass.py pipeline/_normalized/procity_fit_box_crate_01.glb \
web/assets/models/procity_fit_box_crate_01.glb
$ GOD3_DEPOT=http://100.94.195.115:8788 python3 pipeline/.validate_r39_ab.py # R39 copy of the gate
OK — 0 errors, 0 warning(s) # old rc=0 ← the blind spot, live
$ GOD3_DEPOT=http://100.94.195.115:8788 python3 pipeline/validate_manifest.py # R40 gate, same tree
ERR procity_fit_box_crate_01.glb (assets/models copy): material[0] 'Material.001' has
transmissionFactor 1 — three.js will run a transmission PRE-PASS ... # new rc=1 ← RED
$ cp $SCRATCH/hold/procity_fit_box_crate_01.glb web/assets/models/ # remove fixture
$ GOD3_DEPOT=http://100.94.195.115:8788 python3 pipeline/validate_manifest.py
OK — 0 errors, 0 warning(s) # rc=0 ← GREEN
RED/GREEN arm 2 — the depot path (BOTH local copies moved aside = the R39 live-demo shape: manifest GLB, no local file anywhere; mock depot on 127.0.0.1:8991 serves the transmissive copy):
$ mv pipeline/_normalized/procity_fit_box_crate_01.glb $SCRATCH/hold/norm_...
$ mv web/assets/models/procity_fit_box_crate_01.glb $SCRATCH/hold/models_...
$ GOD3_DEPOT=http://127.0.0.1:8991 python3 pipeline/validate_manifest.py
ERR procity_fit_box_crate_01.glb (depot copy): material[0] 'Material.001' has
transmissionFactor 1 — ... # rc=1 ← RED
# mock now serves the CLEAN bytes (local still absent):
$ GOD3_DEPOT=http://127.0.0.1:8991 python3 pipeline/validate_manifest.py
OK — 0 errors, 38 warning(s) # rc=0 ← GREEN (mock)
# and the REAL tailnet depot, local still absent — the production fetch path, scanning the R39-fixed copy:
$ GOD3_DEPOT=http://100.94.195.115:8788 python3 pipeline/validate_manifest.py
OK — 0 errors, 0 warning(s) # rc=0 ← GREEN (real depot)
Cleanup + final: both local copies restored (sha1 6c327d86… both), mock killed, temp R39
script deleted, fixtures gone. Full validator on the real tree:
manifest v1 — facades 49, skins 92 groups, GLBs 39 (+40 non-manifest GLBs transmission-swept), audio 34 · OK — 0 errors, 0 warning(s) · rc=0.
§40.4 — the arcade's posts: re-measured, and the verdict is a PLACEMENT RULE, not an asset
Re-measured on today's tree (node pipeline/arcade_measure.mjs + buildings.js read at the
cited lines — all R39 numbers stand): one edge kind:'arcade' 42.00 m × 5 m, lot faces ±2.50 m
⇒ 5.00 m lane; AWNING_DEPTH 2.2 (buildings.js:21) at AWNING_Y 2.95 (:22) ⇒ slabs reach
±0.30 m ⇒ 0.60 m open slot at the centreline; posts (:641-646) at d/2 + 2.2 − 0.2 = 2.00 m
from each lot face = ±0.50 m from the centreline — two rows 1.00 m apart, 0.12 × 0.12 × 2.95 m,
34 posts at seed 20261990 (2 × 17 shop lots), colliderless (:649).
The kit (posts + roof designed together): the roof SPANS (depth 2.5 = half-lane, slabs meet flush
at the centreline), and a spanned roof has NO posts. That is a change in web/js/world/buildings.js
- one line of district plumbing in
web/js/world/chunks.js— Lane B's files, so E edited nothing and handed the exact rule (with the mandatory explicit?classic=1gate — the synthetic IS the classic boot) to B inLANE_E_NOTES.md§40 with file:line.
Proof shots without touching B's tree: the rule was applied in-flight by a rewriting
no-store dev server (session scratchpad patch_server.py, exact-match rewrite that fails loudly if
the source moved) and shot headless (Playwright, DBG.teleport(21.5, 66.67, −π/2) — inside the lane
at seed 20261990, looking down it):
docs/shots/laneE/r40_arcade_posts_before.png (post rows + open slot) ·
r40_arcade_posts_after.png (clear lane, continuous roof) · r40_arcade_posts_pair.png.
HUD draws 124 → 124 (+0), tris 44k → 44k, chunks 25 → 25. Nothing generated, published, or
re-published this round; disk copies of buildings.js/chunks.js byte-identical throughout.
Round 12 (v3.0-alpha gig layer, ?gigs=1) · 2026-07-15
Three asset streams for one-pub-end-to-end, all free/on-device. C works the pub interior in parallel
(I touched none of web/js/interiors/*).
- Instruments (5 GLBs) —
flux_localconcept →trellis_mac→normalize.py, viagen_props.py --batch gig_instruments.json. All 5 kept (electric_guitar, bass_guitar, drum_kit, guitar_amp, mic_stand) — every one reconstructed cleanly (thin mic-boom + cymbal hardware survived, unlike the round-3 wire rack). Heavy tris (30–96k, TRELLIS shell-soup — amp 96k the outlier), accepted for focal/LOD-able stage props; manifest fittings + thumbnails + staged to local-depot. D's rigs hold them; primitive fallback if absent. - Posters (4 skins) —
gen_skins.py --posters: poster-xerox/screenprint/grunge/retro, portrait flyers with a blank name-band (no baked text);skins.poster[].nameZone[x,y,w,h] for B/C to overprint the seeded band name — same discipline as blank-signboard facades. Eyeballed, zero rejects. - Gig audio (3) — extended
gen_audio.pyprocedurally:music-pubrock-live(original 90s pub-rock riff),ambience-crowd-walla(crowd murmur),sfx-applause(cheer sting). Allgig:truein the manifest for F. Pack now 26 assets, 9.1 MB of 25. Verified (spectrogram + seams/peaks). - Manifest:
skins.poster+ audio gig assets + 5 instrument fittings;build_manifest.pyreads_gig_results.json;validate_manifest.pycovers audio. Provenance inAUDIT.md(§ Round 12). - Open — depot publish gated: the 5 instrument GLBs are staged locally (usable via
?localdepot=1) but the tailnet depot push was blocked by the harness classifier (outward-facing) → needs John's OK. Until thenvalidate --depot/ qa Gate 3 flag provenance-drift on the 5. F owns the v3.0-alpha tag; I did not commit yet pending the publish call.
Round 11 (AUDIO round — E leads) · 2026-07-15
Stood up the audio pack, free + on-device. Tool: 100% procedural synthesis (numpy → ffmpeg) — picked over neural gen because it's $0, deterministic/seeded, perfectly loopable, and parody-safe by construction (documented in README § audio, per Fable's ask).
pipeline/gen_audio.py— a small numpy DSP engine (oscillators, ADSR, filters, reverb, seamless-loop crossfade, a note sequencer + drum synth). 23 assets, seeded,--dry-run/--only.web/assets/audio/— 6 ambience beds (street day/night, rain, 3 interior roomtones), 13 sfx (doorbell, door, till, record-riffle, toast, tram bell + rumble, 6 footsteps), 4 instrumental 90s music loops (record-shop, milkbar, video-synth, arcade). OGG/Opus + M4A/AAC. 8.1 MB of 25 MB.- Manifest
audio:{ambience,sfx,music}— files + m4a fallback + loop + gain; interior roomtone/ music carrytypes[]for seed-pick per shop (like facades); footstep is{surface:[variants]}.build_manifest.pybuilds it,validate_manifest.pychecks it. qa green, audio 23, 0 errors. - Verified technically (I synthesise, I can't subjectively listen): spectrogram sheet
docs/shots/laneE/audio_spectrograms.png+ numeric — peaks ≤ -0.4 dBFS (no clip), all loops seamless, durations correct. Subjective ear-check is John's/F's (flagged honestly). - Provenance in
AUDIT.md(§ Round 11). House audio law respected on the E side: silent-happy (manifest lists only shipped files), lazy-load metadata, seeded flavour. B wires WebAudio, C gives theroom.audiocontract, F integrates + owns the v2.1 tag/push — I did not push.
Round 3 and earlier below.
Reviewed against docs/LANES/LANE_E_ASSETS.md + docs/LANES/ROUND3_INSTRUCTIONS.md.
Round 9 (Fable) — VRAM audit + depot hygiene (ship-round DD)
- E1 — VRAM/texture audit: DONE + committed. Measured every texture source (decoded RGBA+mips). Resident VRAM: ~240 MB default boot (skins-in-view ~85 + ped fleet ~90 + GLB ~25 + Lane-B sign atlas ~40), ~420 MB worst case (all flags + all 3 shop types). Cheap win taken: half-res'd book+toy atlases (quick pull-cards; record kept full for the deep dig; covers verified legible) → stock VRAM 323→181 MB, no index/UV change, atlases re-published. Verdict: fine to ship v2.0 — no compression/lazy-load needed for the tag. Flagged for v3: stockpack cache-cap (C) + KTX2/Basis.
- E2 — depot/manifest hygiene: confirmed green.
validate --depot0 err · pack-QA 3/3 · provenance -drift clean (_published.json== 27 live) · 15 stock atlases live, 0 orphans. - On-call: flagged box-primitive-first tram rec to B; audio still parked on John.
Round 8 (Fable) — pack kitchen (book/toy) + pack-QA gate
- E1 — pack pipeline generalized + book/toy sample packs.
build_stock_pack.pynow kind- parametrized (record 256×256, book 96×256 tall-thin spines, toy 256×256), per-kind parody word banks, same laws. Book/toy sample packs built + staged (?localdepot) + indexes committed topipeline/packs/so Lane C wires book/toy shelves now. Parody verified deterministic + no distinctive-token leakage across all 3 kinds. - E2 — pack-QA gate: DONE.
validate_pack.py(wired intovalidate_manifest.py→ qa gate 3): a bad bake (out-of-range/inverted UV, empty title, bad price_band, dangling atlas, dup id) fails qa — proven (inverted-UV+empty-title → exit 1). qa--strictGREEN 5/5. - Tags pushed (John-delegated): origin/main = HEAD (
1a20501) + v1.0/v1.1/v2.0-alpha/v2.0-beta all live on origin — verified. - REAL packs LIVE (John unblocked DB access). All 3 packs rebuilt from GODVERSE (m1ultra
Postgres clones, read-only SELECT, peer auth): record 350 (
discogs_full.inventory— John's real store stock + real Discogs covers), book 311 + toy 273 (dealgod.productsby category, real product photos + prices). Metadata parody-transformed (no real trademarks); covers are real art (decision #2); book prices seeded (info-only source). 15 atlases published to the depot, indexes committed topipeline/packs/+ staged, all pass the pack-QA gate.build_stock_pack.pygained--from-tsv(extract on the DB box → build here) + portrait book cell + seeded-price fallback. Access path: deployment DB was Docker-internal/SSH-denied → used the m1ultra clones per John. No fal spend. - On-call (B rain-streak/puddle, D door-swing tell): none requested.
Round 7 (Fable) — the two feeders (ped merge + stock pack)
- E1 — ped sub-mesh merge: DONE + committed (
447188a, reflane-e/round7-peds). All 19 rigs merged to ONE draw each (1 prim + 1 atlased 1024 material) viapipeline/merge_ped.py(Blender join → UV-pack → EMIT-bake all materials into one atlas). Skinning + 65-joint skeleton preserved,walk/idleuntouched. Fleet 92→19 draws; near-cap 116→24 (79% cut). Verified in the vendored GLTFLoader (all load as SkinnedMesh + bind walk; man_suit poses byte-identical to original; 3-mat man_athletic atlas correct). Handed to Lane D (D1) for full validation + full-density numbers → gates F's roster flip. qa--strictGREEN 5/5. - E2 — GODVERSE stock pack: pipeline BUILT + PROVEN, real pack blocked on 2 external deps.
pipeline/build_stock_pack.py= recordgod → curate → parody-transform (deterministic, zero real-trademark leakage: "Dark Side of the Moon"/"Pink Floyd" → "Quiet Milk Bar"/"The Galahs") → atlas WebPs + JSON index. Proven end-to-end with--sample 24; sample pack staged toweb/assets/models/so Lane C wires?stock=realagainst the real index format now. Real 200-400 pack awaits (1)GODVERSE_DSN(dealgod Postgres creds — flagged to Fable/John) and (2) C's schema confirmation (proposed in LANE_E_NOTES). One command (--from-db --count 300) once they land. - E3 — on-call (B rain-streak/puddle texture): none requested. No fal spend (balance ~$21.36).
Round 6 (Fable) — non-destructive pipeline + on-call
- E1 — publish.py non-destructive + provenance-drift gate: DONE + committed.
publish.pynow merges into_published.json(read existing → union → sorted write), never replaces — fixes the R5--onlyclobber (record dropped 27→1). Proven: a real no-op re-publish ofrecord_cratekept the record at 27 (was 27), not 1.- Post-publish + a new
--verifymode drift-check the record against the live depot (/api/listfiltered toprocity_*) and warn on either direction.--verifyconfirms 27 == 27. validate_manifest.pygains a provenance-drift gate: every manifestprocity_*GLB must be in_published.json, else QA fails. Proven: simulating the R5 clobber (record→1) makes the validator exit 1 with per-file drift errors; restored → green. So the clobber class fails loudly.- Record verified:
_published.json(27) is byte-exact vs the live depot — Fable's R5 regeneration was correct, no fix needed. qa.sh --strictGREEN (5 gates incl. F1's new v2-flags harness).
- On-call for B/C (task 3): available for bus_shelter/bin scale-origin tweaks (B) and glass_case/magazine_rack in-room issues (C). Broken-prop fal re-gen pre-authorized ≤2 attempts (decision #5) — none needed yet. No speculative generation.
Round 5 (Fable) — hero-prop bake + fal fallbacks + depot growth
- E1 — hero-prop bake-to-lowpoly: DONE + committed. New
pipeline/bake_lowpoly.py(Blender headless): voxel-remesh → decimate → Cycles base-colour bake onto fresh UVs (R3's lesson: simplify-only floors on shell-soup; the re-bake is the point). All 6 heavy TRELLIS props ≤8k tris (drinks_fridge 77k→8k, milkshake_mixer 73k→8k, listening_booth 54k→8k, counter_till 32k→8k, novelty_record 26k→8k, arcade_cabinet 25k→8k). Fleet 288k→48k tris, 18M→5.2M. Each eyeballed (docs/shots/laneE/props_baked.png— fridge needed a coarser voxel re-bake, rest clean)- verified loading in the vendored GLTFLoader. Re-published;
validate --depot0 err. Originals gitignored inpipeline/.props_orig/. → Lane C re-measures the record interior (was ~273k).
- verified loading in the vendored GLTFLoader. Re-published;
- E3 — fal.ai fallbacks: DONE. glass_case + bus_shelter + magazine_rack via fal Hunyuan3D v2
(
pipeline/gen_props_fal.py, key from MODELBEAST settings). Frosted/opaque concepts (flux_local) gave the reconstructor a surface — all 3 succeeded where TRELLIS failed. Baked to ≤8k (EMIT albedo bake — Hunyuan materials need base-colour→emission or DIFFUSE bakes near-black), verified loading, published,validate --depot0 err. Total fal spend ~$0.64 (glass 1 att, bus 2 att, mag 1 att × ~$0.16), well under the <$1 estimate; guardrail held. Costs logged in AUDIT R5. Last two primitive-only fittings (glass_case, magazine_rack) retired; new streetbus_shelter. Manifest now 17 fittings + 9 furniture (26 GLBs). Contact sheetdocs/shots/laneE/props_fal.png. - E2 — depot growth: DONE (light, consumer-driven). The v2 features are procedural (dig uses
generated sleeves, B's window shader + D's roster need no GLBs), so per Fable "no unconsumed
entries" I added two clear-consumer props:
street/bin(council-bin → Lane B) +fit/crate_stack(milk-crate-stack → Lane C floor piles/dig). Both 3k tris, re-normalized + published, verified loading,validate --depot0 err. Also resolved Lane B's B3 ask — novelty_record 26.5k→8k via the E1 bake. Manifest now 18 fittings + 10 furniture (28 GLBs). Further growth on request.
Round 4 (Fable) — ped decimation (gate-3 critical path) + asset fixes
- E1 — ped decimation: DONE + committed (
518678d, reflane-e/round4-peds). The 19 ped rigs inweb/models/peds/were 924k tris total (31k–56k each) — the gate-3 blocker. Decimated to ≤3k each (1564–2805; fleet 924k→46.5k, ~2.45k avg) viagltf-transform weld → simplify --ratio 0.05 --error 0.12(meshoptimizer — works on these manifold skinned meshes, unlike R3 TRELLIS shell-soup). Skinning + skeleton fully preserved (65 joints intact,walk/idleuntouched); contact sheetdocs/shots/laneE/peds_decimated.pngclean at street distance. Originals gitignored inpipeline/.peds_orig/; per-ped inpipeline/_peds_decim.json. → Lane D validates (D2) then Lane F re-measures gate 3 (~24-cap × ≤3k ≈ ≤67k fleet contribution). - E2a —
record_crateFIXED, verified + published. Lane C'sundefined 'uri'crash was a sourceless texture (packed AO/roughness map failed WebP encode; material's metallicRoughness+ occlusion slots pointed at it). New toolpipeline/fix_glb_textures.pystrips those slots (three.js loads textures lazily → no crash). Verified it loads in the real vendoredGLTFLoader(meshes=1, tris=5000). Re-published to the depot (the fixed version is live, tex refs all-sourced). - E2b — counter-with-till (~2 m): LIVE. New
counter_tillfitting (procity_fit_counter_till_01.glb, 1.61 × 0.64 m, 1.15 m tall, 32k tris) — timber counter + cabinet doors + period till. Generated on-device (flux_localconcept →trellis_mac, 3 concept rolls to land the ~2:1 aspect), normalized to house law, verified loads inGLTFLoader, and published. Non-destructive (keeps the balcao long-counter ascounter); manifestfittingAliases.counter→counter_till; Lane C re-maps kindcounter→counter_tillinglb.js. - Publish: DONE (John approved). Both re-exports live; manifest
validate --depotGREEN (0 err, 23 GLBs live).publish.pygained an--only <name>filter + tailnet-passwordless detection (GOD3_DEPOT=http://100.94.195.115:8788, decision #4). - Not doing (per Fable decisions): fal.ai spend (glass-case/bus-shelter parked); hero-prop bake-to-lowpoly.
Round 3 (Fable) — hero props on MODELBEAST + local-depot mode
- Hero props, free & on-device (
pipeline/gen_props.py):flux_localconcept image →trellis_macimage→GLB (1024_cascade) →normalize.py→ thumbs → manifest. 5 generated + kept (listening_booth, drinks_fridge, arcade_cabinet, milkshake_mixer, novelty_record — contact sheetdocs/shots/laneE/props_gen_local.png), cash_register reused from the depot (vintage-cash-register.glb). 3 didn't make it: magazine_rack → in-engine primitive (112k-tri wire, the thin-wire gotcha); glass_case + bus_shelter → fal.ai fallback list (TRELLIS can't do transparent glass; sf3d gives a featureless blob) — gated on John, ≤$2.70, both have primitive fallbacks. Every kept thumbnail eyeballed; no legible branding (see AUDIT provenance). - Local-depot mode (
core/loaders.js?localdepot=1+pipeline/stage_local_depot.py): resolvesdepot:<file>toweb/assets/models/served by the dev server, so Lanes B/C/F validate GLB loading with no network / before publish. Verified live in-browser (loads adepot:GLB, CDN path intact). - Manifest: 14 fittings + 8 furniture (5 hero + cash_register added);
validate_manifest.pygreen (0 err). Provenance recorded inAUDIT.md(props are 100% synthetic/local — cleanest in the set). - Known tradeoff: TRELLIS meshes are dense shell-soup — decimate floors 25–77k tris (heavier than the ≤5k library law). Accepted for focal/optional/LOD-able props; texture-bake-to-lowpoly is a follow-up.
- Published (self-serve, John's go 2026-07-14): the 5 prop GLBs + thumbs pushed to the depot via
the passwordless tailnet path (decision #4); all 22 GLBs re-verified live.
qa.sh --strictGREEN (4/4 gates; manifest validator 0 err / 0 warn with the props live). Provenance was recorded first. - Not committed: this round is held for Fable review (per John) — all Lane E round-3 changes are in the working tree, uncommitted. GLB binaries stay git-ignored/staged; the depot has them.
Status at a glance
| Step | Deliverable | State |
|---|---|---|
| 1 Audit | pipeline/AUDIT.md (+ glb_stat.py, _audit_stats.json) |
✅ done |
| 2 Normalize | pipeline/normalize.py → 16 GLBs + 16 thumbs, verified |
✅ done (local); ⛔ publish gated on GOD3_PW |
| 3 Manifest | web/assets/manifest.json + validate_manifest.py (+ build_manifest.py) |
✅ done, validates green |
| 4 Facade skins | gen_skins.py --local backend + 23-skin gap pack |
✅ DONE — 23 skins generated on-device, free, harvested + mapped |
| 5 Hero props | round-3 gen_props.py (MODELBEAST, free) → 5 local + cash-register reuse |
✅ DONE local (see Round 3 above; 2→fal, 1→primitive) |
| 6 Local-depot | loaders.js ?localdepot=1 + stage_local_depot.py |
✅ DONE, verified in-browser |
| — | pipeline/README.md + contact sheets in docs/shots/laneE/ |
✅ done |
Step 4 no longer costs anything. The 23 facades were generated locally on the M3 Ultra
via MODELBEAST flux_local (FLUX.2-klein-4B on MPS) — free, ~5.7s each, weights already cached.
No OpenRouter key, no ~9¢ spend. See "Step 4 (done, local)" below.
Two things still need John (both optional — the game runs asset-free): (1) the depot password
GOD3_PW to publish the 16 GLBs live; (2) a go/no-go on the paid hero-prop step (5 ≈ $2.70).
What landed
Step 1 — Audit
pipeline/glb_stat.py: dependency-free GLB parser (tris, bbox, texture px, Draco) — runs on ultra's py3.9. Full library stats →pipeline/_audit_stats.json.pipeline/AUDIT.md: the library is a raw dump — almost nothing obeys the GLB law (onlyrecord_crate_plastic_01was native-metres; scale/orientation arbitrary; tris 12→960k; textures to 4096; several branded/trademarked assets quarantined). Candidate→PROCITY-id table with status, coverage-vs-registry table, QUARANTINE list, depot cross-reference.- Toolchain reality documented (Blender present on ultra but off PATH; no coreutils/timeout).
Step 2 — Normalize & publish
pipeline/normalize.py: headless Blender, extendsMESHGOD/finish_glb.py(solidify/decimate/ bright/base-origin) + adds height-normalize, outlier cleanup, texture→WebP≤1024, per-asset up/yaw, 256px thumbnail. Driven bypipeline/batch.json.- 16 assets normalized to house law (metres, +Y up, base origin, ≤5k tris, WebP ≤1024, no
Draco). Every one eyeballed — Y-up confirmed by re-importing exports through
render_glb.py. Footprints from Blender-space_normalize_results.json. Thumbs inweb/assets/thumbs/.- fittings (9): record_crate, wire_shelf, clothes_rack, bookshelf, cube_shelf_wide, counter, coffee_table, work_table, box_crate
- furniture (7): bench, bench_modern, bench_wood, longbench, park_bench, streetlight, food_cart
pipeline/publish.py: mirrors the meshgod→3GOD contract exactly (cookiehmac(GOD3_PW,"3god-ok"),POST /api/upload+/api/thumb). Reads the secret from env — never contains/logs it.--dry-runverified all 16 ready. Not published yet:GOD3_PWisn't set here. Run:export GOD3_PW=… && python3 pipeline/publish.py(idempotent, reversible).
Step 3 — Manifest
web/assets/manifest.json(v1): the contract Lanes B/C/D read. All 69 skins catalogued (25 facade / 10 sky / 10 ground / 8 wallpaper / 6 floor / 7 surface / 3 awning) + 16 fittings/ furniture with footprints, heights, thumbs.conventionsblock documents the GLB law, blank-signboard rule, corner-lot policy, fallbacks, and depot load path.- Every registry shop type has ≥2 facades (verified in the validator). Facades tagged
signboard: blank|baked— 5 djsim/market/grimy skins carry baked sign text and must not get a name overlay; the other 20 are clean overlay targets. pipeline/validate_manifest.py: Lane F gate — parses JSON, checks every skin exists, every GLB is local-or-on-depot, thumbs exist, footprints/heights sane, ≥2 facades/type. Green (16 local-only warnings until publish;--depotenforces live).pipeline/build_manifest.py: reproducible generator (curated type-maps + derived skin/fitting lists) so the manifest regenerates when assets change.
Step 4 — Facade skin expansion (DONE, local)
pipeline/gen_skins.py: ported fromthriftgod/gen_assets.py— same STYLE bible + blank- signboard template so new skins match. Resumable.genraw/,--dry-run/--only/--harvest.--localbackend (the one I used): MODELBEASTflux_local→ FLUX.2-klein-4B on the M3 Ultra's MPS GPU. Apache/ungated, weights already cached (15 GB), free, ~5.7 s/img, one GPU job at a time. Direct subprocess to~/Documents/MODELBEAST/server/operators/flux_local/run.py(--outdir/--paramsJSON) — no server, no cloud, no key. Cloud OpenRouter path kept as fallback.- All 23 generated → harvested → mapped. 0 failures, 2:11 total. Eyeballed every one
(contact sheet
docs/shots/laneE/facades_gen_local.png): every shopfront has a genuinely blank signboard (Lane B name-overlay target), style indistinguishable from the 25 anchors. The pack: toy (2), book (2), video (2), milkbar (3), wide dept anchors (2), stall/market (2), warehouse (2), arcade portal (1), residential house fronts (4), corner-lot-sidewalls (3). - Coverage after mapping (
build_manifest.pyFACADE_TYPES, mirrors registry.js + additive): record 3 · opshop 3 · toy 5 · book 5 · video 4 · pawn 7 · milkbar 5 · dept 4 · stall 4 — every registry type ≥3.stallwent 2→4, retiring the Lane A single-facade gap. - Corner-lot
-sideskins now exist (brick/render/weatherboard): taggedface:"side"with emptytypesin the manifest so no shop-front pool ever picks a windowless wall; Lane B maps them to the exposed side face (conventions.corner_lots+ newconventions.facade_face). pipeline/SKIN_PROMPTS.md: identical pack kept for the free manual flow (fallback if MODELBEAST is ever down).
Step 5 — Bespoke hero props
pipeline/meshgod_batch.json+MESHGOD_BATCH.md: 9 solids (glass case, fridge, till, listening booth, arcade cabinet, magazine rack, milkshake mixer, novelty record, bus shelter) in the thriftgod_hero pattern. Documents the MeshGod gotcha (thin wires/flat discs → primitives), reuse-first (cash-register/glass-case likely already on depot), and the primitive fallbacks. Not run — gated on John (~$2.70). Nothing blocks: all have in-engine fallbacks.
Adversarial review & fixes (self-run, 4-dimension × verify)
Ran a 19-agent adversarial review over all deliverables (correctness / contract-fit / doc-accuracy / house-law), each finding independently verified. 15 confirmed (1 blocker, 5 major, 9 minor). All fixed and re-verified:
- BLOCKER — off-origin exports.
normalize.pyjoined meshes but never flattened parent node hierarchies, so assets under a parent empty shipped off-origin:park_benchwas ~1 km off-origin, floating 30 m;food_cart15 m off;clothes_racksank 0.19 m. My earlier spot-check missed it (the clean assets have no hierarchy). Fixed:parent_clear(KEEP_TRANSFORM)+ drop non-mesh nodes before join. Re-verified node-aware: all 16 nowcenter=[0, h/2, 0], minY=0.0. - Major — tri budget counted faces, not triangles (latent for quad FBX/OBJ). Fixed: triangulate
in
clean_meshso the ≤5k gate + reported counts are true triangles. - Major — fitting-id namespace. Added
fittingAliases(registry semantic id → physical id) to the manifest. (Lane C'sglb.jsalready maps its kinds to my physical ids, so it worked — this makes it explicit + future-proof.) - Major — facade map vs registry.js. Aligned my facade→type map to be a consistent superset of
Lane A's
registry.js; flagged that registry'sstallpool has only 1 facade (Lane A gap). - Major — facing -Z not enforced. Corrected the docs: symmetric props are orientation-agnostic;
no auto-facing (per-asset
yawavailable). Removed the false "enforced" claim. - Major — licence provenance. Strengthened the publish-set caveat (record per-asset provenance before pushing to the shared CDN) — a reason publish stays gated on John.
- Minors: hardened
glb_stat.py(node-aware world bounds + WebP size sniff — the exact blind spots that hid the blocker);validate_manifest.pyliveness now uses a Range GET not HEAD; Akai-MPC trademark flag; corrected audit counts (59 parsed.glb, rejects 8, food_cart tris).
Acceptance check (LANE_E)
- ✅
AUDIT.mdcomplete. - ✅ ≥15 normalized GLBs (16) with thumbnails — ⛔ live on 3GOD pending publish (needs cred).
- ✅
manifest.jsonvalidates green; fittings/furniture carry footprints for B/C to place untouched. - ✅ Every registry shop type has ≥2 facades — now ≥3 (48 facades total). New skins
generated (not just planned) and eyeball-matched to the anchor style
(
facades_style_anchor.png= 25 originals;facades_gen_local.png= 23 new local skins;fittings_normalized.png= the 16 GLBs — all indocs/shots/laneE/). - ✅ One-page
pipeline/README.md(re-run each step, costs, where creds live).
Notes for the other lanes
- Lane B / C: read
web/assets/manifest.json.fittings/furniture→depot:<file>viacore/loaders.js(placeholder box on miss — the game runs asset-free). Footprints are[w,d]metres on the ground; heights in metres; all base-origin, +Y up, facing −Z. - Lane B: overlay shop names only on facades with
signboard:"blank". Widedept-*anchors (when generated) want a wider UV plane.awning/ground/skyskins are yours;wall/interiorare Lane C's. - Lane F: wire
python3 pipeline/validate_manifest.pyinto the gate (add--depotpost-publish).
Open questions for Fable/John
- Publish now? I have 16 verified GLBs staged in
pipeline/_normalized/ready to push to the shared depot. It's an outward-facing write to shared infra + needsGOD3_PW, so I left it for your go rather than assume it. Spend the ~9¢ on facades?Resolved — done for free on-device (MODELBEAST flux_local). Only the ~$2.70 hero-prop MeshGod batch (Step 5) still wants a go/no-go; all props have in-engine primitive fallbacks, so it's a polish upgrade, not a blocker. (Note: image→GLB is also local on this box viasf3d/trellis_mac, but those weights are HF-gated — a setup step before that path is free too.)- A few library assets were near-misses (garage_shelves, radiogram, market_stall — see AUDIT rejects). Worth a manual Blender re-origin pass later, or leave to primitives? I left them out.