Lane C R26 (v5.0-beta): three contract lines + the dig price sticker (ledgers #1, #4, #6)

Wave 1 — contract lines published first; G and E build to them.

#1 THE BRIEF SAID `tier`. IT CAN'T BE (§7.2b). G's atlases already emit tier: 1 = the CHARTER'S
LADDER RUNG (0 parody / 1 static-real / 2 live), and `kind` is already the stock type. A mint atlas
is ALSO rung 1 — same mechanism (a statically baked per-shop atlas); only the data's origin differs.
Real-vs-mint is an orthogonal axis, not a rung — overloading `tier` puts two meanings in one name,
the same species as the plan-id/godverse-id collision just standing-noted, and as R25's positional
ids. RULING: top-level `sourcing: "real"|"mint"`, REQUIRED; `tier` stays the numeric rung. A mint
index MUST NOT carry POS-snapshot fields (the separation is the honesty). Gates read `sourcing`;
absent => FAIL.

#4 `condition` ISN'T IN THE INDEX (§7.2c) — the ledger assumed it was. The index carries
artist/title/price/price_band; G's SNAPSHOT has condition + sleeve_cond and drops them at emit, so
pickRealOffers mapped cond:'' and the card rendered dangling separators around a hole. That WAS the
"database row". Ruled optional index fields, emitted when the source has them; no invented grades on
mint items (a plausible-but-fictional grade is what `sourcing` exists to prevent).

#6 THE ATLAS MANIFEST (§7.2d): stock_godverse/index.json is the authority for which shops carry
atlases — no entry => no fetch => zero 404s, so the zero-console-errors law holds with no
attribution exception. Shape + both-ways consistency specified. No manifest + no atlases = a
legitimate silent zero-shop state, not an error.

THE PRICE STICKER (code): the pull panel is now the shop's price sticker — manila card, rotation +
shadow, typewriter caps, italic title, a stamped condition grade, and the round yellow $ sticker the
sleeve art already uses (the panel echoes dig.js's own canvas language instead of fighting it).
Fields compose only when present: a missing field never leaves a stray separator.

VERIFIED (fresh context, 5 card shapes, both star branches): real grail VG+/VG + ★GRAIL; real
standard VG+ + STANDARD; real ungraded COLLECTOR (grade omitted cleanly); parody NM + 1973 PUNK;
parody rare (seed 4) VG + 1973 POP + ★RARE. No dangling separators anywhere.

The verification caught a defect in MY OWN card: a real grail read "GRAIL ★ GRAIL" — `s` carried the
price_band AND `rare` derived from that same band (parody is immune: its `s` is a genre). Fixed at
the mapping. And I didn't leave the parody-rare branch on assumption — scanned seeds until a rare
pull appeared and asserted it says RARE, not GRAIL. An unexercised branch is an unproven one.

Budgets: the card is DOM — zero draws, zero tris, no 3D change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-17 09:38:42 +10:00
parent e7d58eaad5
commit 4f0adf12d8
3 changed files with 150 additions and 13 deletions

View File

@ -3,7 +3,62 @@
*Status: **v1 complete & verified**. Standalone interiors library + test page. Every shop door opens
into a unique, seeded, themed interior, generated on demand in ~4ms, byte-identical every revisit.*
Last updated: 2026-07-17 (round 25) · owner: PROCITY-C · reviewer: Fable
Last updated: 2026-07-17 (round 26) · owner: PROCITY-C · reviewer: Fable
---
## Update 2026-07-17 (round 26, v5.0-beta EVERY CRATE DIFFERENT) — three contract lines + the price sticker
R26 §Lane C, wave 1 (ledgers #1, #4, #6). Contract lines published FIRST — G and E build to them.
### The brief said `tier`. It can't be `tier` — that name is taken twice (§7.2b)
G's atlases already emit **`tier: 1`** = the **charter's ladder rung** (0 parody · 1 static-real · 2 live),
and `kind` is already the stock type. **A mint atlas is also rung 1** — same mechanism (a statically baked
per-shop atlas); only the data's *origin* differs. Real-vs-mint is an **orthogonal axis, not a rung**.
Overloading `tier` would put two meanings in one name — the same species as the plan-id/godverse-id
collision that just became a standing note, and as R25's positional ids. **Ruling: top-level
`sourcing: "real" | "mint"`, REQUIRED; `tier` stays the numeric rung.** A mint index MUST NOT carry
POS-snapshot fields (the separation IS the honesty); gates read `sourcing`, and an index without it FAILS.
### `condition` isn't in the index — the ledger assumed it was (§7.2c)
Ledger #4 said the card gets "the fields the atlas index already carries (artist/title/price/**condition**)".
It carries artist/title/price/price_band — **not condition**. G's *snapshot* has `condition` + `sleeve_cond`
and drops them at emit, so `pickRealOffers` mapped `cond: ''` and the card rendered dangling separators
around a hole. That *was* the "database row". Ruled: `condition`/`sleeve_cond` are **optional** index fields,
emitted when the source has them; **no invented grades** on mint items — a plausible-but-fictional grade is
exactly what `sourcing` exists to prevent.
### The atlas manifest (§7.2d) — existence declared, never probed
`web/assets/stock_godverse/index.json` is the authority for which shops carry atlases; **no entry ⇒ no
fetch ⇒ zero 404s**, so the zero-console-errors law holds with no attribution exception. Shape + both-ways
consistency (atlas without manifest entry FAILS, and vice versa) in §7.2d. A tree with no godverse atlases
and no manifest is a legitimate silent zero-shop state, not an error.
### The price sticker (ledger #4, code)
The pull panel is now the shop's **price sticker**, not a data readout: manila card, slight rotation +
shadow, typewriter caps, italic title, a rubber-stamped condition grade, and **the round yellow $ sticker
the sleeve art already uses** (dig.js's own canvas language — the panel now echoes it instead of fighting
it). Fields compose only when present: **a missing field never leaves a stray separator.**
**Verified — five card shapes, both star branches, fresh context:**
| case | card |
|---|---|
| real grail | `$28` · Deep Dish · *Penetrate Deeper* · `VG+/VG` · **★ GRAIL** |
| real standard, graded | `$15` · `VG+` · `STANDARD` |
| real ungraded (today's index / mint) | `$15` · `COLLECTOR` — grade omitted cleanly |
| parody | `$15` · THE VERANDAHS · *Tinny* · `NM` · `1973 PUNK` |
| parody rare (seed 4) | `$16` · HILLS HOIST · *Servo at Midnight* · `VG` · `1973 POP` · **★ RARE** |
**The verification caught a defect in my own card:** a real *grail* rendered **"GRAIL ★ GRAIL"** — `s` carried
the price_band *and* `rare` derived from that same band. (Parody is immune: its `s` is a genre.) Fixed at the
mapping — grail becomes the ★ stamp and leaves `s` empty. And I did not leave the parody-rare branch on
assumption: scanned seeds until a rare pull appeared (seed 4) and asserted it says RARE, not GRAIL. An
unexercised branch is an unproven one.
**Budgets:** the card is DOM — zero draws, zero tris, no 3D change. Ceilings untouched.
**→ G:** `sourcing` + `condition`/`sleeve_cond` + the manifest. **→ E:** assert `sourcing` (absent ⇒ FAIL),
the mint/POS-field separation both ways, and manifest↔files both ways. **→ F:** consult the manifest before
fetching — that's what retires the 404 attribution exception.
---

View File

@ -15,6 +15,10 @@
> **v5.0-alpha amendment (2026-07-17, R25 ledger #3):** **§7.2a added — slot ids must be sku-derived,**
> unique across packs and stable across re-emits (F's id-collision finding, made structural). Index-only;
> no atlas bytes, no goldens. §0§6 unchanged.
> **v5.0-beta amendment (2026-07-17, R26 ledger #1/#4/#6):** **§7.2b `sourcing` (real|mint — NOT `tier`,
> which is already the ladder rung), §7.2c `condition` for the price card, §7.2d the atlas manifest**
> (existence declared, never probed — kills the 404 attribution exception). All index/manifest-only:
> no atlas bytes, no town caches, no goldens. §0§6 unchanged.
*The district ships **three** venue archetypes behind `?gigs=1``pub`, `band_room`, `rsl`. Lane A
converts a chosen shop to a venue kind in place (`shop.type = kind`); C keys the interior recipe off
@ -286,6 +290,57 @@ must not emit into it.**
**→ Lane E:** assert the form — `id` present, **unique within the pack**, and `sku_`-prefixed for
real-stock packs.
### 7.2b `sourcing` — real vs mint, and why it is NOT `tier` (v5.0-beta, R26 ledger #1)
The beta needs every keyed crate marked as **actual POS inventory** or **seeded plausibility**, so no gate,
doc or human mistakes one for the other. The brief called that field `tier`. **It can't be `tier`: that name
is already taken, twice over.**
- G's atlases already emit **`tier: 1`** = the **charter's ladder rung** (0 parody · 1 static-real · 2 live
GODVERSE). A mint atlas **is also rung 1** — same mechanism (a statically baked per-shop atlas); only the
*data's origin* differs. And `kind` is already the stock type (`record`).
- So real-vs-mint is an **orthogonal axis**, not a rung. Overloading `tier` would put two meanings in one
name — the same species as the plan-id/godverse-id collision now standing-noted, and as the positional
ids in §7.2a. One name, one meaning.
**Rule: top-level `sourcing: "real" | "mint"`, REQUIRED on every real-stock atlas index.** `tier` stays the
numeric ladder rung, unchanged.
- **`"real"`** — baked from that shop's own POS snapshot (Monster Robot Party). The index carries its
`snapshot` provenance (§7.3).
- **`"mint"`** — a deterministic seeded sample (`random.Random(shop_id)`) standing in for a keyed shop with
no POS. Plausible, never claimed as that shop's stock. **A mint index MUST NOT carry POS-snapshot fields**
(the separation IS the honesty — E asserts both directions).
- **Gates read `sourcing`** (vacuous-gate law: an index with no `sourcing` FAILS). A mint crate can never
report as real. The dig card's treatment is C's call — see §7.2c.
### 7.2c Price-card fields — `condition` (v5.0-beta, R26 ledger #4)
The dig card is a **record-shop price sticker**, and a sticker's fields are artist / title / **condition** /
price. `condition` is **not in the index today** — G's snapshot carries `condition` + `sleeve_cond` per item
and drops them at emit, so `pickRealOffers` currently maps `cond: ''` and the card renders dangling
separators around a hole. That's the "database row" the ledger wants gone.
**Rule: `condition` (and optionally `sleeve_cond`) are OPTIONAL index item fields; emit them when the source
has them.** Index-only; the loader never reads them (§7.3). C's card renders whatever is present and omits
what isn't — a missing field must never leave a stray separator. **Mint items:** emit `condition` only if the
sampled listing genuinely carries one; a plausible-but-invented grade is exactly the kind of fiction
`sourcing` exists to prevent.
### 7.2d The atlas manifest — existence is declared, never probed (v5.0-beta, R26 ledger #6)
Fail-soft-by-404 collides with the zero-console-errors law: the runtime probes per-shop atlases blind and
eats the 404 noise (8 in F's R24 gate run), tolerated by attribution — a workaround, not a design.
**Rule: `web/assets/stock_godverse/index.json` is the authority for which shops carry atlases. No entry ⇒ no
fetch.** Shape:
```json
{ "version": 1,
"shops": [ { "godverseShopId": 3962749, "types": ["record"], "sourcing": "real" },
{ "godverseShopId": 31, "types": ["record"], "sourcing": "mint" } ] }
```
- **`godverseShopId` is a number in the GODVERSE namespace** — never compare it to a plan id (standing note).
- **→ F:** consult the manifest before fetching; no entry ⇒ no probe ⇒ **zero 404s, no attribution
exception**. A shop in the manifest whose fetch still fails keeps the existing fail-soft → parody.
- **→ G:** emit it alongside the atlases (mint entries included).
- **→ E:** manifest↔files consistency **both ways** — an atlas on disk absent from the manifest FAILS, and a
manifest entry with no atlas FAILS.
- **Manifest absent entirely** (a tree with no godverse atlases): that is a legitimate zero-shop state — the
wire probes nothing and stays silent. Not an error.
### 7.3 Fields the runtime IGNORES — and the provenance ruling (the drift dies here)
`version`, `atlas_px`, `cell`/`cell_w`/`cell_h`, `count`, `kind`, `tier`, `shop`, `crate`,
`provenance`**`stockpack.js` reads none of them.** So the authority for their shape is not C's

View File

@ -113,13 +113,21 @@ export function createDig(THREE, renderer) {
const css = el('style'); css.textContent = `
.pcdg-hint{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);background:rgba(0,0,0,.7);color:#d8d8e0;padding:6px 14px;font:13px "Courier New",monospace;pointer-events:none;display:none;z-index:60}
.pcdg-cur{position:fixed;left:50%;top:64px;transform:translateX(-50%);color:#ffd23d;font:700 15px "Courier New",monospace;text-shadow:0 1px 4px #000;pointer-events:none;text-align:center;max-width:80%;display:none;z-index:60}
.pcdg-panel{position:fixed;right:6%;top:50%;transform:translateY(-50%);width:250px;background:#0e0e16;border:1px solid #ffd23d;padding:16px;color:#d8d8e0;font:14px "Courier New",monospace;display:none;z-index:61}
.pcdg-panel h3{margin:.1em 0;color:#ffd23d;font-size:15px}
.pcdg-panel .meta{color:#8a8a9a;font-size:12px;margin:4px 0 8px}
.pcdg-panel .price{font-size:24px;color:#3dff8b}
.pcdg-panel button{margin-top:12px;width:100%;padding:9px;border:1px solid #666;background:#1d1d2b;color:#d8d8e0;font:inherit;cursor:pointer}
.pcdg-panel button:hover{border-color:#3dff8b;color:#3dff8b}
.pcdg-panel .x{position:absolute;top:6px;right:11px;cursor:pointer;color:#888}
/* The pull panel is the shop's PRICE STICKER, not a data readout: manila card, typewriter ink, the
round yellow $ sticker that already sits on the sleeve art (see the sleeve canvas above), a rubber-
stamped condition grade. Fields render only when present a missing one leaves no stray separator. */
.pcdg-panel{position:fixed;right:6%;top:50%;transform:translateY(-50%) rotate(-1.1deg);width:262px;background:#f4ecd8;border:1px solid #cbbf9e;box-shadow:0 10px 26px rgba(0,0,0,.55);padding:15px 16px 14px;color:#241f18;font:14px "Courier New",monospace;display:none;z-index:61}
.pcdg-panel .sticker{float:right;width:54px;height:54px;border-radius:50%;background:#f4e11a;border:1px solid #d3bf00;color:#c1121f;font:700 18px Arial;line-height:54px;text-align:center;transform:rotate(5deg);margin:-2px -4px 4px 10px;box-shadow:0 1px 3px rgba(0,0,0,.25)}
.pcdg-panel h3{margin:.1em 0 3px;color:#241f18;font-size:15px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}
.pcdg-panel .ttl{color:#4c4436;font-size:13px;font-style:italic;margin:0 0 9px;line-height:1.25}
.pcdg-panel .stamps{clear:both;display:flex;gap:6px;align-items:center;flex-wrap:wrap;min-height:1px}
.pcdg-panel .grade{border:1.5px solid #b3121f;color:#b3121f;padding:1px 6px;font:700 11px "Courier New",monospace;letter-spacing:.08em;transform:rotate(-2.5deg)}
.pcdg-panel .tag{color:#6f6553;font-size:11px;letter-spacing:.05em}
.pcdg-panel .star{color:#b3121f;font-size:13px}
.pcdg-panel button{margin-top:13px;width:100%;padding:9px;border:1px solid #8a7f66;background:#e7dcc2;color:#241f18;font:700 13px "Courier New",monospace;letter-spacing:.06em;cursor:pointer}
.pcdg-panel button:hover:not(:disabled){background:#241f18;color:#f4e11a;border-color:#241f18}
.pcdg-panel button:disabled{color:#9a917c;border-color:#c3b99f;cursor:default}
.pcdg-panel .x{position:absolute;top:4px;left:8px;cursor:pointer;color:#a89e86;font-size:13px}
.pcdg-out{position:fixed;left:16px;top:16px;background:#1d1d2b;border:1px solid #666;color:#d8d8e0;padding:8px 14px;font:13px "Courier New",monospace;cursor:pointer;display:none;z-index:60}
.pcdg-out:hover{border-color:#3dff8b;color:#3dff8b}
.pcdg-toast{position:fixed;left:50%;top:40%;transform:translate(-50%,-50%);background:#0e0e16;border:1px solid #3dff8b;color:#3dff8b;padding:14px 22px;font:700 15px "Courier New",monospace;z-index:62;display:none}`;
@ -211,10 +219,21 @@ export function createDig(THREE, renderer) {
function showPanel(entry) {
const o = entry.offer;
const cash = getCashCb ? getCashCb() : 999;
panel.innerHTML = `<div class="x">✕</div><h3>${o.a}</h3>`
+ `<div class="meta">“${o.t}” · ${o.y} ${o.s} · ${o.cond}${o.rare ? ' · ★ RARE' : ''}</div>`
+ `<div class="price">$${o.price}</div>`
+ `<button ${o.price > cash ? 'disabled' : ''}>${o.price > cash ? 'not enough cash' : 'BUY IT'}</button>`;
// A price sticker, composed from what this item ACTUALLY has. Real pack items carry no year (and
// may carry no grade); procedural ones carry both. Build the stamp row from present parts only —
// never emit a separator around a hole (that's what read as a database row). §7.2c.
const esc = (s) => String(s).replace(/[&<>"]/g, m => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;' }[m]));
const stamps = [];
if (o.cond) stamps.push(`<span class="grade">${esc(o.cond)}${o.sleeveCond ? '/' + esc(o.sleeveCond) : ''}</span>`);
const line = [o.y, o.s].filter(Boolean).join(' '); // "1974 SOUL" | "SOUL" | "" — never " · "
if (line) stamps.push(`<span class="tag">${esc(line)}</span>`);
if (o.rare) stamps.push(`<span class="star">★ ${o.real ? 'GRAIL' : 'RARE'}</span>`);
panel.innerHTML = `<div class="x">✕</div>`
+ `<div class="sticker">$${esc(o.price)}</div>`
+ `<h3>${esc(o.a)}</h3>`
+ (o.t ? `<div class="ttl">“${esc(o.t)}”</div>` : '')
+ (stamps.length ? `<div class="stamps">${stamps.join('')}</div>` : '')
+ `<button ${o.price > cash ? 'disabled' : ''}>${o.price > cash ? 'NOT ENOUGH CASH' : 'BUY IT'}</button>`;
panel.style.display = 'block';
panel.querySelector('.x').onclick = unpull;
const b = panel.querySelector('button');
@ -271,8 +290,16 @@ export function createDig(THREE, renderer) {
const rr = mulberry32(seed >>> 0), out = [];
for (let i = 0; i < n; i++) {
const it = pack.items[(rr() * pack.items.length) | 0];
// Real fields straight off the atlas index (§7.2c): condition/sleeve_cond are OPTIONAL — present on
// POS-sourced items, absent on mint ones that had no genuine grade. `y` (year) is never in the index,
// so it stays empty and the card simply omits it. No invented values: an unstated grade stays unstated.
// `grail` becomes the ★ stamp, so it must NOT also ride in `s` — otherwise the card reads
// "GRAIL ★ GRAIL". (Procedural sleeves are unaffected: their `s` is a genre, not the band.)
const band = (it.price_band || '').toUpperCase();
out.push({ real: true, pack, item: it, a: it.artist, t: it.title, price: it.price,
s: (it.price_band || '').toUpperCase(), y: '', cond: '', rare: it.price_band === 'grail' });
s: band === 'GRAIL' ? '' : band, y: '',
cond: it.condition || '', sleeveCond: it.sleeve_cond || '',
rare: it.price_band === 'grail' });
}
return out;
}