@ak--47/dungeon-master 1.2.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -4
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +5 -6
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +3 -3
- package/dungeons/vertical/ai-platform.js +858 -0
- package/dungeons/vertical/community.js +84 -40
- package/dungeons/vertical/crypto.js +830 -0
- package/dungeons/vertical/dating.js +744 -0
- package/dungeons/vertical/devtools.js +175 -69
- package/dungeons/vertical/ecommerce.js +242 -94
- package/dungeons/vertical/education.js +330 -313
- package/dungeons/vertical/fintech.js +442 -313
- package/dungeons/vertical/fitness.js +143 -61
- package/dungeons/vertical/food-delivery.js +327 -353
- package/dungeons/vertical/gaming.js +912 -382
- package/dungeons/vertical/healthcare.js +142 -63
- package/dungeons/vertical/insurance-application.js +170 -76
- package/dungeons/vertical/logistics.js +115 -20
- package/dungeons/vertical/marketplace.js +152 -58
- package/dungeons/vertical/media.js +248 -384
- package/dungeons/vertical/real-estate.js +781 -0
- package/dungeons/vertical/sass.js +255 -266
- package/dungeons/vertical/social.js +264 -206
- package/dungeons/vertical/travel.js +117 -41
- package/index.js +17 -17
- package/lib/core/config-validator.js +159 -31
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +11 -14
- package/lib/generators/funnels.js +12 -4
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/orchestrators/user-loop.js +81 -47
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/lib/utils/utils.js +71 -39
- package/package.json +8 -2
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +251 -51
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -438
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// ── TWEAK THESE ──
|
|
2
|
-
const SEED = "
|
|
3
|
-
const num_days =
|
|
4
|
-
const num_users =
|
|
5
|
-
const
|
|
2
|
+
const SEED = "questforge";
|
|
3
|
+
const num_days = 100;
|
|
4
|
+
const num_users = 5_000;
|
|
5
|
+
const avg_events_per_user_per_day = 1.2;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
8
8
|
// ── env overrides ──
|
|
@@ -16,7 +16,6 @@ import * as v from "ak-tools";
|
|
|
16
16
|
|
|
17
17
|
dayjs.extend(utc);
|
|
18
18
|
const chance = u.initChance(SEED);
|
|
19
|
-
|
|
20
19
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
21
20
|
|
|
22
21
|
/*
|
|
@@ -24,497 +23,696 @@ const chance = u.initChance(SEED);
|
|
|
24
23
|
* DATASET OVERVIEW
|
|
25
24
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
26
25
|
*
|
|
27
|
-
* QuestForge —
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* - Character:
|
|
44
|
-
* - Quests: 5 types,
|
|
45
|
-
* -
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
26
|
+
* QuestForge — a D&D-inspired action RPG with a deep character system, party-
|
|
27
|
+
* based dungeon crawls, boss fights, a player-driven economy, guilds, and
|
|
28
|
+
* subscription tiers. Combines the strategic depth of a tabletop RPG with the
|
|
29
|
+
* monetization and engagement loops of a modern live-service game.
|
|
30
|
+
*
|
|
31
|
+
* CORE LOOP: Players create characters (12 classes, 9 races, 9 alignments,
|
|
32
|
+
* 13 backgrounds) and progress through quests, dungeon crawls, boss fights,
|
|
33
|
+
* and combat encounters. Strategic preparation (inspect, search for clues,
|
|
34
|
+
* Ancient Compass) creates skill gaps. Guild membership drives social
|
|
35
|
+
* retention. Monetization via premium currency, lucky charms, season pass,
|
|
36
|
+
* and subscription tiers (Free / Premium / Elite).
|
|
37
|
+
*
|
|
38
|
+
* SCALE: 5,000 users × 120 events = 600K events over 100 days
|
|
39
|
+
* 24 event types, 7 funnels, guild group analytics, subscription tiers
|
|
40
|
+
*
|
|
41
|
+
* KEY SYSTEMS:
|
|
42
|
+
* - Character: 12 D&D classes, 9 races, 9 alignments, 13 backgrounds
|
|
43
|
+
* - Quests: accept → objective → turn in (5 quest types, gold/XP rewards)
|
|
44
|
+
* - Dungeons: enter → find treasure → exit (50 dungeons, party-based, 3 outcomes)
|
|
45
|
+
* - Combat: initiate → complete (6 enemy types), boss fights, attack/defend
|
|
46
|
+
* - Economy: item purchase/sell, real money purchases, vendor types
|
|
47
|
+
* - Progression: level up (1-50), stat points, level-scaled rewards
|
|
48
|
+
* - Social: guild join/leave (5-100 members, guild levels 1-20)
|
|
49
|
+
* - Monetization: premium currency, lucky charms, legendary chests, season pass
|
|
50
|
+
* - Subscriptions: Free / Premium ($9.99/mo) / Elite ($19.99/mo)
|
|
51
|
+
* - Experiments: A/B/C/Control variants across "fast leveling", "tension
|
|
52
|
+
* economy", "free trial"
|
|
50
53
|
*/
|
|
51
54
|
|
|
52
55
|
/*
|
|
53
56
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
54
|
-
* ANALYTICS HOOKS
|
|
57
|
+
* ANALYTICS HOOKS (13 hooks)
|
|
55
58
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
56
59
|
*
|
|
57
|
-
*
|
|
60
|
+
* NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable via
|
|
61
|
+
* behavioral cohorts (count event per user), raw-prop breakdowns
|
|
62
|
+
* (treasure_type, subscription_tier, day-of-user-life), or funnel time-to-convert.
|
|
58
63
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
64
|
+
* 1. CONVERSION: Ancient Compass Effect
|
|
65
|
+
* Players who use the "Ancient Compass" item earn 1.5x quest reward_gold
|
|
66
|
+
* and reward_xp on subsequent quest-turned-in events.
|
|
67
|
+
* → Mixpanel: Cohort A = users who fired "use item" with item_type="Ancient Compass"
|
|
68
|
+
* → Compare avg reward_gold / reward_xp on "quest turned in" between A and rest
|
|
69
|
+
* → Expected: A ~ 1.5x rest
|
|
62
70
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
71
|
+
* 2. TIME-BASED: Cursed Week (days 40-47 of user life)
|
|
72
|
+
* Death rates spike heavily during the user's days 40-47 (relative to their
|
|
73
|
+
* first event). Injected deaths carry cause_of_death="Curse".
|
|
74
|
+
* → Mixpanel: Chart "player death" count by day-of-user-life
|
|
75
|
+
* → Filter cause_of_death = "Curse" — clear cluster in user's days 40-47
|
|
66
76
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* - Expected: Linear increase — level 1 ≈ 55 gold, level 10 ≈ 110 gold,
|
|
73
|
-
* level 20 ≈ 165 gold. Clear positive correlation between level and reward.
|
|
77
|
+
* 3. RETENTION: Early Guild Joiners
|
|
78
|
+
* Players who fire "guild joined" within first 3 days get extra cloned
|
|
79
|
+
* "combat completed" events; non-joiners with 3+ early deaths churn.
|
|
80
|
+
* → Mixpanel: Cohort users who did "guild joined" within first 3 days
|
|
81
|
+
* → Compare D30 retention vs other users — early joiners retain higher
|
|
74
82
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
83
|
+
* 4. CHURN: Death Spiral
|
|
84
|
+
* Players with 3+ "player death" events in first 7 days lose 70% of their
|
|
85
|
+
* post-week-1 events.
|
|
86
|
+
* → Mixpanel: Bucket users by count of "player death" in first 7 days
|
|
87
|
+
* → Compare event volume after day 7 — 3+ deaths bucket has ~30% baseline
|
|
88
|
+
*
|
|
89
|
+
* 5. PURCHASE VALUE: Lucky Charm LTV
|
|
90
|
+
* Users who purchase "Lucky Charm Pack" get 2x price_usd on subsequent
|
|
91
|
+
* real-money purchases under $49.99 + 35% chance of bonus cloned purchase.
|
|
92
|
+
* → Mixpanel: Cohort A = users with any "real money purchase" where product="Lucky Charm Pack"
|
|
93
|
+
* → Compare avg price_usd and total revenue between A and rest
|
|
94
|
+
* → Expected: A ~ 1.5x avg purchase price
|
|
95
|
+
*
|
|
96
|
+
* 6. STRATEGIC EXPLORERS (BEHAVIORS TOGETHER — everything)
|
|
97
|
+
*
|
|
98
|
+
* PATTERN: Players who fire BOTH "inspect" AND "search for clues" events
|
|
99
|
+
* have 85% dungeon completion (vs 45% baseline) and earn 2x treasure value.
|
|
100
|
+
* No flag — derive cohort by joining users who did both event types.
|
|
101
|
+
*
|
|
102
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
103
|
+
*
|
|
104
|
+
* Report 1: Dungeon Completion by Strategic Cohort
|
|
105
|
+
* - Cohort A: users who fired BOTH "inspect" AND "search for clues"
|
|
106
|
+
* - Cohort B: rest
|
|
107
|
+
* - Event: "exit dungeon"
|
|
108
|
+
* - Filter: completion_status = "completed", divide by total exit dungeon
|
|
109
|
+
* - Expected: A ~ 85% completion vs B ~ 45%
|
|
110
|
+
*
|
|
111
|
+
* Report 2: Treasure Value by Strategic Cohort
|
|
112
|
+
* - Cohort A vs B (as above)
|
|
113
|
+
* - Event: "find treasure"
|
|
114
|
+
* - Measure: Average of "treasure_value"
|
|
115
|
+
* - Expected: A ~ 2x B
|
|
116
|
+
*
|
|
117
|
+
* REAL-WORLD ANALOGUE: Players who scout and prepare before encounters
|
|
118
|
+
* consistently outperform those who rush in.
|
|
119
|
+
*
|
|
120
|
+
* 7. SHADOWMOURNE LEGENDARY WEAPON (TIMED RELEASE — event)
|
|
121
|
+
*
|
|
122
|
+
* PATTERN: At day 45, the "Shadowmourne Legendary" weapon releases.
|
|
123
|
+
* 2% of players find it after release. Equipped wielders get 90%
|
|
124
|
+
* combat win rate (vs 60%) and complete dungeons 40% faster.
|
|
125
|
+
*
|
|
126
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
127
|
+
*
|
|
128
|
+
* Report 1: Legendary Drop Adoption Over Time
|
|
129
|
+
* - Report type: Insights
|
|
130
|
+
* - Event: "find treasure"
|
|
131
|
+
* - Measure: Total
|
|
132
|
+
* - Filter: treasure_type = "Shadowmourne Legendary"
|
|
133
|
+
* - Line chart by day
|
|
134
|
+
* - Expected: zero before day 45, then small steady stream after
|
|
135
|
+
*
|
|
136
|
+
* Report 2: Combat Win Rate by Legendary Cohort
|
|
137
|
+
* - Cohort A: users who fired "find treasure" with treasure_type="Shadowmourne Legendary"
|
|
138
|
+
* - Cohort B: rest
|
|
139
|
+
* - Event: "combat completed"
|
|
140
|
+
* - Measure: count where outcome="Victory" / total combat completed
|
|
141
|
+
* - Expected: A ~ 90% wins vs B ~ baseline
|
|
142
|
+
*
|
|
143
|
+
* REAL-WORLD ANALOGUE: Patch-day legendary drops create a small power-
|
|
144
|
+
* user cohort that dominates leaderboards and PvP for weeks after.
|
|
145
|
+
*
|
|
146
|
+
* 8. PREMIUM / ELITE SUBSCRIBER ADVANTAGE (SUBSCRIPTION TIER — everything)
|
|
147
|
+
*
|
|
148
|
+
* PATTERN: Premium subscribers get 50% better combat wins, 1.4x rewards,
|
|
149
|
+
* 45% higher dungeon completion. Elite subscribers get 70% better combat
|
|
150
|
+
* wins, 1.8x rewards, 65% higher dungeon completion, bonus treasure events,
|
|
151
|
+
* and reduced death rates. No flag — discover via subscription_tier breakdown.
|
|
152
|
+
*
|
|
153
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
154
|
+
*
|
|
155
|
+
* Report 1: Quest Rewards by Subscription Tier
|
|
156
|
+
* - Report type: Insights
|
|
157
|
+
* - Event: "quest turned in"
|
|
158
|
+
* - Measure: Average of "reward_gold"
|
|
159
|
+
* - Breakdown: "subscription_tier"
|
|
160
|
+
* - Expected: Premium ~ 1.4x, Elite ~ 1.8x vs Free baseline
|
|
161
|
+
*
|
|
162
|
+
* Report 2: Dungeon Completion by Tier
|
|
163
|
+
* - Report type: Insights
|
|
164
|
+
* - Event: "exit dungeon"
|
|
165
|
+
* - Measure: completion_status="completed" / total exit dungeon
|
|
166
|
+
* - Breakdown: "subscription_tier"
|
|
167
|
+
* - Expected: Free ~ 45%, Premium ~ 65%, Elite ~ 75%
|
|
168
|
+
*
|
|
169
|
+
* REAL-WORLD ANALOGUE: Subscription tiers in live-service games confer
|
|
170
|
+
* XP/loot/rest bonuses that translate into measurable progress speed.
|
|
171
|
+
*
|
|
172
|
+
* 9. GOLD REWARD BY LEVEL (PROGRESSION SCALING — everything)
|
|
173
|
+
*
|
|
174
|
+
* PATTERN: Quest gold reward scales with player level using
|
|
175
|
+
* reward_gold *= (1 + level * 0.1). Level-10 earns 2x, level-20 earns
|
|
176
|
+
* 3x vs level-1. No flag — discover via user-property level breakdown.
|
|
177
|
+
*
|
|
178
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
179
|
+
*
|
|
180
|
+
* Report 1: Avg Gold per Quest by Player Level
|
|
181
|
+
* - Report type: Insights
|
|
182
|
+
* - Event: "quest turned in"
|
|
183
|
+
* - Measure: Average of "reward_gold"
|
|
184
|
+
* - Breakdown: user property "level" (bucketed)
|
|
185
|
+
* - Expected: linear ramp; level-10 ~ 2x, level-20 ~ 3x vs level-1
|
|
78
186
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
187
|
+
* REAL-WORLD ANALOGUE: Quest economies scale rewards with player level
|
|
188
|
+
* so high-level zones remain meaningfully lucrative.
|
|
81
189
|
*
|
|
82
|
-
*
|
|
190
|
+
* 10. WHALE PURCHASES (TOP-SPENDER COHORT — everything)
|
|
83
191
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* - Measure: Average of "purchase amount"
|
|
88
|
-
* - Breakdown: "is_whale"
|
|
89
|
-
* - Expected: is_whale=true shows ~1.8x higher avg purchase amount than
|
|
90
|
-
* is_whale=false (e.g., ~18 vs ~10)
|
|
192
|
+
* PATTERN: ~33% of users (deterministic via user_id char % 3 hash) are
|
|
193
|
+
* "whales" who get 1.8x price_usd on real money purchases. No flag —
|
|
194
|
+
* derive cohort by ranking users by total purchase volume.
|
|
91
195
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
196
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
197
|
+
*
|
|
198
|
+
* Report 1: Avg Purchase by Spend Decile
|
|
199
|
+
* - Cohort A: top 33% of users by SUM(price_usd) on "real money purchase"
|
|
200
|
+
* - Cohort B: rest
|
|
201
|
+
* - Event: "real money purchase"
|
|
202
|
+
* - Measure: Average of "price_usd"
|
|
203
|
+
* - Expected: A ~ 1.8x B
|
|
204
|
+
*
|
|
205
|
+
* Report 2: Revenue Concentration
|
|
206
|
+
* - Sum price_usd by user, plot distribution
|
|
207
|
+
* - Expected: ~33% of users contribute the majority of revenue
|
|
208
|
+
*
|
|
209
|
+
* REAL-WORLD ANALOGUE: A small cohort of high-spending players
|
|
210
|
+
* ("whales") accounts for the majority of mobile-game revenue.
|
|
211
|
+
*
|
|
212
|
+
* 11. HERO / VILLAIN / NEUTRAL ARCHETYPE (USER ENRICHMENT — user)
|
|
213
|
+
*
|
|
214
|
+
* PATTERN: User profiles are enriched with an "archetype" derived
|
|
215
|
+
* from D&D alignment: Good -> "hero" (~22%), Evil -> "villain" (~22%),
|
|
216
|
+
* other -> "neutral" (~56%).
|
|
217
|
+
*
|
|
218
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
219
|
+
*
|
|
220
|
+
* Report 1: User Distribution by Archetype
|
|
221
|
+
* - Report type: Insights
|
|
222
|
+
* - Event: any event
|
|
223
|
+
* - Measure: Total unique users
|
|
224
|
+
* - Breakdown: "archetype" (user property)
|
|
225
|
+
* - Expected: hero ~ 22%, villain ~ 22%, neutral ~ 56%
|
|
226
|
+
*
|
|
227
|
+
* Report 2: Engagement by Archetype
|
|
228
|
+
* - Report type: Insights
|
|
229
|
+
* - Event: any event
|
|
230
|
+
* - Measure: Total per user (average)
|
|
231
|
+
* - Breakdown: "archetype"
|
|
232
|
+
* - Expected: similar volume per user across archetypes
|
|
233
|
+
*
|
|
234
|
+
* REAL-WORLD ANALOGUE: Player-character moral alignment is a useful
|
|
235
|
+
* audience segmentation lens for narrative design and content tuning.
|
|
98
236
|
*
|
|
99
237
|
* ───────────────────────────────────────────────────────────────────────────────
|
|
100
|
-
*
|
|
238
|
+
* EXPECTED METRICS SUMMARY
|
|
101
239
|
* ───────────────────────────────────────────────────────────────────────────────
|
|
102
240
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
241
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
242
|
+
* 12. COMBAT FUNNEL TIME-TO-CONVERT (funnel-post)
|
|
105
243
|
*
|
|
106
|
-
*
|
|
244
|
+
* PATTERN: Elite tier completes the Combat funnel 1.4x faster (factor
|
|
245
|
+
* 0.71); Free tier 1.25x slower (factor 1.25). Mutates funnel timestamps.
|
|
107
246
|
*
|
|
108
|
-
*
|
|
109
|
-
* - Report type: Insights
|
|
110
|
-
* - Event: Any event (e.g., "complete quest")
|
|
111
|
-
* - Measure: Total unique users
|
|
112
|
-
* - Breakdown: User profile "archetype"
|
|
113
|
-
* - Expected: Three segments — "hero" (~22%), "villain" (~22%), "neutral" (~56%)
|
|
114
|
-
* matching the 2/9, 2/9, 5/9 alignment split
|
|
247
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
115
248
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* - Expected: All three archetypes should show similar event volumes
|
|
122
|
-
* (archetype doesn't affect gameplay — just a profiling hook)
|
|
249
|
+
* Report 1: Combat Funnel Median Time-to-Convert by Tier
|
|
250
|
+
* - Funnels > "combat initiated" -> "combat completed" -> "use item"
|
|
251
|
+
* - Measure: Median time to convert
|
|
252
|
+
* - Breakdown: subscription_tier
|
|
253
|
+
* - Expected: Elite ~ 0.71x; Free ~ 1.25x
|
|
123
254
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
255
|
+
* NOTE (funnel-post measurement): visible only via Mixpanel funnel
|
|
256
|
+
* median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
|
|
257
|
+
* show this — funnel-post adjusts gaps within funnel instances, not
|
|
258
|
+
* across the user's full event history. (This dungeon also has an
|
|
259
|
+
* everything-hook companion that compresses the cross-event Elite gap
|
|
260
|
+
* between earliest combat_initiated and earliest use_item.)
|
|
127
261
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* Gold Scaling | Avg gold at level 1 | ~55 | ~55 | 1.0x
|
|
131
|
-
* Gold Scaling | Avg gold at level 10 | ~55 | ~110 | 2.0x
|
|
132
|
-
* Gold Scaling | Avg gold at level 20 | ~55 | ~165 | 3.0x
|
|
133
|
-
* Whale Purchases | Avg purchase (whale) | ~10 | ~18 | 1.8x
|
|
134
|
-
* Whale Purchases | Revenue share (whale) | ~33% | >50% | disprop.
|
|
135
|
-
* Alignment Archetype | Hero users | — | ~22% | 2/9
|
|
136
|
-
* Alignment Archetype | Villain users | — | ~22% | 2/9
|
|
137
|
-
* Alignment Archetype | Neutral users | — | ~56% | 5/9
|
|
262
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
263
|
+
* 13. COMBAT-PREP MAGIC NUMBER (in-funnel, everything)
|
|
138
264
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
265
|
+
* PATTERN: Sweet 3-6 inspect+search-for-clues events between
|
|
266
|
+
* quest-accepted and fight-boss → +30% on find-treasure
|
|
267
|
+
* treasure_value. Over 7+ → 25% of fight-boss victories flip to
|
|
268
|
+
* defeat (analysis paralysis). No flag.
|
|
142
269
|
*
|
|
143
|
-
*
|
|
144
|
-
* Do Legendary quests at high levels create outsized gold rewards?
|
|
270
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
145
271
|
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
272
|
+
* Report 1: Avg Treasure Value by Combat-Prep Bucket
|
|
273
|
+
* - Cohort A: users with 3-6 inspect+search between quest-accepted and fight-boss
|
|
274
|
+
* - Cohort B: users with 0-2
|
|
275
|
+
* - Event: "find treasure"
|
|
276
|
+
* - Measure: Average of "treasure_value"
|
|
277
|
+
* - Expected: A ~ 1.3x B
|
|
148
278
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
279
|
+
* Report 2: Boss Victory Rate on Heavy Preppers
|
|
280
|
+
* - Cohort C: users with >= 7 prep events
|
|
281
|
+
* - Cohort A: users with 3-6
|
|
282
|
+
* - Event: "fight boss"
|
|
283
|
+
* - Measure: Total filtered to victory=true / Total
|
|
284
|
+
* - Expected: C ~ 25% lower victory rate
|
|
151
285
|
*
|
|
152
|
-
*
|
|
153
|
-
* "free trial" on purchase amounts, level progression, and retention.
|
|
286
|
+
* REAL-WORLD ANALOGUE: Calculated prep wins; over-prep is paralysis.
|
|
154
287
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
288
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
289
|
+
* EXPECTED METRICS SUMMARY
|
|
290
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
157
291
|
*
|
|
158
|
-
*
|
|
292
|
+
* Hook | Metric | Baseline | Hook Effect | Ratio
|
|
293
|
+
* ----------------------|----------------------|----------|-------------|------
|
|
294
|
+
* Ancient Compass | Quest reward (compass)| 1x | 1.5x | 1.5x
|
|
295
|
+
* Cursed Week | Death rate days 40-47| 1x | ~ 5x | 5x
|
|
296
|
+
* Early Guild Join | D30 Retention | 20% | 80% | 4x
|
|
297
|
+
* Death Spiral | Retention (3+ deaths)| 100% | 30% | 0.3x
|
|
298
|
+
* Lucky Charm | Avg purchase amt | 1x | 2x | 2x
|
|
299
|
+
* Strategic Explorer | Dungeon completion | 45% | 85% | ~ 1.9x
|
|
300
|
+
* Legendary Weapon | Combat win rate | 60% | 90% | 1.5x
|
|
301
|
+
* Premium Tier | Quest reward | 1x | 1.4x | 1.4x
|
|
302
|
+
* Elite Tier | Quest reward | 1x | 1.8x | 1.8x
|
|
303
|
+
* Gold Scaling (lvl 10) | Avg quest gold | ~ 100 | ~ 200 | 2x
|
|
304
|
+
* Whale Purchases | Avg real money spend | 1x | 1.8x | 1.8x
|
|
305
|
+
* Hero/Villain/Neutral | User share | -- | 22/22/56% | n/a
|
|
306
|
+
* Combat T2C | median min by tier | 1x | 0.71/1.25x | ~ 1.8x range
|
|
307
|
+
* Combat-Prep Magic Num | sweet treasure_value | 1x | 1.3x | 1.3x
|
|
308
|
+
* Combat-Prep Magic Num | over boss victory | 1x | 0.75x | -25%
|
|
159
309
|
*/
|
|
160
310
|
|
|
311
|
+
// Generate consistent item/location IDs for lookup tables
|
|
312
|
+
const dungeonIds = v.range(1, 51).map(n => `dungeon_${v.uid(6)}`);
|
|
313
|
+
const questIds = v.range(1, 201).map(n => `quest_${v.uid(8)}`);
|
|
314
|
+
const itemIds = v.range(1, 301).map(n => `item_${v.uid(7)}`);
|
|
315
|
+
|
|
161
316
|
/** @type {Config} */
|
|
162
317
|
const config = {
|
|
163
318
|
token,
|
|
164
|
-
seed:
|
|
165
|
-
|
|
166
|
-
|
|
319
|
+
seed: SEED,
|
|
320
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
321
|
+
datasetEnd: "2026-04-28T23:59:59Z",
|
|
322
|
+
// numDays: num_days,
|
|
323
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
167
324
|
numUsers: num_users,
|
|
168
325
|
hasAnonIds: false,
|
|
169
|
-
hasSessionIds:
|
|
326
|
+
hasSessionIds: true,
|
|
170
327
|
format: "json",
|
|
171
|
-
|
|
328
|
+
gzip: true,
|
|
329
|
+
alsoInferFunnels: false,
|
|
172
330
|
hasLocation: true,
|
|
173
|
-
hasAndroidDevices:
|
|
174
|
-
hasIOSDevices:
|
|
175
|
-
hasDesktopDevices:
|
|
331
|
+
hasAndroidDevices: true,
|
|
332
|
+
hasIOSDevices: true,
|
|
333
|
+
hasDesktopDevices: true,
|
|
176
334
|
hasBrowser: false,
|
|
177
335
|
hasCampaigns: false,
|
|
178
336
|
isAnonymous: false,
|
|
179
337
|
hasAdSpend: false,
|
|
180
|
-
percentUsersBornInDataset: 25,
|
|
181
338
|
|
|
182
339
|
hasAvatar: true,
|
|
183
340
|
|
|
184
341
|
concurrency: 1,
|
|
185
342
|
writeToDisk: false,
|
|
343
|
+
|
|
186
344
|
funnels: [
|
|
187
345
|
{
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
346
|
+
sequence: ["character created", "tutorial completed", "quest accepted"],
|
|
347
|
+
isFirstFunnel: true,
|
|
348
|
+
conversionRate: 75,
|
|
349
|
+
timeToConvert: 0.5,
|
|
192
350
|
},
|
|
193
351
|
{
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
352
|
+
// Core combat loop: most frequent player activity
|
|
353
|
+
sequence: ["combat initiated", "combat completed", "use item"],
|
|
354
|
+
conversionRate: 75,
|
|
355
|
+
timeToConvert: 0.5,
|
|
356
|
+
weight: 5,
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
// Dungeon crawl: enter, explore, loot, exit
|
|
360
|
+
sequence: ["enter dungeon", "find treasure", "exit dungeon"],
|
|
361
|
+
conversionRate: 60,
|
|
362
|
+
timeToConvert: 2,
|
|
363
|
+
weight: 4,
|
|
197
364
|
props: {
|
|
198
|
-
"
|
|
199
|
-
|
|
200
|
-
"Retrieve",
|
|
201
|
-
"Explore",
|
|
202
|
-
"Destroy",
|
|
203
|
-
"Investigate",
|
|
204
|
-
],
|
|
205
|
-
"quest difficulty": [
|
|
206
|
-
"Easy",
|
|
207
|
-
"Medium",
|
|
208
|
-
"Hard",
|
|
209
|
-
"Legendary",
|
|
210
|
-
],
|
|
211
|
-
"quest location": [
|
|
212
|
-
"Forest",
|
|
213
|
-
"Dungeon",
|
|
214
|
-
"Mountain",
|
|
215
|
-
"City",
|
|
216
|
-
"Desert",
|
|
217
|
-
]
|
|
365
|
+
"dungeon_id": dungeonIds,
|
|
366
|
+
"difficulty": ["Easy", "Medium", "Hard", "Deadly"],
|
|
218
367
|
}
|
|
219
368
|
},
|
|
369
|
+
{
|
|
370
|
+
// Quest lifecycle
|
|
371
|
+
sequence: ["quest accepted", "quest objective completed", "quest turned in"],
|
|
372
|
+
conversionRate: 55,
|
|
373
|
+
timeToConvert: 3,
|
|
374
|
+
weight: 3,
|
|
375
|
+
props: { "quest_id": questIds },
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
// Preparation before dungeon: inspect + search for strategic explorer hook
|
|
379
|
+
sequence: ["inspect", "search for clues", "enter dungeon"],
|
|
380
|
+
conversionRate: 50,
|
|
381
|
+
timeToConvert: 1,
|
|
382
|
+
weight: 3,
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
// Economy: buy gear, sell loot
|
|
386
|
+
sequence: ["item purchased", "use item", "item sold"],
|
|
387
|
+
conversionRate: 45,
|
|
388
|
+
timeToConvert: 6,
|
|
389
|
+
weight: 2,
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
// Social and progression
|
|
393
|
+
sequence: ["guild joined", "level up", "real money purchase"],
|
|
394
|
+
conversionRate: 25,
|
|
395
|
+
timeToConvert: 24,
|
|
396
|
+
weight: 1,
|
|
397
|
+
},
|
|
220
398
|
],
|
|
399
|
+
|
|
221
400
|
events: [
|
|
222
|
-
{ event: "app install", weight: 10, isFirstEvent: true },
|
|
223
401
|
{
|
|
224
|
-
event: "character
|
|
402
|
+
event: "character created",
|
|
403
|
+
weight: 1,
|
|
404
|
+
isFirstEvent: true,
|
|
405
|
+
properties: {
|
|
406
|
+
"character_class": [
|
|
407
|
+
"Barbarian", "Bard", "Cleric", "Druid", "Fighter", "Monk",
|
|
408
|
+
"Paladin", "Ranger", "Rogue", "Sorcerer", "Warlock", "Wizard"
|
|
409
|
+
],
|
|
410
|
+
"starting_race": [
|
|
411
|
+
"Human", "Elf", "Dwarf", "Halfling", "Dragonborn",
|
|
412
|
+
"Gnome", "Half-Elf", "Half-Orc", "Tiefling"
|
|
413
|
+
],
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
event: "tutorial completed",
|
|
225
418
|
weight: 2,
|
|
226
419
|
properties: {
|
|
227
|
-
|
|
228
|
-
|
|
420
|
+
"completion_time_mins": u.weighNumRange(3, 25, 0.8, 10),
|
|
421
|
+
"skipped": [false, false, false, false, false, false, true],
|
|
422
|
+
}
|
|
229
423
|
},
|
|
230
424
|
{
|
|
231
|
-
event: "
|
|
232
|
-
weight:
|
|
425
|
+
event: "quest accepted",
|
|
426
|
+
weight: 15,
|
|
233
427
|
properties: {
|
|
234
|
-
"
|
|
235
|
-
"
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
"Fighter",
|
|
239
|
-
"Rogue",
|
|
240
|
-
"Wizard",
|
|
241
|
-
"Paladin",
|
|
242
|
-
"Ranger",
|
|
243
|
-
"Sorcerer",
|
|
244
|
-
"Warlock",
|
|
245
|
-
],
|
|
246
|
-
},
|
|
428
|
+
"quest_id": questIds,
|
|
429
|
+
"quest_type": ["Main Story", "Side Quest", "Bounty", "Exploration", "Escort"],
|
|
430
|
+
"recommended_level": u.weighNumRange(1, 50),
|
|
431
|
+
}
|
|
247
432
|
},
|
|
248
433
|
{
|
|
249
|
-
event: "
|
|
434
|
+
event: "quest objective completed",
|
|
250
435
|
weight: 12,
|
|
251
436
|
properties: {
|
|
252
|
-
"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
"Explore",
|
|
256
|
-
"Destroy",
|
|
257
|
-
"Investigate",
|
|
258
|
-
],
|
|
259
|
-
"quest difficulty": [
|
|
260
|
-
"Easy",
|
|
261
|
-
"Medium",
|
|
262
|
-
"Hard",
|
|
263
|
-
"Legendary",
|
|
264
|
-
],
|
|
265
|
-
"quest location": [
|
|
266
|
-
"Forest",
|
|
267
|
-
"Dungeon",
|
|
268
|
-
"Mountain",
|
|
269
|
-
"City",
|
|
270
|
-
"Desert",
|
|
271
|
-
],
|
|
272
|
-
"party size": u.weighNumRange(1, 6),
|
|
273
|
-
"used hint": ["no", "no", "yes"],
|
|
274
|
-
"used boost": ["no", "no", "no", "no", "yes"],
|
|
275
|
-
"level at start": u.weighNumRange(1, 20),
|
|
276
|
-
},
|
|
437
|
+
"quest_id": questIds,
|
|
438
|
+
"objective_number": u.weighNumRange(1, 5),
|
|
439
|
+
}
|
|
277
440
|
},
|
|
278
441
|
{
|
|
279
|
-
event: "
|
|
280
|
-
weight:
|
|
442
|
+
event: "quest turned in",
|
|
443
|
+
weight: 10,
|
|
281
444
|
properties: {
|
|
282
|
-
"
|
|
283
|
-
"
|
|
284
|
-
"
|
|
285
|
-
|
|
286
|
-
"used boost": ["no", "no", "no", "no", "yes"],
|
|
287
|
-
"number of deaths": u.weighNumRange(0, 5, 1, 2),
|
|
288
|
-
"level at start": u.weighNumRange(1, 20),
|
|
289
|
-
"level at end": u.weighNumRange(1, 20),
|
|
290
|
-
},
|
|
445
|
+
"quest_id": questIds,
|
|
446
|
+
"reward_gold": u.weighNumRange(10, 500, 0.5, 100),
|
|
447
|
+
"reward_xp": u.weighNumRange(50, 2000, 0.5, 500),
|
|
448
|
+
}
|
|
291
449
|
},
|
|
292
450
|
{
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
"
|
|
297
|
-
"
|
|
298
|
-
"
|
|
299
|
-
"# gold found": u.weighNumRange(0, 1000),
|
|
451
|
+
event: "enter dungeon",
|
|
452
|
+
weight: 18,
|
|
453
|
+
properties: {
|
|
454
|
+
"dungeon_id": dungeonIds,
|
|
455
|
+
"difficulty": ["Easy", "Medium", "Hard", "Deadly"],
|
|
456
|
+
"party_size": u.weighNumRange(1, 5),
|
|
300
457
|
}
|
|
301
458
|
},
|
|
302
459
|
{
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
"
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
"Items: Weapons",
|
|
310
|
-
"Items: Armor",
|
|
311
|
-
"Items: Potions",
|
|
312
|
-
"Items: Scrolls",
|
|
313
|
-
"Boosts",
|
|
314
|
-
"Currency",
|
|
315
|
-
],
|
|
316
|
-
"purchase amount": u.weighNumRange(1, 50, 1, 20),
|
|
317
|
-
is_whale: [false],
|
|
460
|
+
event: "exit dungeon",
|
|
461
|
+
weight: 14,
|
|
462
|
+
properties: {
|
|
463
|
+
"dungeon_id": dungeonIds,
|
|
464
|
+
"time_spent_mins": u.weighNumRange(5, 120, 0.6, 30),
|
|
465
|
+
"completion_status": ["completed", "abandoned", "died"],
|
|
318
466
|
}
|
|
319
467
|
},
|
|
320
468
|
{
|
|
321
|
-
event: "
|
|
469
|
+
event: "find treasure",
|
|
470
|
+
weight: 16,
|
|
471
|
+
properties: {
|
|
472
|
+
"treasure_type": ["Gold", "Weapon", "Armor", "Potion", "Scroll", "Rare Artifact"],
|
|
473
|
+
"treasure_value": u.weighNumRange(5, 1000, 1.2, 50),
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
event: "player death",
|
|
478
|
+
weight: 8,
|
|
479
|
+
properties: {
|
|
480
|
+
"cause_of_death": ["Monster", "Trap", "Fall Damage", "Poison", "Friendly Fire"],
|
|
481
|
+
"player_level": u.weighNumRange(1, 50),
|
|
482
|
+
"resurrection_used": [false, false, false, true],
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
event: "level up",
|
|
487
|
+
weight: 5,
|
|
488
|
+
properties: {
|
|
489
|
+
"new_level": u.weighNumRange(2, 50),
|
|
490
|
+
"stat_points_gained": u.weighNumRange(1, 5),
|
|
491
|
+
"new_abilities": ["Attack", "Spell", "Feat", "Skill"],
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
event: "item purchased",
|
|
496
|
+
weight: 11,
|
|
497
|
+
properties: {
|
|
498
|
+
"item_id": itemIds,
|
|
499
|
+
"item_type": ["Weapon", "Armor", "Potion", "Scroll", "Mount", "Cosmetic"],
|
|
500
|
+
"price_gold": u.weighNumRange(10, 500, 0.8, 100),
|
|
501
|
+
"vendor_type": ["Town", "Dungeon", "Special Event"],
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
event: "item sold",
|
|
506
|
+
weight: 7,
|
|
507
|
+
properties: {
|
|
508
|
+
"item_id": itemIds,
|
|
509
|
+
"item_type": ["Weapon", "Armor", "Potion", "Scroll", "Junk"],
|
|
510
|
+
"sell_price": u.weighNumRange(5, 250, 0.5, 50),
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
event: "real money purchase",
|
|
322
515
|
weight: 3,
|
|
323
516
|
properties: {
|
|
324
|
-
"
|
|
325
|
-
"
|
|
326
|
-
"
|
|
327
|
-
"
|
|
328
|
-
"
|
|
329
|
-
"
|
|
517
|
+
"product": [
|
|
518
|
+
"Premium Currency (1000)",
|
|
519
|
+
"Premium Currency (5000)",
|
|
520
|
+
"Lucky Charm Pack",
|
|
521
|
+
"Legendary Weapon Chest",
|
|
522
|
+
"Cosmetic Bundle",
|
|
523
|
+
"Season Pass"
|
|
330
524
|
],
|
|
331
|
-
"
|
|
332
|
-
"
|
|
333
|
-
|
|
334
|
-
"Legendary",
|
|
335
|
-
"Impossible",
|
|
336
|
-
],
|
|
337
|
-
"fight duration (mins)": u.weighNumRange(1, 60),
|
|
338
|
-
|
|
339
|
-
},
|
|
525
|
+
"price_usd": [4.99, 9.99, 19.99, 49.99, 99.99],
|
|
526
|
+
"payment_method": ["Credit Card", "PayPal", "Apple Pay", "Google Pay"],
|
|
527
|
+
}
|
|
340
528
|
},
|
|
341
529
|
{
|
|
342
|
-
event: "
|
|
530
|
+
event: "guild joined",
|
|
531
|
+
weight: 4,
|
|
532
|
+
properties: {
|
|
533
|
+
"guild_size": u.weighNumRange(5, 100),
|
|
534
|
+
"guild_level": u.weighNumRange(1, 20),
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
event: "guild left",
|
|
343
539
|
weight: 1,
|
|
344
540
|
properties: {
|
|
345
|
-
"
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
541
|
+
"reason": ["Inactive", "Found Better Guild", "Conflict", "Disbanded"],
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
event: "inspect",
|
|
546
|
+
weight: 9,
|
|
547
|
+
properties: {
|
|
548
|
+
"inspect_target": ["NPC", "Monster", "Treasure Chest", "Door", "Statue", "Bookshelf"],
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
event: "search for clues",
|
|
553
|
+
weight: 8,
|
|
554
|
+
properties: {
|
|
555
|
+
"location_type": ["Dungeon Entrance", "Hidden Room", "Quest Location", "Town Square"],
|
|
556
|
+
"clue_found": [false, false, true, true, true],
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
event: "use item",
|
|
561
|
+
weight: 14,
|
|
562
|
+
properties: {
|
|
563
|
+
"item_id": itemIds,
|
|
564
|
+
"item_type": [
|
|
565
|
+
"Health Potion", "Mana Potion", "Buff Scroll",
|
|
566
|
+
"Ancient Compass", "Lucky Charm", "Resurrection Stone"
|
|
350
567
|
],
|
|
351
|
-
|
|
568
|
+
"context": ["Combat", "Exploration", "Boss Fight", "Casual"],
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
event: "combat initiated",
|
|
573
|
+
weight: 20,
|
|
574
|
+
properties: {
|
|
575
|
+
"enemy_type": ["Goblin", "Skeleton", "Dragon", "Demon", "Undead", "Beast"],
|
|
576
|
+
"enemy_level": u.weighNumRange(1, 50),
|
|
577
|
+
"combat_duration_sec": u.weighNumRange(10, 300, 0.7, 60),
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
event: "combat completed",
|
|
582
|
+
weight: 18,
|
|
583
|
+
properties: {
|
|
584
|
+
"outcome": ["Victory", "Defeat", "Fled"],
|
|
585
|
+
"loot_gained": [false, false, false, true, true, true, true, true, true, true],
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
event: "fight boss",
|
|
590
|
+
weight: 3,
|
|
591
|
+
properties: {
|
|
592
|
+
"boss_type": ["Dragon", "Demon", "Lich", "Vampire", "Beholder"],
|
|
593
|
+
"boss_level": u.weighNumRange(10, 50),
|
|
594
|
+
"boss_difficulty": ["Hard", "Legendary", "Impossible"],
|
|
595
|
+
"fight_duration_mins": u.weighNumRange(1, 60),
|
|
596
|
+
"victory": [false, true, true],
|
|
597
|
+
}
|
|
352
598
|
},
|
|
353
599
|
{
|
|
354
600
|
event: "attack",
|
|
355
601
|
weight: 5,
|
|
356
602
|
properties: {
|
|
357
|
-
|
|
603
|
+
"attack_type": ["Melee", "Ranged", "Spell", "Special"],
|
|
604
|
+
"damage_dealt": u.weighNumRange(1, 200, 0.7, 30),
|
|
358
605
|
}
|
|
359
606
|
},
|
|
360
607
|
{
|
|
361
608
|
event: "defend",
|
|
362
609
|
weight: 3,
|
|
363
610
|
properties: {
|
|
364
|
-
|
|
611
|
+
"defense_type": ["Block", "Parry", "Dodge", "Shield"],
|
|
612
|
+
"damage_blocked": u.weighNumRange(0, 150, 0.7, 20),
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
event: "gameplay summary",
|
|
617
|
+
weight: 6,
|
|
618
|
+
properties: {
|
|
619
|
+
"enemies_defeated": u.weighNumRange(0, 100),
|
|
620
|
+
"respawns": u.weighNumRange(0, 10, 5),
|
|
621
|
+
"total_attacks": u.weighNumRange(0, 100, 6, 12),
|
|
622
|
+
"gold_found": u.weighNumRange(0, 1000),
|
|
365
623
|
}
|
|
366
624
|
},
|
|
367
625
|
],
|
|
368
|
-
superProps: {
|
|
369
|
-
Platform: [
|
|
370
|
-
"Mobile",
|
|
371
|
-
"Xbox",
|
|
372
|
-
"Playstation",
|
|
373
|
-
"Switch",
|
|
374
|
-
"Web"
|
|
375
|
-
],
|
|
376
|
-
"game mode": u.pickAWinner([
|
|
377
|
-
"Single Player",
|
|
378
|
-
"Multiplayer",
|
|
379
|
-
], 1),
|
|
380
|
-
language: [
|
|
381
|
-
"English",
|
|
382
|
-
"Spanish",
|
|
383
|
-
"French",
|
|
384
|
-
"German",
|
|
385
|
-
"Japanese",
|
|
386
|
-
"Korean",
|
|
387
|
-
"Chinese",
|
|
388
|
-
],
|
|
389
626
|
|
|
627
|
+
superProps: {
|
|
628
|
+
Platform: ["PC", "Mac", "PlayStation", "Xbox", "Switch"],
|
|
629
|
+
graphics_quality: ["Low", "Medium", "High", "Ultra"],
|
|
630
|
+
subscription_tier: ["Free", "Free", "Free", "Premium", "Elite"],
|
|
390
631
|
},
|
|
632
|
+
|
|
391
633
|
scdProps: {
|
|
392
|
-
"subscription MRR" : {
|
|
393
|
-
frequency: "week",
|
|
394
|
-
values: u.weighNumRange(0, 250, 1, 200),
|
|
395
|
-
timing: "fixed",
|
|
396
|
-
max: 250,
|
|
397
|
-
},
|
|
398
634
|
player_rank: {
|
|
399
|
-
values: ["
|
|
635
|
+
values: ["recruit", "veteran", "elite", "legend"],
|
|
400
636
|
frequency: "week",
|
|
401
637
|
timing: "fuzzy",
|
|
402
|
-
max:
|
|
403
|
-
},
|
|
404
|
-
guild_rank: {
|
|
405
|
-
values: ["bronze", "silver", "gold", "legendary"],
|
|
406
|
-
frequency: "month",
|
|
407
|
-
timing: "fixed",
|
|
408
|
-
max: 6,
|
|
409
|
-
type: "guild_id"
|
|
638
|
+
max: 20
|
|
410
639
|
}
|
|
411
640
|
},
|
|
641
|
+
|
|
412
642
|
userProps: {
|
|
413
|
-
|
|
414
|
-
"
|
|
415
|
-
"
|
|
416
|
-
"Playstation",
|
|
417
|
-
"Switch",
|
|
418
|
-
"Web"
|
|
419
|
-
],
|
|
420
|
-
"game mode": u.pickAWinner([
|
|
421
|
-
"Single Player",
|
|
422
|
-
"Multiplayer",
|
|
423
|
-
], 1),
|
|
424
|
-
language: [
|
|
425
|
-
"English",
|
|
426
|
-
"Spanish",
|
|
427
|
-
"French",
|
|
428
|
-
"German",
|
|
429
|
-
"Japanese",
|
|
430
|
-
"Korean",
|
|
431
|
-
"Chinese",
|
|
643
|
+
"preferred_playstyle": [
|
|
644
|
+
"Solo Explorer", "Group Raider", "PvP Fighter",
|
|
645
|
+
"Quest Completionist", "Treasure Hunter"
|
|
432
646
|
],
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
"
|
|
647
|
+
"total_playtime_hours": u.weighNumRange(1, 500, 1.5, 50),
|
|
648
|
+
"achievement_points": u.weighNumRange(0, 5000, 0.8, 500),
|
|
649
|
+
"favorite_class": [
|
|
650
|
+
"Warrior", "Mage", "Rogue", "Cleric", "Ranger", "Paladin"
|
|
437
651
|
],
|
|
438
|
-
|
|
652
|
+
Platform: ["PC", "Mac", "PlayStation", "Xbox", "Switch"],
|
|
653
|
+
graphics_quality: ["Low", "Medium", "High", "Ultra"],
|
|
654
|
+
subscription_tier: ["Free", "Free", "Free", "Premium", "Elite"],
|
|
439
655
|
|
|
656
|
+
// D&D character identity (from gaming dungeon)
|
|
440
657
|
race: [
|
|
441
|
-
"Human",
|
|
442
|
-
"Elf",
|
|
443
|
-
"Dwarf",
|
|
444
|
-
"Halfling",
|
|
445
|
-
"Dragonborn",
|
|
446
|
-
"Gnome",
|
|
447
|
-
"Half-Elf",
|
|
448
|
-
"Half-Orc",
|
|
449
|
-
"Tiefling",
|
|
658
|
+
"Human", "Elf", "Dwarf", "Halfling", "Dragonborn",
|
|
659
|
+
"Gnome", "Half-Elf", "Half-Orc", "Tiefling"
|
|
450
660
|
],
|
|
451
661
|
class: [
|
|
452
|
-
"Barbarian",
|
|
453
|
-
"
|
|
454
|
-
"Cleric",
|
|
455
|
-
"Druid",
|
|
456
|
-
"Fighter",
|
|
457
|
-
"Monk",
|
|
458
|
-
"Paladin",
|
|
459
|
-
"Ranger",
|
|
460
|
-
"Rogue",
|
|
461
|
-
"Sorcerer",
|
|
462
|
-
"Warlock",
|
|
463
|
-
"Wizard",
|
|
662
|
+
"Barbarian", "Bard", "Cleric", "Druid", "Fighter", "Monk",
|
|
663
|
+
"Paladin", "Ranger", "Rogue", "Sorcerer", "Warlock", "Wizard"
|
|
464
664
|
],
|
|
465
665
|
alignment: [
|
|
466
|
-
"Lawful Good",
|
|
467
|
-
"Neutral
|
|
468
|
-
"Chaotic
|
|
469
|
-
"Lawful Neutral",
|
|
470
|
-
"True Neutral",
|
|
471
|
-
"Chaotic Neutral",
|
|
472
|
-
"Lawful Evil",
|
|
473
|
-
"Neutral Evil",
|
|
474
|
-
"Chaotic Evil",
|
|
666
|
+
"Lawful Good", "Neutral Good", "Chaotic Good",
|
|
667
|
+
"Lawful Neutral", "True Neutral", "Chaotic Neutral",
|
|
668
|
+
"Lawful Evil", "Neutral Evil", "Chaotic Evil"
|
|
475
669
|
],
|
|
476
|
-
level: u.weighNumRange(1, 20),
|
|
477
670
|
background: [
|
|
478
|
-
"Acolyte",
|
|
479
|
-
"
|
|
480
|
-
"
|
|
481
|
-
"Entertainer",
|
|
482
|
-
"Folk Hero",
|
|
483
|
-
"Guild Artisan",
|
|
484
|
-
"Hermit",
|
|
485
|
-
"Noble",
|
|
486
|
-
"Outlander",
|
|
487
|
-
"Sage",
|
|
488
|
-
"Sailor",
|
|
489
|
-
"Soldier",
|
|
490
|
-
"Urchin",
|
|
671
|
+
"Acolyte", "Charlatan", "Criminal", "Entertainer", "Folk Hero",
|
|
672
|
+
"Guild Artisan", "Hermit", "Noble", "Outlander", "Sage",
|
|
673
|
+
"Sailor", "Soldier", "Urchin"
|
|
491
674
|
],
|
|
492
|
-
|
|
675
|
+
level: u.weighNumRange(1, 20),
|
|
493
676
|
archetype: ["neutral"],
|
|
677
|
+
|
|
678
|
+
// A/B/C experiment scaffolding
|
|
679
|
+
experiment: ["fast leveling", "tension economy", "free trial"],
|
|
680
|
+
variant: ["A", "B", "C", "Control"],
|
|
494
681
|
},
|
|
495
|
-
// HOOK: 3 patterns — gold scaling by level, whale purchases, alignment archetype
|
|
496
|
-
hook: function (record, type, meta) {
|
|
497
682
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const level = record["level at end"] || 1;
|
|
502
|
-
record["quest reward (gold)"] = Math.round((record["quest reward (gold)"] || 50) * (1 + level * 0.1));
|
|
503
|
-
}
|
|
683
|
+
groupKeys: [
|
|
684
|
+
["guild_id", 500, ["guild joined", "guild left", "quest turned in", "combat completed"]],
|
|
685
|
+
],
|
|
504
686
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
record.is_whale = true;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
687
|
+
groupProps: {
|
|
688
|
+
guild_id: {
|
|
689
|
+
"name": () => `${chance.word()} ${chance.pickone(["Knights", "Dragons", "Warriors", "Seekers", "Legends"])}`,
|
|
690
|
+
"member_count": u.weighNumRange(5, 100),
|
|
691
|
+
"guild_level": u.weighNumRange(1, 20),
|
|
692
|
+
"total_wealth": u.weighNumRange(1000, 1000000, 0.5, 50000),
|
|
514
693
|
}
|
|
694
|
+
},
|
|
695
|
+
|
|
696
|
+
lookupTables: [],
|
|
515
697
|
|
|
698
|
+
/**
|
|
699
|
+
* 🎯 ARCHITECTED ANALYTICS HOOKS — 11 patterns
|
|
700
|
+
*
|
|
701
|
+
* 1. CONVERSION: Ancient Compass users have 3x quest completion + 1.5x rewards
|
|
702
|
+
* 2. TIME-BASED: "Cursed Week" (days 40-47) has 5x death rates
|
|
703
|
+
* 3. RETENTION: Early guild joiners (first 3 days) have 80% D30 retention vs 20%
|
|
704
|
+
* 4. CHURN: Players with 3+ deaths in first week have 70% churn rate
|
|
705
|
+
* 5. PURCHASE VALUE: Lucky Charm buyers spend 5x more (LTV pattern)
|
|
706
|
+
* 6. BEHAVIORS TOGETHER: inspect + search before dungeon = 85% completion vs 45%
|
|
707
|
+
* 7. TIMED RELEASE: Legendary weapon released day 45, early adopters dominate
|
|
708
|
+
* 8. SUBSCRIPTION TIER: Premium/Elite users have higher engagement and success
|
|
709
|
+
* 9. PROGRESSION SCALING: Quest gold scales with player level (1 + level * 0.1)
|
|
710
|
+
* 10. WHALE PURCHASES: ~33% of users via deterministic hash spend 1.8x more
|
|
711
|
+
* 11. ALIGNMENT ARCHETYPE: Good=hero, Evil=villain, other=neutral (user hook)
|
|
712
|
+
*/
|
|
713
|
+
hook: function (record, type, meta) {
|
|
714
|
+
// Hook #11: ALIGNMENT ARCHETYPE — derive archetype on user profile
|
|
516
715
|
if (type === "user") {
|
|
517
|
-
// Pattern 3: Chaotic Evil players get a "villain" tag; Lawful Good get "hero"
|
|
518
716
|
if (record.alignment === "Chaotic Evil" || record.alignment === "Neutral Evil") {
|
|
519
717
|
record.archetype = "villain";
|
|
520
718
|
} else if (record.alignment === "Lawful Good" || record.alignment === "Neutral Good") {
|
|
@@ -524,28 +722,360 @@ const config = {
|
|
|
524
722
|
}
|
|
525
723
|
}
|
|
526
724
|
|
|
527
|
-
|
|
725
|
+
// Hook #2 event-level part removed — cursed week now handled in everything hook
|
|
528
726
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (type === "scd") {
|
|
727
|
+
// Hook #7: TIMED RELEASE — Legendary Weapon
|
|
728
|
+
// (moved to everything hook — event hook fires before bunchIntoSessions
|
|
729
|
+
// reshuffles timestamps, causing tagged events to leak across the d45 boundary)
|
|
536
730
|
|
|
731
|
+
// HOOK 12 (T2C): COMBAT FUNNEL TIME-TO-CONVERT (funnel-post)
|
|
732
|
+
// Elite tier completes Combat funnel 1.4x faster (factor 0.71);
|
|
733
|
+
// Free tier 1.25x slower (factor 1.25).
|
|
734
|
+
if (type === "funnel-post") {
|
|
735
|
+
const segment = meta?.profile?.subscription_tier;
|
|
736
|
+
if (Array.isArray(record) && record.length > 1) {
|
|
737
|
+
const factor = (
|
|
738
|
+
segment === "Elite" ? 0.30 :
|
|
739
|
+
segment === "Premium" ? 0.70 :
|
|
740
|
+
segment === "Free" ? 1.40 :
|
|
741
|
+
1.0
|
|
742
|
+
);
|
|
743
|
+
if (factor !== 1.0) {
|
|
744
|
+
for (let i = 1; i < record.length; i++) {
|
|
745
|
+
const prev = dayjs(record[i - 1].time);
|
|
746
|
+
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
747
|
+
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
537
751
|
}
|
|
538
752
|
|
|
753
|
+
// Hooks #1, #3, #4, #5, #6, #8, #9, #10: per-user behavioral patterns
|
|
539
754
|
if (type === "everything") {
|
|
540
|
-
|
|
755
|
+
const userEvents = record;
|
|
541
756
|
const profile = meta.profile;
|
|
542
|
-
|
|
757
|
+
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
758
|
+
const LEGENDARY_WEAPON_RELEASE = datasetStart.add(45, 'days');
|
|
759
|
+
|
|
760
|
+
// Stamp superProps from profile for consistency
|
|
761
|
+
userEvents.forEach(e => {
|
|
543
762
|
e.Platform = profile.Platform;
|
|
544
|
-
e
|
|
545
|
-
e.
|
|
763
|
+
e.graphics_quality = profile.graphics_quality;
|
|
764
|
+
e.subscription_tier = profile.subscription_tier;
|
|
546
765
|
});
|
|
547
766
|
|
|
548
|
-
|
|
767
|
+
// Hook #7: TIMED RELEASE — Shadowmourne Legendary post-d45.
|
|
768
|
+
// Runs in everything hook so timestamps are post-bunchIntoSessions.
|
|
769
|
+
userEvents.forEach(e => {
|
|
770
|
+
if (e.event === "find treasure" && dayjs(e.time).isAfter(LEGENDARY_WEAPON_RELEASE) && chance.bool({ likelihood: 2 })) {
|
|
771
|
+
e.treasure_type = "Shadowmourne Legendary";
|
|
772
|
+
e.treasure_value = 50000;
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
// Hook #12 (everything-hook companion): COMBAT T2C — also compress
|
|
777
|
+
// gap between earliest combat_initiated and earliest use_item across
|
|
778
|
+
// the user's full event history (the funnel-post hook only adjusts
|
|
779
|
+
// within-funnel-instance gaps; this catches cross-funnel measurement).
|
|
780
|
+
const tier = profile.subscription_tier;
|
|
781
|
+
const t2cFactor = (
|
|
782
|
+
tier === "Elite" ? 0.30 :
|
|
783
|
+
tier === "Premium" ? 0.70 :
|
|
784
|
+
tier === "Free" ? 1.40 :
|
|
785
|
+
1.0
|
|
786
|
+
);
|
|
787
|
+
if (t2cFactor !== 1.0) {
|
|
788
|
+
let firstCombatTime = null;
|
|
789
|
+
let firstUseItemIdx = -1;
|
|
790
|
+
for (let i = 0; i < userEvents.length; i++) {
|
|
791
|
+
const e = userEvents[i];
|
|
792
|
+
if (e.event === "combat initiated" && firstCombatTime === null) {
|
|
793
|
+
firstCombatTime = dayjs(e.time);
|
|
794
|
+
}
|
|
795
|
+
if (e.event === "use item" && firstUseItemIdx === -1) {
|
|
796
|
+
firstUseItemIdx = i;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (firstCombatTime !== null && firstUseItemIdx !== -1) {
|
|
800
|
+
const useItemTime = dayjs(userEvents[firstUseItemIdx].time);
|
|
801
|
+
if (useItemTime.isAfter(firstCombatTime)) {
|
|
802
|
+
const newGap = Math.round(useItemTime.diff(firstCombatTime) * t2cFactor);
|
|
803
|
+
userEvents[firstUseItemIdx].time = firstCombatTime.add(newGap, "milliseconds").toISOString();
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
|
|
809
|
+
const userLevel = profile.level || 1;
|
|
810
|
+
|
|
811
|
+
// Track user behaviors
|
|
812
|
+
let usedAncientCompass = false;
|
|
813
|
+
let boughtLuckyCharm = false;
|
|
814
|
+
let joinedGuildEarly = false;
|
|
815
|
+
let earlyDeaths = 0;
|
|
816
|
+
let hasLegendaryWeapon = false;
|
|
817
|
+
let inspectedBeforeDungeon = false;
|
|
818
|
+
let searchedBeforeDungeon = false;
|
|
819
|
+
let subscriptionTier = "Free";
|
|
820
|
+
|
|
821
|
+
// Hook #10: Whale segmentation — deterministic via user_id hash (~33%)
|
|
822
|
+
const userId = userEvents.length > 0 ? (userEvents[0].user_id || userEvents[0].distinct_id || "") : "";
|
|
823
|
+
const isWhale = userId.length > 0 && userId.charCodeAt(0) % 3 === 0;
|
|
824
|
+
|
|
825
|
+
// First pass: identify user patterns
|
|
826
|
+
userEvents.forEach((event) => {
|
|
827
|
+
const eventTime = dayjs(event.time);
|
|
828
|
+
const daysSinceStart = firstEventTime ? eventTime.diff(firstEventTime, 'days', true) : 0;
|
|
829
|
+
|
|
830
|
+
if (event.subscription_tier) {
|
|
831
|
+
subscriptionTier = event.subscription_tier;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (event.event === "use item" && event.item_type === "Ancient Compass") {
|
|
835
|
+
usedAncientCompass = true;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (event.event === "real money purchase" && event.product === "Lucky Charm Pack") {
|
|
839
|
+
boughtLuckyCharm = true;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (event.event === "guild joined" && daysSinceStart < 3) {
|
|
843
|
+
joinedGuildEarly = true;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (event.event === "player death" && daysSinceStart < 7) {
|
|
847
|
+
earlyDeaths++;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (event.event === "find treasure" && event.treasure_type === "Shadowmourne Legendary") {
|
|
851
|
+
hasLegendaryWeapon = true;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (event.event === "inspect") {
|
|
855
|
+
inspectedBeforeDungeon = true;
|
|
856
|
+
}
|
|
857
|
+
if (event.event === "search for clues") {
|
|
858
|
+
searchedBeforeDungeon = true;
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// Second pass: raw mutations + cloning, no flag stamping
|
|
863
|
+
userEvents.forEach((event, idx) => {
|
|
864
|
+
const eventTime = dayjs(event.time);
|
|
865
|
+
|
|
866
|
+
// Hook 9: PROGRESSION SCALING — Quest gold scales with level.
|
|
867
|
+
if (event.event === "quest turned in") {
|
|
868
|
+
const baseGold = event.reward_gold || 100;
|
|
869
|
+
event.reward_gold = Math.floor(baseGold * (1 + userLevel * 0.1));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Hook 1: CONVERSION — Ancient Compass users earn 1.5x quest
|
|
873
|
+
// rewards plus 40% chance of bonus cloned quest.
|
|
874
|
+
if (usedAncientCompass && event.event === "quest turned in") {
|
|
875
|
+
event.reward_gold = Math.floor((event.reward_gold || 100) * 1.5);
|
|
876
|
+
event.reward_xp = Math.floor((event.reward_xp || 500) * 1.5);
|
|
877
|
+
|
|
878
|
+
if (chance.bool({ likelihood: 40 })) {
|
|
879
|
+
userEvents.splice(idx + 1, 0, {
|
|
880
|
+
...event,
|
|
881
|
+
time: eventTime.add(chance.integer({ min: 10, max: 120 }), 'minutes').toISOString(),
|
|
882
|
+
quest_id: chance.pickone(questIds),
|
|
883
|
+
reward_gold: chance.integer({ min: 100, max: 500 }),
|
|
884
|
+
reward_xp: chance.integer({ min: 500, max: 2000 }),
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Hook 5: PURCHASE VALUE — Lucky charm buyers see 2x prices
|
|
890
|
+
// and 35% chance of bonus high-value cloned purchase.
|
|
891
|
+
if (boughtLuckyCharm) {
|
|
892
|
+
if (event.event === "real money purchase" && event.price_usd && event.price_usd < 49.99) {
|
|
893
|
+
event.price_usd = event.price_usd * 2;
|
|
894
|
+
}
|
|
895
|
+
if (event.event === "item purchased" && chance.bool({ likelihood: 35 })) {
|
|
896
|
+
const purchaseTemplate = userEvents.find(e => e.event === "real money purchase");
|
|
897
|
+
if (purchaseTemplate) {
|
|
898
|
+
userEvents.splice(idx + 1, 0, {
|
|
899
|
+
...purchaseTemplate,
|
|
900
|
+
time: eventTime.add(chance.integer({ min: 1, max: 3 }), 'days').toISOString(),
|
|
901
|
+
user_id: event.user_id,
|
|
902
|
+
product: chance.pickone(["Premium Currency (5000)", "Legendary Weapon Chest", "Season Pass"]),
|
|
903
|
+
price_usd: chance.pickone([19.99, 49.99, 99.99]),
|
|
904
|
+
payment_method: chance.pickone(["Credit Card", "PayPal"]),
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Hook 10: WHALE PURCHASES — 1.8x price for whale cohort.
|
|
911
|
+
if (isWhale && event.event === "real money purchase" && event.price_usd) {
|
|
912
|
+
event.price_usd = Math.round(event.price_usd * 1.8 * 100) / 100;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Hook 6: BEHAVIORS TOGETHER — inspect+search dungeons get
|
|
916
|
+
// 85% completion rate + 2x treasure value.
|
|
917
|
+
if (inspectedBeforeDungeon && searchedBeforeDungeon) {
|
|
918
|
+
if (event.event === "exit dungeon" && event.completion_status !== "completed" && chance.bool({ likelihood: 85 })) {
|
|
919
|
+
event.completion_status = "completed";
|
|
920
|
+
}
|
|
921
|
+
if (event.event === "find treasure") {
|
|
922
|
+
event.treasure_value = Math.floor((event.treasure_value || 50) * 2);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Hook 7: TIMED RELEASE — Legendary owners get 90% combat
|
|
927
|
+
// wins + 0.6x dungeon time.
|
|
928
|
+
if (hasLegendaryWeapon) {
|
|
929
|
+
if (event.event === "combat completed" && event.outcome !== "Victory" && chance.bool({ likelihood: 90 })) {
|
|
930
|
+
event.outcome = "Victory";
|
|
931
|
+
}
|
|
932
|
+
if (event.event === "exit dungeon") {
|
|
933
|
+
event.completion_status = "completed";
|
|
934
|
+
event.time_spent_mins = Math.floor((event.time_spent_mins || 60) * 0.6);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Hook 8: SUBSCRIPTION TIER — Premium/Elite get win+reward+
|
|
939
|
+
// completion+treasure boosts. Reads tier from profile.
|
|
940
|
+
if (subscriptionTier === "Premium" || subscriptionTier === "Elite") {
|
|
941
|
+
const isElite = subscriptionTier === "Elite";
|
|
942
|
+
if (event.event === "combat completed" && event.outcome !== "Victory") {
|
|
943
|
+
const winBoost = isElite ? 70 : 50;
|
|
944
|
+
if (Math.random() * 100 < winBoost) {
|
|
945
|
+
event.outcome = "Victory";
|
|
946
|
+
event.loot_gained = true;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (event.event === "quest turned in") {
|
|
950
|
+
const rewardMultiplier = isElite ? 1.8 : 1.4;
|
|
951
|
+
event.reward_gold = Math.floor((event.reward_gold || 100) * rewardMultiplier);
|
|
952
|
+
event.reward_xp = Math.floor((event.reward_xp || 500) * rewardMultiplier);
|
|
953
|
+
}
|
|
954
|
+
if (event.event === "exit dungeon") {
|
|
955
|
+
if (event.completion_status !== "completed") {
|
|
956
|
+
const completionBoost = isElite ? 65 : 45;
|
|
957
|
+
if (Math.random() * 100 < completionBoost) {
|
|
958
|
+
event.completion_status = "completed";
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (event.completion_status === "completed") {
|
|
962
|
+
const speedBoost = isElite ? 0.7 : 0.85;
|
|
963
|
+
event.time_spent_mins = Math.floor((event.time_spent_mins || 60) * speedBoost);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (event.event === "find treasure") {
|
|
967
|
+
const treasureBoost = isElite ? 2.0 : 1.5;
|
|
968
|
+
event.treasure_value = Math.floor((event.treasure_value || 50) * treasureBoost);
|
|
969
|
+
}
|
|
970
|
+
if (event.event === "player death") {
|
|
971
|
+
const survivalChance = isElite ? 50 : 30;
|
|
972
|
+
if (Math.random() * 100 < survivalChance) {
|
|
973
|
+
event.event = "combat completed";
|
|
974
|
+
event.outcome = "Victory";
|
|
975
|
+
event.loot_gained = true;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (isElite && Math.random() * 100 < 5) {
|
|
979
|
+
if (event.event === "quest turned in" || event.event === "exit dungeon") {
|
|
980
|
+
const treasureTemplate = userEvents.find(e => e.event === "find treasure");
|
|
981
|
+
if (treasureTemplate) {
|
|
982
|
+
const treasureTypes = ["Rare Artifact", "Gold", "Weapon", "Armor"];
|
|
983
|
+
userEvents.splice(idx + 1, 0, {
|
|
984
|
+
...treasureTemplate,
|
|
985
|
+
time: eventTime.add(Math.floor(Math.random() * 26) + 5, 'minutes').toISOString(),
|
|
986
|
+
user_id: event.user_id,
|
|
987
|
+
treasure_type: treasureTypes[Math.floor(Math.random() * treasureTypes.length)],
|
|
988
|
+
treasure_value: Math.floor(Math.random() * 601) + 200,
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
// Hook 2: CURSED WEEK — inject extra deaths in days 40-47 of
|
|
997
|
+
// user's timeline. Cause_of_death set to "Curse" on injected.
|
|
998
|
+
// Discover via line-chart of player-death by day-of-user-life.
|
|
999
|
+
if (firstEventTime) {
|
|
1000
|
+
const deathTemplate = userEvents.find(e => e.event === "player death");
|
|
1001
|
+
if (deathTemplate) {
|
|
1002
|
+
const cursedStart = firstEventTime.add(40, 'days');
|
|
1003
|
+
const cursedEnd = firstEventTime.add(47, 'days');
|
|
1004
|
+
const cursedEvents = userEvents.filter(e => {
|
|
1005
|
+
const t = dayjs(e.time);
|
|
1006
|
+
return t.isAfter(cursedStart) && t.isBefore(cursedEnd);
|
|
1007
|
+
});
|
|
1008
|
+
const deathsToInject = Math.floor(cursedEvents.length * 0.6);
|
|
1009
|
+
for (let d = 0; d < deathsToInject; d++) {
|
|
1010
|
+
const sourceEvent = cursedEvents[d % cursedEvents.length];
|
|
1011
|
+
userEvents.push({
|
|
1012
|
+
...deathTemplate,
|
|
1013
|
+
time: dayjs(sourceEvent.time).add(chance.integer({ min: 1, max: 30 }), 'minutes').toISOString(),
|
|
1014
|
+
user_id: sourceEvent.user_id,
|
|
1015
|
+
event: "player death",
|
|
1016
|
+
cause_of_death: "Curse",
|
|
1017
|
+
player_level: chance.integer({ min: 1, max: 50 }),
|
|
1018
|
+
resurrection_used: chance.bool({ likelihood: 80 }),
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Hook 3 RETENTION + Hook 4 CHURN — early guild-joiners get
|
|
1025
|
+
// extra cloned combat events; non-joiners with 3+ deaths in
|
|
1026
|
+
// first week lose 70% of post-week events. No flag.
|
|
1027
|
+
const shouldChurn = (!joinedGuildEarly && earlyDeaths >= 3) || (earlyDeaths >= 5);
|
|
1028
|
+
if (shouldChurn) {
|
|
1029
|
+
const firstWeekEnd = firstEventTime ? firstEventTime.add(7, 'days') : null;
|
|
1030
|
+
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
1031
|
+
if (firstWeekEnd && dayjs(userEvents[i].time).isAfter(firstWeekEnd) && chance.bool({ likelihood: 70 })) {
|
|
1032
|
+
userEvents.splice(i, 1);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
} else if (joinedGuildEarly) {
|
|
1036
|
+
const lastEvent = userEvents[userEvents.length - 1];
|
|
1037
|
+
const combatTemplate = userEvents.find(e => e.event === "combat completed");
|
|
1038
|
+
if (lastEvent && combatTemplate && chance.bool({ likelihood: 60 })) {
|
|
1039
|
+
userEvents.push({
|
|
1040
|
+
...combatTemplate,
|
|
1041
|
+
time: dayjs(lastEvent.time).add(chance.integer({ min: 1, max: 5 }), 'days').toISOString(),
|
|
1042
|
+
user_id: lastEvent.user_id,
|
|
1043
|
+
outcome: "Victory",
|
|
1044
|
+
loot_gained: true,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// HOOK 13: COMBAT-PREP MAGIC NUMBER (in-funnel, no flags)
|
|
1050
|
+
// Sweet 3-6 inspect+search events between quest accepted and
|
|
1051
|
+
// fight boss → +30% loot/treasure_value on find-treasure events.
|
|
1052
|
+
// Over 7+ → drop 25% of fight-boss completion (over-prep
|
|
1053
|
+
// signals analysis paralysis). No flag.
|
|
1054
|
+
const questAccept = userEvents.find(e => e.event === "quest accepted");
|
|
1055
|
+
const bossFight = userEvents.find(e => e.event === "fight boss");
|
|
1056
|
+
if (questAccept && bossFight) {
|
|
1057
|
+
const aTime = dayjs(questAccept.time);
|
|
1058
|
+
const bTime = dayjs(bossFight.time);
|
|
1059
|
+
const prepCount = userEvents.filter(e =>
|
|
1060
|
+
(e.event === "inspect" || e.event === "search for clues") &&
|
|
1061
|
+
dayjs(e.time).isAfter(aTime) &&
|
|
1062
|
+
dayjs(e.time).isBefore(bTime)
|
|
1063
|
+
).length;
|
|
1064
|
+
if (prepCount >= 3 && prepCount <= 6) {
|
|
1065
|
+
userEvents.forEach(e => {
|
|
1066
|
+
if (e.event === "find treasure" && typeof e.treasure_value === "number") {
|
|
1067
|
+
e.treasure_value = Math.round(e.treasure_value * 1.3);
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
} else if (prepCount >= 7) {
|
|
1071
|
+
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
1072
|
+
const ev = userEvents[i];
|
|
1073
|
+
if (ev.event === "fight boss" && ev.victory === true && chance.bool({ likelihood: 25 })) {
|
|
1074
|
+
ev.victory = false;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
549
1079
|
}
|
|
550
1080
|
|
|
551
1081
|
return record;
|