diff --git a/F-progress.md b/F-progress.md index d80263f..462171d 100644 --- a/F-progress.md +++ b/F-progress.md @@ -4,6 +4,19 @@ --- +## Round 33 (v7.0-beta wave 2) — THE HUNT: wantlist · rumor · travel (Fable, solo, JING5) + +☆ WANT on the pull card → `wants` on procity-save/1 (optional, capped, deduped); buying a +matching item clears the hunt; THE WANTLIST section in the crate panel. The keeper gossips: +seeded 45% banner mutter naming a hunted want + a real cached town. TRAVEL COSTS A DAY: +adopting a save from another townKey (town OR seed) charges +1 day in save.js adopt() — load +and import both pay, same-town stays free (byte-equality gates untouched); arrival toast names +the fare. New STRICT `smoke_wantlist_travel` (real-card want · rumor scan · auto-clear · ++1-day with intact cash/collection · same-town control · return fare · ?game=0 zero-DOM). +Full record: LANE_F_NOTES §33. + +--- + ## Round 32 (v7.0-beta wave 1) — THE BIBLE + scarcity + the §31.6 queue (Fable, solo, JING5) Guide bands derived in `js/interiors/bible.js` (E's thresholds mirrored, no pack re-emit) → diff --git a/README.md b/README.md index 4af4586..152d987 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Click **START** to lock the mouse. Then: | **E** (inside a shop) | open the crate at a record bin — then it's cursor-driven: click sleeves to riffle, click BUY | | **E** (at the counter) · **C** (anywhere) · **SLEEP** (bottom bar) | sell a find to the keeper (he pays under the **guide band**, never over) · open THE CRATE — your collection, in shops too now · sleep = save, the day advances, stock rotates | | read the **guide** stamp on every price card | the 90s price-guide band ("guide $8–24"). An item **asking under its band is a gem** — buy it, sell it to a keeper, pocket the margin. Bought slots stay gone for the day (the bin remembers) | +| **☆ WANT IT** on a pull card | start hunting it — keepers gossip ("saw one in Castlemaine"), and buying one clears the hunt. **Switching towns costs a day** — the 23 towns are a map now, not a dropdown | | click a book spine / toy box | pull-and-buy card (real price stickers in godverse shops) | | walk out the door / **Esc** | leave the shop (Esc also frees the mouse anywhere) | | **M** | 2D town map (street only) | diff --git a/docs/LANES/LANE_F_NOTES.md b/docs/LANES/LANE_F_NOTES.md index 2fd0aec..726773f 100644 --- a/docs/LANES/LANE_F_NOTES.md +++ b/docs/LANES/LANE_F_NOTES.md @@ -1813,3 +1813,35 @@ band, which is what charter law #2 always said. `web/assets/models/` is GITIGNORED build output — a fresh checkout runs stock=real/book-toy/ no-pump gates RED with 404s (the R31-green tree lives on m3ultra). Fixed here by rsync from m3ultra. If a third machine joins the rotation, sync that dir (49 M) or re-run E's pipeline. + +## §33 — R33: v7.0-beta wave 2 — THE HUNT (wantlist · rumor · travel) (Fable, solo, JING5) + +- **save.js:** optional `wants: [{artist?|title?|sku?}]` on procity-save/1 (validated + exact-and-loud when present; ≥1 string field per entry; cap 40; absent-when-empty keeps the + alpha byte shape). `recordWant` (case-insensitive dedupe) / `findWant` (a want covers an item + when every field the WANT names matches — sku exact, artist/title by value) / `removeWant` + (identity). **TRAVEL in `adopt()`:** adopted save's `town !== townKey` ⇒ `day += 1`, + `traveled = true`, prunePulls — load AND import both pay the fare; same-town adoption free + (save-determinism byte-equality untouched). `game.traveled` getter → the shell's arrival toast. +- **dig.js:** the pull card's ☆ WANT line — construction-gated on `opts.onWant` (game absent ⇒ + zero want DOM, the classic-pure pattern). ☆ click → ★ ON THE WANTLIST (a stamp, not a button, + once wanted). +- **interior_mode.js:** onWant/isWanted seams into dig.open; buy of a matching item removes the + want (+ '✓ off the wantlist' toast). **THE RUMOR** in the enter() banner: seeded + `xmur3('rumor:::')` → 45% roll → hunted want + a town from + RUMOR_TOWNS (the repo's real cached towns, current town excluded). Runtime-only stream — + A's boundary holds; no game / empty wants ⇒ the line never exists. +- **hud.js:** THE WANTLIST section in the crate panel (non-empty only): artist/title rows, ✕ + stops hunting; refresh watches wants length so an open panel follows adds/clears. +- **Gate:** `smoke_wantlist_travel` STRICT (see ROUND33_INSTRUCTIONS for the green run) — real + card path, seeded rumor scan, auto-clear, +1-day travel with same-town negative control and + the return fare, `?game=0` zero-DOM control, 0 console errors. +- **Matrix note:** town_matrix shares one browser across towns, so travel drifts the day as it + walks the matrix — safe because determinism double-boots the SAME town (free by the + same-town rule) and no matrix gate reads absolute day. Reasoned before landing, not after. +- **The shot found what the gate didn't (harness lesson #5 candidate):** R32's crate-in-shops + surface BUILT only in street update() frames — a door entered fast after boot had no game + surface inside at all, and the interior tickGame() happily refreshed a UI that never existed. + Green gates missed it because every gate's boot idles on the street long enough to build. + Fixed: tickGame() now builds AND refreshes (any mode); found by taking the money shot, which + entered the shop faster than any gate does. Shots are a detector, not decoration. diff --git a/docs/LANES/ROUND33_INSTRUCTIONS.md b/docs/LANES/ROUND33_INSTRUCTIONS.md new file mode 100644 index 0000000..968a637 --- /dev/null +++ b/docs/LANES/ROUND33_INSTRUCTIONS.md @@ -0,0 +1,52 @@ +# PROCITY — Round 33 instructions (from Fable, integrator) + +Date: 2026-07-20 · **R32 closed all green** (7/0/0, matrix 10 towns — THE BIBLE + scarcity live). +Round 33 is **v7.0-beta wave 2 — THE HUNT**: the wantlist, the rumor, and travel that costs a +day ([V7_THE_GAME.md](../V7_THE_GAME.md) system #6, the last unbuilt beta system). Solo round +again (John's "keep going"), same treaty, lane ownership honoured in the commits. + +## The design rulings this round encodes + +1. **A want is something you SAW.** The wantlist grows from the pull card (☆ WANT IT — you + pulled it, it's dear, you'll hunt one cheaper) — not from an abstract search box the 90s + didn't have. Buying a matching item clears its want automatically (the hunt ends when + you're holding it). Wants ride the save (`wants: [{artist?|title?|sku?}]`, optional field, + same procity-save/1 back-compat law as `pulls`). +2. **The keeper finally gossips** (charter: "D's citizens finally gossip for a reason"). + Seeded per (citySeed, shop, day): ~45% of shop entries mutter a hunted want + a real cached + town in the banner — flavor and a travel nudge, honest about being neither promise nor + stock lookup (town-wide packs mean any town CAN surface the record eventually). Runtime + stream, freshly keyed — no plan/golden moves. Full persona lines remain D's when the v6 + clip work lands; the banner is the keeper's voice until then. +3. **Travel = the townKey changing hands.** Booting a save in a different town (different + plansrc/town/**seed** — a new seed is a new town) costs exactly one day, applied in + save.js `adopt()` so load and import both pay it, and pulls prune on the way. Same-town + reloads and export→import round trips stay free — the save-determinism gate's byte + equality survives untouched. The town selector IS the bus; the arrival toast names the + fare. The 23 towns are now a map you spend days moving across, not a dropdown. + +## Gate (F) + +`smoke_wantlist_travel` (STRICT): ☆→★ through the real card → the save carries the want · +a keeper names the hunted artist within the open shops (seeded scan) · buying the wanted +sleeve auto-clears it · synthetic→melbourne costs exactly one day with cash/collection intact +· same-town reboot is FREE (the control) · the ride home costs a day again · `?game=0` builds +zero want DOM and mutters nothing. + +## ROUND CLOSED (Fable) — ALL GREEN + +`qa.sh --strict --matrix`: **7 passed · 0 failed · 0 warn · 0 skipped**, MATRIX 10 towns green +(the shared-context day drift travel introduces across matrix towns was reasoned through +before landing: determinism double-boots the SAME town, so no gate sees it). The new gate's +run: want carried {THE VERANDAHS — "Servo at Midnight"} · rumor after 2 shops ("🗣 THE +VERANDAHS? saw one in Castlemaine just last week") · buy cleared the want · travel day 1→2 +with $179 and the find riding along · same-town free · return trip day 3 · game=0 clean · +0 console errors. Full record: LANE_F_NOTES §33. + +Also caught and fixed here: the R32 crate-in-shops surface only BUILT in street frames — a +door entered fast after boot had no game surface inside (the money-shot script entered faster +than any gate does and exposed it; tickGame() now builds-or-refreshes in any mode). Money +shots: `docs/shots/v7_beta/rumor_and_wantlist.png` + `want_card.png`. + +**What remains of v7:** §31.6-5 (dig Esc in person — John's hands) · then the v7.0 close: +GodBay offline auctions (G's lane), THE FIRST FIVE MINUTES (onboarding), the tour, the tag. diff --git a/docs/shots/v7_beta/rumor_and_wantlist.png b/docs/shots/v7_beta/rumor_and_wantlist.png new file mode 100644 index 0000000..8c28647 Binary files /dev/null and b/docs/shots/v7_beta/rumor_and_wantlist.png differ diff --git a/docs/shots/v7_beta/want_card.png b/docs/shots/v7_beta/want_card.png new file mode 100644 index 0000000..bb2b8b8 Binary files /dev/null and b/docs/shots/v7_beta/want_card.png differ diff --git a/tools/flags_check.py b/tools/flags_check.py index 9a713f5..4f414f9 100644 --- a/tools/flags_check.py +++ b/tools/flags_check.py @@ -2136,6 +2136,123 @@ def smoke_bible(p): b.close() +def smoke_wantlist_travel(p): + """R33 gate — THE WANTLIST + THE RUMOR + TRAVEL COSTS A DAY (v7.0-beta wave 2). Legs: + (a) ☆ WANT on the pull card (real path) → the save carries the want; (b) a keeper rumor + naming the hunted artist surfaces in some shop's banner (seeded per shop×day — scan); + (c) buying the wanted item auto-clears the want; (d) booting ANOTHER town on the same save + costs exactly one day (cash/collection intact), (e) re-booting the SAME town costs nothing + (the control discriminates), (f) the return trip costs a day again; (g) ?game=0 builds ZERO + want DOM and mutters no rumor (construction-gating holds).""" + head('GATE R33: wantlist · rumor · travel-costs-a-day (+ same-town and ?game=0 controls)') + b, pg, errs = new_page(p) + try: + boot(pg, '') + r0 = _enter_record_shop(pg) + if r0.get('err'): FAIL(f"wantlist: {r0['err']}"); return + # (a) pull the front sleeve, click ☆ WANT through the real card + _at_bin(pg) + pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' }))") + pg.wait_for_timeout(400) + pg.evaluate("""() => { const c = window.PROCITY.renderer.domElement; + c.dispatchEvent(new PointerEvent('pointerdown', { clientX: 640, clientY: 360, bubbles: true })); + c.dispatchEvent(new PointerEvent('pointerup', { clientX: 640, clientY: 360, bubbles: true })); }""") + pg.wait_for_timeout(300) + rw = pg.evaluate("""() => { + const w = document.querySelector('.pcdg-panel .want'); + if (!w) return { err: 'no WANT line on the card' }; + const before = w.textContent; w.click(); + const g = window.PROCITY.game, ex = JSON.parse(g.export()); + return { before, after: w.textContent, wants: ex.wants || [], live: g.wants.length }; }""") + if rw.get('err'): FAIL(f"wantlist: {rw['err']}"); return + if 'WANT IT' in rw['before'] and 'ON THE WANTLIST' in rw['after'] and len(rw['wants']) == 1 and rw['live'] == 1: + OK(f"wantlist: ☆→★ through the real card — the save carries {rw['wants'][0]}") + else: + FAIL(f"wantlist: want did not take — {rw}") + artist = (rw['wants'][0].get('artist') or rw['wants'][0].get('title') or '').upper() + pg.evaluate("""() => { const p = document.querySelector('.pcdg-panel'); + const x = p && p.querySelector('.x'); if (x) x.click(); + const o = document.querySelector('.pcdg-out'); if (o) o.click(); }""") + pg.wait_for_timeout(200) + pg.evaluate("() => window.DBG.exitShop()") + pg.wait_for_timeout(300) + # (b) the rumor: seeded ~45% per (shop, day) — walk the town's open shops until a keeper mutters + rr = pg.evaluate("""() => { + const P = window.PROCITY, D = window.DBG; + const open = (P.plan.shops || []).filter(s => P.isOpen(s)).slice(0, 10); + const seen = []; + for (const s of open) { + D.enterShop(s.id); + const t = [...document.querySelectorAll('div')].map(e => e.textContent) + .find(t => t && t.includes('🚪') && t.includes('🗣')); + D.exitShop(); + if (t) return { rumor: t.slice(t.indexOf('🗣')), tried: seen.length + 1 }; + seen.push(s.id); + } + return { rumor: null, tried: seen.length }; }""") + if rr.get('rumor') and artist and artist in rr['rumor']: + OK(f"rumor: a keeper muttered after {rr['tried']} shop(s) — {rr['rumor'][:80]}") + else: + FAIL(f"rumor: no keeper named {artist!r} in {rr.get('tried')} open shops — {rr}") + # (c) buy the wanted sleeve → the want clears itself + r1 = _enter_record_shop(pg) + if r1.get('err'): FAIL(f"wantlist/buy: {r1['err']}"); return + _at_bin(pg) + rb = _dig_buy(pg) + rc = pg.evaluate("() => ({ wants: window.PROCITY.game.wants.length, n: window.PROCITY.game.collection.length })") + if not rb.get('err') and not rb.get('disabled') and rc['wants'] == 0 and rc['n'] >= 1: + OK('wantlist: bought the hunted sleeve — the want cleared itself, the find is in the crate') + else: + FAIL(f"wantlist/buy: {rb} · after {rc}") + base = pg.evaluate("() => ({ day: window.PROCITY.game.day, cash: window.PROCITY.game.cash, n: window.PROCITY.game.collection.length })") + # (d) travel: another town on the SAME save — the ride costs exactly one day + boot(pg, 'plansrc=osm&town=melbourne') + t1 = pg.evaluate("() => ({ day: window.PROCITY.game.day, cash: window.PROCITY.game.cash, n: window.PROCITY.game.collection.length, trav: window.PROCITY.game.traveled })") + if t1['day'] == base['day'] + 1 and t1['trav'] and t1['cash'] == base['cash'] and t1['n'] == base['n']: + OK(f"travel: synthetic → melbourne cost exactly one day (day {base['day']}→{t1['day']}), " + f"cash ${t1['cash']} and {t1['n']} find(s) rode along") + else: + FAIL(f"travel: {base} → {t1} (want day+1, traveled, cash/collection intact)") + # (e) same-town control: re-boot melbourne — no day moves, traveled stays false + boot(pg, 'plansrc=osm&town=melbourne') + t2 = pg.evaluate("() => ({ day: window.PROCITY.game.day, trav: window.PROCITY.game.traveled })") + if t2['day'] == t1['day'] and not t2['trav']: + OK(f"travel/control: re-booting the SAME town is free (day {t2['day']}, traveled false)") + else: + FAIL(f"travel/control: same-town reboot moved the day — {t2}") + # (f) the return trip costs a day again + boot(pg, '') + t3 = pg.evaluate("() => ({ day: window.PROCITY.game.day, trav: window.PROCITY.game.traveled })") + if t3['day'] == t1['day'] + 1 and t3['trav']: + OK(f"travel: the ride home cost a day too (day {t3['day']})") + else: + FAIL(f"travel: return trip wrong — {t3}") + # (g) ?game=0: zero want DOM on the card, zero rumor in any banner + boot(pg, 'game=0') + r2 = _enter_record_shop(pg) + if r2.get('err'): FAIL(f"wantlist/g0: {r2['err']}") + else: + _at_bin(pg) + pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' }))") + pg.wait_for_timeout(350) + pg.evaluate("""() => { const c = window.PROCITY.renderer.domElement; + c.dispatchEvent(new PointerEvent('pointerdown', { clientX: 640, clientY: 360, bubbles: true })); + c.dispatchEvent(new PointerEvent('pointerup', { clientX: 640, clientY: 360, bubbles: true })); }""") + pg.wait_for_timeout(300) + g0 = pg.evaluate("""() => ({ + want: !!document.querySelector('.pcdg-panel .want'), + panel: !!document.querySelector('.pcdg-panel'), + rumor: [...document.querySelectorAll('div')].some(e => e.textContent && e.textContent.includes('🗣')) })""") + if g0['panel'] and not g0['want'] and not g0['rumor']: + OK('wantlist/control: ?game=0 card has ZERO want DOM and no keeper mutters (construction-gated)') + else: + FAIL(f"wantlist/control: game=0 leaked want/rumor DOM — {g0}") + if errs: FAIL(f"wantlist: {len(errs)} console error(s); first: {errs[0][:140]}") + else: OK('wantlist: 0 console errors') + finally: + b.close() + + def smoke_classic_purity(p): """R30 gate (c) — ?classic=1 and ?game=0: ZERO Storage-prototype calls (instrumented before any page script), zero game DOM (B's ids), game null. Default boot is the POSITIVE control (the @@ -2290,6 +2407,7 @@ def main(): smoke_classic_purity(p) # R30 gate c — instrumented Storage zero + zero game DOM + positive control smoke_rotation(p) # R30 gate d — vertex-data rotation determinism (the wave-3 binding) smoke_bible(p) # R32 — THE BIBLE (bands · gems · ledger) + per-day scarcity + ?game=0 control + smoke_wantlist_travel(p) # R33 — wantlist · keeper rumor · travel-costs-a-day (+ controls) smoke_tier2(p) # R27 — the kill-the-server gate (charter law #1) smoke_real_crate(p) # R24 — THE REAL CRATE (per-shop atlas identity · soft fall · no-op off the path) finally: