Lane F R30 (v7.0-alpha ledger #1, THE CONTRACT FIRST): LANE_F_NOTES §30 — window.PROCITY.game published ({day,cash,collection,townKey,save,load,sleep,export,import,wallet,recordFind,removeFind}), procity-save/1 schema (the delta law by shape), the day-salt rotation law (day 1 = pre-v7, real never rotates, gig night = day%7), C's §9 asks honoured (type/title/artist + removeFind)

This commit is contained in:
m3ultra 2026-07-18 14:09:09 +10:00
parent c8503a5a2e
commit 53fcd3ca1b

View File

@ -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: `<binKey>#<offerIndex>` (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`.