RPG Loyalty System — Design Summary

Concept

A gamified membership program modeled on tabletop RPG progression. The store functions as a quest hub. Members earn XP through purchases, event attendance, and real-world quests. The goal is healthy gamification: get people into the shop, spending money, making friends, doing things outside. No dark patterns.

XP System

Source: BECMI (Basic/Expert/Companion/Master/Immortal)

BECMI Fighter XP table chosen over B/X for its higher level cap (36 vs ~14), providing years of progression runway.

LevelCumulative XPNotes
10Starting level
22,000
34,000
48,000
516,000
632,000
764,000
8120,000
9240,000Name Level
10–36+120,000/levelLinear tail

All values are cumulative totals. The curve doubles through early levels (fast initial progression), then flattens to a fixed 120,000 XP per level from 10 onward (steady endgame grind).

XP Earn Rates

Base rate: $1 spent = 100 XP

SourceXP RangeAnalogy
Purchases100 XP per $1 (base)Gold-for-XP (treasure)
Event attendance500–1,500 flatSession XP
Easy quests (e.g., photo of a dog)~50 XPKobold encounter
Hard quests (e.g., visit 5 local businesses)~2,000 XPDragon encounter
Streaks, referrals, seasonal challengesVariableStory awards

Target ratio: ~3:1 or 4:1 purchase XP to activity XP, matching B/X treasure-to-monster ratios in actual play. Most XP comes from spending, but quests and events provide meaningful acceleration.

What does NOT earn XP

Not every dollar or action should count. RC is explicit: “salaries don’t count, only money obtained during dangerous or challenging experiences.” Magical item sales cap at 10% of creation cost, not full value. Applied to our system:

ActionEarns XP?Reasoning
Purchase at shopYes (base × multiplier)The core “treasure” mechanic
Membership subscription fee (monthly)NoPassive renewal; “salary-equivalent”
Membership upgrade (tier change)One-time XP on upgrade onlyFirst payment at new tier counts; subsequent renewals do not
Store credit redemptionNoAlready-earned money being spent; would double-count
Gift card purchase (for self)NoPrepaid; XP awarded when the credit is used on a real purchase
Gift card purchase (for someone else)Yes (buyer earns on purchase, recipient does not)The buyer spent money; the recipient didn’t
RefundsNegative points AND negative XP — both reversed, level may dropIf XP didn’t decrease, big-purchase-then-refund is a trivial exploit to level up for free. See “Refund Handling” below for mechanics.
Check-in at kioskYes (flat, dedup’d)Deliberate action, requires presence
Event attendanceYes (flat)Deliberate action
Quest completionYes (variable)Deliberate effort
Browsing the portal / opening the appNoNot a meaningful action

Every ambiguity here is a future “why did X get more points than me” complaint. Make the exclusions explicit in member-facing docs when we ship them.

Discretionary XP Bonuses (Exceptional Action)

Adapted from Rules Cyclopedia: DMs can award small, capped, discretionary XP bonuses for things the rules don’t cover — exceptional role-play, clever solutions, heroic sacrifice, exceptional skill use. Amount is 1/20 of the points needed to reach the next level, max once per session.

Retail translation

Staff can award a discretionary XP bonus from the admin panel for cultural contributions the automated system can’t see:

  • Helped a newcomer learn a game (Magic, D&D, a new board game) at the shop
  • Brought a friend who signed up as a member
  • Handled a customer-service moment well (even for someone else)
  • Contributed to a shop event beyond what was asked
  • Showed up to clean after an event
  • Any other qualitative contribution staff notices on the floor

Mechanics

  • Amount: 5% of the XP gap to the member’s next level, calculated at award time (matches RC’s 1/20).
  • Cap: one bonus per member per month (the RC “one per session” rule, adapted to our cadence where monthly is a reasonable session-equivalent).
  • Award authority: Staff role only (not GM, not Member). See guild-roles — this is one of the levers that differentiates the Staff tier from GM and Member.
  • Audit: every bonus logged with staff name, member, amount, and reason text. Appears in the member’s XP ledger as a distinct transaction type (“Staff commendation” or similar flavor).
  • Visibility: member sees the bonus and the reason; bonuses are not publicly visible to other members (avoids social comparison and incentive-gaming).

Why this mechanic matters

The automated system rewards transactions the system can observe (purchases, check-ins, event signups). It cannot observe the social fabric of the shop — the moments that actually make the community work. Giving staff a small, capped, discretionary lever lets them reward those moments without creating a parallel grinding economy. This is also the cleanest answer to “how do staff recognize cultural contribution without conflating it with labor/work expectations.”

