--- id: wiki-2026-0508-dynamic-offers title: Dynamic Offers category: 10_Wiki/Topics status: verified canonical_id: self aliases: [dynamic-offer, contextual-offer, personalized-offer] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [monetization, mobile, offers, personalization, live-ops] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: live-ops-offer --- # Dynamic Offers ## 매 한 줄 > **"매 player state 에 매 따라 매 individually-tailored 의 매 IAP bundle"**. 매 static catalog 의 evolution — 매 trigger event + 매 player feature + 매 ML scoring. 매 2026 mobile 의 매 default monetization surface — 매 LTV uplift 매 15-40%. ## 매 핵심 ### 매 trigger 종류 - **Lifecycle**: 매 install D1, 매 first-defeat, 매 churn-risk. - **Progression**: 매 level-up, 매 stage-clear, 매 stuck (3 fails). - **Event**: 매 holiday, 매 weekend, 매 anniversary. - **Contextual**: 매 low-resource, 매 PvP loss-streak. ### 매 offer 구성 요소 - **Price tier**: 매 $0.99 / $4.99 / $19.99 / $99.99. - **Contents**: 매 currency + items + cosmetic. - **Value display**: 매 "300% value" / "Best deal". - **Urgency**: 매 timer, 매 limited inventory. - **Eligibility**: 매 player feature gated. ### 매 응용 1. 매 starter pack (D1 high-conversion). 2. 매 stuck-pack (frustration relief). 3. 매 win-back (churned player 복귀). ## 💻 패턴 ### Offer eligibility engine ```typescript type OfferRule = { id: string; predicate: (p: PlayerState) => boolean; weight: number; cooldown_hours: number; }; const RULES: OfferRule[] = [ { id: "starter_pack", predicate: p => p.spend_total === 0 && p.session_count > 5, weight: 100, cooldown_hours: 24 * 7, }, { id: "stuck_pack", predicate: p => p.consecutive_losses >= 3, weight: 50, cooldown_hours: 6, }, { id: "winback", predicate: p => p.days_since_last_session >= 7 && p.spend_total > 0, weight: 80, cooldown_hours: 24 * 14, }, ]; function selectOffer(p: PlayerState, now: number): OfferRule | null { const eligible = RULES .filter(r => r.predicate(p)) .filter(r => !inCooldown(p, r.id, now, r.cooldown_hours)); if (!eligible.length) return null; return weightedPick(eligible); } ``` ### Price-tier model (player-specific) ```typescript function suggestPriceTier(p: PlayerState): number { const tiers = [0.99, 4.99, 9.99, 19.99, 49.99, 99.99]; if (p.spend_total === 0) return 4.99; // starter const avg = p.spend_total / Math.max(1, p.purchase_count); // Pick tier near player's typical spend let best = tiers[0]; for (const t of tiers) { if (Math.abs(t - avg) < Math.abs(best - avg)) best = t; } return best; } ``` ### Bundle value calculator ```typescript type BundleItem = { id: string; quantity: number; reference_price: number }; function bundleValueRatio(items: BundleItem[], price: number): number { const total_ref = items.reduce((s, i) => s + i.quantity * i.reference_price, 0); return total_ref / price; } function valueLabel(ratio: number): string { if (ratio >= 5) return "BEST DEAL"; if (ratio >= 3) return "GREAT VALUE"; if (ratio >= 2) return "GOOD VALUE"; return ""; } ``` ### Urgency timer rendering ```typescript class OfferTimer { expires_at: number; remainingLabel(now: number): string { const sec = Math.max(0, Math.floor((this.expires_at - now) / 1000)); if (sec > 3600) return `${Math.floor(sec / 3600)}h ${Math.floor((sec % 3600) / 60)}m`; if (sec > 60) return `${Math.floor(sec / 60)}m ${sec % 60}s`; return `${sec}s`; } isExpired(now: number) { return now >= this.expires_at; } } ``` ### Conversion-rate A/B harness ```typescript async function logOfferEvent(player_id: string, offer_id: string, event: "shown" | "clicked" | "purchased") { await analytics.track({ user_id: player_id, event: `offer_${event}`, properties: { offer_id, ts: Date.now() }, }); } async function offerConversionRate(offer_id: string, since_ms: number) { const shown = await analytics.count({ event: "offer_shown", offer_id, since: since_ms }); const purchased = await analytics.count({ event: "offer_purchased", offer_id, since: since_ms }); return shown > 0 ? purchased / shown : 0; } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | 매 D0-1 player | Starter pack ($4.99, high value) | | 매 stuck player | Stuck-pack (resource focus) | | 매 churning | Winback (deep discount) | | 매 whale | Limited cosmetic + currency bundle | **기본값**: 매 trigger + eligibility + cooldown + 매 explainable rules first, ML overlay second. ## 🔗 Graph - 부모: [[Game_Monetization_Strategy]] · [[Live-Ops]] - 응용: [[Capybara GO!]] · [[Data-Driven Personalization]] - Adjacent: [[CPI (Cost Per Install)]] ## 🤖 LLM 활용 **언제**: 매 offer design, conversion analysis, rule engine setup. **언제 X**: 매 cosmetic-only product — 매 dynamic 의 over-engineering. ## ❌ 안티패턴 - **Predatory targeting**: 매 frustrated whale 의 매 immediate offer. - **Cooldown 무시**: 매 spam → 매 desensitization. - **Fake urgency**: 매 reset timer → 매 trust loss. - **Value 거짓 라벨**: 매 reference price inflation → 매 regulator risk. ## 🧪 검증 / 중복 - Verified (deltaDNA / Unity Gaming Services 2025 case studies). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — dynamic offers trigger + eligibility + value model. |