diff --git a/docs/LANES/LANE_F_NOTES.md b/docs/LANES/LANE_F_NOTES.md index 3840f17..980b40a 100644 --- a/docs/LANES/LANE_F_NOTES.md +++ b/docs/LANES/LANE_F_NOTES.md @@ -1447,3 +1447,124 @@ subject-presence check now reads the disk. *The vacuous-gate law has a sibling: own subject being absent.* **No tag** — wave 0 rides Spike 2's close, which is gated on John's footage. Nothing else is owed on m3. + +--- + +## 30. Round 30 — v7.0-alpha THE SAVE CORE (ledger #1) + SLEEP=SAVE=TOMORROW (ledger #3) + +### §30.1 THE CONTRACT — `window.PROCITY.game` (PUBLISHED FIRST, per the brief; B builds on this, C reads it at the counter) + +`window.PROCITY.game` is **null** under `?classic=1` and under `?game=0`; on every other boot it is: + +```js +window.PROCITY.game = { + day, // getter, int ≥ 1. DAY 1 IS THE PRE-v7 TOWN: stock streams carry no day salt on day 1, + // so a fresh game's shops are byte-identical to a ?game=0 boot. Rotation begins at the + // first sleep. (Falsifiable: day-1 stock == no-game stock; day-2 stock != day-1 stock.) + cash, // getter, int dollars — always equals PROCITY.wallet.cash() + collection, // getter → the LIVE array. READ-ONLY for every consumer (C's §9.3 law); the only + // mutations are game.recordFind (buy seam) and game.removeFind (sell seam). + townKey, // string for THIS boot: `${plansrc}/${town || 'default'}@${seed}` + // e.g. 'synthetic/default@20261990' · 'osm/redhill_godverse@20261990' + save(), // → bool. Serializes schema procity-save/1 → localStorage['procity-save']. + load(), // → bool (ran once at construction). Corrupted/foreign blob → LOUD reject + // (console.error), raw stashed at localStorage['procity-save.rejected'], fresh start. + // THE TOWN NEVER BREAKS: the save holds only player deltas (the delta law), so a bad + // save can cost you your stuff, never the world. + sleep(), // → the new day (int). day+1 → gig weekNight re-keys to day % 7 → wake at DAWN + // (lighting segment 0) → save(). Stock pick streams re-seed via the day salt (§30.3). + export(), // → JSON string — the exact procity-save/1 payload (John moves machines). + import(json), // → bool. Validated exactly like load(). true ⇒ state replaced + saved + day-derived + // state applied (weekNight etc). false ⇒ loud reject, CURRENT STATE UNTOUCHED. + wallet, // the wallet-compatible facade — PROCITY.wallet IS this object when the game is on. + // Full Lane C v0 interface: cash() start() canBuy(p) buy(o) sell(o, offer) + // inventory() count() onChange(fn). buy/sell/inventory keep C's exact semantics + // (sell credits the offer + drops the matching v0-inventory entry, per §9.3); + // cash is game-backed so a loaded save's cash is authoritative. + recordFind(shop, info), // F's buy-seam internal (interior_mode calls it) — appends a collection + // entry stamped with townKey + day. Exposed so the gates can drive it. + removeFind(entry), // → bool. Identity (===) removal — THE only way the collection shrinks. + // C's sell counter: onSell ⇒ F calls wallet.sell(item, offer) + + // game.removeFind(entry). Returns false (nothing moves) if entry ∉ collection. +}; +``` + +**Collection entry shape** (C's §9.2 asks honoured: `type` stamped at buy time, `title`/`artist` carried): + +```js +{ townKey, // where in the world — the boot key above + shopId, // plan shop id (always present) + godverseShopId, // the real POS id, ONLY when the shop has one (the two id spaces stay separate — R24 law) + type, // the ITEM's stock type: 'record' (any dig, incl. an opshop bin) · 'book' · 'toy' (shelf + // buys). Sellability is type = type, fail-closed (C §9.2) — this field is load-bearing. + sku, // pack item id (`sku_*` / `mint_*` / `rec_*` — the sku IS the identity, R25/26 fences)… + slotId, // …OR, for parody items with no pack id: `#` (dig). Exactly one + // of sku|slotId is set. + title, artist, // display (B's collection card; C's sell card falls back to sku/slotId when absent) + pricePaid, // int dollars actually debited + dayFound } // game.day at purchase +``` + +**Save schema `procity-save/1`** (localStorage key `procity-save`): + +```js +{ schema: 'procity-save/1', day, cash, town: townKey, collection: [entries], savedAt } +``` + +THE DELTA LAW, enforced by shape: no plan, no shop, no stock, no clock, no world field of any kind ever +enters this object — the world regenerates from seed. Validation on load/import is exact-schema-string + +field checks (day int ≥ 1 · cash finite ≥ 0 · collection entries carry townKey/pricePaid/dayFound and +one of sku|slotId); any miss rejects the WHOLE save loudly. Versioning: a future `procity-save/2` +migrates-or-rejects, never silently coerces. + +**Save points:** `sleep()` and `beforeunload`. (Note, measured against the brief's "save, day+1" order: +sleep increments FIRST and saves the post-sleep state, so disk always carries the morning you woke into — +the literal order would leave disk one day stale between sleep and unload. Same falsifiable outcome, one +fewer window.) + +### §30.2 The laws, wired from birth + +- **`?classic=1`** → `createGame` is NEVER CALLED: no game object, no wallet facade, no listener, ZERO + localStorage touches (save.js has no module-scope storage access — the classic-purity gate in ledger #5 + measures this rather than trusting it). `?game=0` → same absence, C's plain `createWallet` runs the + session exactly as pre-v7. Default boot → game on (the v3.1 flip precedent). +- **Game money is game money:** the save carries dollars that exist nowhere else; import validates shape, + not provenance — there is no bridge to anything real, in either direction. + +### §30.3 SLEEP = TOMORROW — the rotation law (ledger #3, runtime only) + +- **The day salt.** The parody/mint stock pick streams re-seed per (shop, day) by salting the EXISTING + runtime streams — plan generation NEVER sees `day` (A verifies the boundary, ledger #6): + - interior stock: `buildInterior(shop, THREE, { …, stockDay: day })` → `ctx.stockSalt = ':d'+day` → + layout.js appends it to the `stk-*` sub-stream salts (one marked seam line each in interiors.js + + layout.js — `[Lane F R30 seam]`). Fittings/layout/audio streams untouched: the ROOM is the same + room every day; what rotates is which items the streams pick into it. + - the dig: `binSeed(shop.seed, binKey + '@d' + day)` (interior_mode, F-owned). + - `day === 1` or no game layer ⇒ NO salt (byte-identical to pre-v7 — the day-1 convention above). +- **REAL-sourced stock never rotates.** A shop whose per-shop pack the godverse manifest declares + `sourcing:'real'` (Monster Robot Party) gets NO day salt, ever — its crate is a real crate. `mint` + crates and parody stock rotate. (The town-wide v2 packs are real *images* used as seeded set-dressing, + not a real shop's inventory — they rotate with the parody streams. Ruled here so nobody re-derives it.) +- **Gig night = day % 7.** `gig_state.js` (F-owned) gains `setWeekNight(n)` + `get weekNight` — every + venue's latch re-keys from its night-0 gig to its night-(day % 7) gig (plan.gigs is already the whole + week; the three hard-won latch laws are untouched). Cover stamps reset with the re-key (new night ⇒ + cover due), which is correct by John's R12 ruling. +- **Wake at dawn:** `sleep()` → `lighting.setSegment(0)` (fires `procity:segment`, so every latch + + facade observes the morning — the listens-not-polls law does the work for free). + +### §30.4 Cross-lane notes on this contract + +- **→ B (collection UI + SLEEP surface, ledger #4):** everything you need is `PROCITY.game` + (`day`/`cash`/`collection`/`sleep()`) + `wallet.onChange` for the cash chip. Cover thumbs: resolve via + the entry's `sku` against the shop's pack where present; parody entries have no thumb (title/artist + card is correct-and-readable, per the brief). +- **→ B (found while wiring weekNight):** `venue.js:38` keys "tonight" to **night 0** — correct pre-game, + but after the first sleep the marquee/frontage would advertise night-0's band while the latch plays + night-(day%7)'s. `PROCITY.gigs.weekNight` (and `game.day`) are published; one line in venue.js when + you touch the HUD. Filed, not silently fixed (your file). +- **→ C (sell counter, §9.3):** your asks are honoured — `type` stamped at buy time, `title`/`artist` + carried, removal via `game.removeFind(entry)`, credit via the facade's `sell(item, offer)` (mirrors + your wallet.js exactly). The E-key routing hook (aimed bin → dig · near counter + sellable → sell card + · else shelf-buy) is F's and lands with the sell wiring pass, not this one. +- **→ A (ledger #6):** the day salt rides `buildInterior` opts / `binSeed` only — grep-provable that + `generatePlan*`/plan_osm never receive `day`.