The cap matters: without it, this becomes a parallel economy. With it, it’s a genuine recognition mechanism.

Retroactive Progression

Adapted from Rules Cyclopedia Chapter on Creating High-Level Player Characters: a framework for spinning up a character at higher than level 1 when they’re joining an existing campaign or have a prior history that should count.

The problem for us

Customers who shopped at the store for years before the Guild system existed have real lifetime contributions that aren’t reflected in their current XP. A long-time patron who’s spent thousands of dollars pre-Guild is starting at level 1 alongside someone who signed up yesterday. That’s wrong.

The framework

When onboarding a pre-existing customer (or auditing an existing member who predates the Guild):

  1. Find their lifetime spend at the shop (Square historical data).
  2. Apply the standard XP conversion: $1 = 100 XP at base rate.
  3. Decide whether to apply tier multipliers retroactively. Default: no — they weren’t members during those purchases, so base rate only. This also keeps retroactive XP from vastly exceeding current-member XP.
  4. Cap the backfill at level 8 (one level below name level). Let them earn the final name-level transition under the current system — it’s the meaningful milestone. Pre-system activity should not deliver name level directly; that moment needs to happen under the current mechanic.
  5. Log the backfill as a distinct XP transaction with a “Retroactive progression” flavor so the ledger shows it explicitly. No surprises.

Who this applies to

  • Pre-Guild long-time customers joining the system now
  • Existing members who may have been under-credited due to earlier system bugs or pre-launch purchases not being counted
  • Special cases at staff discretion (e.g., someone who’s been volunteering at events for years but isn’t in the purchase log)

Why cap at level 8

Name level (9) is the design’s biggest ritual moment. Triggering it via backfill would waste it. Better to backfill to level 8 and let the next real purchase or event push them to 9 — turning the ritual into a genuine event, not an administrative recalculation.

Hard Caps

Adapted from RC: “A character cannot gain more than one level of experience in one adventure, regardless of how many experience points are awarded.” Prevents runaway advancement from a single big treasure haul.

Our equivalents

  • Max XP per calendar day: TBD. Prevents kiosk-tap grinding (if someone finds a way) and bulk-purchase level-dumps. Should be calibrated to allow a genuinely big in-shop day (e.g., event + purchase + check-in combo) without capping it, but stop a $5K bundle from vaulting multiple levels.
  • Max level gain per month: 1. Even if XP accrual would support more, only one level-up can process per calendar month. Extra XP carries over but the level itself takes another month to realize. Matches RC’s “one level per adventure” while adapted to a longer real-world cadence. Preserves the meaningfulness of each level-up moment.
  • Max retroactive level: 8 (per Retroactive Progression above).

These caps should be configured, not hardcoded. Calibrate against real member data once the member base is large enough to see the distribution.

Why these caps matter

Without them, one generous customer with disposable income can outlevel the community overnight. That breaks the progression signal — the leaderboard stops meaning “who’s engaged” and starts meaning “who spent big.” Both axes matter; caps force them to stay somewhat proportional to time spent, not just money spent.

Refund Handling

Implemented 2026-04-14.

Refunds reverse both points and XP awarded by the original purchase. The member’s level is recomputed from the new XP and may drop. Without this, there’s an obvious exploit: buy something big → earn XP / level up → refund it → keep the progression. Same logic for points — refunded money is no longer earned.

Dungeon Books has a 7-day refund policy (customers have 7 days to return). Refunds happen in practice, both for legitimate returns and because people can otherwise read books and return them. Refund handling has to be correct or the loyalty system is gameable.

Mechanics (webhook path)

  • On a Square refund.updated webhook with status === 'COMPLETED', resolve the originating order via payment.order_id (not refund.order_id — Square creates a separate refund-order for card refunds; only the payment reliably links back to the original purchase).
  • Find the Payload Purchase row by squareOrderId. Read its earnMultiplier — this is the multiplier in effect at the time of the original purchase, which is what we reverse at (not the member’s current multiplier, which may have changed).
  • Points reversed: refundCents × Purchase.earnMultiplier, floored.
  • XP reversed: look up the original purchase transaction by squareOrderId. If it exists and has a higher xp than points (because a buff was active), scale its xp proportionally to the refunded fraction. Otherwise XP reversed = points reversed (the backfilled-purchase case, no buffs).
  • Create a loyalty-transactions entry with type: 'adjustment', source: 'manual', points: -reversePoints, xp: -reverseXp, and metadata.kind: 'refund-reversal' (plus squareRefundId for idempotency).
  • Decrement member.loyaltyPoints by the reverse amount (allowed to go negative — a member who earned and redeemed before a refund legitimately has a negative balance; the redemption guard in redeemPoints prevents further redemptions until the debt is repaid).
  • Decrement member.xp by the xp-reverse amount (floored at 0 — never negative total).
  • Recompute member.level from new xp using levelFromXp.
  • Decrement Purchase.pointsEarned by the reverse amount (floored at 0 for partial-refund accumulation).

