@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.
Files changed (89) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +51 -13
  3. package/dungeons/technical/ad-spend.js +2 -2
  4. package/dungeons/technical/anonymous-users.js +2 -2
  5. package/dungeons/technical/array-of-object-lookup.js +2 -4
  6. package/dungeons/technical/experiments.js +2 -2
  7. package/dungeons/technical/foobar.js +2 -2
  8. package/dungeons/technical/group-analytics.js +2 -2
  9. package/dungeons/technical/mirror-strategies.js +2 -2
  10. package/dungeons/technical/nested-objects.js +2 -2
  11. package/dungeons/technical/retention-cadence.js +2 -3
  12. package/dungeons/technical/sanity.js +2 -2
  13. package/dungeons/technical/scale-test.js +2 -2
  14. package/dungeons/technical/scd.js +2 -2
  15. package/dungeons/technical/simple.js +5 -6
  16. package/dungeons/technical/simplest-schema.json +5 -0
  17. package/dungeons/technical/simplest.js +2 -2
  18. package/dungeons/technical/text-generation.js +3 -3
  19. package/dungeons/vertical/ai-platform.js +858 -0
  20. package/dungeons/vertical/community.js +84 -40
  21. package/dungeons/vertical/crypto.js +830 -0
  22. package/dungeons/vertical/dating.js +744 -0
  23. package/dungeons/vertical/devtools.js +175 -69
  24. package/dungeons/vertical/ecommerce.js +242 -94
  25. package/dungeons/vertical/education.js +330 -313
  26. package/dungeons/vertical/fintech.js +442 -313
  27. package/dungeons/vertical/fitness.js +143 -61
  28. package/dungeons/vertical/food-delivery.js +327 -353
  29. package/dungeons/vertical/gaming.js +912 -382
  30. package/dungeons/vertical/healthcare.js +142 -63
  31. package/dungeons/vertical/insurance-application.js +170 -76
  32. package/dungeons/vertical/logistics.js +115 -20
  33. package/dungeons/vertical/marketplace.js +152 -58
  34. package/dungeons/vertical/media.js +248 -384
  35. package/dungeons/vertical/real-estate.js +781 -0
  36. package/dungeons/vertical/sass.js +255 -266
  37. package/dungeons/vertical/social.js +264 -206
  38. package/dungeons/vertical/travel.js +117 -41
  39. package/index.js +17 -17
  40. package/lib/core/config-validator.js +159 -31
  41. package/lib/core/context.js +10 -24
  42. package/lib/core/storage.js +6 -1
  43. package/lib/generators/events.js +11 -14
  44. package/lib/generators/funnels.js +12 -4
  45. package/lib/generators/mirror.js +3 -2
  46. package/lib/generators/product-names.js +1 -1
  47. package/lib/generators/scd.js +2 -1
  48. package/lib/generators/text.js +1 -1
  49. package/lib/orchestrators/user-loop.js +81 -47
  50. package/lib/templates/macro-presets.js +111 -0
  51. package/lib/templates/soup-presets.js +19 -36
  52. package/lib/utils/utils.js +71 -39
  53. package/package.json +8 -2
  54. package/scripts/smoke-test-all.mjs +162 -0
  55. package/scripts/verify-runner.mjs +72 -24
  56. package/types.d.ts +251 -51
  57. package/dungeons/technical/ad-spend-schema.json +0 -128
  58. package/dungeons/technical/anonymous-users-schema.json +0 -92
  59. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  60. package/dungeons/technical/experiments-schema.json +0 -203
  61. package/dungeons/technical/foobar-schema.json +0 -362
  62. package/dungeons/technical/group-analytics-schema.json +0 -241
  63. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  64. package/dungeons/technical/nested-objects-schema.json +0 -145
  65. package/dungeons/technical/retention-cadence-schema.json +0 -37
  66. package/dungeons/technical/sanity-schema.json +0 -185
  67. package/dungeons/technical/scale-test-schema.json +0 -70
  68. package/dungeons/technical/scd-schema.json +0 -467
  69. package/dungeons/technical/simple-schema.json +0 -362
  70. package/dungeons/technical/text-generation-schema.json +0 -1062
  71. package/dungeons/user/.gitkeep +0 -0
  72. package/dungeons/vertical/community-schema.json +0 -579
  73. package/dungeons/vertical/devtools-schema.json +0 -601
  74. package/dungeons/vertical/ecommerce-schema.json +0 -604
  75. package/dungeons/vertical/education-schema.json +0 -5686
  76. package/dungeons/vertical/fintech-schema.json +0 -630
  77. package/dungeons/vertical/fitness-schema.json +0 -530
  78. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  79. package/dungeons/vertical/gaming-schema.json +0 -438
  80. package/dungeons/vertical/healthcare-schema.json +0 -549
  81. package/dungeons/vertical/insurance-application-schema.json +0 -485
  82. package/dungeons/vertical/logistics-schema.json +0 -574
  83. package/dungeons/vertical/marketplace-schema.json +0 -533
  84. package/dungeons/vertical/media-schema.json +0 -4749
  85. package/dungeons/vertical/rpg-schema.json +0 -2491
  86. package/dungeons/vertical/rpg.js +0 -976
  87. package/dungeons/vertical/sass-schema.json +0 -3128
  88. package/dungeons/vertical/social-schema.json +0 -620
  89. package/dungeons/vertical/travel-schema.json +0 -580
