Shops

The key words MUST, MUST NOT, REQUIRED, SHALL, SHOULD, MAY, and OPTIONAL below are used as described in RFC 2119 — they define expected behavior, not current implementation status.

A shop is where the player exchanges one resource for another: meseta for goods, Photon Drops for goods, materials for crafted gear, or items between the inventory and the bank. Every shop presents the same skeleton and the same input grammar so a player who has learned one shop has learned them all. The storage counter is built from the same skeleton and is treated as a shop for the purposes of this contract, even though it moves items rather than selling them.

The shop is presented diegetically: rather than cutting to a separate full-screen menu, the game keeps the player in the world. On interaction the camera moves to a fixed angle directly in front of the shopkeeper NPC — who stays exactly where they stand in the city, at their authored position and rotation — the player character is ghosted so it never hides the clerk, and the shop UI is drawn as an overlay on top of the still-rendered 3D scene. Closing the shop returns the camera to following the player and restores the player to solid. The framing contract is in Presentation; the UI skeleton, input grammar, and economy rules below are unchanged by it.

Shops MUST share behavior through composition — preloaded static helpers (PszStyle, ShopNav, ShopUI) — and MUST NOT share it through a common base class. A cross-script base class fails to resolve in the Android export at runtime (it works in-editor and on every other platform); see docs/shop-dedup.md. Each shop is therefore its own extends Control script that calls into the shared helpers.

Presentation — diegetic 3D framing

The shop screen is drawn over the live city, not in place of it. When the player accepts an interaction with a shop NPC, the game MUST:

Everything below — the row grammar, tabs, detail-panel contents, feedback, and economy rules — is unchanged by the framing; it describes the same panels, now composited over the 3D scene rather than a flat background.

shopkeeper NPC · head-on, right
player (ghosted)
SHOP
tab · tab · tab
selected row ▸
row
row
Balance
hint line
DETAIL
name · stats · effect
Confirm
1280×720
The diegetic shop framing (Style A). The shopkeeper is framed head-on on the right; the tall list panel (~90% height) and the shorter info panel (~40% height) sit side-by-side on the left; the player stands off to the side, ghosted. The camera has tweened from the follow cam to this fixed pose; the city renders behind.

The common interface

TITLE · shop name — above center (keyboard-safe)
TabTabTabTab BALANCE TAB BAR (mode bar)
LIST selected row ▸ row disabled row · can't afford row [E] equipped · can't sell row scrolls · selection stays in view
HINT — Left/Right · Up/Down · Accept · Cancel
DETAIL PANELstats (gear) / effect + count (item) for the selected row — the white scan-lined card
PORTRAITshopkeeper, overlaid on the right
The shared shop / storage skeleton at the game's native 1280 × 720 (16:9). Every shop fills these regions; only the tab set, the list contents, and the detail differ. The storage counter uses the same layout (its tabs are Deposit / Withdraw · Items / Meseta).

Every shop screen MUST:

Input grammar

All shops MUST honor this mapping, dispatched by ShopNav.handle():

List scrolling

A list longer than its panel scrolls. The behavior MUST be identical across every shop and the storage counter:

The row contract

A list row carries an optional icon, a name, and a right-aligned value (price, count, or yield). One rule governs every shop so the disabled state reads the same everywhere:

Open question — consumable usage restrictions

Weapons and armor carry a usable_by class/race restriction (WeaponData / ArmorData can_be_used_by); the same field now exists on ConsumableData but no consumable populates it yet, so every consumable is currently usable by all and the item-shop capability grey is a no-op for the Items/Materials tabs. Resolving which consumables a class/race cannot use — and specifically why PSOBB greys antidote/antipara on a CAST without the ✕ marker while greying techniques with it — is the follow-up that makes the Items tab match PSOBB. Until then the item shop drops only the (incorrect) affordability/full-inventory greying.

The detail panel

The detail panel is the right-hand column that describes the selected row. Where a shop presents items with stats or effects, it MUST render them here rather than in the list. It MUST distinguish item kinds:

A shop whose entries have no per-item stats (a flat exchange list) MAY omit the detail panel and present a single full-width list. Such list-only shops SHOULD adopt the detail panel as their content grows. (photon / crafting / tekker are list-only today; migrating them to the standard list + detail layout is tracked on issue #368.)

Feedback: sounds, messages, and failures

Every economy screen — the shops, storage, the guild counter, and Blackjack — MUST give the same audible and textual feedback for the same event. Today each screen hand-rolls this and they diverge (issue #368, Rozalin playtest): three different "can't buy" modals, two different full-inventory strings, and per-screen sound choices. The rules below are the single contract; a screen MUST route through the shared helpers (below) rather than re-deciding any of it.

Sounds

One sound per event class, identical on every economy screen:

Messages

Failure text comes from one shared vocabulary, defined once (a shared constant), never a per-screen literal:

Failure modals

Quantity

Tab chrome

How each shop differs

Within the shared skeleton, each shop specializes its tabs, its currency, and what "confirm" does:

ShopTabs / modesCurrencyConfirm doesDetail panel
Item Shop Items · Materials · Disks · Sell Meseta Buy a consumable / technique disk, or sell back at half price Effect, cost, max stack, held count (qty stepper on buy + sell)
Weapon Shop Weapons · Armor · Units · Sell Meseta Buy gear, or sell owned gear Gear stats + Can equip / Cannot equip: class
Photon Shop — (single list) Photon Drops Exchange Photon Drops for a reward item List-only
Synthesis (Crafting) Craft · Boards Meseta + materials Craft a weapon from a recipe; a photon crystal sets the element / special tier List-only (materials "have of need" planned)
Tekker — (Grind only) Meseta + grinders Grind a weapon up (raise its attack power) List-only
Storage Deposit Items · Withdraw Items · Deposit Meseta · Withdraw Meseta — (no cost) Move items / meseta between the inventory and the bank Gear stats / item effect + count, storable status
Guild Counter (quest board) Quest list · accept / report / cancel — (no cost) Accept, report, or cancel the selected quest Quest brief — area, type, objective

The guild counter shares this skeleton and input grammar — it is effectively a shop — so it sits in the table. It diverges in one place: its rows are not the buy-affordance row contract (see below).

Shop-specific rules

The guild counter: same layout, different rows

The guild counter is effectively a shop — it shares the exact skeleton above (title, tab bar, list, detail, portrait, hint) and the same input grammar. What it does not share is the row contract: its row colours encode quest-board action types — report, cancel, locked, cleared — not buy affordance, and the red "Cancel Quest" row is a deliberate destructive-action cue. So the actual shops MUST NOT borrow those colours for affordance, and the guild counter MUST NOT be flattened into the single-grey disabled rule. See Story Progression for the counter's REPORT / LOCKED / CLEAR derivation.

Conformance

  1. Open any shop. The title sits above center; the balance is top-right of the tabs; the hint line names the Left/Right, Up/Down, Accept, Cancel actions.
  2. Move Left/Right — the tab changes and the selection resets to the top.
  3. Move Up/Down onto a row you cannot afford — it is greyed (one grey, no second colour), and the detail panel states the reason.
  4. Select equipped gear (weapon shop sell, or storage deposit) — the row shows [E] as a prefix and is greyed; the detail explains it cannot be sold / deposited.
  5. Press Cancel — the shop closes with no charge and no inventory change.

Implemented by