Ledger #3 (E's G item), #6 (provenance prep), + the C/E contract review.
- MODELBEAST notes (#3): pipeline/README.md's toolchain row claimed sf3d/trellis_mac were 'HF-gated
(setup needed)' -- ultra was right, that was stale (weights cached; trellis_mac 15/15 on thriftgod
Tier-1; the :8777 queue also runs bg_remove_local/mflux_image_edit/seedvr2_upscale). Corrected.
Ultra's finish-farm post-mortem folded into the props section VERBATIM + credited, with Fable's
Ruling 1 amendment (NO-DRACO: drop --compress draco, rest of the command stands). Noted that our
bake_lowpoly.py / decimate_props.py are NOT the finish farm, so nobody conflates them.
- Provenance v4-FINAL (#6): the frozen table carries the pack (22 towns, all 8 states/territories,
1192 shops + roads[], ODbL, raw snapshots) + a row for Lane G's newtown_godverse attributed to G.
The licence spine is complete -- nothing shipped is unaccounted.
- BUG: index.json still listed 22 -- generated before the dd4489c merge, so newtown_godverse was
invisible to B's selector while R22 verifies at 23-town scale. Regenerated -> 23 towns (selfcheck
ALL GREEN 161300/161300). Index is now self-describing: state reads from the cache first, then my
config, so any lane's town rosters itself.
- G2a atlas contract review (E's half; C answers interior ceilings in LANE_C_PUB): Fable's priors all
hold. Atlas-per-shop is an UPGRADE if the loader disposes on room exit -- today stockpack.js never
disposes (R9: 181MB resident, accreting). Without dispose, per-shop strands ~16MB/shop (~190MB per
town visited, unbounded across 23 towns). With dispose: ~4MB resident at 1024², ~45x better than
today. Also: size the atlas to the shop (<=1024², not 2048² for a 6-item shop); reuse my index
schema verbatim so stockpack.js/fail-soft-to-parody/?noassets/validate_pack all work unchanged;
the R8 parody law (parodied metadata, real cover art) is unchanged.
-> Lane G: add "state":"NSW" to newtown_godverse.json and the index picks it up automatically.
-> Lane C: the R9 dispose-on-exit cache-cap is now load-bearing for G2a (my VRAM numbers above).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
48 KiB
LANE E — cross-lane notes
Round 22 — v4.0 THE EPOCH CLOSE: notes refresh · provenance v4-final · the G2a review ⟨v4.0⟩
1. MODELBEAST notes refreshed (ledger #3). pipeline/README.md's toolchain row said
sf3d/trellis_mac were "local but HF-gated (setup needed)" — ultra was right, that was stale
(weights cached; trellis_mac went 15/15 on thriftgod's Tier-1 props; the :8777 queue also runs
bg_remove_local/mflux_image_edit/seedvr2_upscale). Corrected, and ultra's ⚠️ finish-farm
post-mortem is folded into the props section verbatim + credited — with Fable's Ruling 1 amendment
(NO-DRACO: drop --compress draco, the rest of ultra's command stands; the depot holds ONE law and
our loader stays decoder-free). Our own bake_lowpoly.py / decimate_props.py are noted as not the
finish farm, so nobody conflates them.
2. Provenance table → v4-FINAL (ledger #6). The frozen table now carries the pack (22 real AU
towns · all 8 states/territories · 1192 shops + roads[], ODbL, raw snapshots committed) and — for a
complete licence spine — a row for Lane G's newtown_godverse, explicitly attributed to G
(GODVERSE census + OSM roads, their godverse_town.py, their fields). Nothing shipped is unaccounted.
3. 🐛 The index was missing Lane G's town. index.json still listed 22 — it was generated
before the dd4489c merge, so newtown_godverse was invisible to B's selector, and R22 verifies at
23-town scale. Regenerated → 23 towns, selfcheck ALL GREEN 161300/161300. I also made the
index self-describing: state now reads from the cache first, then my config — so any lane's town
rosters itself. → Lane G: add "state": "NSW" to newtown_godverse.json and the index picks it up
(it's the one town showing a blank state).
→ LANE G: the G2a atlas contract — E's half (C answers the interior ceilings in LANE_C_PUB)
Verdict: Fable's three priors hold. Atlas-per-shop isn't just viable — done right it FIXES a VRAM problem we already have. Two conditions and one reuse:
a. Atlas per shop — YES, with one hard condition: dispose on exit. Today stockpack.js caches
type-pack atlases and never disposes (my R9 audit: record+book+toy = 181 MB resident, accreting
as you visit types). Per-shop makes that much worse or much better, on one line:
- Without dispose: every shop entered strands its atlas — a 12-shop town ⇒ ~12 atlases ⇒ at 2048² (~16 MB each) ~190 MB per town visited, unbounded across 23 towns. Non-starter.
- With dispose-on-exit (the R9 cache-cap I flagged to C back then — now load-bearing): only the shop you're standing in is resident ⇒ ~4 MB at 1024². That's ~45× better than today's 181 MB. G2a is the reason to finally land that cap. Ask: the loader disposes the previous shop's atlas on room exit (C's interiors seam).
b. Size the atlas to the shop, not to the format. A real crate is ~20–60 items; at our 256² cell, 1024² holds 16 and 2048² holds 64. Ship ≤1024² for a typical shop — don't hand a 6-item shop a 2048². Spill to a second atlas (or fall back to the shared type-pack) for the rare 60+ shop. Book cell stays 176×256 (portrait cover-face — a real constraint: I have cover art, no spines).
c. Reuse my index schema verbatim — then nothing downstream changes.
{version, atlas_px, cell, atlases[], items:[{id,title,artist,price,price_band,atlas,uv:[u0,v0,u1,v1]}]},
keyed per shop (e.g. stock_shop_<godverseShopId>_index.json). You get for free: stockpack.js consumes
it unchanged · fail-soft to parody is already law (pack absent ⇒ parody canvas, proven since R7) ·
?noassets already fetches zero · and validate_pack.py gates your packs (UV range, price bands,
dangling atlas, dup ids) — already wired into qa gate 3 through validate_manifest. Add index fields and
tell me; I'll extend REQUIRED.
d. The parody law is unchanged and non-negotiable (R8, John's): metadata deterministically parody-transformed (sha256-seeded word-banks, no real trademarks), cover art is the real image, prices real-or-seeded. Real dealgod inventory changes nothing here.
e. Interior draw/tex ceilings are C's call — my only input to C is the number in (a): per-shop @1024² with dispose is ~4 MB resident, i.e. cheaper than the status quo, not more expensive.
Round 21 — the v4.0 town pack (pull-forward, DATA-ONLY) ⟨v4.0⟩
Ledger #5 — NON-GATING: nothing here touches a golden until A absorbs in R22, and F tags
v4.0-betawith or without it. The pack: 22 towns · 1192 shops · 298 heroes · all 8 states/ territories. selfcheck ALL GREEN 154141/154141, synthetic0x3fa36874unmoved, all 22 byte-identical on re-run. → Lane A (R22): 34 goldens await your pin (17 new towns × base+gig).
Curation. Picked on the instincts the first five earned: secondhand density first (the heroes are the game — a town with no op shops isn't a PROCITY town), a walkable centre inside a 2.0–3.4 km bbox (the mega-strip law), and a national spread. 18 candidates proposed, 17 survived.
| tier | towns (heroes/shops) |
|---|---|
| meccas | fitzroy 40/160 · adelaide 29/116 · brunswick 24/96 · katoomba 20/80 · hobart 20/80 · fremantle 20/80 |
| strong | newtown 18/72 · geelong 17/68 · northbridge 15/60 · glebe 13/52 · westend 11/44 |
| solid | newcastle 9/36 · bendigo 9/36 · launceston 8/32 · daylesford 8/32 · bowral 8/32 · marrickville 7/28 · castlemaine 6/24 |
| thin tail | braddon 5/20 · ballarat 5/20 · toowoomba 3/12 · darwin 3/12 |
The cap is a curation filter, not just a density knob (the round's nicest finding). Because texture is pegged at 3× heroes, a town without a secondhand strip cannot inflate into a retail strip. Wollongong had ~62 real shops but only 1 hero → capped to 4 → dropped by the ≥6 floor. It curated itself out. John's "subtle" directive is doing quality control for free: a town has to earn its place with real op shops. The thin tail (toowoomba/darwin, 3 heroes) is the same signal, weaker — A/Fable may want to drop them in R22; I've left them in as honest data rather than pad or hide.
Newcastle: my centre was wrong, not the town. The CBD bbox held 2 heroes; Newcastle's real secondhand strip is Hamilton / Beaumont St. Re-centred → 2 → 9 heroes, 8 → 36 shops.
🐛 The bug I shipped in R18 and caught here — stale roads on a re-centre
Re-centring Newcastle refetched the shops (my query-comparison caught the bbox change) but
silently reused the old CBD roads — fetch_roads_raw had no query-comparison, only
fetch_raw did. Result: Hamilton shops seated against a road graph 2.5 km away → A's lift
marched the town out with ZERO shops, which surfaced as 32 bogus "open-late invariant" failures
(a symptom — the real cause was shops=0). Ten other towns lack a video shop and pass fine, which is
what proved it wasn't the open-late rule. Fixed: fetch_roads_raw now compares its query like the
shops path — the bbox lives in the query, so a re-centre must invalidate both snapshots. Newcastle
rebuilt: 34 shops seated, openLate=1. Lesson: a cache-validity check applied to one of two symmetric
paths is a trap with a fuse on it.
Discipline unchanged: bounded bboxes, raw snapshots committed (shops + roads per town), C's §6
mapping, SUBTLE guardrails (heroes never subsampled, texture 3×, 2392 drops counted), deterministic
emit, ODbL (SOURCES.md + per-cache fields). Overpass: batch 1 died on a fatal 504 after 25 retries
(pack scale genuinely rate-limits) — courtesy sleep raised to 4 s, retry/backoff carried the rest; I
split into two polite batches rather than hammer a public API.
→ Lane A (R22 absorb): 17 new towns to pin (34 goldens). Sizes range 12 → 160 shops (fitzroy is
2× katoomba — your capacity/overflow fences get a real load test). → Lane B: index.json now has
22 towns; your selector reads it already (thanks — R20 #5 closed). Also: thanks for the selfcheck
index.json skip, A — my R20 seam flag landed clean.
Round 20 — v4.0-beta THE FIVE TOWNS: the density widening ⟨v4-beta⟩
Ledger #2, E's half — built to C's LANE_C_PUB §6 table under John's SUBTLE directive ("texture, not takeover"). Widened fetch → re-emitted 5 caches →
towns/index.json. → Lane A: caches re-emitted, densities are yours to absorb + re-pin (see the two flags below). → Lane B:index.jsonis live — drop theREAL_TOWNShardcode inhud.js:11.
The widening. build_towns.py now fetches C's tasteful staple list (shop=* + amenity=cafe,
per C's †) and maps every class through C's copy-paste table onto existing registry types — no new
archetypes. Guardrails honoured: no vehicle/tyres/fuel, funeral, storage, industrial, or pure
services (hairdresser/beauty/bank/laundry/estate_agent). Raw is a new snapshot (the fetcher
auto-refetches when the class list changes — the query is recorded in each snapshot, so nobody has to
remember --refetch), committed like the first. Deterministic: same raw → byte-identical cache.
SUBTLE, enforced by construction. The secondhand heroes are never subsampled (they're the
game). Texture is capped at 3× the hero count (the top of C's ~2–3× band — keeps the town alive
without letting retail take over); over-cap towns are seed-subsampled deterministically
(sha256(townKey:osmId) rank — stable across rebuilds so A's goldens hold) and every drop counted
in counts.textureDropped:
| town | heroes | texture | ratio | dropped | total | shop mix |
|---|---|---|---|---|---|---|
| katoomba_real | 20 | 60 | 3.0× | 4 | 80 | milkbar 33 · dept 22 · opshop 13 · stall 5 · book 4 · record 2 · video 1 |
| newtown_real | 18 | 54 | 3.0× | 156 | 72 | milkbar 30 · dept 21 · opshop 6 · record 5 · book 4 · stall 3 · toy 2 · video 1 |
| fremantle_real | 20 | 60 | 3.0× | 81 | 80 | milkbar 43 · dept 16 · opshop 9 · book 5 · toy 2 · video 2 · pawn 1 · record 1 · stall 1 |
| bendigo_real | 9 | 27 | 3.0× | 81 | 36 | milkbar 14 · dept 12 · opshop 3 · book 3 · stall 1 · record 1 · video 1 · pawn 1 |
| castlemaine_real | 6 | 18 | 3.0× | 43 | 24 | milkbar 9 · dept 6 · opshop 4 · stall 3 · book 2 |
Density is now 3–4× pre-widening (6–21 → 24–80 shops) — inside the ledger's 3–6× expectation for A. Katoomba reads right: the op shops (Cancer Help, Tanglecare, Cat Defence, Red Shield) still anchor the strip, now with The Hatters Cafe, Blue Mountains Chocolate Co, K Hub and Bunnings as texture around them.
web/assets/towns/index.json (new, E-owned): {schema:"procity-towns-index/1", generated, license, attribution, towns:[{key, town, state, shops, roads}]} — 5 towns, built from every cache on disk.
⚠ Two flags for Lane A (both land in your absorb pass)
- 10 expected golden re-pins — the widening moves every real town's base + gig golden (5 × 2).
That's the designed E→A handoff (ledger #2). Synthetic
0x3fa36874is UNMOVED; fixtures frozen. index.jsontrips selfcheck's town glob (a real seam —selfcheck.js:467takes every top-leveltowns/*.jsonas a cache, so the index validates as a town and fails). One-line fix in your file:.filter(f => f.endsWith('.json') && f !== 'index.json'). I didn't touchselfcheck.js— it's yours and you're mid-round in it. Fable spec'd the index at this path (ledger #2/#5, B reads it there), so the path stays; the glob is what needs to learn about it.
→ Lane C (one-line map addition)
video_games wasn't in your §6 table (you listed video/games → video). It was already shipping as
video pre-widening, so I kept video_games → video rather than regress those shops to opshop
via the fallback. Same family, same target — flagging so your table and my emit stay the one truth.
Round 18 — v4.0-alpha REAL ROADS: the road-geometry fetch ⟨v4-alpha⟩
Ledger #3 — E fetches the real street geometry; A's lift builds the CityPlan graph from it.
katoomba_realis now schema v2 with 484 real streets (the alpha town). → Lane A: roads are live; harden the lift, resolve the surfaced poster-clearance finding below, re-pin the golden.
The data. build_towns.py extended with a bounded way["highway"~…](bbox); out geom; per town →
raw cached in _raw/<key>.roads.overpass.json (fetch once, John's standing Overpass auth) → roads[]
in A's v2 contract ({id, kind, pts:[[lat,lon],…]}; kind = raw OSM highway class, A's ROAD_KIND
maps it, full geometry shipped for A's Douglas–Peucker). katoomba_real: 484 streets (residential
243, tertiary 65, trunk 58, secondary 51, primary 32, unclassified 23, pedestrian 12 — real names:
Bathurst Rd, Cliff Drive, Civic Place, …), 5344 points.
Fidelity cut (charter risk #4 — flagged to A, who owns the knob). I FETCH every ROAD_KIND-mapped
tier (→ raw cache, full provenance / beta-ready) but EMIT only the town street tier — drivable
roads + pedestrian malls — EXCLUDING service/track/footway/path/steps/cycleway. In
Katoomba's 3 km bbox that excluded tier is 1461 of 2506 ways (Blue-Mountains bushwalking tracks,
sidewalks, stairs, driveways, parking aisles) — noise for a town street graph and a ~2500-edge blow-up.
The full set is in the raw cache if A wants a tier back (one filter change). This is the honest cut.
A's lift builds a real town from it (verified, generatePlanFor seed 20261990): 484 streets →
872 nodes / 799 edges (main 232, side 531, arcade 36) → 12 blocks, 20 lots, 20 shops, 3 venues
(pub/band_room/rsl), 12 gigs. pickVenues lands venues on the real graph — ledger #2 requirement met.
⚠ FINDING for Lane A (the alpha's failure-list deliverable — charter risk #3). On the real graph,
spine posters overhang a crossing carriageway: selfcheck's kerb-clearance gate fails on 4 seeds
(spine poster clears its cross-edge by −1.6 to −2.5 m; e.g. seed 20261990 poster 50 vs edge 58 =
−2.42 m). Same CLASS as A's R15/R16 corner-poster fix, now on real cross-street geometry — a
venue's frontage seats on the spine where a real side street crosses, so its poster reaches over that
carriageway. Owner: A (pickVenues / POSTER_CLEAR on the real graph — the "A again" harden
step). My roads data is valid — the v2 cache validates (roads accepted); the 4 failures are
plan-level poster placement, not cache errors. Not a fidelity artifact either: the crossing streets are
real primary town streets, so trimming roads wouldn't fix it. Golden settles after A's fix (pre-fix
value 0x6014a8e6). Handshake E→A: katoomba roads live + this finding.
Other 4 towns (data-only; A hardens against them, only katoomba gates): all now v2 —
newtown_real 1086 roads, bendigo_real 1552, fremantle_real 622, castlemaine_real 397 (dense/regional
towns → bigger graphs; real data, A's DP + density classification handle it). Roads are ODbL, raw
committed, SOURCES.md updated. (Robustness: build_towns.py gained Overpass retry/backoff — the
public API 504s under load.)
The finding is SYSTEMATIC — the spine-poster clearance fails identically on all 5 real-road
towns, every seed (23 checks, −0.9 to −4.7 m). That it reproduces across five different real street
grids confirms it's plan-level poster placement, not town data → squarely A's (pickVenues /
POSTER_CLEAR). Scoping holds: only the real/* v2 path fails — synthetic + fixtures + classic +
default boots are all green (roads[] is the only changed path, per the alpha law). A's one fix
generalizes to all five.
Round 17 — v3.2 THE SCOUT: real town caches + the verify fix ⟨v3.2 amendment⟩
The real-map scout for v4 THE REAL MAP (John's pick) — bounded data groundwork, no new game systems. 5 real AU town caches produced + the
--verifyfalse-drift fixed. → Lane A: caches are live underweb/assets/towns/; pin the per-town goldens (printed below by selfcheck) and feed them throughplan_osm— E→A handshake.
Ledger #4 — publish.py --verify false-drift fixed (the warm-up). The drift check filtered the
depot list to procity_*, so genuinely-published non-procity_ assets (sit.glb,
vintage-cash-register.glb) were false-flagged "not live." Now it compares the published-NAME record
(_published.json) against the depot's full file list (fetch_depot_files()), confirming any name is
live — and only flags OUR procity_ namespace as unrecorded, ignoring other projects' files on the
shared depot. --verify now: ✓ all 39 recorded assets live.
Ledger #6 — the real-map scout (the round's biggest item). pipeline/build_towns.py ports
thriftgod build_city()'s Overpass route to per-town caches in A's procity-town-cache/1 contract
(validateTownCache()). Pipeline: bounded Overpass query → raw response cached in-repo → dedupe
(OSM node+way) → suburb-fill → parody the trademarked charity/franchise chains (indie shops keep
real OSM names) → OSM shop=* → registry SHOP_TYPE → web/assets/towns/<key>.json.
| cache | town | shops | span | types | selfcheck golden (→ A pins) |
|---|---|---|---|---|---|
katoomba_real |
Katoomba NSW | 20 | 1.11 km | book4 opshop13 record2 video1 | 0x61760542 |
newtown_real |
Newtown, Sydney | 18 | 2.36 km | book4 opshop6 record5 toy2 video1 | 0x95978b45 |
fremantle_real |
Fremantle WA | 21 | 1.89 km | book5 opshop10 pawn1 record1 toy3 video1 | 0xee9fd09a |
bendigo_real |
Bendigo VIC | 9 | 2.06 km | book3 opshop3 pawn1 record1 video1 | 0x5e3e76a6 |
castlemaine_real |
Castlemaine VIC | 6 | 1.25 km | book2 opshop4 | 0xf1a628a1 |
Data discipline: deterministic (same raw → byte-identical cache; shops id-sorted, no RNG — verified
re-run byte-identical); bounded (all spans < 2.4 km, well under the 5 km mega-strip floor); raw
responses committed to web/assets/towns/_raw/ so re-runs never re-fetch (Overpass hit once per
town, John-authorized). katoomba_real is the fixture-comparison town (vs the hand-made katoomba).
All 5 boot through the full structural + gig suites (selfcheck 16596/16596 green, synthetic golden
0x3fa36874 unmoved). Provenance: OSM is ODbL — SOURCES.md (E-owned licence table) +
license/attribution fields on every cache; only public POI data (name/type/coord/suburb), no
private-individual data.
Round 16 — v3.1 THE FLIP: the drummer's sit clip (E asset half) ⟨v3.1 amendment⟩
Ledger #2 asset half — the drummer finally sits. E produces the clip; D wires it (
_rotWithHips
spawnRigfoot re-plant + F's seated-stature gate).sit.glbdelivered, verified, committed + on depot. → Lane D: it's ready (web/models/peds/sit.glb, canon binds 195/195, seated confirmed).
Asset: web/models/peds/sit.glb — a mesh-free animation clip, structurally identical to the
shipped walk.glb/idle.glb: 66 nodes (65 mixamorig bones + root), 0 meshes, 1 dangling skin
(inverseBindMatrices + 65 joints, referenced by 0 nodes — the vestigial artifact Blender glTF I/O
leaves, which makes glTF tools read it as an armature), 195 channels (65 bones × TRS). 109 KB, no Draco.
Source + provenance: Adobe Mixamo Sitting_Idle.fbx (533 KB) — fetched by scp from the
tailnet source box johnking@100.91.239.7:~/Documents/mixamo-fetch/out/ per Lane D's recipe (the
local ~/Documents/mixamo-fetch path is stale/empty). Converted on-device via Blender headless
(pipeline/fbx_to_clip.py, new tool): import FBX → export skinned GLB → pure-python mesh-strip
that keeps the skeleton + dangling skin + animation and re-indexes/compacts. Raw Mixamo mixamorig:
naming is already canonical (rigs.js._canon normalizes mixamorig<N> → mixamorig, so no bone
rename). Licence zone: Adobe Mixamo royalty-free animation license — the SAME zone as the
already-shipped walk/idle clips (the whole ped fleet is Mixamo-rigged). Mesh stripped, so only
the skeleton motion ships (retargeted onto the game's own peds), never Mixamo character geometry.
Verified (E's half — D verifies the wired in-scene pose):
- three.js (vendored GLTFLoader): loads, dur 4.33 s, 195 tracks, 65 bones, 195/195 bind to a
ped skeleton after
_canon, 0 NaN track values (mesh-strip re-index is clean). - Blender (the tool that measures
walkat 1.276 m standing): the clip poses seated — Hips descend 0.955 m (standing) → 0.299 m, stature 1.276 → 0.758 m. Eyes-on bone-dot render confirms the folded seated silhouette vs walk's upright one:docs/shots/laneE/sit_clip.png(left walk, right sit).
Depot: published to digalot.fyi/3god via the passwordless tailnet ingress under John's R13
authorization (_published.json 38 → 39; confirmed 200 on the public depot). NOTE (benign):
sit.glb — like vintage-cash-register.glb — is non-procity_-prefixed, so publish.py --verify's
procity_-only drift filter lists it as "not live" (a false drift — it IS live). Cosmetic only;
validate_manifest gates are unaffected (clips are not manifest GLBs).
"Manifest": ped clips load by convention (rigs.js base='models/peds/'), NOT via
web/assets/manifest.json — so there is no asset-manifest entry, same as walk/idle. D owns the
loader wiring (spawnRig/fleet). Bench-sit loiter (Fable's stretch goal) reuses this same clip.
Round 14 — v3.0 PROVENANCE FREEZE ✅ (release round)
FROZEN for the v3.0 tag (2026-07-16). Lane E R14 is a provenance close-out — no new synthesis (genres final: pubrock / grunge / covers; John "fine for now"). Everything below is the durable Lane-E release contract; the deep per-asset narrative lives in
pipeline/AUDIT.md(current through its R13 section). Counts tool-verified (glb_stat / ffprobe / manifest parse) and adversarially re-audited by two independent passes — both PASS, 0 release blockers.
Release status
- Gate 3 GREEN —
validate_manifest.py --depot→ 0 errors, 0 warnings. Not blocking the tag. publish.py --verify— depot matches_published.json(38 entries). The lone drift note (vintage-cash-register.glbabsent from the tailnet mirror) is cosmetic: it's a depot-reuse asset and IS live on the publicdigalot.fyidepot (Gate 3 confirms), so the manifest's real target is fine.- Licensing 🟢 clean — 0 quarantined/branded assets in the manifest (grep of every AUDIT QUARANTINE token — vitra/eames/calligaris/ikea/technics/1210/tuborg/uploads_files/akai/mpc/turntable — → 0 hits).
- Poster templates (R13 debt #4): no ask from B → no new templates (E builds only on B's ask).
- R14 doc-consistency fixes applied: AUDIT.md R13 section added;
roomtone-videogain 0.3→0.35 aligned to the manifest; manifestglb_lawstring amended to state the ≤8k-hero / ≤14k-instrument exceptions.
Provenance & licensing — by source zone (all 🟢 web-ok)
| zone | assets | source | method | licence |
|---|---|---|---|---|
| House library GLBs | 18 | thriftgod / house depot, John-presorted generic props (benches, shelves, counter, crate, bin, streetlight, food_cart…) — no recognizable trademark | normalize.py (passthrough or COLLAPSE ≤5k) |
🟢 web-ok |
| On-device synthetic GLBs | 11 (+5 _hi) |
MODELBEAST flux_local (FLUX.2-klein-4B) concept → trellis_mac mesh |
6 hero via bake_lowpoly.py (≤8k) · 5 gig instruments via normalize→decimate_props.py (14k) |
🟢 CC0-equiv, 100% on-device |
| fal-Hunyuan GLBs | 3 | flux_local concept → fal-ai/hunyuan3d/v2 (glass_case, magazine_rack, bus_shelter) |
bake_lowpoly.py (≤8k) |
🟢 original (~$0.64 total spend) |
| Depot-reuse GLB | 1 | cash_register = John's own thriftgod_hero (already live) |
reuse (not regenerated) | 🟢 cleared |
| Skins (2D) | 96 | 90sDJsim ports (7 facade + 2 ground) + thriftgod R1 store-art + flux_local gap-fill fronts/posters |
gen_skins.py --local (on-device) |
🟢 web-ok |
| Audio pack | 28 | 100% procedural numpy synthesis (gen_audio.py) |
oscillator/noise render → ffmpeg OGG+M4A | 🟢 CC0-equiv, parody-safe |
| Stock packs | 3 (350/311/273) | GODVERSE dealgod Postgres — real cover art |
build_stock_pack.py: parody-transform metadata + atlas |
🟢 real-art / parody-metadata |
| Ped clip ⟨v3.1⟩ | sit.glb (E R16; + pre-existing walk/idle from D/90sDJsim) |
Adobe Mixamo Sitting_Idle.fbx |
fbx_to_clip.py (Blender, mesh-strip) |
🟢 Mixamo royalty-free (motion only, mesh stripped) |
| Town pack ⟨v4.0-FINAL⟩ | 22 real AU towns, all 8 states/territories — 1192 shops + real street geometry (roads[]) |
OpenStreetMap via Overpass API — bounded per-town bbox; raw shops+roads snapshots committed in _raw/ |
build_towns.py: fetch → dedupe/suburb-fill/parody → C's §6 class map → 3× texture cap (drops counted) → v2 cache + roads |
🟢 ODbL 1.0 (© OSM contributors; web/assets/towns/SOURCES.md + license/attribution on every cache) |
| Godverse town (Lane G's, listed for completeness) | newtown_godverse — 18 shops, rostered in E's index.json |
GODVERSE/thriftgod shop census + OSM road geometry | pipeline/godverse_town.py (Lane G owns this provenance) |
🟢 ODbL 1.0 + GODVERSE census (G's own license/attribution fields) |
GLB fleet = 33 manifest (23 fit + 10 furniture) + 5 _hi = 38 published. Only external spend ever:
~$0.64 (fal-Hunyuan, R5). Everything else is $0 / on-device (electricity only).
Audio inventory — 28 assets (web/assets/audio/, committed; contract = manifest.audio)
All procedural, seeded, $0, parody-safe; OGG/Opus + M4A/AAC; beds+music loudnorm −16 LUFS, SFX peak-norm.
| group | n | assets | gig? |
|---|---|---|---|
| ambience | 7 | street-day, street-night, rain, roomtone-{retail,milkbar,video}, crowd-walla | crowd-walla gig:true |
| sfx | 8 + 6 steps | doorbell, door-open, till, riffle, toast, tram-bell, tram-rumble(loop), applause; footstep{pavement×3, timber×3} | applause gig:true |
| music | 7 | record-shop, milkbar, video-synth, arcade, gig-pubrock, gig-grunge, gig-covers | 3 gig beds gig:true |
- Gig beds (R13, the manifest key IS the gigKey
gig-<genreKey>):gig-pubrock(pub, 0.6, 13.4s),gig-grunge(band_room, 0.6, 17.2s),gig-covers(rsl, 0.5, 19.2s). Seamless loops, non-silent (verified −15.7…−17.6 dB mean). Oldpubrock-livefully retired (files gone, no live refs).
GLB inventory — 38 (tri counts glb_stat-verified; footprints/heights match manifest; no Draco, tex ≤1024)
Fittings (23): record_crate 5k · wire_shelf 330 · clothes_rack 3.2k · bookshelf 5k · cube_shelf_wide 5k ·
counter 276 · coffee_table 495 · work_table 1.6k · box_crate 78 · crate_stack 3k · listening_booth 8k ·
drinks_fridge 8k · arcade_cabinet 8k · milkshake_mixer 8k · counter_till 8k (bake_lowpoly hero) ·
glass_case 8k · magazine_rack 8k (fal) · electric_guitar / bass_guitar / drum_kit / guitar_amp /
mic_stand = 14k each (R13 decimate) · cash_register 6k (reuse).
Furniture (10): bench 1.9k · bench_modern 640 · bench_wood 4k · longbench 1.2k · park_bench 5k ·
streetlight 5k · food_cart 5k · novelty_record 8k (bake_lowpoly) · bus_shelter 8k (fal) · bin 3k.
Preserved _hi originals (5, on depot, NOT manifest-referenced — LOD/re-bake source):
electric_guitar 43.5k · bass_guitar 36k · mic_stand 34.1k · drum_kit 26.2k · guitar_amp 96.6k.
Skins — 96 (web/assets/gen/*.jpg, local; all present, 0 missing/orphan)
facade 48 (43 blank + 5 baked-sign: djsim-video/milkbar/pub, market, grimy) · sky 10 · ground 10 · wall 8 · interior floor 6 · interior surface 7 · awning 3 · poster 4 (grunge/retro/screenprint/xerox, blank name-band). Per-shop-type facade coverage ≥2 for all types (validate_manifest asserts; min = 3).
Stock packs — 3 (indexes committed in pipeline/packs/; atlases on depot; read directly by Lane C)
record 350 (6 atlases, 256²) · book 311 (4 atlases, 176×256) · toy 273 (5 atlases, 256²). Real cover art
(GODVERSE), parody-transformed metadata (sha256-seeded word-banks; no real trademarks). All pass validate_pack.
By-design notes (documented so a future reader doesn't re-flag them)
- 5
_hiin_published.jsonbut not the manifest — intentional archival hi-poly originals (R13). - Stock-pack indexes not in the manifest — by design; Lane C reads
pipeline/packs/*directly, atlases ship on the depot. glb_lawtiers — ≤5k library / ≤8k baked hero (TRELLIS shell-soup, texture-intact) / ≤14k gig instruments; now stated in the manifest convention string + AUDIT.validate_manifestprints "skins 85 groups" — a shallowsk.values()count (the nestedinteriordict counts as 1); true leaf total is 96. Cosmetic label, flagged for v3.1.- Minor stale ledger figures (non-live):
food_cart4991 vs_normalize_results5000 (9-tri); a supersededcube_shelf_01entry in_normalize_results(DROP-filtered from the manifest, rejected in AUDIT). Harmless.
Deferred to v3.1 (documented, not started)
street_bindecimation — B's non-blocking, conditional ask ("if the combined-lane budget tightens"); the bin is ~3k tris (within house law), and B measured ~100k tri / ~150 draw margin at the busiest block, so no action for v3.0.- Texture-bake-to-lowpoly / KTX2-Basis atlas pass — if a future budget tightens; the
_hiinstrument originals + the hero props are the LOD source (R9 VRAM note + AUDIT).
Round 13 — two genres, the gigKey rename (debt #1), lighter instruments
v3.0-beta THE DISTRICT. All local work landed + verified; the one gated step (depot publish)
waits on John. Verification: core gates GREEN (scaffold · consistency 0-warn · citygen selfcheck
3273/3273 incl. v3 gig-layer + assets-on-disk), manifest validator --depot GREEN, and the real
app boots clean flags-off / ?gigs=1 (A's 3-venue district, 14 gigs/posters — all three gig beds
resolve by canonical key, no stale key, 0 console errors) / ?noassets=1&gigs=1 (0 audio + 0 GLB
fetches, silent-and-fine).
1. gigKey rename — DONE (debt #1). → Lane B + Lane F: switch your readers.
The manifest music key IS the gigKey, always gig-<genreKey> — one key, zero mapping. Renamed
pubrock-live → gig-pubrock; added gig-grunge, gig-covers. Files are now
audio/music-gig-{pubrock,grunge,covers}.{ogg,m4a}; the old music-pubrock-live.* are deleted.
Manifest regen'd + validates 0/0; no stale pubrock-live anywhere.
- → Lane F (interior_mode.js): DELETE the
GIG_BEDbridge (the{'gig-pubrock':'pubrock-live'}map +gigBedKey()); readra.gigKey(gig-<genre>) straight againstAUDIO.music[...]. The bridge currently maps to the now-gonepubrock-live→ fails soft to silence (I verified: no crash, just a silent band) until you delete it. This is your R13 task #4. - → Lane B (audio.js ~L274/276): you hardcode
'pubrock-live'for the street spill — read each venue'sgigKeybed instead (AUDIO.music[venue.gigKey], i.e.'gig-'+genreKey). Same fail-soft-silent until you switch. Your R13 task #2 (per-genre spill) covers this. - CITY_SPEC §v3 contract line (coordinate wording w/ A): "The audio manifest key for a gig
genre IS the gigKey, always
gig-<genreKey>(gig-pubrock,gig-grunge,gig-covers). No mapping table: C emitsroom.audio.gigKey, E names the bed, B/F read the same key."
2. Two new gig beds — grunge (band_room) + covers (RSL).
Procedural, seeded, loopable, OGG+M4A, loudnorm, $0 on-device (same discipline as R11/12). All three
are audibly + spectrally distinct (spectral centroids pubrock 2336 / grunge 2271 / covers 1115 Hz;
contact sheet docs/shots/laneE/gig_beds_spectrograms.png):
gig-grunge(0.6 gain, 17s): down-tuned sludge, quiet-loud dynamic (clean-ish verse → full-fuzz chorus — visible in the spectrogram), loose heavy kit. Sits at −14.6 LUFS (genre-dense; a 1.02 inter-sample peak, not real clipping — plays at gain 0.6 so absolute output ≈0.6).gig-covers(0.5 gain, 19s): mellow RSL covers band — new drawbar-organ()voice, walking bass, soft kit, sparse organ answer. Warm, dark highs (that low continuous band + horizontal harmonic striations in the spectrogram = the organ). New synth helpers:organ(),grungechord().- John may re-flavour genres (names only) — I synthesize to order. Note it's now a coordinated
A+E change: the genreKey lives in A's
registry.js(drivesgigKeyFor→ the bed name), so a re-flavour = A renames the genreKey + E renames/re-synths the bed. Defaults grunge/covers are live.
3. Instrument decimation — DONE. 5 GLBs → 14k tris each (≤15k target). → Lane D.
Collapse-decimated (Blender, pipeline/decimate_props.py) — not voxel-remesh, so thin features
survive: mic pole + hanging cable, drum cymbals/stands, guitar necks/headstocks all read (thumbs
re-rendered + eyeballed). Dims/orientation/base-origin/WebP preserved; GLB-law clean (no Draco, tex
≤1024). Fleet 236k → 70k tris (−70%) across the district's multiple stages.
| instrument | tris (norm→14k) | footprint (m) | height |
|---|---|---|---|
| electric_guitar | 43.5k → 14k | 0.38×0.08 | 1.00 |
| bass_guitar | 36.0k → 14k | 0.34×0.13 | 1.15 |
| mic_stand | 34.1k → 14k | 0.57×0.57 | 1.70 |
| drum_kit | 26.2k → 14k | 1.50×1.24 | 1.30 |
| guitar_amp | 96.6k → 14k | 0.52×0.29 | 0.61 |
- → Lane D: wire against the SAME filenames (
procity_fit_{electric_guitar,bass_guitar,mic_stand, drum_kit,guitar_amp}_01.glb) — footprints/heights in the manifest are unchanged, so yourinstrumentFor(role)fittings are unaffected; they're just lighter now. (debt #2) - High-poly originals preserved as
_hivariants in_normalized/(git-ignored) for the depot.
4. Depot publish — DONE (John OK'd direct publish). Gate 3 flag CLEARED.
Published via the passwordless tailnet ingress (GOD3_DEPOT=http://100.94.195.115:8788 — this box is
allowlisted, so no secret handling). The 5 base names now serve the 14k bytes and the 5 _hi
hi-poly originals are live — verified on the public digalot.fyi depot (amp base 5.9MB→1.3MB, _hi
5.9MB live, byte-identical to local). _published.json 33→38 (the 5 _hi recorded).
validate_manifest --depot GREEN. The −70% decimation win is now live in production, not just
?localdepot. (The vintage-cash-register.glb drift note from publish.py is pre-existing + unrelated —
a depot-reuse asset, not a Lane E file.)
Round 9 — VRAM audit verdict + one rec for C
VERDICT: fine to ship v2.0. Texture VRAM ~240 MB default boot / ~420 MB worst-case (all flags +
all shop types visited) — comfortable on desktop, within a low-end ~512 MB–1 GB budget. Full numbers
in AUDIT.md. Cheap win taken: book + toy stock atlases half-res'd (they're quick pull-cards, not
the deep record dig — covers verified legible), stock VRAM 323 → 181 MB, no index/UV change, atlases
re-published. Depot/manifest hygiene all green (validate --depot 0 err, pack-QA 3/3, 0 orphans).
→ Lane C (optional, NOT blocking v2.0 — v3 follow-up): stockpack.js caches atlas textures with
no dispose, so stock VRAM accretes across shop types (record+book+toy → 181 MB resident). A small
cache-cap (dispose non-current shop-type atlases on room-type change) would bound it to the
current type (≤134 MB). Not needed for the tag; flagging for when you next touch stockpack.
→ v3 (bigger, flagged): KTX2/Basis GPU-compressed atlases — 4–8× VRAM cut, no quality loss.
→ Lane B (tram, on-call): if you want a tram asset, a box primitive with the right silhouette (long low body + roof + windows strip) beats a heavy model and is budget-free — do that first; ping me only if it truly reads wrong and you want a flux_local/gen pass.
Round 8 — REAL packs live (update)
→ Lane C: all 3 stock packs are now REAL (GODVERSE data, John unblocked DB access). Same schema you wired — just richer data, no code change needed:
- record — 350 real vinyl (John's store stock, real Discogs covers), 6 atlases, cell 256×256.
- book — 311 real book covers (
dealgod.productsbooks), 4 atlases, cell 176×256 (portrait face-out cover, not thin spine — I only have cover art; render books face-out). - toy — 273 real toy/collectible photos, 5 atlases, cell 256×256.
Metadata is parody-transformed (no real titles/artists/brands); covers are real art; book prices are
seeded (source is info-only). Indexes committed to
pipeline/packs/+ staged (assets/models/, your?localdepotpath); atlases published to the depot. All pass the pack-QA gate. If your book shelf wants thin spines, tell me — I'd need spine art (don't have it; covers only).
Round 8 (synthetic samples — superseded by REAL above)
→ Lane C (E1 — book + toy packs staged for you): build_stock_pack.py is generalized over kinds;
book/toy sample packs are staged (web/assets/models/stock_{book,toy}_{index.json,atlas_00.webp},
?localdepot) + indexes committed to pipeline/packs/. Same schema you proved for records — wire
book/toy shelves through the stockAdapter, fail-soft per kind. Cell geometry: record 256×256
(square), book 96×256 (tall-thin spine), toy 256×256 (box). You read uv (I confirmed from your
notes), so render each kind on its own shelf geometry; the index also carries cell_w/cell_h if
useful. artist carries author (book) / brand (toy). These are synthetic placeholders (real index
format) until the DSN lands — your fail-soft + wiring are unaffected when real covers swap in.
E2 — pack-QA gate is live (validate_pack.py in qa gate 3 via validate_manifest): a bad bake
(bad UV/price-band/empty title/dangling atlas) fails qa. If you add index fields, tell me and I'll
extend REQUIRED.
→ Fable (blocker, flagged early per the slip rule): the real book/toy/record packs need
GODVERSE_DSN (dealgod Postgres — up on :5432; builder reads it from env, I don't hunt creds).
One command per kind once set (--kind book --from-db --count 200). Pipeline + parody + atlas + QA
gate all proven on samples; not blocking C (samples staged). No fal spend.
Round 7
→ Lane D (D1 — the flip gate): merged peds ready to validate. web/models/peds/*.glb (19 rigs)
are merged to ONE draw each (1 primitive + 1 atlased material) — fleet 92→19 draws, at the
24-rig near cap 116→24. Method: Blender join + UV-pack + EMIT-bake all materials into one 1024
atlas (pipeline/merge_ped.py). Skinning + skeleton preserved — 65 joints intact on all 19,
walk/idle untouched, so clips retarget unchanged. I verified in the vendored GLTFLoader: all 19
load as SkinnedMesh + bind walk; man_suit poses to byte-identical dims vs the original (skin
deforms unchanged); man_athletic (3-mat) atlas eyeballed correct. Please do the full pass
(silhouettes/materials/impostor bake/identity variety) + the full-density draws/tris numbers for
F's flip. Pre-merge peds backed up (gitignored) at pipeline/.peds_r6/ if you need a diff; per-ped
in pipeline/_peds_merge.json. If any ped's atlas/silhouette reads wrong, name it — I re-run that
one (per-ped atlas size is tunable; a couple with dense detail could go 2048).
→ Lane C (E2 — stock pack): pipeline built + PROVEN, sample staged for you now.
pipeline/build_stock_pack.py produces the pack (parody-transform → atlas WebPs + JSON index). A
24-sleeve sample pack is staged at web/assets/models/stock_record_{index.json,atlas_00.webp} —
wire ?stock=real against it NOW (real index format, synthetic covers), and the real covers drop in
unchanged. Proposed index schema (I built to this — confirm/adjust in LANE_C_NOTES and I'll match
INDEX_FIELDS):
{ "version":1, "atlas_px":2048, "cell":256, "atlases":["stock_record_atlas_00.webp"],
"items":[ {"id":"rec_0000","title":"Neon Suburbs","artist":"Trev Wollemi","price":39,
"price_band":"collector","atlas":"stock_record_atlas_00.webp","uv":[0,0,0.125,0.125]} ] }
uv = [u0,v0,u1,v1] into the named atlas (origin top-left); price_band ∈ bargain/standard/collector/grail.
Seeded pick per bin is yours (same shop.seed → same crate). Fail-soft: pack missing → parody canvas.
→ Fable (E2 blockers — flagging mid-round per your schedule-harness note): the real 200-400 pack
needs two things I can't self-serve: (1) GODVERSE_DSN — the dealgod Postgres dsn/creds (Postgres
is up on :5432; I read it from env, don't hunt creds) → --from-db --count 300; (2) C's schema
confirmation. Both external. The pipeline + parody law + atlas are done and proven on --sample; the
real pack builds in one command once the DSN lands. Not blocking C's consumer wiring (sample staged).
Round 6
E1 done — publish.py now merges into _published.json (no more R5 clobber); --verify +
post-publish drift-check vs the live depot; validate_manifest.py fails if a manifest GLB isn't in
_published.json. Record verified 27==depot. All 27 procity GLBs stay live + recorded.
→ Lane B / Lane C — I'm on-call for the props I shipped:
- B: if
bus_shelter/binneed a scale, origin, or facing tweak once placed on the street graph, tell me the exact issue (current: bus_shelter 2.33×1.41 m / 2.4 m, bin 0.57×0.68 m / 1.0 m, both base-origin minY=0). I can re-normalize + re-publish same-name (idempotent, merge-safe now). - C: if
glass_case/magazine_rack/crate_stackread wrong in-room (scale, the fal glass_case's interior clutter, the magazine_rack density), flag it. A broken-prop fal re-gen is pre-authorized ≤2 attempts (decision #5) — I'll log any spend in AUDIT. Footprints in the manifest; all base-origin. Mapping guidance for the 3 orphans is in the Round-5 → Lane C section below.
Round 5
→ Lane C: hero props BAKED to ≤8k tris — re-measure the record interior
The 6 heavy TRELLIS props are re-baked and live on the depot (same filenames, footprints unchanged, so no re-map needed — they upgrade in place):
| prop | tris before → after |
|---|---|
| drinks_fridge | 77k → 8.0k |
| milkshake_mixer | 73k → 8.0k |
| listening_booth | 54k → 8.0k |
| counter_till | 32k → 8.0k |
| novelty_record | 26k → 8.0k |
| arcade_cabinet | 25k → 8.0k |
Fleet 288k → 48k tris. Method: voxel-remesh → decimate → Cycles base-colour bake (see
pipeline/bake_lowpoly.py, AUDIT R5). Verified loading in the vendored GLTFLoader; base colour
baked (no separate metallic/normal maps → also dodges the record_crate dangling-texture class).
→ Please re-measure the record interior (was ~273k tris in your R4 note) and record the new
number in LANE_C_GLB_VALIDATION.md — it should drop by ~most of the prop weight.
→ Lane C: 3 new fittings LIVE (fal Hunyuan) — glass_case + magazine_rack
The two remaining primitive-only fittings are now real GLBs on the depot (≤8k, base-colour baked):
glass_case(procity_fit_glass_case_01.glb, 1.08×1.42 m, 1.0 m tall) — timber base + glass panels + items inside. Registry idsglass_case(toy/dept) +glass_cabinets(pawn) both alias to it. → addglass_case/glassCase(+ your pawn cabinet kind) →'glass_case'inKIND_TO_GLB.magazine_rack(procity_fit_magazine_rack_01.glb, 0.54×0.44 m, 1.2 m tall) — tiered timber rack w/ magazines (milkbar/pawn). Registrymagazine_rackaliases to it. → map your kind →'magazine_rack'.- Both base-origin, verified loading in the vendored GLTFLoader.
manifest.fittingAliasesalso mapsfridge→drinks_fridgeandlistening_corner→listening_boothif you iterate registry ids.
→ Lane B: new street props — bus_shelter + bin
procity_street_bus_shelter_01.glb(furniture.bus_shelter, 2.33×1.41 m, 2.4 m, ≤8k, base-origin). Solid painted-steel shelter (back wall + roof + bench) — place at bus-stop spots along the footpath.procity_street_bin_01.glb(furniture.bin, 0.57×0.68 m, 1.0 m, 3k) — red council wheelie bin, an obvious town-furniture gap. Mapbinin your furniture GLB-upgrade path if you want it on footpaths.- B3 resolved:
novelty_recordis now 8k (was 26.5k) via the E1 bake — re-measure the busy view. All furniture GLBs base-origin + footprint-accurate; fail-soft as usual.
→ Lane C also: crate_stack for floor piles / dig
procity_fit_crate_stack_01.glb (fittings.crate_stack, 0.71×0.72 m, 0.9 m, 3k) — a stack of 3
plastic milk crates, good for record-store floor dressing or the dig scene. Optional; map a kind to
'crate_stack' if useful.
→ Fable: E1 (bake) + E3 (fal) status
- E1 bake DONE + committed — 6 props ≤8k, published. Feeds C's re-measure + your v1.1 tag.
- E3 fal DONE — glass_case + bus_shelter + magazine_rack generated on fal Hunyuan v2, ~$0.64
total (log in AUDIT R5), ≤2 attempts each, all baked ≤8k + published. Guardrail held (nothing
else on fal).
validate --depot0 err. Last two primitive-only fittings retired. - E2 depot growth next (v2-serving library props).
Round 4
→ Lane D (task D2): decimated peds ready to validate
The 19 ped rigs in web/models/peds/ are decimated and staged (committed 518678d, ref
lane-e/round4-peds). Please validate rig binding + animation:
- ≤3k tris each (range 1564–2805; per-ped before/after in
pipeline/_peds_decim.json). Fleet total 924k → 46.5k tris (48.6k → 2.45k avg). At the 24-rig near cap ≈ ≤67k. - Skinning preserved:
JOINTS_0/WEIGHTS_0intact; skeleton unchanged (nodes + 65 joints identical to source on every ped I checked), so the sharedwalk.glb/idle.glbclips retarget unchanged.walk.glb/idle.glbwere NOT touched. - Method:
gltf-transform weld→simplify --ratio 0.05 --error 0.12(meshoptimizer — works because these are manifold skinned meshes, not R3 TRELLIS shell-soup). Contact sheet:docs/shots/laneE/peds_decimated.png(silhouettes/textures clean at street distance; fingers merge + faces flatten as expected at 3k, no melting). - Originals backed up (gitignored) at
pipeline/.peds_orig/if you need a before/after diff.
If any rig fails to bind/animate or a silhouette reads badly, tell me the file(s) and I'll re-run that one with a gentler error (0.08 keeps ~50% more tris but still <5k). Then F re-measures gate 3 after your sign-off.
→ Lane C: E2 asset re-exports
procity_fit_record_crate_01.glb— FIXED. Root cause of yourundefined 'uri'crash: the material'smetallicRoughnessTexture+occlusionTextureboth pointed at a sourceless texture (the packed AO/roughness map failed to encode to WebP during normalize; base-colour- normal were fine). Fix:
pipeline/fix_glb_textures.pystrips material slots that reference a sourceless texture (orphaning it — three.js loads textures lazily, so no crash). Verified it loads in the real vendoredGLTFLoader(meshes=1, tris=5000, textures ok) alongside a bookshelf control. Re-published to the depot — re-maprecord_crate/recordBininglb.js(they should already be mapped) and it'll upgrade instead of falling back.
- normal were fine). Fix:
- counter-with-till — LIVE. New fitting
counter_till(procity_fit_counter_till_01.glb): 1.61 × 0.64 m footprint, 1.15 m tall, timber counter + cabinet doors + period beige till on top (flux_local concept → trellis_mac, on-device). Fixes the R3 4 m till-less counter that squashed. Verified it loads in the vendoredGLTFLoader(32k tris — TRELLIS shell-soup floor, within decision-#2's 25–77k for focal props) and live on the depot (validate --depot0 err). → Inglb.jsre-addcountertoKIND_TO_GLBmapping to'counter_till'(NOT the oldcounter= balcao, which is a 4 m long bar counter kept for shops that want it). The manifestfittingAliases.counter→counter_tilltoo. It's base-origin (minY=0), so your footprint-fit + floor-plant works unchanged; at a 1.4–2.0 m counter slot it scales to a believable ~1.0–1.4 m tall counter.
→ Fable: status
- E1 (gate-3 critical path): DONE. Peds ≤3k, committed + pinned. Unblocks D2 → F3.
- E2a record_crate: DONE + verified (loads in vendored GLTFLoader). Re-published.
- E2b counter: in progress (local, on-device — no fal spend per decision #3).
- Not doing: fal.ai spend (glass-case/bus-shelter stay parked), hero-prop bake-to-lowpoly.