docs: DIG_FLIP_HANDOVER — port THRIFTGOD's evolved crate-riffle into /store
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b6994a96e3
commit
916b26102c
103
docs/DIG_FLIP_HANDOVER.md
Normal file
103
docs/DIG_FLIP_HANDOVER.md
Normal file
@ -0,0 +1,103 @@
|
||||
# HANDOVER: the record flip/riffle animation — port THRIFTGOD's dig back into RecordGod
|
||||
|
||||
**For:** the RecordGod agent. **Goal:** adopt the evolved crate-digging mechanic into the
|
||||
3D store served at **robotmonster.party/store/** (this repo: `webstore/index.html` +
|
||||
`webstore/dig.js`). **Written:** 2026-07-02 by the THRIFTGOD session. Self-contained —
|
||||
you don't need that conversation.
|
||||
|
||||
## Lineage (read this first)
|
||||
|
||||
RecordGod's `webstore/dig.js` is the ORIGINAL crate-inspect scene. THRIFTGOD
|
||||
(`/Users/johnking/Documents/OPSHOPGAME/web/dig.js`) forked it and evolved it through a
|
||||
day of live play-testing. **The THRIFTGOD file is a superset of yours with the same
|
||||
architecture** — same `createDig(renderer, opts)` factory, same scene/camera/update/open/
|
||||
close contract, same procedural audio. The cleanest port is: diff the two files, take
|
||||
everything, then re-wire the two integration points listed at the end.
|
||||
|
||||
`diff webstore/dig.js /Users/johnking/Documents/OPSHOPGAME/web/dig.js`
|
||||
|
||||
## The mechanics, and the numbers that make them feel right
|
||||
|
||||
All tuned by hand against real play. Change them at your peril; record them if you do.
|
||||
|
||||
1. **Riffle = a damped cursor over the stack.**
|
||||
- `cursor` (float index) + `vel`; per frame: `vel *= Math.pow(0.0008, dt)`,
|
||||
`cursor += vel * dt`, clamped to [0, n−1].
|
||||
- Wheel: `vel += e.deltaY * 0.012`. Drag: `cursor += dy * 0.02; vel = dy * 0.6`.
|
||||
- When `Math.floor(cursor)` changes → play the **fwip** + load nearby covers + update
|
||||
the title label.
|
||||
|
||||
2. **The flip is a hinge at the sleeve's bottom edge.**
|
||||
- Each record = a Group at the bin floor; the sleeve mesh is offset `y = h/2` inside
|
||||
it, so rotating the GROUP hinges at the bottom edge — this is the whole trick.
|
||||
- Rest angle `BACK = −0.12` (sleeves lean back); flipped-forward max `MAXFLIP = 1.95`.
|
||||
- Per frame, each record i targets:
|
||||
`target = BACK + smoothstep(cursor − i, −0.4, 1.2) * (MAXFLIP − BACK)`
|
||||
then eases: `angle += (target − angle) * min(1, dt*12)`.
|
||||
Records behind the cursor stand; records ahead lie flopped forward. The smoothstep
|
||||
window (−0.4…1.2) is what makes riffling feel like fingers walking sleeves.
|
||||
|
||||
3. **Real format sizes + packing by thickness** (THRIFTGOD addition):
|
||||
- `FMT = { lp:[0.31,0.31,0.0035], cd:[0.142,0.125,0.010], dvd:[0.136,0.19,0.014],
|
||||
vhs:[0.105,0.187,0.025], cass:[0.11,0.07,0.017] }` — [w, h, thickness] metres.
|
||||
- Stack: `z -= thickness + 0.010` per sleeve. VHS riffles chunkier than vinyl for free.
|
||||
- RecordGod is LP-only today, but keep the map — CDs are inevitable.
|
||||
|
||||
4. **Secondhand lean** (cheap, huge): per sleeve seeded jitter —
|
||||
`rotation.z = ((i*2654435761>>>0)%100−50)/1800`, `position.x = ((i*40503>>>0)%100−50)/12000`.
|
||||
No more parade-ground vinyl.
|
||||
|
||||
5. **The crate is drawn around the stack** (THRIFTGOD addition): floor/left/right/back
|
||||
walls + a LOWER front lip (`H*0.55`) the sleeves flip over; sized from max sleeve dims
|
||||
+ stack depth; wood texture (any wood jpg with RepeatWrapping ×2). See `buildCrate()`.
|
||||
|
||||
6. **Covers trickle-load; near-cursor loads win.**
|
||||
- Priority window on cursor move: indices `c−10 … c+15` load immediately.
|
||||
- Background: `fillQueue()` loads ONE unloaded cover every 120ms until the crate is
|
||||
full (clear the timer in `close()`). Never load all covers eagerly; never texture
|
||||
more than the window synchronously. `tx.colorSpace = THREE.SRGBColorSpace` always.
|
||||
|
||||
7. **Pull-to-inspect presents at arm's length ON THE VIEW AXIS** (final tuning after two
|
||||
bad iterations — first it cropped off-screen, then it became IMAX):
|
||||
- Dig camera: `position (0, 0.5, 0.92)`, `lookAt (0, 0.12, −0.2)`, FOV 45.
|
||||
- Pull target FOR THAT CAMERA: `position → (−0.1, 0.23 − h/2, 0.12)` (h = sleeve
|
||||
height; the −0.1 x keeps it clear of a right-side info panel), and
|
||||
`rotation.x → −0.33` so the cover faces the lens.
|
||||
- **If your camera differs, derive it**: `target = cam.pos + viewDir * 0.85`, then
|
||||
`y −= h/2`, and tilt to face the camera. 0.85m gives an LP ~21° of a 45° FOV.
|
||||
- Lerp position `min(1, dt*5)`, rotation `min(1, dt*6)`.
|
||||
- `unpull()` restores `position.set(0, 0, homeZ)`; the angle system reclaims rotation.
|
||||
- Click-vs-drag: pull only fires if total pointer movement `< 4px`.
|
||||
|
||||
8. **After a sale, re-pack the stack**: remove the record, then walk the survivors
|
||||
re-assigning `homeZ` with the same thickness accumulation. Don't leave a gap.
|
||||
|
||||
9. **Procedural audio, no asset files** (already in your ancestor, keep it):
|
||||
- *fwip*: 50ms white-noise burst → bandpass 2100Hz Q0.7 → gain 0.22.
|
||||
- *thunk* (pull): sine 105Hz, 5ms attack to 0.5, exp decay over ~0.28s.
|
||||
- Lazily create AudioContext on first use (autoplay policies).
|
||||
|
||||
10. **Pointer-lock gotcha** (if your store uses PointerLockControls): Chrome refuses
|
||||
re-lock within ~1.3s of Esc. Never call `controls.lock()` programmatically after
|
||||
closing a panel — wrap it: request, `.catch()` → show a click-to-resume overlay
|
||||
(a fresh user gesture always succeeds). THRIFTGOD calls this `safeLock()` in
|
||||
`web/index.html`.
|
||||
|
||||
## Integration points (the only THRIFTGOD-specific bits to rewire)
|
||||
|
||||
- **Data in:** THRIFTGOD uses `createDig(renderer, { onClose, getItems, onBought })` with
|
||||
`open(kind)`; your ancestor used `getRecords` + `open(crateId)` fetching
|
||||
`/virtual/crate/{id}/records`. Keep YOUR fetch, adopt their buildStack. Records need
|
||||
`{ title, artist, thumb, price, condition, fmt? }` (fmt defaults to `lp`).
|
||||
- **Buy action:** THRIFTGOD's pull-panel button POSTs `/api/take` (its basket system).
|
||||
Replace with RecordGod's add-to-cart. Everything else in `pull()`/`unpull()` ports as-is.
|
||||
- **Images:** THRIFTGOD proxies external covers via `/img?u=`. RecordGod serves its own
|
||||
webp at `/store/assets/` — use your `thumb` URLs directly, no proxy needed.
|
||||
|
||||
## Definition of done
|
||||
|
||||
Riffle with wheel AND drag feels weighted; sleeves lean individually; the crate is
|
||||
visible around the stack; covers fill in behind you; pulling any format presents fully
|
||||
in frame, facing the camera, beside (not under) the info panel; buying closes the gap
|
||||
in the stack; Esc steps back out cleanly twice (pull → crate → store) with no
|
||||
pointer-lock console errors.
|
||||
Loading…
Reference in New Issue
Block a user