PROCITY/pipeline/AUDIT.md
m3ultra 23c0667dca Lane E round 9 (E1+E2): VRAM/texture audit + half-res book/toy atlases
E1 memory audit: measured all texture sources (decoded RGBA+mips). Resident VRAM
~240MB default boot / ~420MB worst case (all flags + all shop types); stock packs
the swing. Cheap win: half-res'd book+toy atlases (quick pull-cards, not the deep
record dig — covers verified legible) → stock VRAM 323->181MB, no index/UV change,
re-published to depot. Verdict: fine to ship v2.0; flagged stockpack cache-cap (C)
+ KTX2/Basis (4-8x) for v3. E2 hygiene: validate --depot 0 err, pack-QA 3/3,
provenance-drift clean (27 live), 0 orphan atlases. Numbers + verdict in AUDIT.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:59:22 +10:00

30 KiB
Raw Blame History

PROCITY Asset Audit — Lane E, Step 1

Generated 2026-07-14 from a live inspection of johnking@100.91.239.7:~/Documents/3D=models/ (the sorted house library) + the 3GOD depot GET /api/list. Tri counts / bounding boxes from pipeline/glb_stat.py (dependency-free GLB parser); orientation & final scale verified by rendering every candidate through Blender and looking at the thumbnails (never "should look right").

TL;DR

  • The library is a raw dump: almost nothing obeys the house GLB law. Scale is arbitrary (only record_crate_plastic_01 was native-metres), up-axis varies, tri counts span 12 → 960k, textures reach 4096px, and several assets carry branded/trademarked designs (quarantined).
  • pipeline/normalize.py fixes scale/orientation/tris/textures uniformly. 16 assets normalized clean, verified upright & metre-scaled, WebP ≤1024, ≤5k tris, +Y up, base origin — ready to publish.
  • Gaps with no usable source (→ MESHGOD_BATCH.md or in-engine primitives): glass display case, fridge, till/register, spinner rack, magazine rack, market stall, escalator, VHS shelving.
  • Depot already holds a few reusable house props (street-bench, council-bin, milk-crate-stack) and 138 assets total (mostly TRELLIS *-views-3d-* / image-3d-* MeshGod outputs).
  • Skin caveat: 5 of the 25 facades carry baked-in sign text (djsim-milkbar "MILK BAR", djsim-video, djsim-pub, market, grimy) — they break the blank-signboard convention, so the manifest tags them signboard:"baked" and Lane B must not overlay a name on them. The other 20 are genuinely blank.

Toolchain reality (differs from the lane brief)

thing brief said actual
Blender on ultra "runs on ultra against Blender" present but not on PATH/Applications/Blender.app/Contents/MacOS/Blender (v5.0.1). Renders 4 angles in ~0.9s.
coreutils / timeout none confirmed — used ssh ConnectTimeout, no timeout/setsid
python on ultra 3.9 3.9.6 — glb_stat.py/normalize.py written to that floor
OpenRouter key thriftgod/.env absent; house convention ~/Documents/dealgod/lore/keys.env (auto-read by gen scripts)
depot upload cred GOD3_PW env, not set here → publish is gated on John (see publish.py)

Normalized set (16 — ready; manifest.jsonfittings / furniture)

house law enforced on every one: metres · +Y up · origin at base (minY=0, centred X/Z — verified node-aware) · ≤5k tris · WebP ≤1024 · no Draco. Facing is not auto-corrected — symmetric props are orientation-agnostic; the few directional ones (counter, food_cart) keep their native front and Lane B/C rotate when placing (per-asset yaw is available in batch.json).

PROCITY id source GLB src tris → out footprint (m) height (m) notes
fit record_crate shop-fittings/record_crate_plastic_01 18320→5000 0.46 × 0.63 0.41 ★ native-metres, textured red milk crate; serves record bin too
fit wire_shelf shop-fittings/shelf_metal_wire_01 330→330 1.17 × 0.44 1.80 ★ clean upright wire shelving
fit clothes_rack shop-fittings/clothes_rack_01 3152→3152 1.34 × 0.43 1.50 ★ rolling clothes rack (opshop)
fit bookshelf shop-fittings/bookshelves 45128→5000 0.20 × 1.02 2.00 tall bookshelf w/ books (book/video/opshop)
fit cube_shelf_wide shop-fittings/shelf_cube_bookshelf_01 17734→4999 0.75 × 1.24 1.10 stocked cube shelving (toy/book)
fit counter shop-fittings/balcao 276→276 1.06 × 4.02 1.10 long bar counter — Lane C may want to scale to lot
fit coffee_table shop-fittings/coffeetable 495→495 0.67 × 1.49 0.45 low display/side table
fit work_table shop-fittings/computertable 1556→1556 0.44 × 1.04 0.75 work/trestle table w/ lower shelf
fit box_crate shop-fittings/warehouse_box 78→78 1.35 × 1.32 0.60 open bulk bin / bric-a-brac box
street bench street-furniture/bench 1942→1942 0.44 × 1.21 0.85 slatted seat
street bench_modern street-furniture/eb_hipster_bench 640→640 1.68 × 0.47 0.45 low modern bench
street bench_wood street-furniture/woodbench 11728→3999 1.34 × 0.59 0.85 timber park bench
street longbench street-furniture/longbench 1172→1172 5.86 × 1.08 0.85 long feature bench (arcade/market)
street park_bench street-furniture/park_bench_01 9884→5000 1.92 × 1.62 0.85 classic slatted park bench
street streetlight street-furniture/streetlight 6592→4999 1.64 × 1.82 5.00 ornate lamppost
street food_cart street-furniture/food_cart_bakso_01 30316→5000 3.00 × 2.07 2.00 textured food cart (~3MB — heavy, 28 tex; optimize later)

