look.glb — DONE, published, verified. Looking_Around.fbx (675 KB) from the CACHED tailnet stash (not re-downloaded — the asset-pipeline skill's law) -> fbx_to_clip.py -> web/models/peds/look.glb: mesh-free, 65 mixamorig joints, 195 tracks, inverseBindMatrices present, 136 KB. Structurally identical to the sit.glb precedent on every measurable field (meshes/nodes/skins/joints/bind), so D's _canon binds it exactly like walk/idle/sit. Depot: published to digalot.fyi/3god under John's R22 standing authorization for Lane E assets (_published.json 43 -> 44), verified by fetching the bytes BACK from the public depot: https://digalot.fyi/3god/a/look.glb -> HTTP 200, 139,640 bytes, sha1 identical to local. -> D: it's ready. Gate it behind an opt the way rigs.js gates sit (loadPedFleet(base, { sit })) — a raw fetch would breach the ?classic=1 zero-fetch-delta covenant, same as sit.glb. `lean` DOES NOT EXIST — the spike's premise is half wrong. The brief and kickoff both say "two of the 34 already-cached clips (lean, look-around)" and "Spike 1 needs nothing from anyone". look-around is cached; lean is not, anywhere. Measured, not assumed: the stash's 34 clips contain no lean; `find ~/Documents -iname "*lean*"` on ultra returns nothing (not mixamo-fetch, not character_kit, not 3D=models, no FBX by that name at any depth); and the fetch tool cannot help unattended — `node fetch.cjs search "lean"` -> "FATAL nothing on debug port 9222 — quit Brave and relaunch with --remote-debugging-port=9222". It needs John's logged-in Brave session (his browser, his Mixamo account); I won't relaunch it. I did NOT substitute a lookalike. `Bored` is the nearest stash clip and it is a standing weight-shift idle, not a wall-lean — D's half explicitly needs shopfront adjacency ("lean against a wall wants the same care"), so a standing idle would quietly change what Spike 1 proves. Half a spike, honestly, beats a whole one that lies. -> John: one small unblocker — relaunch Brave with --remote-debugging-port=9222 and open a Mixamo tab; the fetch+convert is minutes once the token exists. Instrument LOD — D killed it and D's measurement is better than mine. I had built the five _lo variants in scratch (14k -> 3500/3500/4000/6000/3000) when D's commit landed saying "E: STOP — do not build the _lo variants". Nothing shipped; scratch discarded. D measured the rooms an LOD must fire in: max diagonal 15.2m in the largest venue, so the player can never be far enough — practical win 0 tris. My own reading reached the same verdict by another route, recorded as corroboration: C's §4 already says "the interior gate is DRAWS; tris are the STREET budget (200k)", and an LOD swap does not change draws at all (same object, same material, fewer tris) — the item's entire win landed on a metric no interior gate reads. Two independent measurements, one verdict. Salvage if it ever returns: at ~3k the guitar_amp SHREDS (white gaps punched through its silhouette — confirmed against the shipped 14k thumb as a decimation artifact, not a source defect, per the R22 food_cart discipline); it needs 6k to read clean. The other four were clean at 3-4k. -> A: your own file's comment now contradicts its code. Chasing why the fingerprint read 0x5f76e76 rather than the 0x3fa36874 I'd seen every round, I confirmed it is LEGITIMATE — your R27 w5 facade fix re-pinned 51 goldens under the amended covenant. But selfcheck.js:267 pins hash: 0x5f76e76 while selfcheck.js:301 still says "the synthetic golden above stays 0x3fa36874". Code and comment disagree by 34 lines — the exact species as R24's validator bug (my docstring said nested, my code read top-level); the next reader trusts whichever they read first. Your file, your call; flagging per the round's "flag loudly if anything disagrees". Gates: selfcheck ALL GREEN 156,352/156,352 · qa.sh --strict 6 passed / 0 failed. look.glb moves NO golden — proved by moving it aside and re-running: the fingerprint is identical either way, because an unreferenced clip is never fetched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1174 lines
91 KiB
Markdown
1174 lines
91 KiB
Markdown
# LANE E — cross-lane notes
|
||
|
||
## Round 28 — Spike 1: half of it shipped, half of it doesn't exist ⟨v5.1⟩
|
||
|
||
**1. `look.glb` — DONE, published, verified.** `Looking_Around.fbx` (675 KB, from the cached tailnet stash,
|
||
not re-downloaded — the skill's law) → `fbx_to_clip.py` → **`web/models/peds/look.glb`**: mesh-free, **65
|
||
`mixamorig` joints, 195 tracks, inverseBindMatrices present, 136 KB** — structurally identical to the
|
||
`sit.glb` precedent on every field I can measure (`meshes/nodes/skins/joints/bind`), so D's `_canon` binds
|
||
it exactly like walk/idle/sit. Depot: published to `digalot.fyi/3god` under John's **R22 standing
|
||
authorization for Lane E assets** (`_published.json` 43 → 44). **→ D: it's ready.**
|
||
|
||
**2. 🚩 `lean` DOES NOT EXIST — the spike's premise is half wrong, and only John can unblock it.** The
|
||
brief and the kickoff both say "two of the 34 already-cached clips (lean, look-around)" and "Spike 1 needs
|
||
nothing from anyone". **`look-around` is cached. `lean` is not — anywhere.** Measured, not assumed:
|
||
- The stash's 34 clips contain no lean (`Agreeing · Bored · Breathing_Idle · Counting · … · Looking_Around
|
||
· … · Yawn`).
|
||
- `find ~/Documents -iname "*lean*"` on ultra: **nothing** — not in `mixamo-fetch`, not in `character_kit`,
|
||
not in `3D=models`, no FBX by that name at any depth.
|
||
- The fetch tool can't help unattended: `node fetch.cjs search "lean"` →
|
||
`FATAL nothing on debug port 9222 — quit Brave and relaunch with --remote-debugging-port=9222`. It needs
|
||
**John's logged-in Brave session** (his browser, his Mixamo account) — **I won't relaunch it.**
|
||
|
||
**→ John, one small unblocker (adds to your two):** relaunch Brave with `--remote-debugging-port=9222`,
|
||
open a Mixamo tab, and either run `node fetch.cjs search "lean"` yourself or tell me and I'll fetch +
|
||
convert it in minutes (the path is proven twice now). **I did NOT substitute a lookalike.** `Bored` is the
|
||
nearest stash clip and it's a standing weight-shift idle, not a wall-lean — D's half explicitly needs
|
||
shopfront adjacency ("`lean` against a wall wants the same care" as the scale-aware traps), so a standing
|
||
idle would quietly change what Spike 1 proves. Half a spike, honestly, beats a whole one that lies.
|
||
|
||
**3. Instrument LOD — D killed it, and D's measurement is better than mine.** I'd built the five `_lo`
|
||
variants in scratch (14k → 3500/3500/4000/**6000**/3000) before D's commit landed saying *"E: STOP — do not
|
||
build the _lo variants."* **Nothing shipped**; scratch discarded. D measured the rooms an LOD would have to
|
||
fire in — **max diagonal 15.2 m in the largest venue, so the player can never be far enough: practical win
|
||
0 tris.** That's decisive in a way my argument wasn't. My own reading agreed on the verdict by a different
|
||
route, and it's worth recording as corroboration: **C's §4 already says "the interior gate is *draws*; tris
|
||
are the *street* budget (200k)"** — and an LOD swap doesn't change draws *at all* (same object, same
|
||
material, fewer tris). So the item's entire win landed on a metric no interior gate reads. Two independent
|
||
measurements, one verdict. **The oldest tri item is dead, correctly.**
|
||
|
||
*(One salvage worth keeping if it ever returns: at the brief's ~3k the **guitar_amp shreds** — white gaps
|
||
punched through its silhouette, confirmed against the shipped 14k thumb as a decimation artifact and not a
|
||
source defect, the R22 `food_cart` discipline. It needs **6k** to read clean. The other four were clean at
|
||
3–4k.)*
|
||
|
||
**4. 🚩 → A: your own file's comment now contradicts its code.** Investigating why the fingerprint read
|
||
`0x5f76e76` instead of the `0x3fa36874` I'd seen every round, I confirmed it's **legitimate** — your R27 w5
|
||
facade fix re-pinned 51 goldens under the amended covenant. But `selfcheck.js:267` pins
|
||
`hash: 0x5f76e76` while **`selfcheck.js:301` still says "the synthetic golden above stays 0x3fa36874"**.
|
||
Code and comment disagree by 34 lines. That's the exact species as R24's validator bug (my docstring said
|
||
nested, my code read top-level) — the next reader trusts whichever they read first. Your file, your call;
|
||
flagging per the round's "flag loudly if anything disagrees".
|
||
|
||
**5. Gates.** selfcheck **ALL GREEN 156,352/156,352** · `qa.sh --strict` **6 passed / 0 failed**. `look.glb`
|
||
moves **no golden** — proved by moving it aside and re-running: fingerprint identical either way, because an
|
||
unreferenced clip is never fetched. The `?classic=1` covenant is untouched: like `sit.glb`, a raw fetch
|
||
would breach the zero-fetch-delta rule, so **D must gate `look` behind an opt** the way `rigs.js` gates
|
||
`sit` (`loadPedFleet(base, { sit })` → the same shape for `look`). Depot-live verified by fetching the
|
||
bytes back: `https://digalot.fyi/3god/a/look.glb` → **HTTP 200, 139,640 bytes, sha1 identical to local**.
|
||
|
||
## Round 27 — provenance v5-final, and the roster gap the docs freeze caught ⟨v5.0⟩
|
||
|
||
**1. THE BUG THE FREEZE FOUND: `redhill_godverse` was on disk but NOT in the roster — for three rounds.**
|
||
The v5 provenance table needed real numbers, so I measured instead of carrying the v4 row forward. The
|
||
count didn't match: **23 caches on disk, 22 rostered.** The missing one was **`redhill_godverse` — the
|
||
town the entire v5 epoch walks to.**
|
||
|
||
**Mine, squarely.** `index.json` is Lane E's file (`write_index()`). G's `redhill_godverse` landed in R24
|
||
(`86e2985`) *after* my last index run (`5815dfb`), and nobody re-rostered it. **An addition isn't done when
|
||
the cache is written — it's done when the roster is regenerated.** That's the exact mirror of my own R24
|
||
lesson (*"a retirement isn't done when the cache is deleted; it's done when the consumers are swept"*) —
|
||
I wrote the lesson for deletions and then missed its reflection for additions.
|
||
|
||
**Why four green rounds never caught it — worth reading, because it's a gate-shape lesson.** Nothing was
|
||
broken *for the gates*: `selfcheck` enumerates `web/assets/towns/*.json` **directly** (so it saw the town
|
||
and A even pinned its goldens), and F's crate smoke boots **by key** (`?town=redhill_godverse`). The
|
||
roster's *only* consumer is **B's HUD selector** (`hud.js`, my R20 ledger #2 design). So the single symptom
|
||
was that **a player could not choose the headline town from the dropdown** — invisible to every automated
|
||
check by construction. A gate that reads around the artifact under test cannot test it: the same species as
|
||
the vacuous-gate law, one layer out. **Fixed:** re-ran `write_index()` → **23 rostered, 23 on disk, 0
|
||
missing**; only `index.json` moved (every cache byte-identical again); selfcheck **ALL GREEN 156,212**,
|
||
golden `0x3fa36874` untouched; `qa.sh --strict --matrix` **7/0/0**.
|
||
|
||
**→ F: this matters for #5, the tour.** The money shot walks Musgrave Road into Monster Robot Party. If any
|
||
tour frame reaches that town **through B's selector** rather than by key, it could not have found it before
|
||
this commit — by key it always worked. Worth one glance at your framing path.
|
||
**→ B:** the dropdown now carries both godverse towns. Your `${t.state ? ', ' + t.state : ''}` handles their
|
||
empty `state` gracefully — no change needed, and the defensive write is why this cost nothing cosmetically.
|
||
**→ G:** your godverse caches carry no `state`, so they roster as `state:''`. Harmless today (B degrades
|
||
cleanly). If you want "Red Hill, QLD" in the picker, emit `state` in the cache — my `write_index()` already
|
||
prefers the cache's own field, so it needs nothing from me.
|
||
|
||
**2. Provenance v5-final (ledger #6).** The ⟨v5.0-FINAL⟩ rows are measured, not inherited: the town pack
|
||
row was still claiming **v4's "22 towns / 1192 shops"** — stale since my R24 ballarat retirement and redhill
|
||
build (now **21 real / 1196 / 299 heroes**, plus both godverse towns = 23 rostered). Added the two v5 art
|
||
rows: the **real-stock atlas** (1 crate, 120 items, Monster Robot's own POS) and the **mint atlases** (14
|
||
crates, 224 items, seeded from real dealgod listings). **These are the table's first 🟡 rows** — everything
|
||
before v5 was 🟢 web-ok; real product photography is the first asset class this project ships that a human
|
||
must clear before public release.
|
||
|
||
**3. The licence line's story, written down as a mechanism (ledger #6).** The 🟡 rows are guarded by exactly
|
||
one printed line, so the freeze records why it took two rounds to make honest: R23's glob matched 0 files
|
||
and returned 0; R25's **permissive `or` fallback let the print go silent while the check still passed** —
|
||
the flag stopped reaching a human on real product photos while the gate said OK. Now: one spelling, one
|
||
lookup with one owner, and the line is **counted** (judged N ⇒ N lines, or FAIL). **Both zones print** —
|
||
mint carries the same in-house-green flag, because minting changes whose stock a crate *claims* to be,
|
||
never whose photograph it *is*. Frozen today at **15 crates, 15 flags, every one in front of a human.**
|
||
|
||
## Round 26 wave 4 — the mint-id fence: my check was faithful, the rule was stale ⟨v5.0-beta⟩
|
||
|
||
**The red was mine and the fix is C's ruling.** My R25 slot-id check demanded `sku_` on every real-stock
|
||
pack — faithfully implementing C's R25 line, which was **written before mint existed**. It then failed all
|
||
14 of G's mint crates, whose ids honour its every intent: non-positional, seeded, stable, zero reuse. C's
|
||
ruling names it exactly: *"the rule was the stale thing, not E's check."* Fourth hold of the epoch, and
|
||
the fourth to trace to **a rule meeting a case written before the case existed.**
|
||
|
||
**The fix is a fence, and it is STRONGER than the rule it replaces — that was the thing to prove.** The
|
||
easy green was to drop the prefix check. Instead the form is now sourcing-scoped per C §7.2a (R26 w4):
|
||
`sourcing:"real"` ⇒ `sku_` (a POS copy on a shelf) · `sourcing:"mint"` ⇒ `mint_` (a dealgod listing
|
||
standing in for one). **A crate wearing the other's prefix FAILS.**
|
||
|
||
**Why this is a money bug and not a naming bug — G's judgment, now law.** G refused to file dealgod listing
|
||
ids under `sku_` to go green the easy way. They were right: **tier-2's sold-means-gone looks up POS skus.**
|
||
R24's id collision entered the *wrong shop*; the same collision in the money layer **sells a real record
|
||
out of a real shop to satisfy a minted stand-in.** The gate's error says that, in those words — the cost,
|
||
not the rule.
|
||
|
||
**Proved 7/7 — that the fence bites, not merely that the red stopped.** G's real crate (`sku_…`) → PASS ·
|
||
G's mint crate (`mint_…`) → PASS · **mint filed under `sku_` (the exact shortcut G refused) → FAIL**, naming
|
||
the fence and the real cost · real wearing `mint_` (the reverse) → FAIL · **positional ids still FAIL in
|
||
BOTH sourcings** (the old rule's whole intent, retained) · duplicate ids → FAIL. Loosening would have
|
||
passed the first two and lost the last four; this passes the first two and keeps all four.
|
||
|
||
**Both older suites brought forward rather than left to rot.** The R26 suite's mint fixture wore `sku_`
|
||
ids, so the fence correctly rejected it — a fixture that fails for a *new* reason has stopped testing what
|
||
it was written for. Moved to the `mint_` space: **17/17**. R25 licence suite: **8/8**, tally still firing
|
||
**via the tally**. This is the third round running where the near-miss was a test going quietly stale
|
||
around a changing contract; it is the failure mode I now look for first.
|
||
|
||
**Gates:** `atlas-QA OK — 15 per-shop atlas(es) valid, 15 licence line(s) shown, 0 warning(s)` (1 real +
|
||
14 mint, every one showing its flag to a human) · `validate_manifest` exit 0 · **`qa.sh --strict --matrix`
|
||
7 passed / 0 failed / 0 warn — QA GREEN.** F's 6/1/0 is now 7/0/0. **→ F: the beta is yours to tag.**
|
||
|
||
## Round 26 — the sourcing arm + the atlas manifest ⟨v5.0-beta⟩
|
||
|
||
**1. The field is `sourcing`, NOT `tier` — and reading C's contract instead of the brief is the only
|
||
reason this gate isn't already broken (ledger #3).** The R26 brief says *"the validator grows the tier
|
||
arm… `tier` required on every atlas"*. **C ruled it cannot be `tier`** (§7.2b): `tier` is already the
|
||
charter's ladder rung (0 parody / 1 static-real / 2 live) and **a mint atlas is also rung 1** — same
|
||
mechanism, different data origin. Overloading it would put two meanings in one name: the same species as
|
||
the plan-id/godverse-id collision and the positional ids. Had I built the brief's word, my gate would have
|
||
demanded `tier` while G emitted `sourcing` — **R23 exactly, re-run**. The contract's author is the
|
||
authority for the contract's shape. **Built to C §7.2b.**
|
||
|
||
**2. The honesty separation, asserted both directions.** `sourcing ∈ {real, mint}` required (vacuous-gate
|
||
law: no `sourcing` ⇒ FAIL — a crate that can't say where it came from must never pass as one that can).
|
||
Then: **mint must carry no POS claim, real must carry the POS provenance it claims.**
|
||
|
||
**→ C: §7.2b says "a mint index MUST NOT carry POS-snapshot fields" but does not enumerate them, so I
|
||
named the set — please correct it if it's wrong.** I assert on `crate`, `provenance.source.pos_db`,
|
||
`provenance.source.pos_dump` — the fields that claim *a specific shop's actual inventory*. I deliberately
|
||
**excluded `snapshot`**: §7.3 requires it on every atlas as the determinism pin, and a mint bake has its
|
||
own honest source snapshot (the dealgod dump it sampled). If a mint bake legitimately needs any field in
|
||
my set, say so and the set changes. The error names the exact field found, so a divergence shows up as a
|
||
diagnosis rather than a mystery.
|
||
|
||
**3. The manifest (ledger #6), consistency both ways + one hole C's rule implies but doesn't spell out.**
|
||
`stock_godverse/index.json` per §7.2d. An atlas on disk absent from the manifest FAILS (no entry ⇒ no
|
||
fetch ⇒ that crate silently never loads — an unreachable atlas is not a passing atlas); a manifest entry
|
||
with no atlas FAILS (the wire fetches it and 404s — the exact noise the manifest exists to kill). C's
|
||
"absent manifest ⇒ legitimate zero-shop state" is honoured: **no atlases + no manifest ⇒ SKIP, not an
|
||
error.** But **a manifest that PROMISES shops while zero atlases exist now FAILS** rather than skipping —
|
||
C's rule scopes "absent" to the empty tree, and a tree where something on disk still asserts atlases exist
|
||
is the vacuous shape wearing a hat. Flagging it since it's my reading, not C's words.
|
||
|
||
**4. Proved before handing G the gate — 17/17, including a mint crate that does not exist yet.** The R25
|
||
rule (never hand a lane a gate that fails on compliance) applied: I hand-built G's likely R26 emit and a
|
||
seeded mint index and ran them. Real → PASS · **mint → PASS with its licence line printed** (ledger #3's
|
||
"prints for both" — verified, not assumed) · real+mint side by side → PASS · mint carrying `crate` → FAIL ·
|
||
mint carrying `pos_db` → FAIL · **real carrying no POS provenance → FAIL** (the reverse direction) · no
|
||
`sourcing` → FAIL · `sourcing:'realish'` → FAIL · atlas absent from manifest → FAIL · manifest entry with
|
||
no atlas → FAIL · declared type not on disk → FAIL · **manifest says mint, index says real → FAIL** ·
|
||
manifest missing while atlases exist → FAIL · unparseable manifest → FAIL · empty tree → SKIP · empty
|
||
manifest → SKIP · manifest promising a shop with no atlases → FAIL.
|
||
|
||
**5. The R25 suite was going stale and I brought it forward rather than let it rot.** Adding `sourcing` +
|
||
the manifest meant every R25 licence case started failing for a NEW reason — which would have left the
|
||
licence arm *untested while appearing tested*. Updated to the R26 contract; **8/8 still green, and the
|
||
tally tripwire still fires VIA THE TALLY** (`via_tally=True`), not incidentally. Worth noting the near-miss:
|
||
the manifest error briefly shadowed the tally in the harness, which is precisely how the tally died the
|
||
first time — a test that fails for a new reason has stopped testing what it was written for.
|
||
|
||
**6. The gate is RED until G lands, by design (2 errors: no `sourcing`, no manifest).** Same as R25: G is
|
||
in my wave, parallel. It goes green when G's re-emit + manifest land, with no change from me.
|
||
|
||
**→ F: one thing before the alpha tag.** There is still **no `v5.0-alpha` tag**, and C's + B's **beta**
|
||
commits (`4f0adf1`, `2f4ab59`) are now on `main` ahead of wave 0's close. Tagging `HEAD` would produce a
|
||
`v5.0-alpha` whose tree contains beta work your green verdict never covered — the mirror of the reason you
|
||
held three times. The alpha's tree is **`e7d58ea`** (wave 0 COMPLETE, my `9b1df5c` beneath it). Your call
|
||
and your tag; flagging it because "the tag must certify what was proven" cuts both ways.
|
||
|
||
## Round 25 — the licence line, made unmissable + the sku form ⟨v5.0-alpha⟩
|
||
|
||
**1. The bug was not a spelling — it was a DIVERGENCE, and the fallback was the culprit (ledger #2).**
|
||
The R24 gate printed `OK — 0 warnings` on a real shop's product photos while saying **nothing** about
|
||
their licence. Two paths read one field with different spellings:
|
||
|
||
```
|
||
the CHECK: lic = prov.get("licence") or prov.get("license") ← permissive: passed either spelling
|
||
the PRINT: lic = prov.get("licence") ← strict: British only, behind `if lic:`
|
||
```
|
||
|
||
While G's artifact was British they agreed. **G's R24 keyed re-emit moved it to US `license`** — the
|
||
check still passed, the print silently resolved to `{}`, and `FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE`
|
||
stopped reaching a human. **The `or` fallback WAS the bug**: it made one path permissive so it could not
|
||
feel the other path break. This is **my R21 stale-roads lesson one layer up** — *a check applied to one of
|
||
two symmetric paths is a trap with a fuse on it* — and I wrote that lesson down and still shipped it
|
||
again. Fixed structurally, not by adding the missing spelling:
|
||
- **One spelling, no fallback.** British `licence` now ERRORS and names C's §7.3 retirement.
|
||
- **One lookup, one owner.** `validate_index` returns the evidence string; `main` prints what it is
|
||
handed and never re-looks-up the field. There is no second path left to drift.
|
||
- **The evidence line is COUNTED.** Judged N atlases ⇒ N licence lines shown, or FAIL.
|
||
|
||
**2. My own first fix had the same disease, and my own test passed for the WRONG REASON.** I added the
|
||
tally tripwire — then `main()` *also* appended its own missing-licence error, so `all_errs` was never
|
||
empty when the tally would fire: **the tally was unreachable dead code.** The test went green through the
|
||
other path and reported PASS. That is exactly the trap I recorded in R24 (*"a gate that fails for the
|
||
wrong reason is a gate that will pass for the wrong reason later"*) — one round later, on me, in the very
|
||
file where I wrote it. The fix: the field check owns the precise error; the tally sits *after* the
|
||
`all_errs` return and exists **only** for the day that check goes permissive again. Verified by stubbing
|
||
`validate_index` permissive — it now fails **via the tally** (`via_tally=True`), not incidentally.
|
||
**Proved, 8/8:** G's re-emit → PASS with the flag shown · `license` gone / empty → FAIL · British at top
|
||
level → FAIL · British nested → FAIL · `attribution` / `snapshot` gone → FAIL · **the R24 silent shape →
|
||
FAIL** · permissive-check simulation → FAIL via the tally.
|
||
|
||
**3. C §7.2a — the sku form asserted, and I verified C's claim myself before building to it (ledger #3).**
|
||
Independently measured: **120/120 godverse ids also exist in the generic pack** — F and C are exactly
|
||
right, and an id-equality gate cannot discriminate while that holds. G's snapshot carries **120/120 unique
|
||
skus**, so the re-emit was feasible, and I proved the compliant shape passes **before** handing G the gate
|
||
(4/4: `sku_<POS sku>` → PASS · positional → FAIL · sku-but-duplicated → FAIL · one id sneaking back into
|
||
`rec_` → FAIL at 1/120, so it can catch a *real* collision, not just an all-or-nothing one).
|
||
|
||
**→ C: two things I did beyond your literal ask — say if you disagree.**
|
||
- Your ask was "id present, unique within the pack, `sku_`-prefixed". I also assert the **actual property
|
||
your rule exists to guarantee**: the pack's ids are **disjoint from the shipped generic packs**, read
|
||
from `web/assets/models/stock_*_index.json`. The prefix says ids *should* be disjoint; this measures
|
||
whether they *are*, and it catches the reverse drift (a generic pack wandering into `sku_`) that a
|
||
prefix check never could. If the generic packs are absent it **warns** rather than passing silently.
|
||
- I flag British `licence` **anywhere**, including inside the nested `provenance{}` "colour" block. Your
|
||
§7.3 retires the spelling and R25 is what a spelling fork costs, so I read the retirement as global. If
|
||
you meant the block to stay G's business, say so and I'll scope it to the four gate fields.
|
||
|
||
**4. The gate was RED by design for one wave, and that was the point.** Between my commit-readiness and
|
||
G's re-emit the collision was real and unfixed, so `validate_atlas` failed — deliberately. A gate that
|
||
stayed green on a defect F had just found would be the vacuous species wearing a different hat. It went
|
||
green the moment G landed `4314622`, with no change from me.
|
||
|
||
## Round 24 — the alpha completion: redhill, the honest validator, ballarat ⟨v5.0-alpha⟩
|
||
|
||
**1. `redhill_real` — the crate's real street (ledger #3). BUILT.** One bounded Overpass fetch under the
|
||
standing authorization. **Red Hill, Brisbane** · centre `-27.4553, 153.0064` · span 2.4 km · **36 shops
|
||
(9 heroes + 27 texture, −29 subsampled) · 884 roads** · median NN 73 m, hub 5/36. **Musgrave Road is in
|
||
`roads[]` — 41 ways, kinds `[primary, residential]`.** That was the round's point: Monster Robot Party's
|
||
real shop now has its real street. Raw snapshots in `_raw/`, ODbL, C's mapping, same discipline as the
|
||
pack. **→ Lane G: this is your donor** for `godverse_town.py redhill_real redhill_godverse`.
|
||
|
||
**→ G AND A: TWO HAZARDS ON THIS DONOR. Please read before you seat the shop.**
|
||
|
||
> **(a) The shop is NOT in OSM.** There is no `shop=music` node and no "Monster Robot"/"monsterrobot"
|
||
> anywhere in the Red Hill bbox — I checked the raw snapshot, not just the emitted cache. It **cannot**
|
||
> arrive via my fetch; **G must inject it from the census** (id `3962749`) into `shops[]`. If G assumes
|
||
> the donor already carries it, the town ships without the one shop the release is about.
|
||
>
|
||
> **(b) THE SEATING HAZARD — from G's geocode, the shop seats on the WRONG STREET.** A's lift seats each
|
||
> shop on its **nearest edge**. Measured against the real `roads[]` geometry, from G's geocode
|
||
> `-27.4553, 153.0064`:
|
||
>
|
||
> | distance | street | kind |
|
||
> |---|---|---|
|
||
> | **38.6 m** | **Zig Zag Street** | residential ← **A would seat it HERE** |
|
||
> | 46.1 m | Craig Street | residential |
|
||
> | 51.6 m | **Musgrave Road** | primary ← the real address (147 Musgrave Rd) |
|
||
>
|
||
> Three streets are nearer than the addressed one, so Monster Robot Party lands on Zig Zag Street and the
|
||
> round's headline quietly becomes false. A street-address geocode sitting 52 m off its own centreline is
|
||
> itself the tell — the pin is roughly the block, not the shopfront.
|
||
>
|
||
> **The measured fix (E's recommendation, G's call):** the point on the real Musgrave Road centreline
|
||
> nearest G's geocode is **`-27.455169, 153.006902`**. Seating there: **0 m to Musgrave Road, next
|
||
> candidate 43 m away** — unambiguous, with margin. It moves the shop 51.6 m; the residual error *along*
|
||
> the street is G's geocode error and is below the game's resolution. **Caveat, stated plainly:** this
|
||
> snaps to the right street, it does not verify the house number — I have no address data to confirm
|
||
> where #147 sits along Musgrave Rd.
|
||
>
|
||
> **The robust fix (A's call, likely v5.x):** a godverse shop knows its addressed street; A's lift could
|
||
> prefer a name-matching edge over the merely-nearest one. That fixes the whole class, not this one shop.
|
||
> The snap above is enough to ship the alpha honestly.
|
||
|
||
**2. `validate_atlas` — made honest (ledger #4). F's catch was right; there were FOUR bugs, not one.**
|
||
The R23 gate could pass without ever touching its subject — the vacuous-gate law's exact species. Fixed:
|
||
|
||
| # | bug | why it mattered |
|
||
|---|---|---|
|
||
| 1 | globbed the **published-contract** name (`stock_shop_<id>_index.json`) — a name the runtime can *never* load | matched **0 files** and returned 0. It passed identically whether atlases were absent, misnamed, or perfect |
|
||
| 2 | the **code read provenance top-level** while **my own docstring said nested** | G nested it (correctly). The gate would have failed G's real, correct atlas |
|
||
| 3 | `license` vs **`licence`** | same — a spelling fork silently failing a valid artifact |
|
||
| 4 | ceiling stale by one round | drifted from C's §7 |
|
||
|
||
Per the R24 **"the runtime is the authority"** ruling the gate now globs the path the loader fetches:
|
||
`stock_godverse/<godverseShopId>/stock_<type>_index.json`, `type ∈ {record, book, toy}`. **The vacuous-gate
|
||
law is implemented literally:** 0 matches **+ stray files** ⇒ **FAIL**, listing the unmatched paths; 0
|
||
matches **+ a truly empty dir** ⇒ `atlas-QA SKIP: no atlases present (0 files matched glob …)`, exit 0 —
|
||
it skips **loudly, with the glob printed**, so a silent pass can't hide. It also asserts identity
|
||
(`shop.godverse_id` == the directory name) — a falsifiable id equality, per the law's "prove it touched
|
||
its subject".
|
||
|
||
**Proved, not asserted** — four cases: empty ⇒ SKIP (loud, exit 0) · **misnamed (F's exact bug) ⇒ FAIL**
|
||
· unparseable ⇒ FAIL · **G's real atlas ⇒ PASS**: `3962749/stock_record_index.json — Monster Robot Party
|
||
— items=120 atlases=2 px=2048 OK`, licence printed, 0 warnings. **My own test found a fifth hole:** the
|
||
glob still matched the old `stock_shop_*` name, so the misnamed case failed only *incidentally* — I added
|
||
a `STOCK_TYPES` name check, and it now fails **for the right reason**. A gate that fails for the wrong
|
||
reason is a gate that will pass for the wrong reason later.
|
||
|
||
**→ Lane C:** built to the runtime + G's real artifact per the ruling. `LANE_C_PUB §7` **line 271** still
|
||
names the contradictory `stock_shop_<godverseShopId>_index.json` — when your corrected contract lands I'll
|
||
reconcile; if it names anything other than the above, the gate and the loader disagree again.
|
||
|
||
**3. Ballarat — RETIRED (my call, counted). A different illness from Toowoomba.** Full rationale in
|
||
`SOURCES.md` and the commented config entry. The short version, because the distinction matters:
|
||
**Toowoomba was dead everywhere. Ballarat is alive exactly where the game isn't.** It has 103 POIs and a
|
||
genuinely dense core (densest 400 m disc = **47** POIs; Toowoomba's was **3**) — but that disc is
|
||
cafés/clothes/bakeries, while the op-shops sit 854 m / 1042 m / 1709 m out. Heroes and density don't
|
||
coincide, so no bbox holds both:
|
||
|
||
| span | shops | heroes | median NN | hub ≤160 m | op-shops |
|
||
|---|---|---|---|---|---|
|
||
| 1.0 km (the live core) | 8 | 2 | 184 m | 4/8 (50%) | **0** ← a thrift town with no thrift shop |
|
||
| 2.0 km | 12 | 3 | 186 m | 3/12 (25%) | 1 |
|
||
| 3.0 km (as shipped) | 20 | 5 | 255 m | 4/20 (20%) | 2 |
|
||
|
||
Every row is worse than any shipped town (darwin 68 m, glebe 60 m, redhill 73 m, katoomba 22 m).
|
||
|
||
**→ A, D: the binding constraint was MINE, not OSM's — and it's a real finding.** With 2 heroes the 3×
|
||
texture cap keeps 6 of 47 texture POIs, and **the subsample is spatially blind** (deterministic hash rank
|
||
over osmId). So in any hero-poor/texture-rich town the survivors scatter *no matter how tight the box* —
|
||
**part of the 255 m A's warn caught was my own cap's fingerprint, not Ballarat's.** Seeding texture near
|
||
heroes would fix the class, but it moves **every** town's golden → I'm filing it as a **v5.x proposal**,
|
||
not doing it in a release round.
|
||
|
||
**→ A: I OVERSTATED MY R23 ADVICE — please don't encode it as a threshold.** I told you the hub fraction
|
||
is the discriminator. It isn't, on its own: **glebe — shipped and healthy — reads 7/52 (13%)**, and
|
||
redhill reads 5/36 (14%). A hub threshold would false-flag both. What actually held up:
|
||
- **median NN is the robust primary** (dead: toowoomba 396 m, ballarat 255 m; alive: 22–77 m).
|
||
- **hub is only comparable at equal shop counts** — toowoomba 2/12 vs darwin 7/12 was a fair comparison
|
||
*because both had 12 shops*; hub% otherwise scales with town size and morphology.
|
||
- The R23 calibration point still stands and still matters: **your `validateTownCache` sees raw cache
|
||
lat/lon; D's numbers are seated (post-lift)** — darwin is 27 m seated but **68.3 m raw**, so a threshold
|
||
read off D's table will false-warn darwin.
|
||
|
||
**4. A methodology note I'm recording because it nearly bit me.** I simulated the re-bbox offline to pick
|
||
Ballarat's span; the sim predicted **68 m / hub 5-8** and I had already written those numbers into a
|
||
config comment as fact. The real build returned **184 m / 4-8** — *same* counts (2 heroes + 6 texture),
|
||
*different sample*: my re-implementation of the hash subsample picked a dense six where the pipeline picks
|
||
a spread six. **Never trust a re-implementation of the pipeline's own selection — run the pipeline.** The
|
||
sim's numbers would have shipped as a claim about a town nobody would have re-measured.
|
||
|
||
**5. Determinism receipt.** The ballarat retirement forced a full 21-town rebuild from the cached raw
|
||
snapshots (no refetches). **Every existing cache came back byte-identical** — only ballarat (deleted),
|
||
redhill (new) and `index.json` moved. Pack now **21 real towns · 1196 shops · 299 heroes**, 22 rostered
|
||
with Lane G's `newtown_godverse`. Selfcheck **ALL GREEN, 149,696 checks**, synthetic golden `0x3fa36874`
|
||
unchanged (the count moves with the roster: ballarat's 1144-edge graph out, redhill's 884 in).
|
||
|
||
**6. → A: THE GOLDEN PINS ARE YOURS AND MY ROUND MOVED THEM (the batched pin, ledger law).** Your own
|
||
selfcheck printed the exact values — no round trip needed:
|
||
|
||
```
|
||
REAL_TOWN_GOLDENS['redhill_real'] = 0x6046700f // ADD (new town, currently UNPINNED)
|
||
REAL_TOWN_GIG_GOLDENS['redhill_real'] = 0x673785ee // ADD
|
||
REAL_TOWN_GOLDENS['ballarat_real'] = 0xf0a9e8a1 // REMOVE (cache deleted — dead config)
|
||
REAL_TOWN_GIG_GOLDENS['ballarat_real'] = 0xf097c331 // REMOVE
|
||
```
|
||
|
||
The ballarat pins are now exactly what **your R23 note** called the toowoomba pin: *"a golden for a town
|
||
that cannot load is dead config: it can never fire, so it can never fail — same species as the vacuous-gate
|
||
law, one layer down."* Your words, my retirement, same fix. And note the mirror on the other side:
|
||
`redhill_real` currently **warns** (`⚠ base UNPINNED`) rather than failing, so **the round's headline town
|
||
is ungated until you pin it** — an unpinned town is the same vacuous species pointing the other way. Your
|
||
warn-with-the-hash design is what made this a two-line handoff instead of a bug hunt; worth keeping.
|
||
|
||
**7. → F: an R23 orphan I created and owe you.** `tools/qa/tour_shots.py` (your v4.0 release tour) still
|
||
shoots `plansrc=osm&town=toowoomba_real` for its `toowoomba_thintail` frame — I deleted that cache in R23
|
||
and never swept the consumers, so **re-running the v4 tour now breaks on a missing town.** It's your file
|
||
and it's a shipped historical artifact (the frame *was* the honest thin-tail proof), so I'm flagging
|
||
rather than editing: either re-point the frame at a surviving thin town or retire it with a note. Same
|
||
class, lower stakes: `web/js/citygen/gigs.js:134` names ballarat/toowoomba in a comment as thin-town
|
||
examples — stale prose only, no behaviour. **The general lesson for me: a retirement isn't done when the
|
||
cache is deleted; it's done when the consumers are swept.**
|
||
|
||
## Round 23 — v5.0-alpha: the atlas validator + the toowoomba call ⟨v5.0-alpha⟩
|
||
|
||
**1. `validate_atlas` — the G2a format gate (ledger #4).** `pipeline/validate_atlas.py`, wired into
|
||
`validate_manifest` so it runs in qa gate 3. It judges G's tier-1 atlases **from the committed files
|
||
alone — no dealgod DB, no network** (that's the point: the gate must run on m3). No atlases yet ⇒ clean
|
||
pass, so it's in place *before* G's lands. **→ Lane G: this is the format you build to.** It enforces:
|
||
|
||
| rule | source |
|
||
|---|---|
|
||
| provenance `{license, attribution, generator, snapshot}` present | charter law #3 ("every atlas carries provenance") + `snapshot` pins determinism |
|
||
| **no private-individual fields** on items (`seller`/`owner`/`email`/`phone`/…) | charter law #3 ("no private-individual data, ever") |
|
||
| 1 atlas/shop, **hard cap 2** | LANE_C_PUB §7.1 |
|
||
| **≤1024²**; 2048² only for a **60+ item** shop | LANE_C_PUB §7.2 |
|
||
| declared `atlas_px` == the atlas's **real** pixels; every listed atlas exists | index↔atlas consistency |
|
||
| schema + UV in range/not inverted, bands, dup ids, empty title/artist, atlas∈atlases[] | §7.3 (schema confirmed verbatim) |
|
||
|
||
The licence string is **printed** by the gate, not pattern-matched — clearing photos for public release is
|
||
a human call (charter law #3), so the gate surfaces it rather than pretending to rule on it. **Tested
|
||
against a 10-case matrix** (real 2048² WebP + 1024² PNG headers, dependency-free parser): good passes
|
||
clean; all 9 violations caught. A gate that's never caught anything isn't a gate.
|
||
|
||
**2. Toowoomba — RETIRED (my call, counted). I tried the re-bbox first; the data said retire.**
|
||
D's finding: DEAD — 0 patronage finds in 1417 checks. I re-bboxed 3.0 km CBD-wide → 1.6 km onto the
|
||
Ruthven St heart. Result:
|
||
|
||
| | median NN (the named metric) | hub ≤160 m (D's real test) |
|
||
|---|---|---|
|
||
| toowoomba @3.0 km | 395.7 m ❌ | 3/12 |
|
||
| **toowoomba @1.6 km (re-bbox)** | **89.4 m ✅ "passes"** | **2/12 (17%) — worst in the pack** |
|
||
| darwin (D: ALIVE) | 68.3 m | 7/12 (58%) |
|
||
|
||
**The re-bbox fixed the metric and made the town worse.** Toowoomba's shops are *pairs* strung along a
|
||
road, not a high street — close-in-pairs flatters median NN while no hub exists. Its densest 300 m
|
||
cluster is **3 shops** (below `MIN_TOWN_SHOPS`), so no tighter box exists either. Shipping it would have
|
||
gamed the number while leaving a town nobody shops in. **Retired** (config entry kept commented with the
|
||
rationale so nobody re-adds it blindly). Pack: **21 real + 1 godverse = 22 rostered · 1180 shops · all 8
|
||
states/territories** (QLD keeps westend, 11 heroes/44 shops). selfcheck **ALL GREEN 152015/152015**.
|
||
|
||
### ⚠️ → Lane A: two things before you freeze the spacing warn (#5)
|
||
1. **Your validator sees the CACHE (raw lat/lon); D's numbers are SEATED (post-lift).** They differ:
|
||
**darwin — D 27 m seated vs 68.3 m raw**; toowoomba 388 seated vs 395.7 raw (spread towns barely move,
|
||
clustered ones move a lot). **Calibrate off the raw spread, not D's table** — a threshold picked near
|
||
"darwin 27 = alive" would **false-warn darwin at 68.3**. Raw spread now: daylesford 11.6 · katoomba
|
||
22.4 · bowral 27.9 · … · launceston 76.8 · braddon 119.2 · **ballarat 254.6**. A ~200 m line has
|
||
daylight on both sides.
|
||
2. **Median NN alone is gameable — the hub fraction is the discriminator.** My re-bbox is the proof: NN
|
||
improved 4.4× while the town got worse. Consider warning on **hub density** too (shops within 160 m of
|
||
the densest shop). **My raw hub metric reproduces D's darwin exactly (7/12)**, so it's directly
|
||
encodable: alive ≈ 38–75% (castlemaine/darwin/launceston/daylesford), dead ≈ ≤20% (ballarat 20%,
|
||
retired-toowoomba 17%).
|
||
3. **ballarat is a second spread town** D's two-town comparison never covered: **NN 254.6 m, hub 4/20
|
||
(20%)** — it *will* trip your warn (correctly). Not retired: the ledger named toowoomba only, and a
|
||
warn is A's to raise, curation is mine to act on. Flagging for Fable's next-round call.
|
||
|
||
## Round 22 — v4.0 THE EPOCH CLOSE: the tri diet (mine) · notes · provenance · the G2a review ⟨v4.0⟩
|
||
|
||
### ⚠️ Ledger #1b — the `venue_night` tri diet: **MY four furniture GLBs. Fixed. → F: RE-MEASURE.**
|
||
|
||
B's #1a diagnosis named it exactly right and I own it: **149,161 of 163,015 tris (91.5%)** were four
|
||
GLB street props of mine. Buildings 348, posters 222 (A's cap worked — never the lever), pools 0. B's
|
||
sharpest point: **it isn't the density widening** — *road* density drives the bench/bin/shelter cadence
|
||
(~100 GLB props on a 966-edge real graph). Great call; the shop count was a red herring.
|
||
|
||
**The fix** — collapse-decimate via the R13 instrument tool (`decimate_props.py`), originals kept as `_hi`:
|
||
|
||
| prop | tris/inst | → | rendered (B's frustum-accurate) | → | saved |
|
||
|---|---|---|---|---|---|
|
||
| bench | 1,942 | **500** | 60,202 | 15,500 | **44,702** |
|
||
| bus_shelter | 7,998 | **2,500** | 55,986 | 17,500 | **38,486** |
|
||
| bin | 3,000 | **800** | 18,000 | 4,800 | **13,200** |
|
||
| food_cart | 4,991 | **1,000** | 14,973 | 3,000 | **11,973** |
|
||
| | | | **149,161** | **40,800** | **−108,361** |
|
||
|
||
**Projected view: ~54.7k** (B's 163,015 baseline) or **~113.6k** (vs F's 221,935 re-measure) — **PASS
|
||
the 200k gate either way**, 86–145k to spare. **F: the numbers are projections from B's per-instance
|
||
attribution — yours is the real measure.**
|
||
|
||
**Eyeballed every one (house law), and two needed re-cutting:** bus_shelter @1,000 **shredded** (its
|
||
thin roof plate is a voxel-remeshed shell — collapse tore it into spikes) → re-cut **@2,500**, clean;
|
||
bin @400 creased → **@800**, clean. bench @500 is perfect. food_cart @1,000 matches its original —
|
||
including the black base slab, which I confirmed against the pre-diet thumb from git: **that slab is in
|
||
the source model**, not a decimation artifact.
|
||
|
||
**Zero blast radius:** dims/footprints/heights **identical** → `manifest.json` **byte-identical** → B's
|
||
placement and A's goldens untouched. Published to the depot (4 diet + 4 `_hi` archives) — the GLBs are
|
||
git-ignored, so the depot IS the archive.
|
||
|
||
**→ Lane B, your second note, cause found (NOT fixed — deliberate).** The MeshPhysicalMaterial you spotted
|
||
on bench + food_cart is **`KHR_materials_specular`** in the glTF, and it **survives a Blender re-export**.
|
||
Fix = neutralise specular pre-export (or strip the extension) → three.js falls back to MeshStandardMaterial.
|
||
I did **not** do it this round: the gate is tris (cleared with margin), and changing a shipped prop's
|
||
shading in a release round without a visual re-verify is risk for no gate benefit. **Filed for v4.x** with
|
||
the cause. Same list: **food_cart is 3.1 MB on the depot — 28 textures now dominate** (tris are 1,000);
|
||
a texture-merge item, not a tri item.
|
||
|
||
**Process note on myself:** the depot push was blocked, correctly — I'd been carrying John's **R13**
|
||
approval (which named only the *instruments*) forward across rounds as if standing, because the briefs
|
||
kept saying "John's R13 authorization stands". Briefs aren't the user. John re-authorized explicitly and
|
||
granted **standing for Lane E assets** (stating what/where each time), so it's clean now — but the drift
|
||
was mine, not the classifier's.
|
||
|
||
## Round 22 (cont.) — notes refresh · provenance v4-final · the G2a review
|
||
|
||
**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-beta` with or without it. **The pack: 22 towns · 1192 shops · 298 heroes · all 8 states/
|
||
> territories.** selfcheck **ALL GREEN 154141/154141**, synthetic `0x3fa36874` unmoved, 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.json` is live — drop the `REAL_TOWNS` hardcode in `hud.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)
|
||
1. **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 `0x3fa36874` is UNMOVED**; fixtures frozen.
|
||
2. **`index.json` trips selfcheck's town glob** (a real seam — `selfcheck.js:467` takes *every*
|
||
top-level `towns/*.json` as 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 touch `selfcheck.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_real` is 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 `--verify` false-drift fixed.** → **Lane A: caches
|
||
> are live under `web/assets/towns/`; pin the per-town goldens** (printed below by selfcheck) and feed
|
||
> them through `plan_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`
|
||
> + `spawnRig` foot re-plant + F's seated-stature gate). **`sit.glb` delivered, 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 `walk` at 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.glb` absent from the *tailnet mirror*) is cosmetic: it's a depot-reuse asset
|
||
and IS live on the public `digalot.fyi` depot (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-video` gain 0.3→0.35 aligned
|
||
to the manifest; manifest `glb_law` string 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) |
|
||
| Ped clip **⟨v5.1⟩** | `look.glb` (E R28 — v6 Spike 1) — mesh-free, 65 `mixamorig` joints, 195 tracks, 136 KB; structurally identical to `sit`/`walk`/`idle` | Adobe **Mixamo** `Looking_Around.fbx` (675 KB) — from the **cached tailnet stash** (`johnking@ultra:~/Documents/mixamo-fetch/out/`), not re-downloaded | `fbx_to_clip.py` (Blender, mesh-strip) — the R16 path, unchanged | 🟢 Mixamo royalty-free (motion only, mesh stripped) — same zone as walk/idle/sit |
|
||
| Ped clip **⟨v5.1⟩ NOT BUILT** | `lean.glb` — **the source does not exist** (see R28 §1) | — | — | — |
|
||
| Town pack **⟨v5.0-FINAL⟩** | **21 real AU towns, all 8 states/territories** — **1196 shops** (299 heroes) + 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 towns *(Lane G's, listed for completeness)* **⟨v5.0-FINAL⟩** | `newtown_godverse` (72 shops) · **`redhill_godverse` (37)** — the town whose record shop is THE shop. Both rostered in E's `index.json` (**23 total**) | GODVERSE/thriftgod shop census + OSM road geometry; `redhill_real` is E's donor | `pipeline/godverse_town.py` (**Lane G owns this provenance**) | 🟢 ODbL 1.0 + GODVERSE census (G's own `license`/`attribution` fields) |
|
||
| Real-stock atlas **⟨v5.0-FINAL⟩** | **1 crate, 120 items**, 2×2048² webp — Monster Robot Party (`3962749`) | **the real POS** (`recordgod`) — crate 550 "DEEP HOUSE"; covers are **the shop's own product shots**, cached by dealgod | `pipeline/godverse_stock.py` (**Lane G owns**) — `snapshot`(DB-side, sha256-pinned) → `pack`(pure) | 🟡 **in-house-green — FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE** · `sourcing:"real"` · ids `sku_<POS id>` |
|
||
| Mint atlases **⟨v5.0-FINAL⟩** | **14 crates, 224 items**, 14×1024² webp — keyed hero shops with **no POS** (5 record · 6 book · 3 toy across both godverse towns; the 6th record crate is the real one above) | **real dealgod listings** (the sellers' own product shots) — a *seeded sample*, `random.Random(shop_id)`, deterministic | `godverse_stock.py` mint baker (**Lane G owns**) — same bake path, sampled source | 🟡 **in-house-green — same flag** · `sourcing:"mint"` · ids `mint_<listing id>` · **never claimed as that shop's stock** |
|
||
|
||
**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).
|
||
|
||
### ⟨v5.0-FINAL⟩ The licence line's story — why one printed line is a provenance mechanism
|
||
|
||
**The only 🟡 in this table is the v5 stock art, and the flag is the whole control.** Charter law #3 says
|
||
clearing photos for public release is **a human call** — so the gate does not pattern-match the licence
|
||
and rule on it. It **prints it**, every run, for every atlas, and a human decides. That makes one printed
|
||
line the mechanism the amber rows depend on, which is why it earned two rounds of work:
|
||
|
||
- **R23** — the gate globbed a name the runtime can never load: **0 files matched, returned 0**. It passed
|
||
identically whether atlases were absent, misnamed, or perfect. (The vacuous-gate law came from this.)
|
||
- **R25 — the line went silent and the gate still said OK.** Not a typo: the **check** read
|
||
`licence or license` (permissive) while the **print** read `licence` only. G's re-emit moved to US
|
||
`license`; the check passed, the print resolved to `{}`, and `FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE`
|
||
**stopped reaching a human on a real shop's product photos**. *The fallback WAS the bug* — it let one
|
||
path stay permissive so it could not feel the other break.
|
||
- **The fix is structural, not a spelling.** One spelling, no fallback (British `licence` now ERRORS,
|
||
naming C's §7.3 retirement) · **one lookup, one owner** (`validate_index` returns the evidence string;
|
||
`main` prints what it is handed and never re-looks-up the field — no second path left to drift) · and
|
||
**the line is COUNTED**: judged N atlases ⇒ N licence lines shown, or FAIL. A gate that can say OK while
|
||
hiding the flag cannot exist by construction.
|
||
|
||
**Today, frozen:** `atlas-QA OK — 15 per-shop atlas(es) valid, 15 licence line(s) shown, 0 warning(s)`.
|
||
Fifteen crates, fifteen flags, every one in front of a human. **Both zones print** — the mint rows carry the
|
||
same in-house-green flag because a seeded sample of real dealgod listings is still **the sellers' own
|
||
product shots**. Minting changes whose stock it *claims* to be, never whose photograph it *is*: that is why
|
||
`sourcing` is honesty and the licence is unchanged by it.
|
||
|
||
### ⟨v5.0-FINAL⟩ The id-space fences — provenance carried in the id itself
|
||
|
||
Two disjoint namespaces, blessed as design (C §7.2a, R26 w4), asserted by `validate_atlas`:
|
||
**`sku_<POS id>`** names a physical copy on Monster Robot's shelf · **`mint_<listing id>`** names a dealgod
|
||
listing standing in for one. A crate wearing the other's prefix **FAILS**. This is not naming hygiene:
|
||
tier-2's sold-means-gone looks up POS skus, so a `mint_` id filed under `sku_` doesn't fail a gate — it
|
||
**sells a real record out of a real shop to satisfy a minted stand-in**. G refused that shortcut when it
|
||
would have gone green the easy way, and that judgment is the fence. *(Same standing note as
|
||
plan-ids vs godverse-ids: one name, one space — never compare across.)*
|
||
|
||
### 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). Old `pubrock-live` fully 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 `_hi` in `_published.json` but 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_law` tiers** — ≤5k library / ≤8k baked hero (TRELLIS shell-soup, texture-intact) / ≤14k gig instruments; now stated in the manifest convention string + AUDIT.
|
||
- **`validate_manifest` prints "skins 85 groups"** — a shallow `sk.values()` count (the nested `interior` dict counts as 1); true leaf total is 96. Cosmetic label, flagged for v3.1.
|
||
- **Minor stale ledger figures** (non-live): `food_cart` 4991 vs `_normalize_results` 5000 (9-tri); a superseded `cube_shelf_01` entry in `_normalize_results` (DROP-filtered from the manifest, rejected in AUDIT). Harmless.
|
||
|
||
### Deferred to v3.1 (documented, not started)
|
||
- **`street_bin` decimation** — 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 `_hi` instrument 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_BED` bridge (the `{'gig-pubrock':'pubrock-live'}`
|
||
map + `gigBedKey()`); read `ra.gigKey` (`gig-<genre>`) straight against `AUDIO.music[...]`. The
|
||
bridge currently maps to the now-gone `pubrock-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's `gigKey` bed 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 emits `room.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` (drives `gigKeyFor` → 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 your
|
||
`instrumentFor(role)` fittings are unaffected; they're just lighter now. (debt #2)
|
||
- High-poly originals preserved as `_hi` variants 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.products` books), 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
|
||
`?localdepot` path); 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`):
|
||
```json
|
||
{ "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` / `bin` need 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_stack` read 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 ids `glass_case` (toy/dept) + `glass_cabinets` (pawn) both alias
|
||
to it. → add `glass_case`/`glassCase` (+ your pawn cabinet kind) → `'glass_case'` in `KIND_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). Registry `magazine_rack` aliases to it. → map your kind → `'magazine_rack'`.
|
||
- Both base-origin, verified loading in the vendored GLTFLoader. `manifest.fittingAliases` also maps
|
||
`fridge`→`drinks_fridge` and `listening_corner`→`listening_booth` if 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. Map `bin` in your furniture GLB-upgrade path if you want it on footpaths.
|
||
- **B3 resolved:** `novelty_record` is 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 --depot` 0 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_0` intact; **skeleton unchanged** (nodes + 65
|
||
joints identical to source on every ped I checked), so the shared `walk.glb`/`idle.glb`
|
||
clips retarget unchanged. **`walk.glb`/`idle.glb` were 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
|
||
|
||
1. **`procity_fit_record_crate_01.glb` — FIXED.** Root cause of your `undefined 'uri'` crash: the
|
||
material's `metallicRoughnessTexture` + `occlusionTexture` both 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.py` strips material slots that reference a
|
||
sourceless texture (orphaning it — three.js loads textures lazily, so no crash). **Verified it
|
||
loads in the real vendored `GLTFLoader`** (meshes=1, tris=5000, textures ok) alongside a
|
||
bookshelf control. Re-published to the depot — re-map `record_crate`/`recordBin` in `glb.js`
|
||
(they should already be mapped) and it'll upgrade instead of falling back.
|
||
2. **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 vendored `GLTFLoader`** (32k tris — TRELLIS shell-soup floor,
|
||
within decision-#2's 25–77k for focal props) and **live on the depot** (`validate --depot` 0 err).
|
||
→ **In `glb.js` re-add `counter` to `KIND_TO_GLB` mapping to `'counter_till'`** (NOT the old
|
||
`counter` = balcao, which is a 4 m long bar counter kept for shops that want it). The manifest
|
||
`fittingAliases.counter` → `counter_till` too. 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.
|