---
description: Marketplace system architecture - the Gilded Bazaar for buying and selling items in the Hub
globs: src/managers/MarketManager.js, src/ui/screens/MarketplaceScreen.js, src/core/Persistence.js
alwaysApply: false
---

# **Marketplace System Rule**

The Marketplace (The Gilded Bazaar) is the primary gold sink and progression accelerator in the Hub. It allows players to purchase items with currency earned from missions and sell items for buyback.

## **1. System Architecture**

The Marketplace is managed by **MarketManager**, a singleton logic controller instantiated by GameStateManager.

### **Integration Flow**

1. **Mission Complete:** MissionManager dispatches `mission-victory` event. MarketManager listens and sets `needsRefresh` flag.
2. **Hub Entry:** Player enters Hub. MarketManager checks `needsRefresh` flag. If true, generates new stock based on tier and saves immediately.
3. **UI Render:** HubScreen passes the MarketManager instance to the `<marketplace-screen>` component.

### **Persistence**

- Market state (stock, buyback queue) is saved to IndexedDB (`market_state` store) to prevent players from reloading to re-roll shop inventory.
- Stock generation is deterministic based on tier and generation timestamp.
- Each transaction (buy/sell) immediately persists state to prevent duplication or currency desync.

## **2. Data Model**

```typescript
// src/managers/MarketManager.js (JSDoc types)

/**
 * @typedef {Object} MarketItem
 * @property {string} id - Unique Stock ID (e.g. "STOCK_001")
 * @property {string} defId - Reference to ItemRegistry (e.g. "ITEM_RUSTY_BLADE")
 * @property {string} type - ItemType (cached for filtering)
 * @property {string} rarity - Rarity (cached for sorting/styling)
 * @property {number} price - Purchase price
 * @property {number} discount - 0.0 to 1.0 (percent off)
 * @property {boolean} purchased - If true, show as "Sold Out"
 * @property {Object} [instanceData] - If this is a buyback, store the ItemInstance here
 */

/**
 * @typedef {Object} MarketState
 * @property {string} generationId - Timestamp or Mission Count when this stock was generated
 * @property {MarketItem[]} stock - The active inventory for sale
 * @property {MarketItem[]} buyback - Items sold by the player this session (can be bought back)
 * @property {string} [specialOffer] - ID of a specific item (Daily Deal)
 */

/**
 * @typedef {Object} StockTable
 * @property {number} minItems - Minimum items to generate
 * @property {number} maxItems - Maximum items to generate
 * @property {Object} rarityWeights - Rarity distribution weights
 * @property {string[]} allowedTypes - Item types this merchant can sell
 */
```

## **3. Logic & Algorithms**

### **A. Stock Generation (`generateStock(tier)`)**

Triggered only when a run is completed and player enters Hub.

**Tier 1 (Early Game - Before Mission 3):**

- **Smith:\*\*** 5 Common Weapons, 3 Common Armor
- **Alchemist:** 5 Potions (Stacked), 2 Grenades (when consumables are available)

**Tier 2 (Mid Game - After Mission 3):**

- **Weights:** Common (60%), Uncommon (30%), Rare (10%), Ancient (0%)
- **Scavenger:** Unlocks. Sells 3 "Mystery Box" items (Unidentified Relics) - Future feature

**Algorithm:**

1. Filter `ItemRegistry` by `allowedTypes` and current tier availability
2. Roll `RNG` against `rarityWeights` to determine rarity
3. Select random Item ID from filtered pool matching selected rarity
4. Calculate Price: `BaseValue * (1 + RandomVariance(±10%))`
5. Create `MarketItem` with unique stock ID
6. Save state to IndexedDB immediately

### **B. Transaction Processing**

Transactions must be **atomic** to prevent item duplication or currency desync.

**Buy Logic (`buyItem(stockId)`):**

1. Check `Wallet >= Price` (validate currency)
2. Deduct Currency from `hubStash.currency.aetherShards`
3. **Generate Instance:** Create a new `ItemInstance` with a fresh UID (`ITEM_{TIMESTAMP}_{RANDOM}`)
4. Add to `InventoryManager.hubStash`
5. Mark `MarketItem` as `purchased: true`
6. Save State to IndexedDB
7. Return `true` on success, `false` on failure

**Sell Logic (`sellItem(itemUid)`):**

1. Find `ItemInstance` in `hubStash` by UID
2. Remove `ItemInstance` from `hubStash`
3. Calculate Value: `BasePrice * 0.25` (25% of base value)
4. Add Currency to `hubStash.currency.aetherShards`
5. **Create Buyback:** Convert instance to `MarketItem` and add to `buyback` array (Limit 10, oldest removed if full)
6. Save State to IndexedDB
7. Return `true` on success, `false` on failure