Idempotency

All refund handling is idempotent on metadata.squareRefundId. Square’s webhook retries and multi-phase status transitions (PENDING → APPROVED → COMPLETED) may fire refund.updated multiple times for the same refund; only the first COMPLETED event creates a ledger entry. Subsequent duplicates are no-ops.

Backfill

The new-member backfill path in lib/square-customer.ts needs to know about refunds for historical purchases so it doesn’t over-credit on initial import. Square’s Orders API does not populate order.refunds for card-payment refunds — those live on the Payment and only surface via the Refunds API. So backfill now:

  1. Pre-loads a payment_id → refund_cents map from the Refunds API at the start of a backfill.
  2. Per order, looks up the tender payment_ids in that map and subtracts the aggregate refund amount from the gross.
  3. Stores the Purchase row with totalMoney = gross and pointsEarned = effective (post-refund) × multiplier.

For historical refunds that landed before the webhook handler was deployed (i.e., before 2026-04-14), use scripts/reverse-historical-refunds.ts. It scans the Refunds API, cross-references payment.order_id against existing Purchase rows, and issues reversal adjustments. Idempotent, safe to re-run. Supports --apply-xp-correction for one-time migration of reversals that predated the XP-reversal fix.

Known exploit: redemption-refund

Open vulnerability. A member could:

  1. Make a large purchase and earn points (and XP — but XP is now reversed, closing that vector)
  2. Redeem the earned points for store credit before the refund window closes
  3. Refund the original purchase

After step 3:

  • member.loyaltyPoints goes negative (the reversal subtracts the full earn amount).
  • The member’s redeemPoints calls are blocked until the negative balance is cleared (the guard WHERE loyalty_points >= points in redeemPoints).
  • But the store credit already exists — it was issued at step 2 and is now unrecoverable through the loyalty system.

Net loss to the shop: the value of the issued store credit. At current tier rates, a 20 of store credit (10,000 pts/$1 ratio). The exploit cap is roughly 2% of the purchase value — not catastrophic per instance, but cumulative across attackers.

Mitigation (not yet implemented): 7-day redemption hold.

  • Track eligibleForRedemptionAt per purchase transaction (purchase date + 7 days, matching the shop’s refund-window policy).
  • redeemPoints computes available balance as (sum of purchase XP where eligibleForRedemptionAt < now) + (all non-purchase points) - (prior redemptions).
  • Points earned from a purchase are shown in the UI as “pending until [date]” for the first 7 days.
  • After 7 days, the refund window has closed; the points become redeemable.

Why 7 days: matches the refund policy. If a member can’t refund after 7 days, they can’t exploit the loop. Using a shorter hold would leave an exploit window; a longer hold would frustrate legitimate members who want to redeem quickly on earned activity.

Scope priority: the current negative-balance guard is sufficient for the current 13-member cohort (all known, in-person-vouched). Fix before any of:

  • Invite flow that doesn’t require in-person validation
  • Scaling beyond ~50 members
  • Enabling any Solana/on-chain redemption path (where the “recovery by future earning” property is harder to guarantee)

This is a hard blocker for on-chain redemptions specifically because the abstraction of points as a tradable on-chain asset means the negative-balance guard doesn’t cleanly apply.

What refunds still do NOT do

  • Do not revoke already-issued redemption (store credit, merch already shipped, Discord role already assigned). Redemption is a one-way operation; once the value is transferred out of the loyalty system, the system can’t recall it. See the exploit note above.
  • Do not reverse independent non-purchase engagement: check-ins, event attendance, quest completions from the same day are untouched. Only the refunded purchase’s own contribution (including its buff XP portion) is reversed.
  • Do not cascade: refunding Purchase A doesn’t affect Purchase B’s pointsEarned or the XP awarded by B, even if they happened the same day.

Immortality Paths (V5 parking lot)

RC post-name-level endgame: characters who reach level 26+ pursue Immortality via one of four paths — Dynast (builds a great empire), Hero (classic epic hero trajectory), Paragon (masters one profession deeply), Polymath (learns broadly).