Rejected during normalize (needs-manual-fix — NOT in manifest)

source issue (verified by render) disposition
shop-fittings/cube_bookcase (cube_shelf) node-scale distortion → 6.3 × 8.4 m footprint dropped; cube_shelf_wide covers the need
shop-fittings/garage_shelves_metal_v2 imports lying flat (green pallet); baked node transform, up-fix didn't help needs manual re-origin in Blender; wire_shelf covers shelving
shop-fittings/table, saloon_table, table_02, desk_01, kitchen_table degenerate/flat or node-distorted (table → 55×55 m; desk → 17×17 m) rejected; work_table/coffee_table cover tables
shop-fittings/radiogram_furniture_v2_l1 Blender "Please select a file" on import (bad GLB) needs re-export
street-furniture/market_stall_01_CHECK import error (_CHECK = sorter-flagged) → MeshGod/primitive (see MESHGOD_BATCH.md)

QUARANTINE — licence unclear / branded (🔴 do NOT publish or ship)

The house rule is 🟢 web-ok only; anything branded or of unknown provenance is quarantined until cleared. These are excluded from the manifest:

asset reason
furniture/armchair-vitra-eames, ines-chair-calligaris protected designer marques (Vitra/Eames®, Calligaris®)
shop-fittings/ikea-cupboard IKEA® product design (+259k tris)
props-scenes/mk2_1210_turntable_-_old Technics SL-1210® industrial design (trademark)
props-scenes/tuborg_fest_scene Tuborg® brand scene
furniture/uploads_files_*, props-scenes/uploads_files_* (Couch William, Harland Office Chair, nv toufa, Backpack) CGTrader/free3d bulk dumps, licence not recorded
furniture/sofa-c, hug-armchair, dragonchest, kitty-bed-01, bean_bag_chair provenance unrecorded — verify before use

Blanket caveat (applies to the 16-asset publish set): the generic fittings we normalized (benches, shelves, counter, crate…) carry no explicit licence/provenance record — I cross- checked all 16 source filenames and none is a recognizable trademark, and they were pre-sorted into the house library by John for exactly this use, so they're treated as usable per the lane brief. But "unclear ⇒ quarantine" is the house rule: before publish.py pushes to the shared CDN, record a one-line source/licence per asset, and for any CC-BY source carry attribution. If provenance genuinely can't be established for one, pull it from the publish set. This is a reason the publish step is deliberately gated on John, not automated.

Provenance — round-3 hero props (for the depot publish sign-off)

Per the round-3 instruction ("record the per-asset provenance list in AUDIT.md first, then hand John the two-liner"). The hero props have the cleanest provenance in the whole set — they are 100% generated on-device, from no third-party source:

