Skip to content

Qualified referral rewards

The new campaign replaces future registration-based Mushie/Gold milestones. Earned legacy rewards remain claimable using only referrals before the campaign start. All-time achievement counts continue to include registrations.

An eligible new account must redeem once, complete three AI play turns or ten minutes of measured gameplay, and play again at least 24 hours after its first play, within 14 days. BYOK play and verified linked guest-game activity can qualify. Browsing, Studio, idle visits and empty generations cannot.

Default campaign: 500 Bonus per qualifying friend, four rewards per inviter, 1,000 total reward slots, 30 days. Pending claims reserve slots. Reservations can complete after the campaign closes, up to their individual deadlines. Expired reservations release capacity. The limits have no monthly reset. The welcome gift is separate: new-policy Free invitees receive 500 Bonus once; grandfathered welcome terms remain protected.

Accounting and performance

  • PostgreSQL is authoritative for qualification and payment. The dashboard's sampled/aggregated statistics never determine credits.
  • Indexed event triggers wake only an enrolled pending account. One background job processes 25 due accounts per minute, with a five-second query deadline. Qualifying or rendering the invite page never scans the entire usage history.
  • Foreground/server intervals are unioned to avoid counting concurrent worlds twice. Signed cumulative game counters use a pre-enrollment baseline.
  • Verified guest links map games to accounts; unlinked guests earn no credit.
  • Campaign/qualification/wallet locks and unique receipts make payout retryable and atomic. Bonus uses the existing lot expiry and spending implementation.
  • New ledger rows have type referral_reward, so existing analytics reports include them under Invite rewards. Exact origin is referral_qualified.

Deployment

  1. Apply packages/server/scripts/prepare-qualified-referrals.sql with bounded lock timeouts under the schema owner. This creates an inactive campaign and changes no balances. Install the separate concurrent index script.
  2. Deploy API and frontend together, preserving newer production commits.
  3. Verify public welcome-offer GET is 200, while redemption and admin writes still require authentication. Verify database indexes are valid and no already-earned legacy milestones are unpaid.
  4. Set campaign starts_at and ends_at only after the terms UI is deployed, giving at least seven days of visible notice. Once announced, do not silently change reward amounts, deadlines or limits. Stop new reservations with paused=true while continuing to honor reserved rewards.
  5. Never roll back to code that revives the old financial ladder after cutover. Do not disable the wallet Bonus flag to stop invitations: existing credit expiry/spending and reserved rewards depend on it.

The invite screen shows the campaign dates in the viewer's local time, along with amount, limits, qualification progress and each paid reward's expiry. Saved balances and paid-plan allowances/check-ins are unchanged.

Verification

Run pnpm build and pnpm typecheck from the repository root. Run the referral integration test with DATABASE_URL and DATABASE_READ_URL empty, PGLITE_DATA_DIR=memory://, REDIS_URL/POSTHOG_API_KEY empty, and a test BETTER_AUTH_SECRET; never run its fixtures against a real database.

  • packages/server/src/lib/qualified-referrals.integration.test.ts: PostgreSQL evidence queries, duplicate grants, Bonus expiry, real-game counters, overlap, guest linking, limits, legacy cutoff and public/authenticated route boundaries.
  • Existing referral, wallet and check-in suites cover protected legacy behavior.
  • packages/app/src/components/qualified-referral-card.test.tsx: actual rendered components in all five locales, both full and sidebar layouts, progress and expiry text. Use the app's tsconfig.app.json with the Node/tsx test runner.

No browser was opened for these checks.