# Poki Yoki Arcade — "Cup Pass" + Advocate Layer Build Spec

**v1 · 2026-06-14 · companion to `loyalty-momfluencer-engine.html`**

Engineering-ready spec for the Arcade-side mechanics that power the loyalty + advocate engine: (1) **Cup Pass** seasons (battle pass), (2) **streaks** + Rest Day Token, (3) weekly **leagues**, (4) **limited cosmetics**, (5) the **advocate leaderboard** (referral → PokiStars), (6) **permanent achievement score**. Designed to extend the existing Arcade (Poki Pet, mini-games, national leaderboard, Poki Nation, PokiStars, achievement wall) and to layer on top of the rewards `reward_codes`/PokiStars work in `rewards-build-spec.md`.

> **WHY this exists:** a cup is bought ~once/year, so daily Arcade engagement is the program's "frequency engine" (the airline-requalification equivalent). These mechanics are what make a mom open the tab daily, which keeps the brand present at the moment a cup-need arises and keeps PokiStars accruing toward redemption.

---

## 0. Guardrails that constrain every mechanic here

The Arcade has **children on screen** even though the loyalty/advocate program targets the parent. Hard rules (from the engine report §9):

- **No purchase prompts or "buy premium" CTAs inside the Arcade UI.** The Cup Pass premium upgrade happens in a **parent-facing flow** (email link → Shopify checkout). The game shows *status* ("Premium unlocked") but never a buy button.
- **No countdown-to-buy timers** visible in-game. Season-end countdowns are fine as engagement ("Season ends in 5 days"); they must not be attached to a purchase CTA in the kid-facing UI.
- **No loot boxes / randomized reward packs.** Fixed-reward ladders only (battle-pass tiers are deterministic). This is the $20M-Genshin line.
- **No paid streak-shields.** The Rest Day Token is *earned*, never sold.
- **Parent-account identity.** Loyalty/PokiStars/purchases attach to the parent account; a child can play without an account. (COPPA.)

---

## 1. Cup Pass (seasons / battle pass)

### 1.1 Concept

An 8-week season with two tracks. Everyone is on the **Free track**; the **Premium track** ($5–8, bought by the parent) unlocks richer rewards. Players earn **Season XP** by playing and completing quests; XP fills a tier ladder; each tier grants rewards on the track(s) the player holds. Season ends → unclaimed seasonal cosmetics are gone forever (scarcity).

> **WHY 8 weeks:** long enough to build habit and justify the premium price, short enough to create FOMO and natural PR beats ("Season 2 is live"). Confirm with Eric/Cristina before locking.

### 1.2 Data model

```
table seasons
  season_id     int PK
  name          string                 -- "Season 1: First Sips"
  starts_at     timestamp
  ends_at       timestamp              -- starts_at + 8 weeks
  status        enum [upcoming, active, ended]
  tier_count    int                    -- e.g. 30 tiers
  xp_per_tier   int                    -- e.g. 100 XP/tier

table season_rewards
  id            int PK
  season_id     int FK
  tier          int                    -- 1..tier_count
  track         enum [free, premium]
  reward_type   enum [cosmetic, pokistars, accessory_coupon, badge, theme]
  reward_ref    string                 -- cosmetic id / star amount / coupon code id
  is_limited    bool                   -- true = never re-issued after season end

table season_progress
  user_id       string
  season_id     int
  xp            int      default 0
  tier          int      default 0     -- derived = floor(xp / xp_per_tier), capped
  has_premium   bool     default false -- set true when parent buys the pass
  claimed_tiers json                   -- [{tier, track, claimed_at}]  append on claim
  PRIMARY KEY (user_id, season_id)
```

### 1.3 Earning Season XP

| Source | XP | Cap |
|---|---|---|
| Complete a game session | 10 | counts toward daily cap |
| Daily quest (e.g. "play Sip Trail") | 50 | 1 active/day |
| Weekly quest (e.g. "play 5 days this week") | 200 | 1/week |
| Personal best | 25 | 1/day |
| Daily XP cap | — | **150 XP/day** (prevents single-session grind to top) |

XP is **separate** from PokiStars (XP fills the pass; PokiStars is the spendable currency). A tier can *grant* PokiStars as a reward, but playing earns XP, gated by the existing 200-PokiStars/month Arcade cap from `rewards-build-spec.md` §2.1.

### 1.4 Premium unlock (parent-facing, the only money step)

