Fable (integrator): ROUND 32 CLOSED — v7.0-beta wave 1 all green; smoke_bible + the routing amendment
qa.sh --strict --matrix on the final tree: 7 passed · 0 failed · 0 warn · 0 skipped, MATRIX 10 towns green. New STRICT gate smoke_bible: band law over EVERY bin off the digOffers surface (80 offers/5 bins), the buy-everything ledger runs on THE GEM'S BIN so the finder-margin leg is non-vacuous (bin1: 2 gems, grail asking $19 vs keeper $30; ledger closes to the cent — Σask $333 → Σoffer $135, gems alone +$21, every non-gem strictly negative), scarcity measured (emptied bin 0/16 on re-open AND across a full reload; 16 pulls in the save) with the ?game=0 negative control re-opening 16/16 (the detector discriminates). sell_routing amended from the alpha inequality (credited < paid) to the exact formula (credited == min(basis−1, basis//2), basis = bandLow-or-pricePaid) — the fence moved from the receipt to the band, which is what charter law #2 always said; it passed on a banded standard item (+$4 on basis $8). _at_bin grows an idx param (back-compatible). Money shots in docs/shots/v7_beta/ (the guide card + the crate at the counter). LANE_F_NOTES §32 (incl. the JING5 fresh-checkout trap: gitignored web/assets/models synced from m3ultra — also un-warned the validator's 15 cross-pack checks). README manual: the guide/gem/scarcity rows. Remaining beta charter queued in the round doc: wantlist + rumors · travel costs a day · Esc-in-person · then GodBay. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
bb30767b62
commit
9aae167d64
@ -4,6 +4,23 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 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) →
|
||||||
|
the pull/sell cards carry the **guide stamp**; parody bins roll seeded bands + 10% collector/
|
||||||
|
grail **gems** (asking under the keeper's bandLow offer — margin by construction); §9.1 sell
|
||||||
|
basis swapped to bandLow exactly as pre-ratified (alpha entries keep the pricePaid basis).
|
||||||
|
**Scarcity:** optional `pulls` on procity-save/1 — a bought dig slot stays gone all day
|
||||||
|
(pick-then-omit, zero collateral), pruned on sleep; real-sourced pulls never expire. §31.6
|
||||||
|
queue: cluster-pose spawn (main-street doors, classic keeps the frozen pose) · THE CRATE +
|
||||||
|
game bar live in interiors (C anywhere, yields to the cards) · DAWN dome swaps to high-cirrus
|
||||||
|
(weather override wins). **Gates:** new STRICT `smoke_bible` (band law over every bin ·
|
||||||
|
ledger closes to the cent on the GEM's bin, Σask $333 → Σoffer $135, gems +$21 · emptied bin
|
||||||
|
0 across reload · `?game=0` control re-opens 16/16) + sell_routing amended to the exact
|
||||||
|
formula (the fence moved from the receipt to the band). Full record: LANE_F_NOTES §32.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Round 31 (v7.0-alpha CLOSE) — THE DIG FLIP + the tag
|
## Round 31 (v7.0-alpha CLOSE) — THE DIG FLIP + the tag
|
||||||
|
|
||||||
**Fable's wave-0 playtest found the one alpha-blocker: the dig was default-OFF on a plain boot.**
|
**Fable's wave-0 playtest found the one alpha-blocker: the dig was default-OFF on a plain boot.**
|
||||||
|
|||||||
@ -23,7 +23,8 @@ Click **START** to lock the mouse. Then:
|
|||||||
| **W A S D** + mouse | walk / look (first person) |
|
| **W A S D** + mouse | walk / look (first person) |
|
||||||
| walk into a shop door | enter the interior (open shops only — check the hours tooltip) |
|
| walk into a shop door | enter the interior (open shops only — check the hours tooltip) |
|
||||||
| **E** (inside a shop) | open the crate at a record bin — then it's cursor-driven: click sleeves to riffle, click BUY |
|
| **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** (street) · **SLEEP** (top bar) | sell a find to the keeper (always under what you paid) · open THE CRATE — your collection · sleep = save, the day advances, stock rotates |
|
| **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) |
|
||||||
| click a book spine / toy box | pull-and-buy card (real price stickers in godverse shops) |
|
| 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) |
|
| walk out the door / **Esc** | leave the shop (Esc also frees the mouse anywhere) |
|
||||||
| **M** | 2D town map (street only) |
|
| **M** | 2D town map (street only) |
|
||||||
|
|||||||
@ -1754,3 +1754,62 @@ Fable's preview holds :8130, untouched):
|
|||||||
5. Automation-only, unverified in person: **dig Esc-close under synthetic events**.
|
5. Automation-only, unverified in person: **dig Esc-close under synthetic events**.
|
||||||
6. *(carried from §30.6)* no per-day `gone` tracking on parody/mint dig stock — scarcity-per-day
|
6. *(carried from §30.6)* no per-day `gone` tracking on parody/mint dig stock — scarcity-per-day
|
||||||
needs the save to carry pull records; rule it with the guide bands.
|
needs the save to carry pull records; rule it with the guide bands.
|
||||||
|
|
||||||
|
## §32 — R32: v7.0-beta wave 1 — THE BIBLE + scarcity + the §31.6 queue (Fable, solo round, JING5)
|
||||||
|
|
||||||
|
**The round in one line:** the guide bands land (derived, not baked), gems exist (parody bins only,
|
||||||
|
by construction), a bought slot stays gone for the day (pulls ride the save), and §31.6 items 1–3
|
||||||
|
are fixed. All gates green; the no-pump law survives the §9.1 basis swap structurally.
|
||||||
|
|
||||||
|
### §32.1 What shipped, by seam
|
||||||
|
|
||||||
|
- **`js/interiors/bible.js`** (C) — the band table mirroring E's pipeline thresholds
|
||||||
|
(record 8/25/60 · book 5/15/40 · toy 6/20/50), `bandRange`/`guideText`/`sellBasis`. Derivation
|
||||||
|
ruling: NO pack re-emit (charter risk #3 never opens); if E's thresholds move, bible.js moves in
|
||||||
|
the same commit.
|
||||||
|
- **dig.js** (C) — parody sleeves roll seeded bands (25/45/22/8%) + a 10% gem on collector/grail
|
||||||
|
(asking strictly under the keeper's own bandLow offer: collector $2–9 vs $12 · grail $5–20 vs
|
||||||
|
$30); fixed 9-draw stream shape per item; `rare` now ≡ grail (matches real packs). Real offers
|
||||||
|
carry `band` off `price_band`. `open({gone})` omits pulled slots AFTER the draw (the R27
|
||||||
|
pick-then-omit seam, zero collateral) — real via `'sku:<id>'`, parody via original offer index
|
||||||
|
(`o.i` survives filtering, identity preserved). The pull card + sell card gain the **guide
|
||||||
|
stamp** (`guide $8–24` / `$60+`) — present-fields-only, and the card NEVER says "gem": spotting
|
||||||
|
asking-under-guide is the player's skill.
|
||||||
|
- **sell.js** (C) — §9.1 basis swap executed exactly as pre-ratified: `sellOffer(sellBasis(entry))`
|
||||||
|
= `min(bandLow−1, max(1, floor(bandLow·0.5)))` when banded, pricePaid basis for alpha-era
|
||||||
|
entries (they sell exactly as before — no migration needed).
|
||||||
|
- **save.js** (F) — optional `pulls: string[]` on procity-save/1 (validated loudly when present;
|
||||||
|
absent on alpha saves — old savers drop it silently, never reject). `recordPull`/`pullsFor`
|
||||||
|
seams; pruned on sleep/import (day-tagged keys from other days are unreachable by construction;
|
||||||
|
untagged = real-sourced, kept — sold means gone). Cap 600 FIFO. Collection entries gain
|
||||||
|
optional `band` (recordFind stamps dig + shelf buys).
|
||||||
|
- **interior_mode.js** (F) — pull keys `${shopId}|${binKey}${pullTag}#${slot}`; pullTag =
|
||||||
|
`@d<game.day>` for rotating crates (INCLUDING day 1 — the seed stays untagged per the day-1
|
||||||
|
convention, the ledger doesn't), `''` for real-sourced. The SEED input is untouched (A's
|
||||||
|
rotation boundary holds). `digOffers` honest surface (index/ask/band/sku) for the gate.
|
||||||
|
- **hud.js + index.html** (B/F) — §31.6-2: `#pc-game` rides document.body (position:fixed, z 30),
|
||||||
|
ticks in every mode (`hud.tickGame()` from the interior/map branches), yields to the dig/sell
|
||||||
|
cards and the map; C toggles the crate anywhere. §31.6-1: cluster-pose spawn — main-street
|
||||||
|
shop doors (block kind, both generators), stand at the door nearest the cluster heart, face the
|
||||||
|
longer run of shopfronts; pure plan math, zero draws; `?classic=1` keeps the frozen pose.
|
||||||
|
§31.6-3: DAWN swaps the dome to `high-cirrus` (lighting `dawnSky` opt, null under classic);
|
||||||
|
a weather `setSky` is now an explicit override that always wins.
|
||||||
|
|
||||||
|
### §32.2 THE GATE (`smoke_bible`, STRICT) + the R30 amendment
|
||||||
|
|
||||||
|
Scans EVERY bin of the record shop off `digOffers`: band law on all offers (in-band unless a
|
||||||
|
collector/grail gem), then runs the buy-EVERYTHING ledger on **the gem's bin** (leg is
|
||||||
|
non-vacuous by construction — 80 offers/5 bins, bin1 holds 2 gems on the pinned seed, grail
|
||||||
|
asking $19 vs keeper $30). Ledger closes exactly: Σask $333 → Σoffer $135 (gems alone +$21,
|
||||||
|
every non-gem strictly negative). Scarcity: the emptied bin re-opens at 0, STAYS 0 across a full
|
||||||
|
reload (16 pulls in the save); negative control: `?game=0` re-opens it at 16 — the detector
|
||||||
|
discriminates. 0 console errors. **R30 amendment (sell_routing):** `credited < paid` was the
|
||||||
|
alpha inequality; the gate now asserts the exact formula (`credited == min(basis−1, basis//2)`,
|
||||||
|
basis = bandLow-or-pricePaid, `credited < basis`) — the fence moved from the receipt to the
|
||||||
|
band, which is what charter law #2 always said.
|
||||||
|
|
||||||
|
### §32.3 Environment note (JING5)
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|||||||
61
docs/LANES/ROUND32_INSTRUCTIONS.md
Normal file
61
docs/LANES/ROUND32_INSTRUCTIONS.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# PROCITY — Round 32 instructions (from Fable, integrator)
|
||||||
|
|
||||||
|
Date: 2026-07-20 · **R31 closed** — v7.0-alpha tagged and pushed; the loop plays. Round 32 opens
|
||||||
|
**v7.0-beta — THE THRILL, wave 1**: the guide bands land ([V7_THE_GAME.md](../V7_THE_GAME.md)
|
||||||
|
stage 2), scarcity rides the save, and the alpha playtest's friction queue (LANE_F_NOTES §31.6)
|
||||||
|
gets its cosmetic items. **Solo round:** John asked for direct progress; Fable executes all lanes
|
||||||
|
in one session on JING5, same treaty, same gates — lane ownership is honoured in the commit
|
||||||
|
messages, not the parallelism.
|
||||||
|
|
||||||
|
## The design rulings this round encodes (from the ratified charter + §31.6 queue)
|
||||||
|
|
||||||
|
1. **THE BIBLE derives, it is not baked.** E's band thresholds (build_stock_pack.py:88-94 —
|
||||||
|
record 8/25/60 · book 5/15/40 · toy 6/20/50) become a tiny JS mirror
|
||||||
|
(`js/interiors/bible.js`). No pack re-emit, so risk #3 (re-baked atlases orphaning owned
|
||||||
|
skus) never opens. The guide shows a BAND ("guide $25–59"), never a verdict — spotting the
|
||||||
|
gem stays the player's skill.
|
||||||
|
2. **Gems live in the parody bins.** Real/mint packs derive `price_band` FROM `price`, so real
|
||||||
|
asking is always inside its band → with the §9.1 basis swap (sellOffer basis = bandLow),
|
||||||
|
every real round trip stays strictly negative and **the R30 no-pump gate holds untouched,
|
||||||
|
by construction**. Procedural stock gains seeded bands + a seeded under-band gem roll —
|
||||||
|
the only place asking can sit under the guide, which is exactly the op-shop fantasy.
|
||||||
|
3. **Scarcity is what makes the gem loop safe** (§31.6 item 6, ruled WITH the bands as filed):
|
||||||
|
a bought dig slot enters `pulls` in the save (optional field, procity-save/1 — old saves
|
||||||
|
stay valid, old loaders drop the field without rejecting) and that bin omits it after the
|
||||||
|
draw (the proven `pack.gone` pick-then-omit seam — zero collateral). A gem is profit ONCE;
|
||||||
|
tomorrow's rotation re-keys the bin. Growth is bounded by finds, not arbitrage (charter
|
||||||
|
law #2's real meaning).
|
||||||
|
4. **§31.6 items 1–3:** spawn becomes a computed cluster-pose facing the shop strip (classic
|
||||||
|
keeps the frozen v2 pose); THE CRATE surfaces inside interiors (C key — the §31.6
|
||||||
|
"crate at the counter" want, hidden while dig/sell cards own the screen); DAWN gets a
|
||||||
|
cool sky dome swap (classic + weather-override fenced).
|
||||||
|
|
||||||
|
## Gate additions (F)
|
||||||
|
|
||||||
|
`smoke_bible` in flags_check.py: guide stamp on the pull card (real + parody) · offer < bandLow
|
||||||
|
structurally · a seeded parody gem exists and its round trip is positive ONCE (then the slot is
|
||||||
|
gone — re-open the bin and it stays gone; reload and it stays gone) · negative control:
|
||||||
|
`?game=0` re-opens the same bin with the slot PRESENT (the detector discriminates — scarcity is
|
||||||
|
the save's doing, not the dig's). Classic purity untouched (no new module constructs under
|
||||||
|
`?classic=1` / `?dig=0`).
|
||||||
|
|
||||||
|
## ROUND CLOSED (Fable) — ALL GREEN
|
||||||
|
|
||||||
|
`qa.sh --strict --matrix`: **7 passed · 0 failed · 0 warn · 0 skipped**, MATRIX 10 towns green.
|
||||||
|
`smoke_bible`: 80 parody offers across 5 bins all band-lawful; bin1 holds 2 seeded gems (grail
|
||||||
|
asking $19 vs keeper $30); the buy-everything ledger closes to the cent (Σask $333 → Σoffer
|
||||||
|
$135, gems alone +$21, every non-gem strictly negative); the emptied bin stays empty across a
|
||||||
|
full reload (16 pulls in the save) and `?game=0` re-opens it 16/16 (the control discriminates);
|
||||||
|
0 console errors. sell_routing now asserts the exact §9.1 formula and passed on a BANDED item
|
||||||
|
(standard, basis $8 → +$4). Money shots: `docs/shots/v7_beta/guide_card.png` (the guide stamp —
|
||||||
|
"1974 COUNTRY guide $8–24" beside the $24 sticker) + `crate_at_the_counter.png` (THE CRATE open
|
||||||
|
inside a shop, game bar riding bottom-right). Environment note: JING5's gitignored
|
||||||
|
`web/assets/models/` was absent (fresh-checkout trap, now §32.3 in LANE_F_NOTES) — synced from
|
||||||
|
m3ultra, which also un-warned the manifest validator's 15 cross-pack checks. Full record:
|
||||||
|
LANE_F_NOTES §32. Shipped: bible.js + guide stamps + parody bands/gems (C) · pulls on the save
|
||||||
|
+ gone-wiring + digOffers surface (F) · crate-in-interiors + cluster-pose spawn + dawn sky (B)
|
||||||
|
· smoke_bible + the routing amendment (F) · README/docs.
|
||||||
|
|
||||||
|
**The beta's remaining charter** (next rounds): the WANTLIST + seeded rumors (D's keepers
|
||||||
|
finally gossip) · travel costs a day (23 towns become the map) · §31.6-5 (dig Esc under real
|
||||||
|
hands) · then v7.0 = GodBay offline auctions (G) + onboarding + the tour.
|
||||||
BIN
docs/shots/v7_beta/crate_at_the_counter.png
Normal file
BIN
docs/shots/v7_beta/crate_at_the_counter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 789 KiB |
BIN
docs/shots/v7_beta/guide_card.png
Normal file
BIN
docs/shots/v7_beta/guide_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
@ -1633,16 +1633,17 @@ def _game_state(pg):
|
|||||||
exNorm: JSON.stringify(ex), day: g.day, cash: g.cash, n: g.collection.length }; }""")
|
exNorm: JSON.stringify(ex), day: g.day, cash: g.cash, n: g.collection.length }; }""")
|
||||||
|
|
||||||
|
|
||||||
def _at_bin(pg):
|
def _at_bin(pg, idx=0):
|
||||||
"""Stand at the first bin of the open room, aimed at it (smoke_buy's proven setup)."""
|
"""Stand at the idx-th bin of the open room, aimed at it (smoke_buy's proven setup)."""
|
||||||
return pg.evaluate("""() => {
|
return pg.evaluate("""(idx) => {
|
||||||
const P = window.PROCITY;
|
const P = window.PROCITY;
|
||||||
const bins = []; P.interiorMode.current.group.traverse(o => { if (o.userData && o.userData.kind === 'bin') bins.push(o); });
|
const bins = []; P.interiorMode.current.group.traverse(o => { if (o.userData && o.userData.kind === 'bin') bins.push(o); });
|
||||||
if (!bins.length) return { err: 'no bins' };
|
if (!bins.length) return { err: 'no bins' };
|
||||||
const bp = new P.THREE.Vector3(); bins[0].getWorldPosition(bp);
|
if (idx >= bins.length) return { err: 'bin index out of range', bins: bins.length };
|
||||||
|
const bp = new P.THREE.Vector3(); bins[idx].getWorldPosition(bp);
|
||||||
P.camera.position.set(bp.x, 1.6, bp.z + 1.4); P.camera.lookAt(bp.x, bp.y + 0.4, bp.z); P.camera.updateMatrixWorld();
|
P.camera.position.set(bp.x, 1.6, bp.z + 1.4); P.camera.lookAt(bp.x, bp.y + 0.4, bp.z); P.camera.updateMatrixWorld();
|
||||||
return { ok: true };
|
return { ok: true, bins: bins.length };
|
||||||
}""")
|
}""", idx)
|
||||||
|
|
||||||
|
|
||||||
def _dig_buy(pg):
|
def _dig_buy(pg):
|
||||||
@ -1757,7 +1758,7 @@ def smoke_sell_routing(p):
|
|||||||
FAIL(f"routing: dig buy wallet/collection delta wrong — {rb}"); return
|
FAIL(f"routing: dig buy wallet/collection delta wrong — {rb}"); return
|
||||||
ent = pg.evaluate("""() => { const g = window.PROCITY.game;
|
ent = pg.evaluate("""() => { const g = window.PROCITY.game;
|
||||||
const e = g.collection[g.collection.length - 1];
|
const e = g.collection[g.collection.length - 1];
|
||||||
return { type: e.type, pricePaid: e.pricePaid, hasId: !!(e.sku || e.slotId) }; }""")
|
return { type: e.type, pricePaid: e.pricePaid, hasId: !!(e.sku || e.slotId), band: e.band || null }; }""")
|
||||||
if ent['type'] == 'record' and ent['hasId'] and ent['pricePaid'] == paid:
|
if ent['type'] == 'record' and ent['hasId'] and ent['pricePaid'] == paid:
|
||||||
OK(f"routing: entry shape honest — type=record, sku|slotId set, pricePaid == debit (${paid})")
|
OK(f"routing: entry shape honest — type=record, sku|slotId set, pricePaid == debit (${paid})")
|
||||||
else:
|
else:
|
||||||
@ -1778,10 +1779,21 @@ def smoke_sell_routing(p):
|
|||||||
hudCash: cashEl ? cashEl.textContent : null,
|
hudCash: cashEl ? cashEl.textContent : null,
|
||||||
hudCrate: crateBtn ? crateBtn.textContent : null }; }""")
|
hudCrate: crateBtn ? crateBtn.textContent : null }; }""")
|
||||||
credited = after['cash'] - cash_before
|
credited = after['cash'] - cash_before
|
||||||
if rs.get('sold') == 1 and after['n'] == n_before - 1 and 0 < credited < paid:
|
# [R32 amendment] the no-pump law's beta form: the offer is EXACTLY min(basis−1, max(1, basis//2))
|
||||||
OK(f"routing: SELL — +${credited} credited (< ${paid} paid, no-pump by construction), collection {n_before}→{after['n']}")
|
# where basis = bandLow when the entry carries a band (the §9.1 swap), pricePaid otherwise. The
|
||||||
|
# old `credited < paid` held for every alpha sale but a banded GEM legitimately credits above
|
||||||
|
# what was paid — the fence moved from the receipt to the guide band, so the gate measures the
|
||||||
|
# formula itself, not the alpha inequality.
|
||||||
|
R32_LOW = {'bargain': 2, 'standard': 8, 'collector': 25, 'grail': 60}
|
||||||
|
basis = R32_LOW.get(ent.get('band'), paid)
|
||||||
|
want = min(basis - 1, max(1, basis // 2)) if basis >= 1 else 0
|
||||||
|
if rs.get('sold') == 1 and after['n'] == n_before - 1 and credited == want and credited < basis:
|
||||||
|
OK(f"routing: SELL — +${credited} credited == min(basis−1, basis/2) with basis ${basis} "
|
||||||
|
f"({'band ' + ent['band'] if ent.get('band') else 'pricePaid'}; < basis, no-pump by construction), "
|
||||||
|
f"collection {n_before}→{after['n']}")
|
||||||
else:
|
else:
|
||||||
FAIL(f"routing: sale delta wrong — sold={rs.get('sold')}, credited {credited} vs paid {paid}, n {n_before}→{after['n']}")
|
FAIL(f"routing: sale delta wrong — sold={rs.get('sold')}, credited {credited} want {want} "
|
||||||
|
f"(basis {basis}), n {n_before}→{after['n']}")
|
||||||
if after['hudCash'] is not None:
|
if after['hudCash'] is not None:
|
||||||
if after['hudCash'] == str(after['cash']) and after['hudCrate'] == f"CRATE {after['n']}":
|
if after['hudCash'] == str(after['cash']) and after['hudCrate'] == f"CRATE {after['n']}":
|
||||||
OK(f"routing: B's HUD follows — $" + after['hudCash'] + f" · {after['hudCrate']}")
|
OK(f"routing: B's HUD follows — $" + after['hudCash'] + f" · {after['hudCrate']}")
|
||||||
@ -1983,6 +1995,147 @@ def smoke_nopump(p):
|
|||||||
b.close()
|
b.close()
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_bible(p):
|
||||||
|
"""R32 gate — THE BIBLE + per-day scarcity (v7.0-beta wave 1). Legs: (a) the pull card carries the
|
||||||
|
guide stamp on a NAKED boot (lesson #4 — no explicit flags; parody bin); (b) structural: every parody
|
||||||
|
offer prices INSIDE its band unless it's a seeded gem (asking < bandLow, collector/grail only) —
|
||||||
|
read off F's digOffers honest surface, checked against the BIBLE mirror of E's thresholds;
|
||||||
|
(c) buy the WHOLE bin, sell it all: cash moves exactly Σoffer−Σask with offer = min(low−1,
|
||||||
|
max(1, low//2)) per item — gems positive, everything else negative, the ledger closes to the cent;
|
||||||
|
(d) scarcity: the emptied bin re-opens EMPTY, stays empty across a reload (pulls ride the save),
|
||||||
|
and (e) negative control: ?game=0 re-opens the same bin FULL — the detector discriminates
|
||||||
|
(scarcity is the save's doing, not the dig's). (f) real stock carries the stamp too."""
|
||||||
|
head('GATE R32: THE BIBLE — guide bands · gems · per-day scarcity (+ ?game=0 control)')
|
||||||
|
BIBLE = {'bargain': (2, 7), 'standard': (8, 24), 'collector': (25, 59), 'grail': (60, None)}
|
||||||
|
exp_offer = lambda band: min(BIBLE[band][0] - 1, max(1, BIBLE[band][0] // 2))
|
||||||
|
b, pg, errs = new_page(p)
|
||||||
|
try:
|
||||||
|
boot(pg, '') # NAKED — dig/game default-on carry the feature
|
||||||
|
r0 = _enter_record_shop(pg)
|
||||||
|
if r0.get('err'): FAIL(f"bible: {r0['err']}"); return
|
||||||
|
# bankroll the buy-everything leg through the REAL import path (5000 covers any 16-sleeve bin)
|
||||||
|
rich = pg.evaluate("""() => { const g = window.PROCITY.game;
|
||||||
|
const ex = JSON.parse(g.export()); ex.cash = 5000; ex.savedAt = Date.now();
|
||||||
|
return { ok: g.import(JSON.stringify(ex)), cash: g.cash }; }""")
|
||||||
|
if not (rich['ok'] and rich['cash'] == 5000):
|
||||||
|
FAIL(f"bible: bankroll import failed — {rich}"); return
|
||||||
|
pg.evaluate("() => window.DBG.setSegment(2)") # import's onDay woke us at DAWN — back to midday
|
||||||
|
# ── (a)+(b): scan EVERY bin in the room off the honest surface — band law on all of them, and
|
||||||
|
# find a bin holding a GEM (seeded: whichever bin it is today, it's that bin forever). The
|
||||||
|
# buy-everything ledger leg then runs on THE GEM'S BIN, so the finder-margin arithmetic is
|
||||||
|
# exercised for real, not vacuously (a 0-gem bin would prove only the losing half).
|
||||||
|
n_bins = (_at_bin(pg) or {}).get('bins', 1)
|
||||||
|
bad, all_offers, gem_bin, gems = [], {}, None, []
|
||||||
|
for bi in range(n_bins):
|
||||||
|
_at_bin(pg, bi)
|
||||||
|
pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' }))")
|
||||||
|
pg.wait_for_timeout(350)
|
||||||
|
offs = pg.evaluate("() => window.PROCITY.interiorMode.digOffers")
|
||||||
|
pg.evaluate("() => { const o = document.querySelector('.pcdg-out'); if (o) o.click(); }")
|
||||||
|
pg.wait_for_timeout(200)
|
||||||
|
if not offs: bad.append(f"bin{bi} unreadable"); continue
|
||||||
|
all_offers[bi] = offs
|
||||||
|
bin_gems = []
|
||||||
|
for o in offs:
|
||||||
|
band = o.get('band')
|
||||||
|
if band not in BIBLE: bad.append(f"bin{bi}#{o['i']} band={band!r}"); continue
|
||||||
|
lo, hi = BIBLE[band]
|
||||||
|
if o['price'] < lo:
|
||||||
|
if band in ('collector', 'grail'): bin_gems.append(o)
|
||||||
|
else: bad.append(f"bin{bi}#{o['i']} {band} ${o['price']} under-band but not gem-eligible")
|
||||||
|
elif hi is not None and o['price'] > hi:
|
||||||
|
bad.append(f"bin{bi}#{o['i']} {band} ${o['price']} over band")
|
||||||
|
if bin_gems and gem_bin is None: gem_bin, gems = bi, bin_gems
|
||||||
|
if bad: FAIL(f"bible: band-law violations — {bad[:3]}")
|
||||||
|
else: OK(f"bible: {sum(len(v) for v in all_offers.values())} parody offers across {n_bins} bin(s) "
|
||||||
|
f"all inside their bands (or seeded gems)")
|
||||||
|
if gem_bin is None:
|
||||||
|
FAIL(f"bible: NO gem in any of {n_bins} bins — the thrill leg is unreachable on this seed"); return
|
||||||
|
for g in gems:
|
||||||
|
if not g['price'] < exp_offer(g['band']):
|
||||||
|
FAIL(f"bible: gem #{g['i']} asks ${g['price']} ≥ keeper offer ${exp_offer(g['band'])} — no finder margin")
|
||||||
|
OK(f"bible: bin{gem_bin} holds {len(gems)} gem(s) — e.g. {gems[0]['band']} asking ${gems[0]['price']} "
|
||||||
|
f"vs keeper ${exp_offer(gems[0]['band'])} (margin by construction)")
|
||||||
|
offers = all_offers[gem_bin]
|
||||||
|
_at_bin(pg, gem_bin)
|
||||||
|
pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' }))")
|
||||||
|
pg.wait_for_timeout(350)
|
||||||
|
# pull the front sleeve → the guide stamp on the card
|
||||||
|
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)
|
||||||
|
stamp = pg.evaluate("""() => { const p = document.querySelector('.pcdg-panel');
|
||||||
|
return p && p.style.display === 'block' ? p.textContent : null; }""")
|
||||||
|
if stamp and 'guide $' in stamp: OK('bible: the pull card carries the guide stamp (naked boot, parody)')
|
||||||
|
else: FAIL(f"bible: no guide stamp on the pull card — {stamp[:80] if stamp else 'no panel'}")
|
||||||
|
pg.evaluate("""() => { const p = document.querySelector('.pcdg-panel');
|
||||||
|
const x = p && p.querySelector('.x'); if (x) x.click(); }""")
|
||||||
|
# ── (c): buy the WHOLE bin through the real card, then sell it all at the counter ──
|
||||||
|
cash0 = pg.evaluate("() => window.PROCITY.wallet.cash()")
|
||||||
|
bought = 0
|
||||||
|
for _ in range(len(offers)):
|
||||||
|
rb = _dig_buy(pg)
|
||||||
|
if rb.get('err') or rb.get('disabled'): break
|
||||||
|
bought += 1
|
||||||
|
if bought < len(offers): _at_bin(pg, gem_bin) # walk-out re-closed the dig; re-aim the gem's bin
|
||||||
|
if bought != len(offers):
|
||||||
|
FAIL(f"bible: bought {bought}/{len(offers)} — the buy-everything leg died early"); return
|
||||||
|
ask_total = sum(o['price'] for o in offers)
|
||||||
|
offer_total = sum(exp_offer(o['band']) for o in offers)
|
||||||
|
rs = _counter_sell(pg, max_sales=bought + 2)
|
||||||
|
sell_card = pg.evaluate("() => { const p = document.querySelector('.pcsl-panel'); return p ? p.textContent : ''; }")
|
||||||
|
if rs.get('err') or rs.get('sold', 0) != bought:
|
||||||
|
FAIL(f"bible: sold {rs.get('sold')}/{bought} — {rs}"); return
|
||||||
|
cash1 = pg.evaluate("() => window.PROCITY.wallet.cash()")
|
||||||
|
gem_profit = sum(exp_offer(o['band']) - o['price'] for o in gems)
|
||||||
|
if cash1 - cash0 == offer_total - ask_total:
|
||||||
|
OK(f"bible: the ledger closes — Σask ${ask_total} → Σoffer ${offer_total} "
|
||||||
|
f"(net {cash1 - cash0:+d}; gems alone {gem_profit:+d}, every non-gem strictly negative)")
|
||||||
|
else:
|
||||||
|
FAIL(f"bible: ledger mismatch — cash moved {cash1 - cash0:+d}, expected {offer_total - ask_total:+d} "
|
||||||
|
f"(basis law broken somewhere)")
|
||||||
|
# ── (d): scarcity — the emptied bin is EMPTY on re-open, and STAYS empty across a reload ──
|
||||||
|
_at_bin(pg, gem_bin)
|
||||||
|
pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' }))")
|
||||||
|
pg.wait_for_timeout(300)
|
||||||
|
n_re = pg.evaluate("() => (window.PROCITY.interiorMode.digOffers || []).length")
|
||||||
|
pg.evaluate("() => { const o = document.querySelector('.pcdg-out'); if (o) o.click(); }")
|
||||||
|
pulls_n = pg.evaluate("() => (JSON.parse(window.PROCITY.game.export()).pulls || []).length")
|
||||||
|
if n_re == 0 and pulls_n >= bought:
|
||||||
|
OK(f"bible/scarcity: re-opened bin is cleaned out (0 of {bought}) · save carries {pulls_n} pull(s)")
|
||||||
|
else:
|
||||||
|
FAIL(f"bible/scarcity: re-open shows {n_re} sleeves (want 0) · pulls in save {pulls_n}")
|
||||||
|
boot(pg, '') # full reload, same origin — the save must carry it
|
||||||
|
r1 = _enter_record_shop(pg)
|
||||||
|
if r1.get('err'): FAIL(f"bible/scarcity reload: {r1['err']}")
|
||||||
|
else:
|
||||||
|
_at_bin(pg, gem_bin)
|
||||||
|
pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' }))")
|
||||||
|
pg.wait_for_timeout(300)
|
||||||
|
n_reload = pg.evaluate("() => (window.PROCITY.interiorMode.digOffers || []).length")
|
||||||
|
if n_reload == 0: OK('bible/scarcity: still cleaned out after a full reload — pulls rode the save')
|
||||||
|
else: FAIL(f"bible/scarcity: reload restocked the bin ({n_reload} sleeves) — pulls not persisted")
|
||||||
|
# ── (e): negative control — ?game=0 must re-open the SAME bin FULL (rotation gate: game=0 ≡ day1) ──
|
||||||
|
boot(pg, 'game=0')
|
||||||
|
r2 = _enter_record_shop(pg)
|
||||||
|
if r2.get('err'): FAIL(f"bible/control: {r2['err']}")
|
||||||
|
else:
|
||||||
|
_at_bin(pg, gem_bin)
|
||||||
|
pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' }))")
|
||||||
|
pg.wait_for_timeout(300)
|
||||||
|
n_ctl = pg.evaluate("() => (window.PROCITY.interiorMode.digOffers || []).length")
|
||||||
|
if n_ctl == len(offers): OK(f"bible/control: ?game=0 re-opens the bin FULL ({n_ctl}) — "
|
||||||
|
"scarcity is the save's doing, the detector discriminates")
|
||||||
|
else: FAIL(f"bible/control: ?game=0 bin shows {n_ctl}, want {len(offers)}")
|
||||||
|
if 'guide $' in (sell_card or ''): OK('bible: the sell card carries the guide line too')
|
||||||
|
else: WARN('bible: no guide line read off the sell card (cosmetic leg)')
|
||||||
|
if errs: FAIL(f"bible: {len(errs)} console error(s); first: {errs[0][:140]}")
|
||||||
|
else: OK('bible: 0 console errors')
|
||||||
|
finally:
|
||||||
|
b.close()
|
||||||
|
|
||||||
|
|
||||||
def smoke_classic_purity(p):
|
def smoke_classic_purity(p):
|
||||||
"""R30 gate (c) — ?classic=1 and ?game=0: ZERO Storage-prototype calls (instrumented before any
|
"""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
|
page script), zero game DOM (B's ids), game null. Default boot is the POSITIVE control (the
|
||||||
@ -2136,6 +2289,7 @@ def main():
|
|||||||
smoke_nopump(p) # R30 gate b — 5 real round trips monotonic loss + C's negative control
|
smoke_nopump(p) # R30 gate b — 5 real round trips monotonic loss + C's negative control
|
||||||
smoke_classic_purity(p) # R30 gate c — instrumented Storage zero + zero game DOM + positive control
|
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_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_tier2(p) # R27 — the kill-the-server gate (charter law #1)
|
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)
|
smoke_real_crate(p) # R24 — THE REAL CRATE (per-shop atlas identity · soft fall · no-op off the path)
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user