asset (procity_fit/street_<id>_01.glb) origin licence zone
5 generated — listening_booth, drinks_fridge, arcade_cabinet, milkshake_mixer (fit) · novelty_record (street) fully synthetic, local: concept image = MODELBEAST flux_local (FLUX.2-klein-4B, Apache-2.0 model) → mesh = trellis_mac (TRELLIS.2, an original reconstruction of that concept image). No scanned/third-party geometry, no copyrighted source asset. 🟢 original work (CC0-equivalent)
cash_register (vintage-cash-register.glb) reuse of John's own thriftgod_hero asset, already live on 3GOD (round-3 reuse check) — not regenerated. 🟢 same as existing depot set
magazine_rack not shipped — TRELLIS wire rack floored ~112k tris (thin-wire gotcha) → in-engine primitive. n/a
glass_case, bus_shelter not shipped — TRELLIS failed on the transparent glass → fal.ai fallback list (John's go — Hunyuan v2 ~1417¢/gen: ~$0.280.34 for the pair, ~$1 worst case w/ retries) or primitive. n/a
  • Prompts were generic period objects with explicit no text, no brands, no people. Every kept prop was eyeballed for accidental baked branding (contact sheet docs/shots/laneE/props_gen_local.png). The only textual marks are the drinks-fridge's weathered faux-stickers — verified at full concept resolution to be garbled, illegible pseudo-lettering (no real trademark; e.g. a faux barcode + nonsense glyphs), which reads as period flavour and blurs out at mesh-texture resolution. 🟢 cleared.
  • Tri/size note: TRELLIS meshes are dense multi-shell scans; normalize.py's COLLAPSE decimate floors well above the ≤5k library budget (2577k tris, ~0.33.5 MB/prop; neither COLLAPSE nor meshopt can reduce shell-soup without a voxel-remesh that destroys the baked UVs). Accepted for focal, optional, LOD-able props (texture-intact) — flagged for a later texture-bake-to-lowpoly / gltfpack pass.
  • These GLBs stage locally (web/assets/models/, git-ignored, served by ?localdepot=1) and await John's depot publish; they are not committed binaries (same policy as the library _normalized set).

Coverage vs the registry fittings needs

shop type fittings need covered by gap →
record bins, crates, counter, listening corner record_crate, counter, wire_shelf listening booth → MeshGod
opshop clothes racks, bric-a-brac shelving, book wall clothes_rack, wire_shelf, bookshelf, box_crate
toy cube shelves, display tables, glass case cube_shelf_wide, coffee_table glass case → MeshGod
book bookshelves, spinner racks, armchair bookshelf, cube_shelf_wide spinner rack, armchair → MeshGod/primitive
video aisle shelves, returns slot, poster wall bookshelf, wire_shelf returns slot, poster wall → primitive
pawn glass cabinets, wall hooks, barred window counter glass cabinet → MeshGod; pegboard = tex-pegboard skin
milkbar counter, fridge, magazine rack counter fridge, magazine rack → MeshGod/primitive
dept mixed sections, escalator prop (all above) escalator → primitive
stall trestle tables, crates work_table, box_crate, record_crate market stall → MeshGod/primitive
street bench/streetlight/bin/tree bench×4, longbench, park_bench, streetlight, food_cart bin (council-bin on depot), tree → billboard (Lane B)

Gaps with no clean source are shortlisted in MESHGOD_BATCH.md (budget-gated) — everything has an in-engine primitive fallback, so none of these block the game.

Depot cross-reference (GET /api/list, 138 assets)

  • 90 named + 48 TRELLIS-hash. Reusable house props already live (verify visually + licence before wiring): street-bench.glb, council-bin.glb, milk-crate-stack.glb, cashbot.glb. ⚠️ akai-mpc-live-iii-* is a trademarked product design — same quarantine class as Technics/ IKEA; do not treat as clean web-ok reuse without a licence check.
  • No procity_* assets yet — this lane is the first to publish that namespace.
  • Our 16 normalized GLBs are staged in pipeline/_normalized/ and upload via pipeline/publish.py once GOD3_PW is provided (idempotent, reversible).

Full library inventory (dispositions)

shop-fittings/ (33): normalized 9 · rejected 8 (7 node-distorted/degenerate + radiogram bad-GLB) · quarantine 1 (ikea) · unused/dup 15 (heavier or duplicate tables/shelves — shelf_storage_rack_01 alone is 960k tris). street-furniture/ (9): normalized 7 · error 1 (market_stall) · building_shell_01 reserved for Lane B (38k-tri building shell — not a fitting). furniture/ (17): mostly domestic (beds/sofas/chairs) — out of scope for shop fittings; 6 quarantined. props-scenes/ (10): dj_roomv01 + turntable = record-shop dressing (turntable quarantined: Technics); rest branded/scene dumps.

Raw stats for every parsable .glb (59 of the ~69 library entries; the rest are .fbx/.obj or subdir sources not counted): pipeline/_audit_stats.json. glb_stat.py reports world-space dims (node-transform-aware) and WebP/PNG/JPEG texture sizes. Normalizer output: pipeline/_normalize_results.json.

Round 4 — ped-rig decimation (gate-3 blocker)

The 19 street ped rigs in web/models/peds/ were ~31k56k 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 weldsimplify (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 15642805, all ≤3k), 36M → 5M on disk. Skinning fully preservedJOINTS_0/WEIGHTS_0 intact, skeleton unchanged (nodes + 65 joints identical to source), so the shared walk.glb/ idle.glb clips retarget unchanged (both left untouched). Contact sheet: docs/shots/laneE/peds_decimated.png — silhouettes/textures read clean, no melted faces/hands (fine for street distance). Originals backed up (gitignored) at pipeline/.peds_orig/; per-ped before/after in pipeline/_peds_decim.json. Lane D validates rig binding + anims (task D2); Lane F re-measures gate 3 (~24-rig near cap × ≤3k ≈ ≤67k fleet contribution).

Round 4 — E2 asset re-exports (published)

  • procity_fit_record_crate_01.glb re-export (fixed). The R3 normalize left a sourceless texture (packed AO/roughness map didn't survive the WebP export; the material's metallicRoughnessTexture + occlusionTexture pointed at it), which crashed three.js GLTFLoader with undefined 'uri'. pipeline/fix_glb_textures.py strips material slots that reference a sourceless texture (base-colour + normal kept; the orphan is never loaded). Verified it loads in the vendored GLTFLoader (5000 tris, textures ok); re-published to the depot.
  • procity_fit_counter_till_01.glb (new). ~2 m timber shop counter with a period beige till — flux_local concept (3 rolls for the ~2:1 aspect) → trellis_mac mesh → normalize.py. 1.61 × 0.64 m, 1.15 m tall, 32k tris (TRELLIS shell-soup floor, within decision-#2's 2577k for focal props). Provenance: 100% synthetic/on-device (FLUX.2-klein-4B + TRELLIS.2 on the M3 Ultra), no third-party source — cleanest licence zone. Replaces the R3 4 m till-less counter for the interior counter slot (Lane C maps countercounter_till); the balcao 4 m counter stays for long-bar use.

Round 5 — E1: hero-prop bake-to-lowpoly (un-parked)

The 6 heavy TRELLIS hero props were 25k77k 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 19 primitives / 13 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 FloydQuiet Milk Bar/ The Galahs) so there is zero real-trademark leakage, yet it reads as plausible AU record stock, and the same source row yields the same parody every build. Verified deterministic + token-disjoint.

Pipeline PROVEN end-to-end with --sample 24 (synthetic solid-colour sleeves): 24 → one 2048 atlas

  • index, UVs correct, price bands (bargain/standard/collector/grail) working; sample pack staged to web/assets/models/ so Lane C can wire the consumer against the real index format now.

The real 200-400 pack is blocked on two external deps (flagged to Fable):

  1. GODVERSE_DSN — the dealgod Postgres DSN (creds). Postgres is up on localhost:5432; the builder reads the DSN from env (I don't hunt for creds). --from-db --count 300 runs once it's set.
  2. Lane C's pack schema (their task 1, not yet posted) — I built to the proposed fields above; INDEX_FIELDS adjusts to C's LANE_C_NOTES contract when it lands. Provenance of the real pack: source = Discogs metadata + cover images (recordgod), metadata parody-transformed (no real titles/artists ship); covers are real release art (read-only, non-economy per decision #2). Small book/toy packs are a follow-up once the record pack lands.

Round 8 — E1/E2: book + toy stock packs + pack-QA gate

E1 — pipeline generalized (build_stock_pack.py now kind-parametrized: record 256×256 sleeves, book 96×256 tall-thin spines, toy 256×256 boxes). Per-kind parody word banks; same laws as the record pack (build-time extract, deterministic parody-transform of metadata, atlas WebPs + JSON index to C's schema — stock_<kind>_index.json, C reads uv not cell). Book/toy sample packs built + staged (web/assets/models/, ?localdepot) so Lane C wires book/toy shelves now; indexes committed to pipeline/packs/ as the contract. Parody verified deterministic + no distinctive-token leakage across all 3 kinds ("Nevermind"/"Nirvana" → "Endless Blue"/"Mullet Milk Crates"; "The Hobbit" → "Under the Homestead"/"C. Underwood").

E2 — pack-QA gate (validate_pack.py, wired into validate_manifest.py → qa gate 3): every pipeline/packs/stock_*_index.json entry must have a valid kind, in-range non-inverted UVs, a known price band, non-empty title/artist, unique id, and an atlas listed in atlases[] — a bad bake fails qa loudly (proven: inverted-UV + empty-title → exit 1). qa --strict GREEN 5/5.

Blocker (flagged to Fable/John — real data): the real ~150250-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) ⋈ releaserelease_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 (~6085 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 ~3040 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 MB1 GB texture budget. The stock packs (181 MB) remain the dominant swing but no longer blow past the base. No compression pass or lazy-load required for the tag. Two follow-ups flagged for v3, not now (bigger than a ship-round change):

  1. stockpack cache-cap (Lane C's stockpack.js): dispose non-current shop-type atlases on room-type change → bounds stock VRAM to ≤134 MB (one type) instead of accreting all three.
  2. KTX2/Basis GPU-compressed atlases: 48× 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.