- The parent buys the Cup Pass via a **Shopify product** ($5–8) or claims it as an included perk (Founding Club / a tier benefit).
- On Shopify `orders/paid` containing the Cup Pass SKU → webhook sets `season_progress.has_premium = true` for the linked account, **retroactively** unlocking premium rewards for tiers already reached (claimable next time they open the Arcade).
- The Arcade shows "Premium unlocked ✓" — **never a buy button** (guardrail §0).

### 1.5 Claim + season end

- Reaching a tier marks it claimable on the track(s) the user holds; claiming credits the reward and appends to `claimed_tiers`.
- At `ends_at`: set `status=ended`; **limited** cosmetics become permanently unavailable; unclaimed non-limited PokiStars auto-credit (don't punish). A new season row goes `active`. Players keep cosmetics already claimed forever.

---

## 2. Streaks + Rest Day Token

### 2.1 Data model

```
table streaks
  user_id          string PK
  current_streak   int   default 0
  longest_streak   int   default 0
  last_play_date   date                 -- date (account TZ) of last qualifying play
  rest_tokens      int   default 0      -- max 1 held at a time (earned, never bought)
  rest_token_week  isoweek              -- the week the current token was granted
```

### 2.2 Daily check (on first qualifying play of the day)

```
let today = date(now, account_tz)
if last_play_date == today: no-op
elif last_play_date == today - 1:           # consecutive
    current_streak += 1
elif last_play_date == today - 2 and rest_tokens > 0:   # one missed day, covered
    rest_tokens -= 1
    current_streak += 1                     # token bridges the gap
else:                                       # streak broken
    current_streak = 1
last_play_date = today
longest_streak = max(longest_streak, current_streak)
award_streak_badges(current_streak)
```

- **Rest Day Token grant:** +1 token at the start of each ISO week (cap 1 held), OR as a Stacker+ tier perk. Earned only — never purchasable (guardrail §0).
- **Streak badges:** 7-day → "Sip Streak", 30 → "Flow State", 90 → limited "Founding Sipper" title (only the first cohort to reach 90 earns it; never re-issued). Badges render on the Poki Pet + leaderboard nameplate.

> **WHY a Rest Day Token, not a paid shield:** the 7-day streak is the top retention predictor; loss aversion protects it. The token prevents the quit-event when a real day is missed without the dark-pattern of selling streak insurance to a kids' product.

---

## 3. Weekly leagues

### 3.1 Concept

The existing national leaderboard gets a weekly **league** layer: active players are bucketed into small pools so competition feels winnable. Sits alongside Poki Nation (city battles) — leagues are individual, Poki Nation is collective.

### 3.2 Model + logic

```
table leagues
  league_id     int PK
  iso_week      isoweek
  division      int          -- 1 (Sprout) .. 10 (Founder)
  pool_index    int          -- which ~25-player pool within the division

table league_members
  user_id       string
  iso_week      isoweek
  league_id     int FK
  weekly_xp     int default 0    -- league score for the week (game XP this week)
  PRIMARY KEY (user_id, iso_week)
```

- **Pool size ~25**; top 5 promote a division, bottom 5 demote, rest hold. New/returning players seed into a division near their lifetime achievement score.
- **Reset:** weekly job at Sunday 23:59 (account-region anchor) ranks each pool, applies promote/demote, then assigns next week's pools.
- **Division names (brand voice, placeholder):** Sprout → Sipper → Stacker → Pourer → Magnet → Spark → Current → Flow → Inner → Founder. Division name shows on the profile/nameplate.

> **WHY pools of ~25:** beating 24 peers at your level feels achievable in a way the national board never does; promotion/demotion drives a Sunday re-engagement spike.

---

## 4. Limited cosmetics (scarcity)

```
table cosmetics
  cosmetic_id   string PK
  name          string
  rarity        enum [common, rare, limited]
  hard_cap      int NULL          -- e.g. 700 (founder count); NULL = uncapped
  claimed_count int default 0
  source        enum [season, achievement, founder_gift, code]
  tradeable     bool default false  -- ALWAYS false (no secondary market w/ kids)
```

- "Season 1 OG" Poki Pet skin: `rarity=limited`, `hard_cap` = founder count, `claimed_count` increments atomically on grant; once `claimed_count == hard_cap`, no more grants.
- **Retroactive founder gift:** Kickstarter/early-backers who register an Arcade account auto-receive the OG skin (matches the `founding-backer` tag from `rewards-build-spec.md` §4).
- Non-tradeable always (no resale market involving a kids' product).

---

## 5. Advocate leaderboard (referral → PokiStars)

Bridges Engine 2 into the Arcade: advocates see their referral performance as a leaderboard, and referrals earn PokiStars (not just discounts).

```
table advocate_stats
  user_id          string PK
  tier             enum [scout, maker, champion, founding_circle]
  referred_sales_total   int default 0
  referred_sales_month   int default 0     -- resets monthly
  pokistars_from_referrals int default 0
  code             string                   -- their Shopify Collabs / Social Snowball code
```

- **Attribution:** Shopify/Social Snowball webhook on a referred order → increment `referred_sales_*`, credit **500 PokiStars** per converting referral (after the 14-day post-fulfillment hold from the rewards referral rules), set the "Creator" achievement on first sale.
- **Advocate leaderboard tab:** separate tab next to the game leaderboard, ranked by `referred_sales_month`; resets on the 1st. Top 3 → the §8B prizes in the onboarding kit. This is display + recognition; the money flows through the affiliate tool, not the game.
- **Tier sync:** `tier` updates from advocate milestones (3 referrals → maker eligibility; 10 → champion review). Tier badge renders on the nameplate.

> Keep the **money** in the affiliate platform (Shopify Collabs / Social Snowball) for clean payouts/1099s; the Arcade only mirrors *stats + status + PokiStars*. One source of truth for cash.

---

## 6. Permanent achievement score

- Extend the existing achievement wall with a lifetime **Poki Score** = sum of achievement point values; never resets across seasons.
- Rare achievements (earned by <5% of players) render a glow on the leaderboard nameplate.
- Tiers named in brand voice: Sip / Stack / Pour / Lifetime (the Bronze/Silver/Gold/Platinum equivalent).

---

## 7. Build order (smallest shippable first)

1. **Streaks + Rest Day Token** (§2) — smallest, highest retention ROI, no money flow. Ship first.
2. **Permanent achievement score + rarity glow** (§6) — mostly display on existing achievement data.
3. **Advocate leaderboard + referral→PokiStars** (§5) — unblocks Engine 2's in-game status; depends on the affiliate webhook.
4. **Weekly leagues** (§3) — needs the weekly cron + pool logic.
5. **Cup Pass seasons** (§1) — the biggest build (XP, tiers, premium webhook, cosmetics); ship for "Season 1" once 1–4 are stable.
6. **Limited cosmetics** (§4) — lands with Season 1; the founder OG skin can ship earlier as a retro gift.

Items 1–3 deliver most of the engagement + advocacy value; 4–6 complete the season system.

## 8. Test cases (must pass)

- Streak: consecutive days increment; one missed day with a token bridges (token decremented); missed day w/o token resets to 1; longest_streak tracks max.
- Rest token: max 1 held; granted once per ISO week; never purchasable in any flow.
- Cup Pass: XP daily cap enforced (150); tier = floor(xp/xp_per_tier); premium purchase retroactively unlocks reached tiers; season end freezes limited cosmetics; claimed cosmetics persist after end.
- Premium unlock has **no in-game buy button** (guardrail) — only via Shopify webhook.
- League: pools ≤25; promote top 5 / demote bottom 5 on weekly reset; new player seeds by achievement score.
- Limited cosmetic: `claimed_count` never exceeds `hard_cap` under concurrent grants (atomic); non-tradeable.
- Advocate: first referred sale → Creator achievement + 500 stars after the 14-day hold; monthly stats reset on the 1st; cash never flows through the game.
- COPPA: a child can play with no account; loyalty/advocate/purchase data attaches to the parent account only.

## 9. Open decisions

- **Season length (8 wk?) + tier count (30?) + xp_per_tier.**
- **Cup Pass premium price** ($5–8) and whether it's bundled into a Founding Club / Inner Circle perk.
- **Division names** (brand voice) for the 10 leagues.
- **The 500-PokiStars-per-referral** value vs. the in-game economy peg (coordinate with `rewards-build-spec.md` §1.5).
- **Affiliate platform** that fires the referral webhook (Shopify Collabs vs Social Snowball) — sets the integration target for §5.

---

*This spec extends `rewards-build-spec.md` (codes, PokiStars, tiers, free shipping). The two together are the full technical scope for the loyalty + advocate engine. Sequence behind the founders launch (rewards critical path) — these are the 90-day-window builds.*
