Implementation Plan - Sprint 15: The Global Market & Gacha System (REVISED)
This document outlines the detailed, file-by-file architectural implementation plan for Sprint 15 (The Global Market & Gacha System) in thepcmtg-core monorepo. It establishes a secure, zero-trust, serverless-safe backend transaction ledger utilizing Google Cloud Memorystore (Redis) rate-limiting, coupled with a declared ideological faction system and a premium glassmorphic frontend interface.
🛠 Architectural Constraints & Rules
1. Atomic Economy (Zero-Trust)
To prevent double-spending and race conditions, all market purchases (items, policies, and card packs) must execute within aprisma.$transaction block.
- A pessimistic row lock is obtained on the player’s wallet using raw SQL
FOR UPDATEbefore any validation or balance checks. - User balances must be validated to remain
>= 0after deducting costs. - The deduction of the cost, creation of the
OwnedCardrelation, and insertion of theTransactionLogreceipt must occur atomically.
2. Cloud Run Horizontal Scaling (Redis Rate Limiter)
- Problem: Stateless Cloud Run containers scale horizontally; local in-memory Maps are useless across multiple container instances and allow Sybil attacks.
- Solution: Utilize the Google Cloud Memorystore (Redis) instance provisioned in Sprint 2. Implement a Redis sliding window rate-limiter keyed on
req.playerIdto enforce a strict limit of 5 requests per 10 seconds across all/market/*endpoints.
3. Identity & The “Based Pill” Defection Mechanic
- Problem: Dynamically deriving a player’s quadrant from ELO coordinates breaks the “Based Pill” ideological defection mechanic (Project Bible 3.2.5), which allows players to swap quadrants without resetting their coordinate footprint.
- Solution: Query the player’s explicitly declared
quadrantstring field directly from thePlayerAccounttable. Map native currency strictly based on this string:AUTH_RIGHTdinarsAUTH_LEFTlaborLIB_LEFTpronounsLIB_RIGHTmonke
4. Asymmetrical Cross-Quadrant Gacha (The Meme Bazaar Economy)
- Problem: Generic ‘STANDARD’ and ‘PREMIUM’ packs violate the asymmetrical cross-quadrant economy (Project Bible 3.3.3).
- Solution: Refactor
POST /market/buy-packto accept{ targetQuadrant }in the payload:- Native Synthesis Pack: Cost is 20 units of Native Currency if
targetQuadrantmatches the player’s declared nativequadrant. - Defector Synthesis Pack: Cost is 50 units of Native Currency if
targetQuadrantis different (representing buying into an opposing ideology). - Query Filter: The Gacha candidates query must strictly filter by
affinity = targetQuadrantandcardType = 'MEME'. - Cryptographic Gacha Math: Drop rates are calculated server-side using the Node.js native
cryptomodule (crypto.randomInt) to prevent client-side seed prediction.
- Native Synthesis Pack: Cost is 20 units of Native Currency if
🗺 Proposed Changes
1. Database & Schema Layer
[MODIFY] schema.prisma
Add the explicitly declaredquadrant field to the PlayerAccount model so players can defect/swap quadrants without losing their coordinate footprints.
[!NOTE]
A corresponding migration (or raw SQL run during setup) will add this column:
ALTER TABLE player_accounts ADD COLUMN IF NOT EXISTS quadrant VARCHAR(20) DEFAULT 'AUTH_RIGHT';
2. Backend Layer (The Transaction & Security Engine)
[MODIFY] package.json
Installioredis to manage high-throughput sliding-window rate limiting on Memorystore.
[NEW] redis.js
Export a centralized Redis connection pool for connection reuse across serverless requests.[NEW] rateLimiter.js
Atomic sliding window rate limiter middleware utilizing Redis multi-exec pipelines.[NEW] market.js
The core market controller router that handles purchasing single modifier items/policies and opening cross-quadrant Gacha packs.-
Declared Quadrant helper:
-
GET /market/spot- Fetches the active list of cards with
cardTypeequal to'ITEM'or'POLICY'from theCardtable. - Returns card items with their details, metadata, and costs across currencies.
- Fetches the active list of cards with
-
POST /market/buy-item- Payload:
{ cardId } - Prisma Interactive Transaction Flow:
- Lock the player’s wallet row using raw SQL
FOR UPDATEto ensure atomic isolation: - Query player’s declared quadrant and map their native wallet currency (e.g.
'AUTH_RIGHT''dinars'). - Fetch target modifier card by
cardIdand validate cost (e.g. if player currency isdinars, readmarketCostDinarsfrom the card). Cost must be . - Guard against duplicates: Check if the player already owns this specific item or policy in the
OwnedCardtable. If so, abort with400 Bad Requestto prevent duplicate passive modifiers. - Deduct the cost from the appropriate native currency column in the wallet, ensuring the final balance .
- Mint the card to the user: Create record in
OwnedCard. - Write transaction log: Record entry in
TransactionLogwith type'MARKET_BUY_ITEM'.
- Lock the player’s wallet row using raw SQL
- Payload:
-
POST /market/buy-pack- Payload:
{ targetQuadrant }(e.g.'AUTH_RIGHT','AUTH_LEFT','LIB_LEFT','LIB_RIGHT') - Prisma Interactive Transaction Flow:
- Lock player’s wallet row using raw SQL
FOR UPDATE. - Query player’s declared native quadrant and currency mapping.
- Determine the Gacha pack cost based on cross-quadrant dynamics:
- If
targetQuadrant === player.quadrant: Cost is 20 units (Native Synthesis Pack). - If
targetQuadrant !== player.quadrant: Cost is 50 units (Defector Synthesis Pack).
- If
- Verify and deduct funds from the player’s native currency field.
- Cryptographic Gacha Rolling:
- Roll rarity for each of the 3 cards in the pack using server-side
crypto.randomInt(0, 100):- Native/Defector Synthesis Drop Rates:
COMMON: 60% (Roll 0-59)UNCOMMON: 25% (Roll 60-84)RARE: 10% (Roll 85-94)EPIC: 4% (Roll 95-98)LEGENDARY: 1% (Roll 99)
- Native/Defector Synthesis Drop Rates:
- For each rolled rarity, query
Cardregistry strictly filtering by:cardType = 'MEME'affinity = targetQuadrant(Mapped from QuadrantAffinity enum)rarity = rolledRarity
- Randomly select a card ID from the candidates using
crypto.randomInt(0, candidates.length). - Graceful Fallback: If no cards are found for the rolled rarity under the target quadrant, search for adjacent rarities of
targetQuadrantor a default card to prevent empty roll errors.
- Roll rarity for each of the 3 cards in the pack using server-side
- Create 3
OwnedCardentries in the database representing the opened cards. - Log transaction in
TransactionLogwith type'MARKET_BUY_PACK'. - Commit transaction and return full details of the 3 minted cards in the response.
- Lock player’s wallet row using raw SQL
- Payload:
[MODIFY] index.js
- Import the new market router and mount it under
/market. - Import and apply the Redis
marketRateLimitermiddleware to protect/market/*routes.
3. Frontend Layer (Marketplace UI Shell)
[NEW] page.tsx
An extremely premium, responsive, glassmorphic marketplace interface structured with React state-driven components.- Key Features & Interactive State Flows:
- Tabs: Toggle between
'SPOT_MARKET'(Modifier Items/Policies) and'MEME_BAZAAR'(Gacha Pack Synthesis). - Active State Syncing:
- Displays real-time balances for the player’s native currency with interactive visual shifts.
- Synchronizes directly with the global
<WalletOverlay />component via incrementing awalletSyncTriggercounter state upon a successful transaction, forcing a silent backend refresh of current funds.
- Spot Market Item Cards:
- Highlight items with glassmorphic cards.
- If the user already owns a single-use modifier card, overlay a high-contrast
"OWNED"banner and disable the purchase button.
- Meme Bazaar Pack Opening Overlay:
- Offers 4 synthesized pack options, representing the 4 political quadrants. Hovering dynamically highlights the pack cost (20 native currency if matching the player’s declared native faction, 50 native currency if different, with a clean visual tooltip explaining the “Cross-Quadrant Ideological Premium”).
- Clicking buy triggers a cinematic opening overlay with:
- Shaking booster pack animation via
framer-motion. - Tearing pack wrapper clip-path animation.
- Revealing 3 cards positioned side-by-side, face down.
- Clicking each card triggers a realistic 3D Y-axis flip (
rotateY: 180) to reveal the meme. - Each card features an elegant, rarity-colored drop shadow glow:
COMMON: Subtle silver glow (shadow-zinc-500/20)UNCOMMON: Emerald radiance (shadow-emerald-500/35)RARE: Ocean cobalt glow (shadow-blue-500/50)EPIC: Regal purple neon aura (shadow-purple-500/70 shadow-[0_0_20px_rgba(168,85,247,0.4)])LEGENDARY: Golden celestial fire (shadow-amber-500/90 shadow-[0_0_35px_rgba(245,158,11,0.6)] animate-pulse)
- Shaking booster pack animation via
- Tabs: Toggle between
🔒 Exploitation Defense Guardrails
- Anti-Sybil Rate Limiting (Redis Sliding Window): Stateless instances running under Cloud Run load-balancer pools read and write to Google Memorystore. Parallel “double-click” requests are instantly logged, evaluated, and throttled atomically across container instances.
- Double-Spend Row Locks:
Using
SELECT ... FOR UPDATErow locks on thePlayerWalletwithin an interactive transaction prevents multi-threaded balance bypass hacks. No reads or state determinations are trusted until the exclusive lock is acquired. - Declared Quadrant Enforcement: Player quadrant values are read directly from the declared database record, safeguarding ideological faction structures and defection mechanics.
- Passive Duplicate Protection: The backend transaction engine explicitly aborts card minting if a player already owns an item/policy modifier, sealing the game against duplicate passive buffs.
🧪 Verification & Testing Plan
Automated Test Suite
We will construct an integration test scriptbackend/scripts/test-market.js to execute and verify:
- Rate Limiting Resilience: Simulate a rapid-fire burst of 10 requests within 1 second. Verify that the first 5 requests return
200/201 OK, and requests 6 through 10 are strictly blocked with429 Too Many Requests. - Race Condition Resistance: Fire 5 concurrent item purchase requests for the same unique card item simultaneously using
Promise.all. Verify that exactly one succeeds, while the other 4 return error codes indicating “Duplicate Item” or “Sufficient Row Locked” aborts. - Asymmetric Pricing Compliance: Execute test mock purchases for players in different quadrants buying matching/different packs. Verify that native purchases charge 20 units and cross-quadrant purchases charge 50 units.
- Gacha Distribution Math: Open 100 Gacha packs in test mode and log the frequency distribution to ensure it closely maps to weighted drop rate probabilities.
Manual Verification
- Verify high-fidelity rendering, responsive sizing, and fluid 3D card flips across desktop, tablet, and mobile breakpoints.
- Ensure visual indicators dynamically switch symbols based on user faction (e.g. đ, ⚒, ❀, 🐒).
- Confirm global
<WalletOverlay />syncs immediately without requiring full page loads.