You inherit seven boxes of old clothes and one month’s rent on a bare storefront. The loop: pull pieces from the crate, judge what each is worth from a fuzzy appraisal, price it, hang it, flip the sign — then work the register while shoppers with their own tastes and budgets browse the racks. Every evening rent comes due (and creeps up), and whatever’s left over buys racks, decor, better suppliers, or a fitting room. Reputation compounds: fair deals bring more walk-ins who tolerate higher prices, gouging and register queues bleed it away. Hit 90 rep and the shop becomes a Vintage Institution; miss rent once and the landlord wins.
How it plays
- WASD walk, mouse look (click to grab the pointer; drag-look works where pointer lock isn’t available), E interact with whatever the crosshair rests on.
- Crate: E pulls the next piece out; scroll / [ ] sets its price
(the tag shows a fuzzy
$lo–$hiestimate and the rarity); E again hangs it; B stashes it back. 1/2/3 at the crate orders supplier bundles — charity bins at first, estate lots and archive picks once you’ve bought the contacts. - Sign (in the window): E opens the store. Shoppers walk to a rack, examine a piece, and buy if the price clears their perceived value × tolerance — era and category taste, collector hats, and your reputation all move that bar. Overpriced tags draw audible muttering (and rep loss).
- Register: buyers queue and wait — with finite patience, so ignoring the till costs sales and rep. Some open with a lowball offer: E accepts it, F insists on the tag; whether they cave depends on your rep and the size of the gap.
- Evenings: a ledger (revenue − stock − rent), then the upgrade list. Racks add slots, decor adds rep and tolerance, the mannequin/neon/flyers add traffic, suppliers raise stock quality.
Structure
The sim (src/sim.js + src/items.js + src/layout.js) is framework-free
and headlessly testable: day phases, the customer state machine
(enter → browse → examine → queue → pay → leave) walking real 2D waypoint
routes around the furniture, the pricing/haggling economy, rent, reputation,
milestones, and upgrades. It’s deterministic under an injected seeded rng,
and emits an event list the shell turns into audio and toasts.
src/layout.js is the shared store geometry — the sim routes customers
through it, the scene builds meshes from it, and the shell collides the
player against it. src/scene3d.js takes an injected THREE and builds
everything visual: canvas-drawn garments per category (tees, bombers, slip
dresses, flares…) in era palettes, price-tag sprites, walk-in customers,
and the upgrade decor that appears as you buy it.
test/run.test.mjs plays scripted days end to end: fire-sale prices sell
out, $999 tags sell nothing, insisting on a haggle can lose the sale,
upgrade prerequisites and spend, missing rent ends the run, the rep-90 win
fires, and no items are created or destroyed by a trading day.
The demo is the committed prebuilt bundle per ADR-0002: demo/ carries its
own copy of the modules plus three.js, no build step.
Gotchas
- First-person interaction is math picking, not raycasting: candidates
(crate, sign, register, each hung item) are scored by view-angle and
distance. That’s what makes the whole loop drivable from keyboard alone —
which is also how the Playwright validation and thumbnail choreography
play it (
window.__sim/window.__debugare the scripting hooks). - The sandboxed demo iframe grants
allow-pointer-lock; standalone it requests pointer lock on click and silently falls back to drag-look if the promise rejects (headless Chromium, some embeds). - Customer pathing is two corridor waypoints plus straight lines, with
clearance checked against the active furniture set — cheap, but it means
new furniture must be registered in
layout.colliders()or shoppers will ghost through it. demo/bundles its own copies of the modules. If you touchsrc/, re-copy:cp src/*.js demo/.