These map to real patron archetypes and are worth preserving in notes as a long-term trajectory framework:

PathShop-world archetype
DynastThe network-builder — recruits members, sponsors events, grows the community
HeroThe big-gesture patron — rare but significant contributions, epic moments
ParagonThe subject-matter expert — deep focus on one genre/game/system
PolymathThe generalist — broad engagement across everything the shop does

Not for MVP. Most members will take years to approach a level where this matters. Captured here so it’s not lost; revisit when the first member approaches that tier.

Seasonal/Special Events

IRL events modeled on MMO seasonal content: double XP periods, limited-time quest chains, community-wide unlock thresholds. Mithril members get 50% off event costs, making them the most likely attendees — correct feedback loop.

Membership Tiers

TierMonthly CostStore DiscountEvent DiscountXP Multiplier
Bronze$10None10%1x
Silver$2010%10%2x
Gold$5020%25%2.5x
Mithril$10025%50%3x

Tier Design Logic

  • Bronze is a cover charge, not a value proposition. It filters for engaged players and covers platform costs. No store discount. Value = game access + event discount.
  • Silver break-even: ~$200/month spend on store discount alone.
  • Gold break-even: ~$250/month spend on store discount alone. 25% event discount provides a smooth step between Silver and Mithril.
  • Mithril break-even: ~$400/month spend on store discount alone. 50% event discount is the key differentiator from Gold.

Multiplier Rule

XP multipliers apply to purchase XP only, not quest/event XP. This prevents whales from outleveling active community members who engage with the full system. A high-spending member who never participates shouldn’t outlevel a lower-spending member who does everything.

Free Tier

No free tier at launch. Costs associated with maintaining free accounts aren’t justified pre-scale. If the platform goes viral, introduce a spectator mode: view-only access to leaderboards, other players’ levels, upcoming events. No XP earning, no participation. Functions as a demo and FOMO funnel toward Bronze.

Dual Currency: XP + Points

Every dollar earns both XP and points simultaneously at the same rate with the same multipliers.

  • XP: Permanent. Tracks level. Never decreases.
  • Points: Spendable. Tracks balance. Redeemed for rewards.

A player who spends all their points remains at their current level. No de-leveling.

Points-to-Store-Credit Conversion

Target ratio: 100 points = $0.01 (1 cent)

This yields effective cashback rates of:

TierMultiplierEffective Cashback
Bronze1x1%
Silver2x2%
Gold2.5x2.5%
Mithril3x3%

These rates are competitive with major credit cards (Chase Sapphire Preferred offers 1–5% category-dependent, Discover rotates 1–5% quarterly). Combined with store/event discounts, Mithril delivers ~28% total value return on store purchases — no credit card competes at a single merchant.

The conversion ratio is intentionally inefficient enough that most players will prefer keeping points for leveling and rewards rather than cashing out.

Point Sinks

All purchases are permanent. No decay, no expiring roles, no maintenance costs. Earned = kept.

TierCost RangeExamples
Low5,000–20,000 ptsStickers, Discord roles (zero fulfillment cost)
Mid50,000–200,000 ptsTote bags, store credit
High500,000+ ptsT-shirts, exclusive merch, event VIP access

Seasonal items rotate but are never permanently exclusive. Past seasonal items return annually or become available off-season at slightly higher cost.

Classes

Four cosmetic classes at launch: Fighter, Magic-User, Cleric, Rogue (renamed from Thief).

  • Purely cosmetic. No mechanical differentiation.
  • Provides player identity, community sorting (Discord channels, class leaderboards, team events).
  • One character, one class, chosen at signup. No rerolling, no alts.
  • Mechanical class bonuses can be added later based on community feedback.
  • If class changes are eventually allowed: expensive one-time reincarnation that costs significant points but preserves level.

BECMI race-as-class options (Elf, Dwarf, Halfling) deferred. Start small with four human classes and expand based on demand.

Launch Strategy

Early access at $10 minimum (Bronze). Ship with:

  • BECMI XP curve
  • Four cosmetic classes
  • Tiered membership with discounts and multipliers
  • Point sinks for physical goods and Discord roles
  • Quest system
  • Event calendar

Iterate based on player feedback. The community will signal what needs expansion and what’s unused.

Open Questions

  • Exact XP values for quest tiers need calibration against real purchase data
  • Middle-tier quest design (between “photo of a dog” and “visit 5 businesses”)
  • Whether post-level-9 XP should increase beyond 120k/level to slow Mithril progression
  • Discord integration specifics (role automation, class channels, leaderboard bot)
  • Seasonal event cadence and structure