C1 — buy loop v0 (new wallet.js): runtime-only economy — seeded cash (~$60–200), buy()/broke-gate/in-memory bag, onChange for a cash chip. World gen untouched (purchases never re-enter the plan/seeded build). Test page: #cashChip HUD + I-toggle inventory + dig BUY IT button (pull → bag, cash down, bin depletes); digSoak buys ≤2/visit. C2 — book spines + toy boxes wired to ?stock=real. stockpack.js keys each pack to a slot (record→sleeve, book→spine, toy→box); stock.js shelfLine builds real atlas-UV cover planes for book/toy shelves (static, no dig), fail-soft per pack. E's packs live: record 350/6, book 311/4, toy 273/5 (items/atlases). C3 — validated: draws ≤350 (record 41, book 42, toy 51), determinism 0 fails, leak-free (0 geo/tex delta ×20; buy-soak 12 visits/5 bought, leakGeo0/tex0), reload resets wallet, flag-off + ?noassets untouched, qa.sh --strict GREEN 5/5. Shot: docs/shots/laneC/buyloop_r8.jpg. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
34 lines
1.5 KiB
JavaScript
34 lines
1.5 KiB
JavaScript
// PROCITY Lane C — buy loop v0 (round 8). In-memory wallet: seeded starting cash, purchases, inventory.
|
||
// RUNTIME state only — reload resets, world generation is untouched (purchases never enter the plan or
|
||
// the seeded room build). One wallet is shared across dig sessions + shops so F's shell can render the
|
||
// same cash chip on the street. Economy is deliberately shallow (no persistence, no backend) — v0.
|
||
|
||
import { mulberry32 } from '../core/prng.js';
|
||
|
||
export function createWallet(seed = 1) {
|
||
const r = mulberry32(seed >>> 0);
|
||
const start = 60 + ((r() * 140) | 0); // seeded starting cash ~$60–200
|
||
let cash = start;
|
||
const inv = [];
|
||
const listeners = new Set();
|
||
const notify = () => listeners.forEach(fn => { try { fn(); } catch (e) {} });
|
||
return {
|
||
cash: () => cash,
|
||
start: () => start,
|
||
canBuy: (price) => (price || 0) <= cash,
|
||
// buy(offer) — offer is a dig offer ({a:artist, t:title, price, ...}) or a pack item ({title,artist,price}).
|
||
// Returns false (no state change) if broke. Deducts + records on success.
|
||
buy(o) {
|
||
const price = (o && o.price) || 0;
|
||
if (price > cash) return false;
|
||
cash -= price;
|
||
inv.push({ title: o.t || o.title || '?', artist: o.a || o.artist || '', price, band: (o.s || o.price_band || '') });
|
||
notify();
|
||
return true;
|
||
},
|
||
inventory: () => inv.slice(),
|
||
count: () => inv.length,
|
||
onChange(fn) { listeners.add(fn); return () => listeners.delete(fn); },
|
||
};
|
||
}
|