@@ -1,8 +1,8 @@
1
1
  // ── TWEAK THESE ──
2
- const SEED = "my-seed";
3
- const num_days = 180;
4
- const num_users = 12_000;
5
- const avg_events_per_user = 90;
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 — A D&D-inspired action RPG where players create characters,
28
- * join parties, embark on quests, fight bosses, and level up. Think Baldur's
29
- * Gate meets a mobile RPG with in-game purchases and seasonal content.
30
- *
31
- * CORE USER LOOP:
32
- * Players install the app, create a character (race + class + alignment),
33
- * and join a party. They then cycle through quests starting quests,
34
- * engaging in gameplay (attacks, defenses, enemy defeats), completing quests
35
- * for gold rewards, fighting bosses, and leveling up. In-game purchases
36
- * (gold, gems, weapons, armor, boosts) monetize the experience.
37
- *
38
- * SCALE: 12,000 users, ~1.08M events, 180 days
39
- *
40
- * KEY DIMENSIONS:
41
- * - Platform: Mobile, Xbox, Playstation, Switch, Web
42
- * - Game mode: Single Player vs Multiplayer (biased toward Multiplayer)
43
- * - Character: 9 races, 12 classes, 9 alignments, 13 backgrounds
44
- * - Quests: 5 types, 4 difficulties, 5 locations
45
- * - Experiment: fast leveling / tension economy / free trial with A/B/C/Control
46
- *
47
- * FUNNELS:
48
- * 1. Onboarding: app install character creation join party (80% conversion)
49
- * 2. Quest loop: start quest gameplay summary complete quest (99% conversion)
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
- * This dungeon has 3 deliberate patterns engineered via hooks:
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
- * 1. GOLD SCALING BY LEVEL (event hook)
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
- * PATTERN: On "complete quest" events, the quest reward (gold) is multiplied by
64
- * (1 + level * 0.1), so a level-10 player earns 2x gold and a level-20 player
65
- * earns 3x gold compared to level-1.
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
- * HOW TO FIND IT IN MIXPANEL:
68
- * - Report type: Insights
69
- * - Event: "complete quest"
70
- * - Measure: Average of "quest reward (gold)"
71
- * - Breakdown: "level at end"
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
- * 2. WHALE PURCHASES (event hook)
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
- * PATTERN: ~33% of users (determined by first character of user_id hash) are
80
- * "whales" who spend 1.8x on in-game purchases and are tagged is_whale: true.
187
+ * REAL-WORLD ANALOGUE: Quest economies scale rewards with player level
188
+ * so high-level zones remain meaningfully lucrative.
81
189
  *