### **C. Merchant Types**

Merchants filter stock by item type:

- **SMITH:** `["WEAPON", "ARMOR"]`
- **TAILOR:** `["ARMOR"]`
- **ALCHEMIST:** `["CONSUMABLE", "UTILITY"]`
- **SCAVENGER:** `["RELIC", "UTILITY"]` (Tier 2+)
- **BUYBACK:** Shows all items in `buyback` array

## **4. UI Implementation (LitElement)**

**Component:** `src/ui/screens/MarketplaceScreen.js`

### **Visual Layout**

- **Grid Container:** CSS Grid `250px 1fr` (Sidebar | Main Content)
- **Sidebar (Merchants):** Vertical tabs with icons
  - [⚔️ Smith]
  - [🧥 Tailor]
  - [⚗️ Alchemist]
  - [♻️ Buyback]
- **Main Content:**
  - **Filter Bar:** "All", "Weapons", "Armor", "Utility", "Consumables"
  - **Item Grid:** Flex-wrap container of Item Cards with voxel-style borders
- **Modal:** Purchase confirmation dialog ("Buy for 50?")

### **Interactive States**

- **Affordable:** Price Text is Gold/Green, button enabled
- **Unaffordable:** Price Text is Red, button disabled
- **Sold Out:** Card is dimmed (opacity 0.5), overlay text "SOLD", cursor not-allowed

### **Rarity Styling**

Item cards use border colors to indicate rarity:

- **COMMON:** `#888` (Gray)
- **UNCOMMON:** `#00ff00` (Green)
- **RARE:** `#0088ff` (Blue)
- **ANCIENT:** `#ff00ff` (Magenta)

### **Events**

- **`market-closed`:** Dispatched when player clicks close button. HubScreen listens and closes overlay.

## **5. Integration Points**

### **A. GameStateManager**

- MarketManager is instantiated in GameStateManager constructor
- Requires: `persistence`, `itemRegistry`, `hubInventoryManager`, `missionManager`
- Initialized in `GameStateManager.init()` after persistence is ready
- `checkRefresh()` is called when transitioning to `STATE_MAIN_MENU`

### **B. HubScreen**

- When `activeOverlay === "MARKET"`, renders `<marketplace-screen>` component
- Passes `marketManager` instance as property
- Listens for `market-closed` event to close overlay

### **C. Persistence**

- Market state stored in IndexedDB `Market` store (version 3+)
- Key: `"market_state"`
- Saved after every transaction (buy/sell) and stock generation

## **6. Conditions of Acceptance (CoA)**

**CoA 1: Persistence Integrity**

- Buying an item, saving, and reloading the page must result in:
  - Item being in `hubStash` inventory
  - Shop showing "Sold Out" for that item
  - Stock **must not change** upon reload (same `generationId`)
- Test: Buy item → Save → Reload → Verify item in stash and stock unchanged

**CoA 2: Currency Math**

- Buying an item costs exactly the listed price (no rounding errors)
- Selling an item refunds exactly `BasePrice * 0.25` (rounded down)
- Buyback allows repurchasing a sold item for the **exact amount it was sold for** (undo logic)
- Test: Buy for 50 → Wallet decreases by 50. Sell for 12 → Wallet increases by 12. Buyback costs 12.

**CoA 3: Atomic Transactions**

- If currency deduction fails, item should not be added to inventory
- If item removal fails, currency should not be added
- State must be consistent after any transaction (success or failure)
- Test: Attempt buy with insufficient funds → Verify no item added, currency unchanged

**CoA 4: Stock Generation**

- Tier 1 stock must contain only Common items
- Tier 2 stock must follow rarity weights (60% Common, 30% Uncommon, 10% Rare)
- Stock must be generated only when `needsRefresh` is true and player enters Hub
- Test: Complete mission → Enter Hub → Verify new stock generated with correct tier distribution

**CoA 5: Buyback Limit**

- Buyback array must not exceed 10 items
- When adding 11th item, oldest item must be removed
- Test: Sell 11 items → Verify buyback contains only last 10 items

## **7. Future Enhancements (Optional)**

- **Class Filtering:** Visually flag items that cannot be equipped by anyone in current Roster
- **Daily Deals:** Special offers with discounts on specific items
- **Scavenger Merchant:** Sells unidentified relics (Mystery Boxes) that must be identified
- **Price Negotiation:** Skill-based haggling system (future feature)
