Lane C R30 (v7.0-alpha ledger #2, contract first): LANE_C_PUB §9 — the sell counter. Offer = min(p−1, max(1, floor(p·0.5))): the no-pump law structural, not tuned; type=type strict, fail-closed on untyped items; onSell mirrors onBuy (the proven wallet seam); collection read-only, removal via the game API
This commit is contained in:
parent
83ef8c9234
commit
c8503a5a2e
@ -29,6 +29,9 @@
|
|||||||
> **v5.0-beta amendment (2026-07-17, R26 wave 4):** **§7.2a's id form is now SOURCING-SCOPED** —
|
> **v5.0-beta amendment (2026-07-17, R26 wave 4):** **§7.2a's id form is now SOURCING-SCOPED** —
|
||||||
> `sku_<POS id>` for real, `mint_<listing id>` for mint. **Prefixes are namespace fences**, not decoration:
|
> `sku_<POS id>` for real, `mint_<listing id>` for mint. **Prefixes are namespace fences**, not decoration:
|
||||||
> my R25 line was written before mint existed and read as one space where there are two. Doc-only.
|
> my R25 line was written before mint existed and read as one space where there are two. Doc-only.
|
||||||
|
> **v7.0-alpha amendment (2026-07-18, R30 ledger #2):** **§9 added — the sell-counter contract** (the
|
||||||
|
> no-pump law made structural: `offer(p) = min(p−1, max(1, floor(p·0.5)))` — strictly below the buy side
|
||||||
|
> for every p ≥ 1, by construction). §0–§8 unchanged.
|
||||||
|
|
||||||
*The district ships **three** venue archetypes behind `?gigs=1` — `pub`, `band_room`, `rsl`. Lane A
|
*The district ships **three** venue archetypes behind `?gigs=1` — `pub`, `band_room`, `rsl`. Lane A
|
||||||
converts a chosen shop to a venue kind in place (`shop.type = kind`); C keys the interior recipe off
|
converts a chosen shop to a venue kind in place (`shop.type = kind`); C keys the interior recipe off
|
||||||
@ -462,3 +465,85 @@ never persisted.
|
|||||||
a *different* seeded pick than the dig's offers — a fan, not the crate's contents. A sold record's cover may
|
a *different* seeded pick than the dig's offers — a fan, not the crate's contents. A sold record's cover may
|
||||||
linger in the fan until the room rebuilds. If we ever want the fan to honour `gone`, **that** is when I'd
|
linger in the fan until the room rebuilds. If we ever want the fan to honour `gone`, **that** is when I'd
|
||||||
publish a post-build hook; it is not needed for the epoch's claim, because the crate you *riffle* is true.
|
publish a post-build hook; it is not needed for the epoch's claim, because the crate you *riffle* is true.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. The sell counter (v7.0-alpha, R30 ledger #2) → Lanes F/B
|
||||||
|
|
||||||
|
**The mirror of the buy card.** Walk to the keeper's counter holding items the shop trades in → a sell
|
||||||
|
card (the same manila price-sticker treatment as the dig's pull panel): item, OFFER, SELL. Module:
|
||||||
|
`web/js/interiors/sell.js` — DOM-only (zero draws, zero GPU resources), same consumer pattern as
|
||||||
|
`dig.js` (C ships the interaction; F owns the in-game input/mode hook).
|
||||||
|
|
||||||
|
### 9.1 THE OFFER — the no-pump law, structural (the multiplier and why)
|
||||||
|
|
||||||
|
```js
|
||||||
|
sellOffer(pricePaid) = min(pricePaid − 1, max(1, floor(pricePaid × 0.5))) // and never < 0
|
||||||
|
```
|
||||||
|
|
||||||
|
- **The multiplier is 0.5** — the classic secondhand-dealer margin (a keeper pays about half of what
|
||||||
|
he'll sticker it at; he has to eat too). It keeps selling *meaningful* (half your money back beats a
|
||||||
|
full crate of duds) without being an income source.
|
||||||
|
- **The `min(pricePaid − 1, …)` clamp is the law made structural, not tuned:** the offer is **strictly
|
||||||
|
below what you paid for every `pricePaid ≥ 1`** (at `p = 1` the offer is $0 and the SELL button
|
||||||
|
disables — the keeper won't buy what he can't sticker; at `p = 2` the naive `max(1, floor(p/2)) = 1`
|
||||||
|
would meet the clamp anyway — the clamp exists so no future retune of the multiplier can ever push an
|
||||||
|
offer to break even). **Buy-then-immediately-sell therefore loses ≥ $1 per round trip, always** —
|
||||||
|
F's no-pump gate (`R30 ledger #5`, monotonic cash loss) holds **by construction**, and a change to
|
||||||
|
this formula that breaks that property is a contract violation, not a balance pass.
|
||||||
|
- **Alpha basis = `pricePaid`.** The alpha carries no guide-band data (the BIBLE bands are beta), and
|
||||||
|
in the alpha every buy happens at the shop's asking price — so `pricePaid` IS the buy-side price the
|
||||||
|
player faced. **Beta swaps only the basis, not the shape:** when E's guide bands land, the offer
|
||||||
|
becomes `min(bandLow − 1, max(1, floor(bandLow × 0.5)))` — below the band's *low* edge by
|
||||||
|
construction, so a gem bought under band still nets its finder margin only against the band, never
|
||||||
|
against the keeper. The formula's clamp survives the swap untouched.
|
||||||
|
|
||||||
|
### 9.2 What the keeper buys (alpha: type = type, strict)
|
||||||
|
|
||||||
|
- `sellableIn(shopType, item)` ⇔ `item.type === shopType`. A record shop buys records, a book shop
|
||||||
|
books — **any** shop of the type (per-keeper taste, haggling, pawn-buys-everything: all beta, not
|
||||||
|
built).
|
||||||
|
- **An item with no `type` is NOT sellable — fail-closed** (vacuous-gate law: a matcher that passes an
|
||||||
|
untyped item would "match" everywhere). **→ Lane F ASK:** stamp `type` (the registry stock type:
|
||||||
|
`record`/`book`/`toy`/…) on every collection item at buy time — the R30 published shape
|
||||||
|
(`{townKey, godverseShopId|shopId, sku|slotId, pricePaid, dayFound}`) doesn't carry it, and without
|
||||||
|
it nothing is sellable anywhere. Carrying `title`/`artist` too is strongly asked (B's collection UI
|
||||||
|
needs them; my card falls back to the sku/slotId label when absent — ugly but honest).
|
||||||
|
|
||||||
|
### 9.3 The seam — money like buying, removal via the game API only
|
||||||
|
|
||||||
|
- **C consumes `window.PROCITY.game.collection` READ-ONLY.** The card never splices, reorders, or
|
||||||
|
mutates it (same species of law as §8.3's `items[]`).
|
||||||
|
- **`onSell(item, offer)`** is the single side-effect callback — the exact mirror of the dig's
|
||||||
|
`onBuy(offer)` (the proven wallet seam). The **consumer** (F) does both halves: credit the wallet,
|
||||||
|
remove the item from the collection **through the game API**. `onSell` returning `false` = keeper
|
||||||
|
veto: card unchanged, nothing moves.
|
||||||
|
- `wallet.sell(item, offer)` is provided (C-owned `wallet.js`, mirrors `buy()`): credits `offer`,
|
||||||
|
drops the matching v0-inventory entry, notifies. F binds it exactly like it binds `wallet.buy`.
|
||||||
|
- Till ring: the sale rings `'till'` from the counter emitter (the R28 one-shot pattern, same
|
||||||
|
distance-gain math as the dig's) — silent-and-happy with no engine / `?mute` / `?noassets`.
|
||||||
|
|
||||||
|
### 9.4 The trigger + the card
|
||||||
|
|
||||||
|
- **Where:** within **`SELL_DIST = 2.0 m`** of `room.counter.pose` (bench centre, room-local — the
|
||||||
|
frame the interior camera walks in). Helper exported: `nearCounter(room, pos, dist?)`.
|
||||||
|
- **Suggested E routing for F** (matches my harness): aimed bin → dig (unchanged) · else near counter
|
||||||
|
with ≥ 1 sellable → sell card · else shelf-buy. Pointer unlocks while the card is up (dig precedent).
|
||||||
|
- **The card:** one item at a time — artist/title (or the sku/slotId fallback), the round OFFER
|
||||||
|
sticker, SELL, `‹ ›` cycling when more than one sellable, ✕/Esc closes. `open()` returns `false`
|
||||||
|
(no card, no DOM) when nothing is sellable.
|
||||||
|
|
||||||
|
### 9.5 Fail-soft (the classic-pure law, C's half)
|
||||||
|
|
||||||
|
No game layer (`?classic=1` / `?game=0` ⇒ `window.PROCITY.game` absent) → the consumer never opens
|
||||||
|
the sell UI, **and** `open()` itself returns `false` on a missing/empty item list — zero errors, zero
|
||||||
|
DOM, zero localStorage. `createSell()` allocates DOM only; `dispose()` removes every node + listener.
|
||||||
|
|
||||||
|
### 9.6 API (loader-exact, per the §7 rule: the code is the contract)
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { createSell, sellOffer, sellableIn, nearCounter, SELL_MULT, SELL_DIST } from './js/interiors/sell.js';
|
||||||
|
const sell = createSell();
|
||||||
|
sell.open({ shopType, shopName, items, getCash, onSell, onClose, emitters }) // → bool (false = nothing sellable)
|
||||||
|
sell.close(); sell.dispose(); sell.active // mirrors dig.js
|
||||||
|
```
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user