82
- * HOW TO FIND IT IN MIXPANEL:
190
+ * 10. WHALE PURCHASES (TOP-SPENDER COHORT — everything)
83
191
  *
84
- * Report 1: Whale vs Normal Purchase Amounts
85
- * - Report type: Insights
86
- * - Event: "in-game purchase"
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
- * Report 2: Revenue Concentration
93
- * - Report type: Insights
94
- * - Event: "in-game purchase"
95
- * - Measure: Sum of "purchase amount"
96
- * - Breakdown: "is_whale"
97
- * - Expected: ~33% of users (whales) contribute disproportionate total revenue
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
- * 3. ALIGNMENT ARCHETYPE (user hook)
238
+ * EXPECTED METRICS SUMMARY
101
239
  * ───────────────────────────────────────────────────────────────────────────────
102
240
  *
103
- * PATTERN: User profiles are enriched with an "archetype" property based on
104
- * alignment. Evil alignments "villain", Good alignments → "hero", others → "neutral".
241
+ * ───────────────────────────────────────────────────────────────────────────────
242
+ * 12. COMBAT FUNNEL TIME-TO-CONVERT (funnel-post)
105
243
  *
106
- * HOW TO FIND IT IN MIXPANEL:
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
- * Report 1: Archetype Distribution
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
- * Report 2: Archetype Behavior Comparison
117
- * - Report type: Insights
118
- * - Event: "fight boss"
119
- * - Measure: Total events per user
120
- * - Breakdown: User profile "archetype"
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
- * EXPECTED METRICS SUMMARY
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
- * Hook | Metric | Baseline | Hook Effect | Ratio
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
- * ADVANCED ANALYSIS IDEAS
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
- * 1. Gold Economy Analysis: Plot avg gold earned by level AND quest difficulty.
144
- * Do Legendary quests at high levels create outsized gold rewards?
270
+ * HOW TO FIND IT IN MIXPANEL:
145
271
  *
146
- * 2. Whale Segmentation: Cross-reference is_whale with platform and game mode.
147
- * Are whales concentrated on mobile? Do multiplayer whales spend more?
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
- * 3. Archetype × Class Matrix: Do certain class/archetype combos correlate
150
- * with higher boss fight rates or quest completion?
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
- * 4. Experiment Impact: Compare "fast leveling" vs "tension economy" vs
153
- * "free trial" on purchase amounts, level progression, and retention.
286
+ * REAL-WORLD ANALOGUE: Calculated prep wins; over-prep is paralysis.
154
287
  *
155
- * 5. Quest Difficulty Funnel: How does quest difficulty affect completion
156
- * rate, death count, and hint/boost usage? Is "Legendary" too hard?
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: "i am gamer face",
165
- numDays: num_days,
166
- numEvents: num_users * avg_events_per_user,
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: false,
326
+ hasSessionIds: true,
170
327
  format: "json",
171
- alsoInferFunnels: true,
328
+ gzip: true,
329
+ alsoInferFunnels: false,
172
330
  hasLocation: true,
173
- hasAndroidDevices: false,
174
- hasIOSDevices: false,
175
- hasDesktopDevices: false,
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
- "sequence": ["app install", "character creation", "join party"],
189
- "isFirstFunnel": true,
190
- "conversionRate": 0.8,
191
- "timeToConvert": .25,
346
+ sequence: ["character created", "tutorial completed", "quest accepted"],
347
+ isFirstFunnel: true,
348
+ conversionRate: 75,
349
+ timeToConvert: 0.5,
192
350
  },
