From 1c4552a9479aa8989884c692647746dd113b4033 Mon Sep 17 00:00:00 2001 From: m3ultra Date: Mon, 3 Aug 2026 19:27:25 +1000 Subject: [PATCH] =?UTF-8?q?Fable:=20RED=20TICKET=20DAY=20=E2=80=94=20the?= =?UTF-8?q?=20SALE=20mode=20design=20(3=20framings,=20adversarially=20judg?= =?UTF-8?q?ed);=20the=20Band=20Floor=20Law,=20and=20the=20verified=20findi?= =?UTF-8?q?ng=20that=20only=20record=20shops=20and=20stalls=20have=20crate?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- docs/SALE_RED_TICKET_DAY.md | 182 ++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 docs/SALE_RED_TICKET_DAY.md diff --git a/docs/SALE_RED_TICKET_DAY.md b/docs/SALE_RED_TICKET_DAY.md new file mode 100644 index 0000000..13f67f9 --- /dev/null +++ b/docs/SALE_RED_TICKET_DAY.md @@ -0,0 +1,182 @@ +> **FABLE'S NOTE (2026-07-26).** John's pitch; designed by a 3-framing panel (time-attack / +> market-day / daily-challenge), each adversarially judged, then synthesised. **I verified the +> load-bearing finding myself before circulating this:** `kind:'bin'` is minted at exactly +> `fittings.js:60` and `:75` (`recordBin`, `crate`), and those appear in exactly two recipes — +> `record` (`theme.js:38-39`) and market `stall` (`theme.js:196`). **Confirmed: only record +> shops and market stalls have a diggable crate.** The op shop's recipe (`theme.js`) is clothes +> racks, metal shelves, a bookshelf, a trestle table and `crateStack` — which its own comment +> labels *"back-room dressing"*, i.e. scenery, not a bin. +> +> **That is a finding about the MAIN GAME, not just this mode**, and it is filed to the v8 debt +> ledger: PROCITY is built on thriftgod's op-shop DNA and **you cannot dig in an op shop.** +> Whether that is a gap to close (give the op shop a bric-a-brac bin) or a fact to design around +> (records are the deep dig; op shops are pull-and-buy) is a John-level taste call, and RED +> TICKET DAY does not depend on the answer. +> +> Superseded in part by John's later reframe — *"the city being procedural and nebulous… you have +> to even know where to find the store"* — which is running as its own design pass. Read that +> charter alongside this one: this document owns the ROUND and the LAWS (the Band Floor Law is +> the best idea in it); the reframe owns the HUNT. + +# RED TICKET DAY +### synthesis of three designs and three adversarial passes — decision-grade, for John + +--- + +## 1. WHATAYARECKON + +Yes. Build it. Not because the game needs another system — it needs fewer — but because this is the first thing anyone has proposed that **spends the clock**. + +Read our own frontier doc back: *"hours exist! nothing makes you race them"* and *"the clock is scenery."* That is not a missing feature, it is a missing **cost**. Every verb in PROCITY is currently free: riffle a crate, walk a block, dig all day, nothing is consumed except cash. The shops shut and it costs you nothing because there is nothing you were going to do anyway. The sale fixes that from the other end — it makes *today* the only day the $110 grail is $66, and once today has a thing in it that tomorrow doesn't, the hours that already exist start biting without a single new clock. + +And it answers *"what is a session?"* — which has been open since v4. A session is **one town, one trading day, one docket**: wake, read the paper, walk, choose which crates you can afford to open, get shut out of the last shop, sleep. Ten to twenty minutes with a beginning, a middle and an end. + +Two things it is not. It is **not a stopwatch** — all three judges landed on that from different directions and the code agrees (details in §8). And it is **not free money**: the round is a net cash *loss* by construction, and that is the point. You are not trading, you are buying the day you finally got the thing. + +--- + +## 2. WHAT IT IS + +**RED TICKET DAY.** One day in seven, seeded per town, the shops that have crates in them mark stock down. The paper tells you which ones. The markdown never crosses the guide band's floor, so a red ticket is never a gem and a gem is never red — the skill you already have gets *harder*, not easier. You cannot buy them all. The shops shut. What you carried out is the score, and it lives in the crate forever with "day 14, Bendigo" under it. + +Structure and scoring come from **TIMEATTACK** (bounded round, forced-flag gates, DOM-only, the Band Floor Law). Diegesis and rhythm come from **MARKETDAY** (a *day*, not a stopwatch; the morning paper; the market's own FIRST IN BEST DRESSED fiction, already painted on the wall at `theme.js:201`). The time model comes from **DAILY**'s one great idea — *time passes on the street, not in the shop* — **corrected**: seconds don't cost you indoors, but **crates do**. + +--- + +## 3. THE ROUND + +**Wake.** SLEEP rolls the day (`save.js`, `index.html:281` → `lighting.setSegment(0)`). If today is the town's red-ticket day, the wake toast carries the paper: + +> 🌅 day 14 · **RED TICKET DAY** — Vinyl Solution ⅓ off · The Turnstyle ¼ off · Sadie's half price + +The sale module then sets the segment to the first roster shop's opening, so a sale day always starts with a door about to open. + +**The roster: what the town actually has.** Only two shop types carry crates (measured, §4). The roster is the biggest cluster of ≤3 crate-shops all within 400 m of each other, seeded. Across the 26 shipped town keys at seed 20261990: + +| roster | towns | tour | sprint @ 8.8 m/s | +|---|---|---|---| +| 3 shops | 5 (fitzroy, adelaide, brunswick, katoomba_real, melbourne) | 74–369 m | 8–42 s | +| 2 shops | 7 | 6–390 m | 1–44 s | +| 1 shop | 11 | — | — | +| **0 — no sale day** | 3 (bowral, darwin, daylesford) | — | — | + +Synthetic towns (`plansrc` default, ~480 shops, 103–151 crate-shops): roster of 3, tour **8–27 m**. So the mode is a route problem in the default town and in five real ones, and a *depth* problem in the other seventeen. Both are rounds. Neither is a lie. + +**Inside.** A record shop holds 3–8 crates × 16 sleeves = **48–128 records**, of which (modelled off the shipped band table at `bible.js:26-30`) **5–13 carry a red ticket**. A ticket is 25 / 35 / 50% off asking, seeded per shop-day, **floored at the band's low edge**. Mean marked price ≈ **$21**; mean saving ≈ **$10**; a grail asking $90 comes down to $60 and no further. + +**The purse.** Day-1 wallet is `60 + (r()*140|0)` ≈ **$130** (`wallet.js:10`). Six red tickets and you're out. There are 12–30 on a three-shop roster. **You take a fifth to a half of what's marked.** That is the whole game of the round: not "grab everything", but "which of these do I want enough." + +**The clock — the one real change.** Time does not pass while you riffle. It passes when you **walk** (the street clock, exactly as shipped) and when you **open a crate**: one crate ≈ ⅓ of a segment. A trading day gives you roughly **nine crate-openings**. Fitzroy's 369 m tour eats three of them. So three shops = two crates each, out of the 3–8 each shop has. You will never see the bottom of a town, and the shutter comes down mid-crate. + +**The end.** The last roster shop shuts (`isOpen`, already enforced at `index.html:493`). The docket totals what you carried out: + +> 🔴 the day's take — 5 red tickets, $84 saved, $103 spent · you left 9 marked in the crates you opened · you never made it to Sadie's + +Then SLEEP. Six ordinary days until the next one, or travel (which costs a day anyway). + +--- + +## 4. THE THREE FACTS THAT DECIDED THE DESIGN + +Measured this session against the tree, not asserted: + +**(a) Only two shop types have crates.** `userData.kind:'bin'` is minted at exactly `fittings.js:60` (`recordBin`) and `:75` (`crate`), and those appear in exactly two recipes: `record` (`theme.js:38-39`) and market `stall` (`theme.js:196`). Op shops, pawnbrokers, book barns, toy shops have **no diggable bin at all**. Two of the three submitted designs set their flagship scene in an op shop. That scene cannot happen. + +**(b) The real towns are big and thin.** Generated all 26 town keys: crate-shops per town run **0 to 12**, and they can be **2.3 km apart** (hobart, newtown). Three towns have none. The "three shops, three depths, pick a route" premise is a Fitzroy feature. Hence the 400 m clique rule and the honest "the round is however many shops this town has." + +**(c) The day is six values wide.** `HOUR_NUM = [6.5, 9, 12.5, 15.5, 18.5, 22]` (`lighting.js:22`); record hours are 10–18 (`registry.js:40`), stalls 8–14. So **records are open at 12:30 and 15:30 only; stalls at 9:00 and 12:30 only** — two segments each, 80 seconds of street clock. MIDDAY is the only segment where the whole diggable town trades. That is not a bug to design around, it is the trading day, already shipped: **stalls in the morning, records after lunch, everything at midday.** Use it. + +--- + +## 5. MECHANISM, LANE BY LANE + +**Lane C — `dig.js`, ~8 lines, one new optional opt.** +- `dig.js:421` — after `offers = …`, before `buildStack(offers)` at `:424`: `if (opts.sale) offers = offers.map(opts.sale);` A post-draw transform. `proceduralSleeves` (`:36-56`) and `pickRealOffers` (`:379-402`) are complete; zero stream drift, zero re-order, no golden moves. **Never in-place on `pack.items`** — `stockpack.js:30-31` caches by (type, base) and an in-place write leaks town-wide and permanent. `{...o}` copy, always. +- `dig.js:110-115` — red `fillStyle` on the price when `o.was` is present. Rides the existing per-open `CanvasTexture`. +0 draws. +- `dig.js:266-275` — a `🔴 SALE · was $50` chip in the stamps row (present-fields-only, so absent ⇒ byte-identical card). The `guide $25–59` line stays exactly where it is: **that is the discount theatre.** `~~$22~~ $17` inside `guide $8–24` and `~~$50~~ $31` inside `guide $25–59` are the same red ticket and opposite verdicts. +- `dig.js:480-487` — widen `currentOffers()` with `was` and `sale`. Without this the gates cannot see their subject (vacuous-gate law). +- `bible.js`, `sell.js`: **no change.** Not one line. + +**Lane F — one new module + three small edits.** +- `web/js/world/sale.js` (~180 lines): the seeded calendar, the roster, the ticket transform, the crate accumulator, the docket DOM. Constructed only under `flagOn('sale') && game`; never constructed under `?classic=1`, `?sale=0` or `?game=0`, so absence is by non-existence, not by branch (the `washing.js` / `magpie.js` precedent). +- `index.html`: one construction line, one `PROCITY.flags` entry, one `?sale=all` force. **No per-frame update** — the module is event-driven off `procity:segment` and `procity:digClose`. Zero frame cost. +- `save.js`: `recordFind` carries `was` and `sale:1` on the entry (exactly the `band` precedent at `:294`); `validEntry` (`:52-60`) validates both when present; `listFind` (`:366`) refuses a `sale:1` entry. +- `hud.js`: the docket strip gets its **own body-mounted root**, not a child of `#pc-game` — `hud.js:283-285` sets `#pc-game` to `display:none` for the entire dig, and with no countdown to watch that is now fine, but the strip needs its own construction gate and its own DOM assertions. + +**Lane B — one optional four-line API:** `lighting.advance(frac)` so the crate cost can move the day by a third of a segment without `setSegment()` stomping sub-segment position (`lighting.js:113` sets `clock = i + 0.001`). Fallback if B is busy: sale.js keeps its own accumulator and calls `setSegment` only on a boundary crossing. Nothing else from B, A, D or E. **Zero assets, zero fetches, zero draws.** + +--- + +## 6. THE LAWS + +**1 — THE BAND FLOOR LAW.** `price = max(bandLow, floor(ask × (1−d)))`, and no band ⇒ no ticket (fail-closed). One `Math.max` buys both fences at once: the keeper's counter offer is `min(lo−1, ⌊lo/2⌋)` off `bandLow` (`sell.js:28-32` + `bible.js:52-58`), so every marked buy costs ≥ `lo` and sells for < `lo` — **the no-pump gate's `all(d <= -1)` holds with zero changes to sell.js**. And nothing under a band floor is ever a sale, so **under-band still means "nobody noticed"** — the gem survives intact. + +**2 — THE PROVENANCE LAW.** A `sale:1` entry can never be consigned. *"Can't put that through GodBay, mate — half the district was at the same sale."* One guard at `save.js:366`, one hidden `⚖` on the crate row. +> **This replaces the "drop the band" fence that two of the three designs proposed, and it is not a preference — that fence is broken.** With no band, `godbayHammer` sets `lo = hi = pricePaid` (`save.js:36-37`) — but the 12% bidding war multiplies *afterwards* by 1.5–2.0 (`:43`), so **~12% of sold listings profit** and the design's own gate arm would go red. It also prints your own purchase price back at you in the morning paper, and blanks the guide line on the sell card (`sell.js:104`). A refusal is one line, hard-assertable, and diegetic. + +**3 — THE GEM COVENANT.** The gem is never ticketed. On a sale day the eye is trained on red for ten minutes and the money is still in the plain yellow sticker at $6 in a `guide $25–59` band. Free, and it makes the core skill *harder* on the day the game is loudest. + +**4 — DETERMINISM & THE DELTA LAW.** Four new `kind` labels — `'saleday'`, `'saleroster'`, `'saleticket'`, `'salethin'` — independent streams by construction, so nothing existing shifts. **Tickets are keyed to item identity, never array position** (`${shopId}:${day}:${binKey}:${sku ?? i}`): `dig.js:422` filters procedural offers by `gone`, so a position-keyed ticket would move to a different record the moment you bought one. Save side: **zero new top-level fields.** The score is a pure function of the collection (`Σ was − pricePaid` over today's `sale:1` entries); only `was` and `sale` are added, both optional, both on the entry, both validated exact-and-loud. Old saves load untouched. + +--- + +## 7. THE GATES (Lane F) + +1. **Non-vacuity.** `?sale=all` marks every eligible shop at rate 1.0, so `smoke_nopump`'s shop picker (`flags_check.py:2537-2559`) is *guaranteed* on the roster. Re-run the 5 real UI round trips; assert `all(d <= -1)`; **FAIL** — not skip — if fewer than 3 of 5 bought offers carried `was`. +2. **Band floor.** Over ≥5 bins: every offer with `was` has `price ≥ bandRange(band)[0]` and `price < was`. +3. **Provenance.** Buy marked → `listFind === false`. Positive control: buy unmarked in the same shop → `listFind === true`. +4. **Negative control.** `?sale=0` / `?classic=1` / `?game=0` → zero `[id^=pc-sale]` DOM, zero `was` anywhere, and the offer tuples byte-identical to a pre-sale boot. **Mint that offer-price golden this round — it does not exist today** (`flags_check.py` pins plan fingerprints only). +5. **Identity-keyed tickets.** Buy one marked slot, re-open the bin, assert the surviving marked set is unchanged (the R27 zero-collateral law, applied to the ticket). +6. **Calendar determinism.** Same (seed, town, day) ⇒ identical roster and identical marked set across two boots. +7. **(full version)** `PROCITY.sale.debug.crates(n)` — every existing tool pins midday (`dbg.js:325` + 20 call sites), so the crate clock needs an explicit hook or its gate is vacuous by construction. + +--- + +## 8. CUT LIST + +| cut | why | verified at | +|---|---|---| +| **The 120-second stopwatch** | `dt` is clamped to 0.05 s — a 10 fps machine gets a **2× longer round** and `best` is framerate-denominated; `procity:segment` can't tell a real transition from a bracket press; DBG pauses the clock in every gate we own; and the strip would be invisible for the whole dig | `index.html:575`, `lighting.js:109-113`, `dbg.js:325`, `hud.js:283-285` | +| **Op shops, pawn, book, toy from the pool** | no bins, at all | `fittings.js:60,75` vs `theme.js:56-63,134-141` | +| **"Drop the band" as the anti-farm fence** | 12% of sold listings profit via the war multiplier; prints a legible inversion in the paper; blanks the sell card's guide line | `save.js:36-43`, `sell.js:104` | +| **The Wordle bell, share string, `?run=today`** | needs `createGame({startDay})`, which doesn't exist (`let day = 1`, single writer `adopt()`); the board is a query string, so the leaderboard is decorative | `save.js:110-113,167` | +| **`🎟 N marked` bin hover** | forces exporting `proceduralSleeves` or mirroring it (~1,100 PRNG draws per room enter), and points the player *away* from the gems | `dig.js:36` | +| **The sandwich-board InstancedMesh** | +1 draw is affordable; the `frontEdge`/`ry` orientation math, texture, chunk refresh and gate arm are not, when the docket already names the shops | reserve for v9 | +| **The 23-town calendar strip** | would advertise sale days in the 3 towns with nothing to dig | measured §4(b) | +| **Dollar-threshold ranks ("$200 = LEGEND OF THE TIP"), `best`** | measure wealth, not play — start cash is `60 + (r()*140\|0)` and grows unbounded | `wallet.js:10` | +| **`goesAt ∈ {2,3,4,5}` thinning** | 50–75% of that distribution falls in segments the shop is never open in — it resolves to a constant. Replaced by one bit keyed to the shop's *own* hours: **arrive in a shop's last open segment and half the red tickets are gone** | `registry.js:40,128` × `lighting.js:22` | + +--- + +## 9. EFFORT, AND THE MINIMUM SHIPPABLE THING + +**MINIMUM — one round. This is playable this week.** +The sale exists and nothing is scored: `?sale` flag, seeded calendar, roster, band-floor tickets, red sticker + SALE chip, `was`/`sale:1` on the entry, the `listFind` refusal, the paper line on the wake toast. Gate arms 1–6. +Cost: one Lane C afternoon (`dig.js` ~8 lines), one Lane F round (`sale.js` ~120 lines, three `save.js` edits, two `index.html` lines), and the offer-price golden. **No lane conflicts** — nobody is currently in `dig.js:421` or `save.js:366`, and v8 wave 1 is entirely elsewhere. + +**FULL — two more rounds.** ++ the crate clock and the Lane B `advance()` API, + thinning at room-enter, + the docket strip and the end-of-day total, + the keeper's rumour instead of a calendar UI, + gate arm 7 and the DBG hook. The second of those two rounds is mostly QA, because the crate clock touches what *time* means and every existing tool pins midday. + +Ship the minimum first **as a field test**: it tells you whether a red ticket is fun before you rebuild the day around it. + +--- + +## 10. WHERE IT SITS AGAINST v8 + +**The minimum version is a genuine v8 sidebar.** One flag, zero draws, zero assets, zero fetches, DOM-only, construction-gated, no lane collisions with ambient audio, per-town character, the magpie or backyard props. It is content wearing a very thin system, which is exactly what v8 is. + +**The crate clock is not a v8 sidebar — it is the head of v9.** Making a crate cost time is the first time in this game's history that dwelling costs anything, and it is the direct, literal answer to *"the clock is scenery."* That deserves an epoch, its own gates and its own retro, not a wave slot. + +**One flagged follow-up, not this round:** give the op shop a record crate. It is one line in `theme.js:56-63` and it would take the diggable pool from 0–12 per real town to something like 3–25 — which fixes the roster histogram, fixes the 3 dead towns, and delivers the actual op-shop fantasy the pitch is written in. It re-rolls every op shop interior, so it is a Lane C spike with a golden re-mint behind it. Worth costing properly in v9. + +--- + +## 11. WHAT NEEDS JOHN + +1. **Stopwatch: in or out?** The recommendation is out — the pressure is the shutter, the purse, and the crate cost. You said "limited times". Is a *trading day* limited enough for you, or do you want a number counting down? +2. **Does opening a crate cost time?** This is the one real design change in the document, and the one that makes hours bite. It rations *breadth*, never the riffle itself — you can take as long as you like inside a crate; you just can't open the next one for free. Yes or no. +3. **Are red-ticket finds locked out of GodBay forever?** It is the clean fence and it is diegetic, but it is a real restriction on your auction toy. +4. **Is the band floor sacred?** i.e. a red ticket can never be a gem. This buys the no-pump law and the gem signal with one clamp. Breaking it means red tickets and grails start looking alike. +5. **Does the sale run in one-shop towns?** Eleven of twenty-six get a roster of one. Recommendation: yes — a one-shop round is a depth round. Three towns get no sale day at all, honestly. +6. **Is the crate the only trophy, or do you want a score that persists?** Recommendation: crate only, no personal best, no leaderboard. The number is for the day, the record is forever. +7. **The name.** RED TICKET DAY is the working title. Given three designs independently reached for "red ticket", it is probably right — but it is your word to pick. \ No newline at end of file