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