Overview
Status: COMPLETE — Full design documented
Last updated: October 9, 2026
Flip Match — Game Design Overview
Status: COMPLETE — Full design documented
Doc coverage:: COMPLETE — Full code extraction done. All mechanics verified against Java source.
PLAYTEST READY: All handlers call
addText()and setstoryOptionsList. Wrapper has fallback for unhandled input. All core bugs fixed (hints reveal cards, abandon updates stats, achievements wired, par times rebalanced). Ready for pipeline testing.
Flip Match (internally "Memory Snap") is a grid-based memory/matching card game. Players flip cards using chess-style coordinates (A1, B3, H8) to find matching pairs. Features three difficulty levels with time limits, four themes, a scoring system with streak bonuses, achievements, and leaderboards.
Game Identity
| Property | Value |
|---|---|
| play_game ID | memory_snap |
| skill_identifier | MEMORY_SNAP |
| Genre | Memory/matching card game |
| Engine | Grid-based card flip mechanics |
| DB Schema | memory_snap |
Design Pillars
- Simple Core, Deep Mastery — Easy to understand (flip two cards, find matches), hard to perfect (minimum turns, streak bonuses, time pressure)
- Accessibility — Chess-style coordinates work across voice (Alexa), web, and API; case-insensitive, "flip" prefix optional
- Replayability — Randomized boards, four themes, three difficulties, achievements, and leaderboards drive replay
- Progressive Challenge — EASY (no timer, 3 hints) → MEDIUM (4-min timer, 2 hints) → HARD (5-min timer, 1 hint)
Core Loop
Player starts game (selects difficulty + theme):
→ Board generated with shuffled card pairs
→ Player flips first card ("flip A1") — card revealed
→ Player flips second card ("flip B3")
→ MATCH: Both cards stay revealed, score increases, streak builds
→ NO MATCH: Both cards flip back, streak resets
→ Turn counter increments
→ Optional: Use hint (reveals a matching pair, -50 score penalty)
→ Optional: Time expires (MEDIUM/HARD only) → game abandoned
→ Game ends when all pairs found
→ Final score calculated: (base + bonuses - deductions) × difficulty multiplier
→ Personal best updated if higher
→ Leaderboard updated if score qualifies
→ Achievement progress updated
Board Configuration
Difficulty Levels
| Difficulty | Board Size | Pairs | Cards | Hints | Time Limit | Par Time | Score Multiplier |
|---|---|---|---|---|---|---|---|
| EASY | 4×4 | 8 | 16 | 3 | None | 60s | 1.0× |
| MEDIUM | 6×6 | 18 | 36 | 2 | 240s (4 min) | 120s | 1.5× |
| HARD | 8×8 | 32 | 64 | 1 | 300s (5 min) | 150s | 2.0× |
Default: MEDIUM difficulty, ANIMALS theme (stored in player preferences).
Par time is always below the time limit. Completing under par earns +100 bonus points.
Themes (4)
| Theme | Display Name | Description |
|---|---|---|
| ANIMALS | Animals | Cute and wild animals from around the world |
| FLOWERS | Flowers | Beautiful flowers and blossoms |
| FOOD | Food | Delicious foods and treats |
| TRANSPORT | Transport | Vehicles and ways to travel |
Themes are database-driven via MemorySnapCardTheme and MemorySnapCardItem tables. Each theme contains card items with:
itemName(e.g., "lion")itemDisplayName(e.g., "Lion")itemEmoji(e.g., "🦁")itemDescription(e.g., "The king of beasts")audioName— audio file reference for AlexadifficultyWeight— weighted distribution for board generationisActiveflag and optionalunlockRequirementon themes
Store pack decks and card backs
What each STARTER and seasonal pack promises beside its Hint Credits is MemorySnapStorePackGrants,
Flip Match's part of the shared pack-grant path ( § Store pack items). The
listing copy is GAME_COPY["memory_snap"] in scripts/alexa/smapi_products.py.
| Pack | Deck | Card back | Consumable |
|---|---|---|---|
| STARTER, Sharp Focus Starter | none | Sharp Focus | Two perfect round bonus rolls |
| Spring Garden | SPRING_GARDEN, 32 cards | Bloom | none |
| Summer Beach | SEASIDE, 32 cards | Shell | none |
| Autumn Forest | FOREST, 32 cards | Leaf | none |
| Winter Frost | FROST, 32 cards | Snowflake | none |
- Held while a completed purchase stands, read from
game_log.purchase_transactionsthroughStorePackHoldings; nothing is written on delivery. A subscriber holds every pack deck too, as the subscription promises every premium theme. The pack card backs are held by the pack alone. - Settings chooses a held deck or card back by name, and names the pack that sells one the player does not hold. The card back list marks each unheld pack design with its pack.
- A perfect round bonus roll is spent when the last pair is found on a solo board that was not perfect: the round scores the +500 perfect game bonus anyway. A board that was already perfect keeps the roll.
- The decks are content rows in
memory_snap.card_themeandcard_item, 32 items each, with premium set on the theme enum. Until those rows exist, settings treats a deck as unseeded and offers it to no one. - A refund takes back the starter's unused bonus rolls, and the card back and deck once no other delivery of that pack stands; a preferred card back or deck from the refunded pack goes back to Classic or Animals.
Coordinate System
Chess-style letter-number coordinates:
- Columns: A-D (4×4), A-F (6×6), A-H (8×8)
- Rows: 1-4 (4×4), 1-6 (6×6), 1-8 (8×8)
- Case insensitive:
a1=A1 - "flip" prefix optional:
"flip A1"="A1" - Coordinate regex:
([A-Ha-h])\s*([1-8]) - Internal mapping:
positionToCoordinate(row, col)→ e.g., (0,0) → "A1"
Card & Board Model
MemorySnapCard
Each card on the board:
itemId— references the card contentitemName/displayName/emoji— card identityrevealed(boolean, default false) — currently face-upmatched(boolean, default false) — successfully paired
Display logic: getDisplay() returns emoji if revealed/matched, else "🎴" (hidden card).
MemorySnapBoard
2D grid of cards:
size— board dimension (4, 6, or 8)cards—MemorySnapCard[size][size]2D arraymatchedPositions—Set<String>of matched coordinates (e.g., "A1", "B3")firstFlip/secondFlip— current turn's flipped coordinatescurrentStreak— consecutive successful matches
Key methods:
flipCard(coordinate)→MemorySnapFlipResulthideUnmatchedCards()— reset unmatched cards after failed flipisGameComplete()— all pairs foundgetPairsRemaining()— unmatched pairs countgetPlayerView()→MemorySnapBoardView(safe for display)toAsciiArt()— ASCII board representation
Board is serialized as JSON in the database via (MyBatis type handler).
Board Generation
:
- Fetch theme by name
- Get random items from theme (matching required pair count)
- Create two cards per item (the pair)
- Shuffle all cards
- Arrange in 2D grid
Game State
MemorySnapGameSession
| Field | Type | Purpose |
|---|---|---|
sessionId |
Long | Primary key |
accountId |
Long | Player reference |
gameState |
MemorySnapGameState | ACTIVE, COMPLETED, ABANDONED |
boardSize |
int | 4, 6, or 8 |
difficulty |
MemorySnapDifficulty | Enum |
theme |
String | Theme name |
board |
MemorySnapBoard | Serialized game board (JSON) |
turnsTaken |
int | Flip turns completed |
hintsUsed |
int | Hints consumed |
score |
int | Current/final score |
longestStreak |
int | Best consecutive match streak |
startedAt |
LocalDateTime | Game start |
completedAt |
LocalDateTime | Game end |
timeElapsedSeconds |
int | Elapsed time |
Game State Enum
| State | Description |
|---|---|
| ACTIVE | Game in progress |
| COMPLETED | All pairs found |
| ABANDONED | Player quit or time expired |
Flip Result
MemorySnapFlipResult returned from each card flip:
success— whether flip was validcoordinate— flipped positioncard— the card objectisMatch— whether second flip matchedisSecondFlip— first vs second flipgameComplete— whether this flip finished the gamescore— score for this actionstreak/streakBonus— streak state
Game Rules
- Cannot flip an already-matched card
- Cannot flip the same card twice in one turn
- Mismatched cards flip back automatically (
hideUnmatchedCards()) - Turn counter increments on each pair attempt (match or not)
- Time limit enforced on MEDIUM (4 min) and HARD (5 min)
- Abandoned games produce no leaderboard entry
- One active game per player at a time
Scoring System
Formula
Base Score = 1000
Par Time = difficulty.getParTimeSeconds()
EASY: 60s
MEDIUM: 120s
HARD: 150s
Deductions:
Per extra turn beyond minimum: -10 points
Per second over par time: -1 point
Per hint used: -50 points
Bonuses:
Perfect game (minimum turns): +500 points
Under par time: +100 points
No hints used: +100 points
Streak bonus: +10 per consecutive match
Final Score = (Base + Bonuses - Deductions) × Difficulty Multiplier
Minimum Turns
Minimum turns = number of pairs (one turn per pair in a perfect game):
- EASY: 8 turns minimum
- MEDIUM: 18 turns minimum
- HARD: 32 turns minimum
Par Time
Par times are fixed per difficulty (not calculated from pairs):
| Difficulty | Par Time | Time Limit | Headroom |
|---|---|---|---|
| EASY | 60s | None | Unlimited |
| MEDIUM | 120s | 240s | 120s |
| HARD | 150s | 300s | 150s |
Streak Bonus
Consecutive matches build a streak: +10, +20, +30... (cumulative +10 per consecutive match). Streak resets after any non-match.
Score Examples
| Scenario | Calculation | Final |
|---|---|---|
| Perfect 4×4 (8 turns, no hints, under par, max streak 8) | (1000 + 500 + 100 + 100 + 80) × 1.0 | 1,780 |
| Good 4×4 (12 turns, no hints) | (1000 - 40 + 100) × 1.0 | 1,060 |
| 4×4 with 2 hints (10 turns) | (1000 - 20 - 100) × 1.0 | 880 |
| Hard 8×8 (40 turns, 8 extra) | (1000 - 80) × 2.0 | 1,840 |
Every final score also adds the longest-streak bonus, +10 per consecutive match (see Streak Bonus above). A perfect game's streak equals the pair count, so a Perfect 4×4 includes +80 (max streak 8). The non-perfect rows omit a play-dependent streak for illustration.
Score Calculator
provides:
calculateFinalScore(session)— complete game final scorecalculateCurrentScore(session)— in-progress estimated scoreisPerfectGame(session)— minimum turns checkgetParTime(difficulty)— par time for difficulty
Hint System
| Difficulty | Starting Hints |
|---|---|
| EASY | 3 |
| MEDIUM | 2 |
| HARD | 1 |
- Effect: Reveals one unmatched pair's locations (both coordinates shown)
- Cost: -50 points per hint used
- Bonus: +100 points for completing without using any hints
- Limit: Cannot use hints when none remaining
Player Account
MemorySnapAccount
| Field | Type | Purpose |
|---|---|---|
accountId |
Long | Primary key |
userId |
String | User identifier |
displayName |
String | Display name |
totalGamesPlayed |
int | Lifetime games started |
totalGamesWon |
int | Games completed |
bestScore4x4 |
Integer | Personal best (EASY) |
bestScore6x6 |
Integer | Personal best (MEDIUM) |
bestScore8x8 |
Integer | Personal best (HARD) |
totalMatchesFound |
int | Lifetime pairs matched |
currentStreak |
int | Current win streak |
longestStreak |
int | Best win streak |
preferredDifficulty |
String | Default difficulty ("MEDIUM") |
preferredTheme |
String | Default theme ("ANIMALS") |
soundEnabled |
boolean | Sound preference (true) |
hintsEnabled |
boolean | Hints preference (true) |
Key methods: updateBestScore(), recordGameCompletion(), getWinRate()
Leaderboard
MemorySnapLeaderboardEntry
| Field | Type | Purpose |
|---|---|---|
leaderboardId |
Long | Primary key |
accountId |
Long | Player reference |
boardSize |
int | 4, 6, or 8 |
difficulty |
MemorySnapDifficulty | Game difficulty |
score |
int | Final score |
turnsTaken |
int | Turns used |
timeSeconds |
int | Time elapsed |
theme |
String | Theme played |
achievedAt |
LocalDateTime | When achieved |
displayName |
String | Player name (joined) |
rank |
Integer | Calculated, not stored |
- Separate leaderboards per difficulty level
- Only COMPLETED games qualify (not ABANDONED)
- Score-based ranking within difficulty/board size combination
getFormattedTime()returns MM:SS format
Achievement System
Achievement Definitions
MemorySnapAchievement defines achievable milestones:
| Field | Type | Purpose |
|---|---|---|
achievementId |
int | Primary key |
achievementKey |
String | Unique key (e.g., "first_win") |
achievementName |
String | Display name |
achievementDescription |
String | Description |
iconEmoji |
String | Achievement icon |
requirementType |
RequirementType | Category |
requirementValue |
int | Target value |
requirementBoardSize |
Integer | Specific board (null = any) |
points |
int | Points awarded |
Requirement Types (6)
| Type | Description |
|---|---|
| GAMES_WON | Win N games total |
| PERFECT_GAME | Achieve a perfect game (minimum turns) |
| STREAK | Reach N consecutive matches |
| SPEED | Complete under N seconds |
| MATCHES | Find N total matches lifetime |
| THEME_COMPLETE | Complete all themes |
Player Achievement Progress
MemorySnapAccountAchievement tracks per-player progress:
progress(int) — current progress toward goalunlocked(boolean) — whether achievedunlockedAt(LocalDateTime)- Methods:
getProgressPercent(),unlock(),updateProgress()
Commands
| Command | Action |
|---|---|
| start [difficulty] [theme] | Start new game |
| flip [coordinate] / [coordinate] | Flip a card |
| hint | Use a hint |
| status | Show game state |
| leaderboard [difficulty] | View high scores |
| settings | View/change preferences |
| quit | Abandon current game |
| help | Show instructions |
Known Design Issues
| Issue | Description |
|---|---|
| Alexa integration incomplete | 2/12 Alexa integration steps complete. What exists: (1) alexaSpeech field on all DTOs (MemorySnapFlipResult, MemorySnapGameStatus) with speech text populated by MemorySnapBoard.flipCard() and , (2) SkillIdentifier.MEMORY_SNAP registered in the skill identifier enum. What's missing: No Alexa entry point handler (no file in engines/entry/alexa/ for Memory Snap), no Alexa skill manifest or interaction model, no intent handlers mapped to Memory Snap commands, no voice-specific coordinate parsing, no SSML formatting, no Alexa-specific session management, no skill certification, no skill store listing, no multi-turn dialog for coordinate disambiguation, no voice-optimized board state readback. The backend generates alexaSpeech text on every flip/hint/status response, but there is no Alexa handler to consume it. |
| Theme item counts | Each theme must have 32+ items for HARD mode — verify counts |
| Premium themes unseeded | The four premium theme enums (Mythical Creatures, Space, Ocean, Dinosaurs) have no memory_snap.card_theme or card_item rows. Settings gates them as "coming soon"; the board generator falls back to a shipped theme if a stale premium preference is loaded. Seed 4 × 32 items to ship them. |
Launch / Exit / Help Baseline Audit (2026-06-17)
Review of whether Flip Match's launch, exit, and help systems are SURFACED, WELL BUILT, and ride the
shared baseline framework with per-game OVERRIDES (vs bespoke reimplementations). Verified by
play_game target:'local' + handler/NLU inspection. Source of truth for the framework:
engines/handlers/common/baseline/.java (exit), engines/handlers/common/baseline/.java
engines/handlers/common/help/.java(help),engines/entry/shared/(entry/launch lifecycle), (NLU tiers).
Summary
| System | Surfaced? | Well built? | Rides shared baseline w/ overrides? | Verdict |
|---|---|---|---|---|
| Launch — first-time onboarding | Yes | Yes | Yes ( + WelcomeTextUtility.BETA_DISCLAIMER_AND_ASK) |
PASS |
| Launch — returning-player resume | Partial | No | Infra shared, but resume UX is a bespoke fallback | FIXED (FM-LEH-3) |
| Exit (exit/stop/goodbye/bye/quit) | Yes but wrong | No | NO — bespoke override bypasses | FIXED (FM-LEH-1) |
| Help (help/what can I say/options) | Yes (real menu) | Partial | NO — bespoke static string, no | FIXED (FM-LEH-2) |
| Branding (spoken/text name) | n/a | No | n/a | FIXED (FM-LEH-4) |
FM-LEH-1 (HIGH) — Exit bypasses the shared baseline ; player cannot cleanly exit
game_process.process_action_handlers has (story_action=EXIT, game_type=MEMORY_SNAP) -> memorySnapQuitHandler.quitGame. Memory Snap is the only game with a per-game EXIT override (query
confirmed across all games). Because the global skill-exit words all resolve to EXIT at Tier 1
PRIORITY_COMMANDS (exit/stop/goodbye/good bye/bye/quit/leave game each have a
PRIORITY_COMMANDS -> EXIT direct-match row), and the claims EXIT for
the MEMORY_SNAP game type, EXIT never reaches the shared .
Result (reproduced on local):
exit/stop/goodbyefrom the menu -> "You don't have an active game. Say start game to begin a new Memory Snap game." (.java:38-40) — and the skill stays open (quitGamereturns CONTINUE_GAME).quitmid-game -> "Game abandoned. You found 0 of 8 pairs..." (.java:55-58) — abandons the board, stays in the skill.- Compare Willow Farm
exit/ Zoogoodbye-> shared : "Your game is saved on every interaction, so you will pick up exactly where you left off when you return. Thanks for playing! Please consider leaving a rating or review... Bye bye / Farewell" + actual session end (.java:41-89, returns!CONTINUE_GAME).
So Flip Match FAILS both "cleanly exit" and "save on exit" — it abandons (discards) the game instead of saving, never says goodbye, never ends the session, and never runs the account-link / review nudge.
Proper baseline-with-override: REMOVE the (EXIT, MEMORY_SNAP) process_action_handlers row so EXIT
flows to like every other game. If Flip Match needs exit-time cleanup, implement a
GameStateClearer provider (the SPI already invokes,
.java:97-103) — do NOT override the EXIT action. Keep MEMORY_SNAP_QUIT (in-game
"leave game") as the explicit abandon path. Note "quit" has both a PRIORITY_COMMANDS -> EXIT row and a
MEMORY_SNAP -> MEMORY_SNAP_QUIT row; once the EXIT override is removed, "quit" resolves to EXIT (skill
exit) at Tier 1. Decide (design) whether "quit" should mean skill-exit (leave the PRIORITY row) or
in-game abandon (drop the PRIORITY "quit" row so it falls to the MEMORY_SNAP row).
FM-LEH-2 (MEDIUM) — Help is a bespoke static string, not the shared /
(HELP, MEMORY_SNAP) -> memorySnapHelpHandler.showHelp AND (MEMORY_SNAP_HELP) -> showHelp.
(.java:47-72) hand-builds a static string
("Memory Snap. Match pairs of cards to win...") and hardcodes options via addOptionNoSynonym. It never
touches the shared (.java:32, dynamic, aggregates
beans by supports() + priority) and no exists. Every other game
(DarkTower, DarknessFalls, Lineage, Mystery, Zoo, WillowFarm) implements
(engines/handlers/common/help/.java:16).
Help DOES surface a real menu (NOT a canned "not available"), so this is a quality/architecture gap, not
a broken-help gap. Proper pattern: add implements and let the
global HELP flow to (remove the (HELP, MEMORY_SNAP) override; a per-game
MEMORY_SNAP_HELP option may still point at a provider-backed handler).
FOLLOW-UP (2026-06-21): DB rows repointed — the missing step of 22339. added and deleted the bespoke class but never repointed the process_action_handlers rows, which still named the now-deleted bean . A real player saying help in Flip Match hit DatabaseDrivenActionResolver.getBean("memorySnapHelpHandler") -> (prod error 2026-06-21 15:42). Likewise (MEMORY_SNAP_LEADERBOARD) named (the real class is , bean ). Fix (live DB config, no code change, applied to prod + local): (HELP)/(MEMORY_SNAP_HELP) -> (the shared aggregator, which now picks up ); (MEMORY_SNAP_LEADERBOARD) -> . Guarded by scenario_tests 2155/2028 (help, un-quarantined) + 2340 (leaderboard). process_action_handlers is pure live DB config (no code seeder), so the data fix is persistent and the was cleared on both targets to take effect immediately.
FM-LEH-3 (MEDIUM) — Returning-player resume does not re-render the board ("I didn't understand")
First-time onboarding is GOOD: fresh launch shows the shared beta-disclaimer welcome + start screen
(start game / start easy / start hard / leaderboard / help), not a dead-end, via the shared
. But on re-entry mid-game,
(.java:126-144) leads with "I didn't understand that command." and adds in-game
options but does NOT re-render the board or greet the returning player. The resume infrastructure
exists (populateGameDetails loads the active session at .java:97-98; status
re-renders the full board) — it just isn't used on re-entry. Recoverable (options present), so not a
dead-end, but not the clean adaptive "welcome back, here's your board" resume the launch standard wants.
Fix: have the active-session re-entry path render the board (reuse the status board view) with a
"welcome back" lead instead of "I didn't understand".
FM-LEH-4 (LOW) — Internal name "Memory Snap" leaks in spoken/text copy
Public name is "Flip Match" (launch banner + return "Flip Match"), but multiple handlers speak the internal name "Memory Snap": start ("Welcome to Memory Snap!"), help ("Memory Snap. Match pairs..."), status ("Memory Snap Status:"), quit ("begin a new Memory Snap game"). Voice-facing copy should use the published title consistently. Replace hardcoded "Memory Snap" in player-facing strings with the game title.
Resolution (2026-06-17)
All four findings resolved with Memory Snap-specific changes; the shared baseline / were left untouched.
FM-LEH-1 (Exit) — FIXED. The (story_action=EXIT, game_type=MEMORY_SNAP) row in
game_process.process_action_handlers (id 342) is disabled (enabled=0; re-applied 2026-06-21 via after a prod->local resync re-exposed it still present and enabled, which had re-broken Memory Snap exit — scenario test 2154 caught it), so exit / stop / goodbye / bye /
quit flow to the shared -> like every other game (game saved on
every interaction, thanks-for-playing + review nudge, session ends). No GameStateClearer provider
was added: Flip Match has no transient exit-time state to clear and the active board should be
preserved (saved), not discarded — the old override's quitGame abandon was exactly the bug.
The explicit in-game abandon path is unchanged: MEMORY_SNAP_QUIT (row 290) -> ,
reached by the displayed "leave game" option.
FM-LEH-1 design sub-decision — "quit" means skill-exit. quit resolves to EXIT at Tier 1 via the
hardcoded global PRIORITY_COMMANDS set (in ), which is shared across every
game — it is NOT a Memory Snap-scoped synonym row. Dropping it would change quit behaviour for all
games and would mean editing shared code, both out of scope. So we keep quit = clean skill-exit
(consistent with every other game), and keep in-game abandon on the explicit "leave game" option ->
MEMORY_SNAP_QUIT. This is the deliberate choice; no global synonym row was touched.
FM-LEH-2 (Help) — FIXED. New implements
(engines/handlers/memorysnap/help/.java, priority 10, supports = MemorySnap
account loaded) contributes four Flip Match help categories. The (HELP, MEMORY_SNAP) override (row
341) is removed so global help flows to the shared menu; the MEMORY_SNAP_HELP
option (row 293) is repointed to (provider-backed). The bespoke
(+ its test) is deleted.
FM-LEH-3 (Resume) — FIXED. active-session branch now
re-renders the board (reusing the status board view via getGameStatus) led by "Welcome back to Flip
Match." instead of "I didn't understand that command." (new helper renderBoardWithWelcomeBack).
FM-LEH-4 (Branding) — FIXED. Player-facing spoken/text copy now uses the published title
"Flip Match" across the start banner, status header, leaderboard header, achievements header, settings
header, quit/flip/hint/race no-active-game prompts, the delegate resume lead, and the daily login-reward
readout. Internal identifiers (getGameName() = "MemorySnap", package/DB names, log lines, Javadoc)
are unchanged.
Tests. New game_test.scenario_tests rows (MEMORY_SNAP): id 2154 (exit -> baseline goodbye, not
"no active game"), 2155 (help -> shared menu), 2156 (start banner says "Flip Match").
Existing locale-fallback test 2028 was updated to the shared help-menu prompt (intentional design
change per FM-LEH-2). The MEMORY_SNAP scenario suite is green locally (13/13) and the three new rows
pass against the patched backend.
Deploy note (config is code-coupled). The three process_action_handlers data changes (delete
rows 341 + 342, repoint row 293 -> ) are applied to the LOCAL test DB for the
gate. The PROD rows must land together with the code deploy, because the new
bean must exist before the (HELP, MEMORY_SNAP) override is removed on prod. Exact prod SQL:
DELETE FROM game_process.process_action_handlers WHERE id IN (341,342); UPDATE game_process.process_action_handlers SET handler_bean='helpGenerator', handler_method='help' WHERE id=293;
then evict the ACTION_HANDLER_CACHE (or restart) so the running backend re-reads the rows.
Subscription Benefits
Active subscribers (SUBSCRIPTION) unlock four benefits in Flip Match. Each benefit has an explicit upsell touchpoint for free users so they always know what they are missing.
| Benefit | What Subscribers Get | Free-User Upsell Touchpoint |
|---|---|---|
| Unlimited hints | Hint limit bypassed per game; hint button stays available even when free allotment is spent; response shows "Unlimited hints active" | When free user runs out of hints, adds "Subscribers get unlimited hints... Say subscribe to learn more" alongside the buy pitch |
| Premium themes (pending content) | Mythical Creatures, Space, Ocean, and Dinosaurs are defined in the theme enum but have no seeded card content, so none are selectable yet. The settings handler gates any premium theme as "coming soon" for everyone — subscriber or not — until its memory_snap.card_theme rows exist. |
When a player picks an unseeded premium theme, responds "coming soon" and steers them to the four shipped themes. No theme-pack product is sold. |
| Exclusive card backs | 4 subscriber-only card-back designs (Gold Filigree, Obsidian, Crystal Lattice, Royal Sigil) plus free Classic; persisted to memory_snap.account.preferred_card_back |
When free user says "card backs" from settings, designs list marks premium entries as "subscribers only" and closes with subscription upsell |
| Priority leaderboard placement | Current player's row shows ", Subscriber" marker when they appear on a leaderboard they rank on, plus confirmation line in footer | Non-subscribers see "Subscribers earn a priority placement indicator... Say subscribe to learn more" in leaderboard footer |
Premium themes are not yet live. The "all premium themes unlocked" benefit is pending card content — the four premium theme enums (Mythical Creatures, Space, Ocean, Dinosaurs) have no rows in memory_snap.card_theme or card_item. Until seeded, the settings handler gates them as "coming soon" for all players and the board generator falls back to a shipped theme when a stale premium preference is loaded. The other three subscriber benefits (unlimited hints, exclusive card backs, priority leaderboard) are live.
Implementation references:
- Subscription check:
- Handlers: , ,
- Service overload:
.useHint(session, unlimitedHints)with matchingMemorySnapGameSession.useHint(boolean) - Card back enum:
llitd.model.memorysnap.enums.MemorySnapCardBack(CLASSIC free, GOLD/OBSIDIAN/CRYSTAL/ROYAL premium)
Subsystem Design Docs
| Subsystem | Design Doc | Covers |
|---|---|---|
| Board | board.md | Grid config, card model, theme system, generation algorithm, flip FSM |
| Scoring | scoring.md | Score formula, par time, streaks, achievements, leaderboard |
| Multiplayer | multiplayer.md | Duel, Speed Race, Team Snap modes; party/challenge integration; multiplayer scoring |