193
351
  {
194
- "sequence": ["start quest", "gameplay summary", "complete quest"],
195
- conversionRate: 0.99,
196
- timeToConvert: 1,
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
- "quest type": [
199
- "Rescue",
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 creation",
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
- mode: ["fast", "slow", "template"],
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: "join party",
232
- weight: 8,
425
+ event: "quest accepted",
426
+ weight: 15,
233
427
  properties: {
234
- "party size": u.weighNumRange(1, 6),
235
- "party leader": [
236
- "Bard",
237
- "Cleric",
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: "start quest",
434
+ event: "quest objective completed",
250
435
  weight: 12,
251
436
  properties: {
252
- "quest type": [
253
- "Rescue",
254
- "Retrieve",
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: "complete quest",
280
- weight: 12,
442
+ event: "quest turned in",
443
+ weight: 10,
281
444
  properties: {
282
- "completion time (mins)": u.weighNumRange(5, 120, 1, 30),
283
- "quest reward (gold)": u.weighNumRange(10, 500, 1, 100),
284
- "quest success": ["yes", "yes", "yes", "yes", "yes", "yes", "yes", "yes", "yes", "no"],
285
- "used hint": ["no", "no", "yes"],
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
- "event": "gameplay summary",
294
- "weight": 14,
295
- "properties": {
296
- "# enemies defeated": u.weighNumRange(0, 100),
297
- "# respawns": u.weighNumRange(0, 10, 5),
298
- "# attacks": u.weighNumRange(0, 100, 6, 12),
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
- "event": "in-game purchase",
304
- "weight": 2,
305
- "properties": {
306
- "purchase type": [
307
- "Gold",
308
- "Gems",
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: "fight boss",
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
- "boss type": [
325
- "Dragon",
326
- "Demon",
327
- "Lich",
328
- "Vampire",
329
- "Beholder",
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
- "boss level": u.weighNumRange(10, 20),
332
- "boss difficulty": [
333
- "Hard",
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: "level up",
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
- "new abilities": [
346
- "Attack",
347
- "Spell",
348
- "Feat",
349
- "Skill",
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
- generic_prop: ["foo", "bar", "baz", "qux"],
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
- generic_prop: ["foo", "bar", "baz", "qux"],
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: ["bronze", "silver", "gold", "diamond", "legendary"],
635
+ values: ["recruit", "veteran", "elite", "legend"],
400
636
  frequency: "week",
401
637
  timing: "fuzzy",
402
- max: 250
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
- Platform: [
414
- "Mobile",
415
- "Xbox",
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
- experiment: [
434
- "fast leveling",
435
- "tension economy",
436
- "free trial",
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
- variant: ["A", "B", "C", "Control"],
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
- "Bard",
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 Good",
468
- "Chaotic Good",
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
- "Charlatan",
480
- "Criminal",
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
- "subscription MRR": u.weighNumRange(0, 250, 1, 200),
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
- if (type === "event") {
499
- // Pattern 1: Higher-level players earn more gold and defeat more enemies
500
- if (record.event === "complete quest") {
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
- // Pattern 2: In-game purchases are bigger for "free trial" experiment users
506
- if (record.event === "in-game purchase") {
507
- // We don't have profile here, so use a hash-based approach
508
- const uid = record.user_id || record.distinct_id || "";
509
- if (uid.charCodeAt(0) % 3 === 0) {
510
- record["purchase amount"] = Math.round((record["purchase amount"] || 10) * 1.8);
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
- if (type === "funnel-post") {
725
+ // Hook #2 event-level part removed — cursed week now handled in everything hook
528
726
 
529
- }
530
-
531
- if (type === "funnel-pre") {
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
- // Stamp superProps from profile for consistency
755
+ const userEvents = record;
541
756
  const profile = meta.profile;
542
- record.forEach(e => {
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["game mode"] = profile["game mode"];
545
- e.language = profile.language;
763
+ e.graphics_quality = profile.graphics_quality;
764
+ e.subscription_tier = profile.subscription_tier;
546
765
  });
547
766
 
548
- return record;
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;