@ak--47/dungeon-master 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dungeons/technical/array-of-object-lookup.js +0 -2
- package/dungeons/technical/simple.js +3 -4
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/text-generation.js +1 -1
- package/dungeons/vertical/ai-platform.js +192 -133
- package/dungeons/vertical/community.js +64 -34
- package/dungeons/vertical/crypto.js +333 -224
- package/dungeons/vertical/dating.js +228 -282
- package/dungeons/vertical/devtools.js +164 -62
- package/dungeons/vertical/ecommerce.js +240 -91
- package/dungeons/vertical/education.js +328 -310
- package/dungeons/vertical/fintech.js +437 -311
- package/dungeons/vertical/fitness.js +141 -58
- package/dungeons/vertical/food-delivery.js +298 -318
- package/dungeons/vertical/gaming.js +378 -231
- package/dungeons/vertical/healthcare.js +140 -60
- package/dungeons/vertical/insurance-application.js +168 -73
- package/dungeons/vertical/logistics.js +95 -6
- package/dungeons/vertical/marketplace.js +137 -51
- package/dungeons/vertical/media.js +241 -386
- package/dungeons/vertical/real-estate.js +322 -315
- package/dungeons/vertical/sass.js +253 -263
- package/dungeons/vertical/social.js +262 -206
- package/dungeons/vertical/travel.js +59 -16
- package/index.js +17 -17
- package/lib/core/config-validator.js +93 -21
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +11 -14
- package/lib/generators/funnels.js +12 -4
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/orchestrators/user-loop.js +47 -47
- package/lib/utils/utils.js +71 -39
- package/package.json +1 -1
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +38 -15
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/vertical/ai-platform-schema.json +0 -617
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/crypto-schema.json +0 -546
- package/dungeons/vertical/dating-schema.json +0 -401
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -2703
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/real-estate-schema.json +0 -527
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -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
|
/*
|
|
@@ -55,92 +54,258 @@ const chance = u.initChance(SEED);
|
|
|
55
54
|
|
|
56
55
|
/*
|
|
57
56
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
58
|
-
* ANALYTICS HOOKS (
|
|
57
|
+
* ANALYTICS HOOKS (13 hooks)
|
|
59
58
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
60
59
|
*
|
|
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.
|
|
63
|
+
*
|
|
61
64
|
* 1. CONVERSION: Ancient Compass Effect
|
|
62
|
-
* Players who use the "Ancient Compass" item
|
|
63
|
-
* and
|
|
64
|
-
* → Mixpanel:
|
|
65
|
-
* → Compare quest
|
|
66
|
-
* →
|
|
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
|
|
67
70
|
*
|
|
68
|
-
* 2. TIME-BASED: Cursed Week (days 40-47)
|
|
69
|
-
* Death rates spike
|
|
70
|
-
*
|
|
71
|
-
* →
|
|
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
|
|
72
76
|
*
|
|
73
77
|
* 3. RETENTION: Early Guild Joiners
|
|
74
|
-
* Players who
|
|
78
|
+
* Players who fire "guild joined" within first 3 days get extra cloned
|
|
79
|
+
* "combat completed" events; non-joiners with 3+ early deaths churn.
|
|
75
80
|
* → Mixpanel: Cohort users who did "guild joined" within first 3 days
|
|
76
|
-
* → Compare D30 retention
|
|
81
|
+
* → Compare D30 retention vs other users — early joiners retain higher
|
|
77
82
|
*
|
|
78
83
|
* 4. CHURN: Death Spiral
|
|
79
|
-
* Players with 3+
|
|
80
|
-
*
|
|
81
|
-
* →
|
|
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
|
|
82
88
|
*
|
|
83
89
|
* 5. PURCHASE VALUE: Lucky Charm LTV
|
|
84
|
-
* Lucky Charm Pack
|
|
85
|
-
*
|
|
86
|
-
* →
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
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
|
|
186
|
+
*
|
|
187
|
+
* REAL-WORLD ANALOGUE: Quest economies scale rewards with player level
|
|
188
|
+
* so high-level zones remain meaningfully lucrative.
|
|
189
|
+
*
|
|
190
|
+
* 10. WHALE PURCHASES (TOP-SPENDER COHORT — everything)
|
|
191
|
+
*
|
|
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.
|
|
195
|
+
*
|
|
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.
|
|
236
|
+
*
|
|
237
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
238
|
+
* EXPECTED METRICS SUMMARY
|
|
239
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
240
|
+
*
|
|
241
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
242
|
+
* 12. COMBAT FUNNEL TIME-TO-CONVERT (funnel-post)
|
|
243
|
+
*
|
|
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.
|
|
246
|
+
*
|
|
247
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
248
|
+
*
|
|
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
|
|
254
|
+
*
|
|
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.)
|
|
261
|
+
*
|
|
262
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
263
|
+
* 13. COMBAT-PREP MAGIC NUMBER (in-funnel, everything)
|
|
264
|
+
*
|
|
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.
|
|
269
|
+
*
|
|
270
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
271
|
+
*
|
|
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
|
|
278
|
+
*
|
|
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
|
|
285
|
+
*
|
|
286
|
+
* REAL-WORLD ANALOGUE: Calculated prep wins; over-prep is paralysis.
|
|
122
287
|
*
|
|
123
288
|
* ───────────────────────────────────────────────────────────────────────────────
|
|
124
289
|
* EXPECTED METRICS SUMMARY
|
|
125
290
|
* ───────────────────────────────────────────────────────────────────────────────
|
|
126
291
|
*
|
|
127
292
|
* Hook | Metric | Baseline | Hook Effect | Ratio
|
|
128
|
-
*
|
|
129
|
-
* Ancient Compass | Quest
|
|
130
|
-
* Cursed Week | Death rate
|
|
293
|
+
* ----------------------|----------------------|----------|-------------|------
|
|
294
|
+
* Ancient Compass | Quest reward (compass)| 1x | 1.5x | 1.5x
|
|
295
|
+
* Cursed Week | Death rate days 40-47| 1x | ~ 5x | 5x
|
|
131
296
|
* Early Guild Join | D30 Retention | 20% | 80% | 4x
|
|
132
297
|
* Death Spiral | Retention (3+ deaths)| 100% | 30% | 0.3x
|
|
133
|
-
* Lucky Charm |
|
|
134
|
-
* Strategic Explorer | Dungeon completion | 45% | 85% | ~1.9x
|
|
298
|
+
* Lucky Charm | Avg purchase amt | 1x | 2x | 2x
|
|
299
|
+
* Strategic Explorer | Dungeon completion | 45% | 85% | ~ 1.9x
|
|
135
300
|
* Legendary Weapon | Combat win rate | 60% | 90% | 1.5x
|
|
136
|
-
* Premium Tier |
|
|
137
|
-
* Elite Tier |
|
|
138
|
-
* Gold Scaling (lvl 10) | Avg quest gold | ~100
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
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%
|
|
144
309
|
*/
|
|
145
310
|
|
|
146
311
|
// Generate consistent item/location IDs for lookup tables
|
|
@@ -152,7 +317,9 @@ const itemIds = v.range(1, 301).map(n => `item_${v.uid(7)}`);
|
|
|
152
317
|
const config = {
|
|
153
318
|
token,
|
|
154
319
|
seed: SEED,
|
|
155
|
-
|
|
320
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
321
|
+
datasetEnd: "2026-04-28T23:59:59Z",
|
|
322
|
+
// numDays: num_days,
|
|
156
323
|
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
157
324
|
numUsers: num_users,
|
|
158
325
|
hasAnonIds: false,
|
|
@@ -278,9 +445,6 @@ const config = {
|
|
|
278
445
|
"quest_id": questIds,
|
|
279
446
|
"reward_gold": u.weighNumRange(10, 500, 0.5, 100),
|
|
280
447
|
"reward_xp": u.weighNumRange(50, 2000, 0.5, 500),
|
|
281
|
-
"compass_user": [false],
|
|
282
|
-
"subscriber_advantage": ["Free"],
|
|
283
|
-
"level_scaled": [false],
|
|
284
448
|
}
|
|
285
449
|
},
|
|
286
450
|
{
|
|
@@ -299,9 +463,6 @@ const config = {
|
|
|
299
463
|
"dungeon_id": dungeonIds,
|
|
300
464
|
"time_spent_mins": u.weighNumRange(5, 120, 0.6, 30),
|
|
301
465
|
"completion_status": ["completed", "abandoned", "died"],
|
|
302
|
-
"strategic_explorer": [false],
|
|
303
|
-
"legendary_weapon_equipped": [false],
|
|
304
|
-
"subscriber_advantage": ["Free"],
|
|
305
466
|
}
|
|
306
467
|
},
|
|
307
468
|
{
|
|
@@ -310,10 +471,6 @@ const config = {
|
|
|
310
471
|
properties: {
|
|
311
472
|
"treasure_type": ["Gold", "Weapon", "Armor", "Potion", "Scroll", "Rare Artifact"],
|
|
312
473
|
"treasure_value": u.weighNumRange(5, 1000, 1.2, 50),
|
|
313
|
-
"legendary_drop": [false],
|
|
314
|
-
"strategic_explorer": [false],
|
|
315
|
-
"subscriber_advantage": ["Free"],
|
|
316
|
-
"elite_bonus": [false],
|
|
317
474
|
}
|
|
318
475
|
},
|
|
319
476
|
{
|
|
@@ -323,9 +480,6 @@ const config = {
|
|
|
323
480
|
"cause_of_death": ["Monster", "Trap", "Fall Damage", "Poison", "Friendly Fire"],
|
|
324
481
|
"player_level": u.weighNumRange(1, 50),
|
|
325
482
|
"resurrection_used": [false, false, false, true],
|
|
326
|
-
"cursed_week": [false],
|
|
327
|
-
"near_death_survival": [false],
|
|
328
|
-
"subscriber_advantage": ["Free"],
|
|
329
483
|
}
|
|
330
484
|
},
|
|
331
485
|
{
|
|
@@ -370,8 +524,6 @@ const config = {
|
|
|
370
524
|
],
|
|
371
525
|
"price_usd": [4.99, 9.99, 19.99, 49.99, 99.99],
|
|
372
526
|
"payment_method": ["Credit Card", "PayPal", "Apple Pay", "Google Pay"],
|
|
373
|
-
"lucky_charm_effect": [false],
|
|
374
|
-
"is_whale": [false],
|
|
375
527
|
}
|
|
376
528
|
},
|
|
377
529
|
{
|
|
@@ -431,10 +583,6 @@ const config = {
|
|
|
431
583
|
properties: {
|
|
432
584
|
"outcome": ["Victory", "Defeat", "Fled"],
|
|
433
585
|
"loot_gained": [false, false, false, true, true, true, true, true, true, true],
|
|
434
|
-
"legendary_weapon_equipped": [false],
|
|
435
|
-
"subscriber_advantage": ["Free"],
|
|
436
|
-
"near_death_survival": [false],
|
|
437
|
-
"guild_member_retained": [false],
|
|
438
586
|
}
|
|
439
587
|
},
|
|
440
588
|
{
|
|
@@ -563,12 +711,6 @@ const config = {
|
|
|
563
711
|
* 11. ALIGNMENT ARCHETYPE: Good=hero, Evil=villain, other=neutral (user hook)
|
|
564
712
|
*/
|
|
565
713
|
hook: function (record, type, meta) {
|
|
566
|
-
const NOW = dayjs();
|
|
567
|
-
const DATASET_START = NOW.subtract(num_days, 'days');
|
|
568
|
-
const CURSED_WEEK_START = DATASET_START.add(40, 'days');
|
|
569
|
-
const CURSED_WEEK_END = DATASET_START.add(47, 'days');
|
|
570
|
-
const LEGENDARY_WEAPON_RELEASE = DATASET_START.add(45, 'days');
|
|
571
|
-
|
|
572
714
|
// Hook #11: ALIGNMENT ARCHETYPE — derive archetype on user profile
|
|
573
715
|
if (type === "user") {
|
|
574
716
|
if (record.alignment === "Chaotic Evil" || record.alignment === "Neutral Evil") {
|
|
@@ -583,16 +725,27 @@ const config = {
|
|
|
583
725
|
// Hook #2 event-level part removed — cursed week now handled in everything hook
|
|
584
726
|
|
|
585
727
|
// Hook #7: TIMED RELEASE — Legendary Weapon
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
728
|
+
// (moved to everything hook — event hook fires before bunchIntoSessions
|
|
729
|
+
// reshuffles timestamps, causing tagged events to leak across the d45 boundary)
|
|
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
|
+
}
|
|
596
749
|
}
|
|
597
750
|
}
|
|
598
751
|
}
|
|
@@ -601,6 +754,8 @@ const config = {
|
|
|
601
754
|
if (type === "everything") {
|
|
602
755
|
const userEvents = record;
|
|
603
756
|
const profile = meta.profile;
|
|
757
|
+
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
758
|
+
const LEGENDARY_WEAPON_RELEASE = datasetStart.add(45, 'days');
|
|
604
759
|
|
|
605
760
|
// Stamp superProps from profile for consistency
|
|
606
761
|
userEvents.forEach(e => {
|
|
@@ -609,6 +764,47 @@ const config = {
|
|
|
609
764
|
e.subscription_tier = profile.subscription_tier;
|
|
610
765
|
});
|
|
611
766
|
|
|
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
|
+
|
|
612
808
|
const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
|
|
613
809
|
const userLevel = profile.level || 1;
|
|
614
810
|
|
|
@@ -651,7 +847,7 @@ const config = {
|
|
|
651
847
|
earlyDeaths++;
|
|
652
848
|
}
|
|
653
849
|
|
|
654
|
-
if (event.event === "find treasure" && event.
|
|
850
|
+
if (event.event === "find treasure" && event.treasure_type === "Shadowmourne Legendary") {
|
|
655
851
|
hasLegendaryWeapon = true;
|
|
656
852
|
}
|
|
657
853
|
|
|
@@ -663,168 +859,103 @@ const config = {
|
|
|
663
859
|
}
|
|
664
860
|
});
|
|
665
861
|
|
|
666
|
-
// Second pass:
|
|
862
|
+
// Second pass: raw mutations + cloning, no flag stamping
|
|
667
863
|
userEvents.forEach((event, idx) => {
|
|
668
864
|
const eventTime = dayjs(event.time);
|
|
669
865
|
|
|
670
|
-
//
|
|
671
|
-
if (event.event === "quest turned in") {
|
|
672
|
-
event.compass_user = false;
|
|
673
|
-
event.subscriber_advantage = "Free";
|
|
674
|
-
event.level_scaled = false;
|
|
675
|
-
}
|
|
676
|
-
if (event.event === "exit dungeon") {
|
|
677
|
-
event.strategic_explorer = false;
|
|
678
|
-
event.legendary_weapon_equipped = false;
|
|
679
|
-
event.subscriber_advantage = "Free";
|
|
680
|
-
}
|
|
681
|
-
if (event.event === "find treasure") {
|
|
682
|
-
event.strategic_explorer = false;
|
|
683
|
-
event.subscriber_advantage = "Free";
|
|
684
|
-
}
|
|
685
|
-
if (event.event === "combat completed") {
|
|
686
|
-
event.legendary_weapon_equipped = false;
|
|
687
|
-
event.subscriber_advantage = "Free";
|
|
688
|
-
event.near_death_survival = false;
|
|
689
|
-
}
|
|
690
|
-
if (event.event === "player death") {
|
|
691
|
-
event.near_death_survival = false;
|
|
692
|
-
event.subscriber_advantage = "Free";
|
|
693
|
-
}
|
|
694
|
-
if (event.event === "real money purchase") {
|
|
695
|
-
event.is_whale = false;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Hook #9: PROGRESSION SCALING — Quest gold scales with level
|
|
866
|
+
// Hook 9: PROGRESSION SCALING — Quest gold scales with level.
|
|
699
867
|
if (event.event === "quest turned in") {
|
|
700
868
|
const baseGold = event.reward_gold || 100;
|
|
701
869
|
event.reward_gold = Math.floor(baseGold * (1 + userLevel * 0.1));
|
|
702
|
-
event.level_scaled = true;
|
|
703
870
|
}
|
|
704
871
|
|
|
705
|
-
// Hook
|
|
872
|
+
// Hook 1: CONVERSION — Ancient Compass users earn 1.5x quest
|
|
873
|
+
// rewards plus 40% chance of bonus cloned quest.
|
|
706
874
|
if (usedAncientCompass && event.event === "quest turned in") {
|
|
707
875
|
event.reward_gold = Math.floor((event.reward_gold || 100) * 1.5);
|
|
708
876
|
event.reward_xp = Math.floor((event.reward_xp || 500) * 1.5);
|
|
709
|
-
event.compass_user = true;
|
|
710
877
|
|
|
711
878
|
if (chance.bool({ likelihood: 40 })) {
|
|
712
|
-
|
|
879
|
+
userEvents.splice(idx + 1, 0, {
|
|
713
880
|
...event,
|
|
714
881
|
time: eventTime.add(chance.integer({ min: 10, max: 120 }), 'minutes').toISOString(),
|
|
715
882
|
quest_id: chance.pickone(questIds),
|
|
716
883
|
reward_gold: chance.integer({ min: 100, max: 500 }),
|
|
717
884
|
reward_xp: chance.integer({ min: 500, max: 2000 }),
|
|
718
|
-
|
|
719
|
-
};
|
|
720
|
-
userEvents.splice(idx + 1, 0, extraQuest);
|
|
885
|
+
});
|
|
721
886
|
}
|
|
722
887
|
}
|
|
723
888
|
|
|
724
|
-
// Hook
|
|
889
|
+
// Hook 5: PURCHASE VALUE — Lucky charm buyers see 2x prices
|
|
890
|
+
// and 35% chance of bonus high-value cloned purchase.
|
|
725
891
|
if (boughtLuckyCharm) {
|
|
726
|
-
if (event.event === "real money purchase") {
|
|
727
|
-
|
|
728
|
-
const currentPrice = event.price_usd;
|
|
729
|
-
if (currentPrice < 49.99) {
|
|
730
|
-
event.price_usd = currentPrice * 2;
|
|
731
|
-
}
|
|
732
|
-
event.lucky_charm_effect = true;
|
|
733
|
-
}
|
|
892
|
+
if (event.event === "real money purchase" && event.price_usd && event.price_usd < 49.99) {
|
|
893
|
+
event.price_usd = event.price_usd * 2;
|
|
734
894
|
}
|
|
735
|
-
|
|
736
895
|
if (event.event === "item purchased" && chance.bool({ likelihood: 35 })) {
|
|
737
896
|
const purchaseTemplate = userEvents.find(e => e.event === "real money purchase");
|
|
738
897
|
if (purchaseTemplate) {
|
|
739
|
-
|
|
898
|
+
userEvents.splice(idx + 1, 0, {
|
|
740
899
|
...purchaseTemplate,
|
|
741
900
|
time: eventTime.add(chance.integer({ min: 1, max: 3 }), 'days').toISOString(),
|
|
742
901
|
user_id: event.user_id,
|
|
743
|
-
product: chance.pickone([
|
|
744
|
-
"Premium Currency (5000)",
|
|
745
|
-
"Legendary Weapon Chest",
|
|
746
|
-
"Season Pass"
|
|
747
|
-
]),
|
|
902
|
+
product: chance.pickone(["Premium Currency (5000)", "Legendary Weapon Chest", "Season Pass"]),
|
|
748
903
|
price_usd: chance.pickone([19.99, 49.99, 99.99]),
|
|
749
904
|
payment_method: chance.pickone(["Credit Card", "PayPal"]),
|
|
750
|
-
|
|
751
|
-
};
|
|
752
|
-
userEvents.splice(idx + 1, 0, extraPurchase);
|
|
905
|
+
});
|
|
753
906
|
}
|
|
754
907
|
}
|
|
755
908
|
}
|
|
756
909
|
|
|
757
|
-
// Hook
|
|
758
|
-
if (isWhale && event.event === "real money purchase") {
|
|
759
|
-
|
|
760
|
-
event.price_usd = Math.round(event.price_usd * 1.8 * 100) / 100;
|
|
761
|
-
}
|
|
762
|
-
event.is_whale = true;
|
|
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;
|
|
763
913
|
}
|
|
764
914
|
|
|
765
|
-
// Hook
|
|
915
|
+
// Hook 6: BEHAVIORS TOGETHER — inspect+search dungeons get
|
|
916
|
+
// 85% completion rate + 2x treasure value.
|
|
766
917
|
if (inspectedBeforeDungeon && searchedBeforeDungeon) {
|
|
767
|
-
if (event.event === "exit dungeon") {
|
|
768
|
-
|
|
769
|
-
if (chance.bool({ likelihood: 85 })) {
|
|
770
|
-
event.completion_status = "completed";
|
|
771
|
-
event.strategic_explorer = true;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
918
|
+
if (event.event === "exit dungeon" && event.completion_status !== "completed" && chance.bool({ likelihood: 85 })) {
|
|
919
|
+
event.completion_status = "completed";
|
|
774
920
|
}
|
|
775
|
-
|
|
776
921
|
if (event.event === "find treasure") {
|
|
777
922
|
event.treasure_value = Math.floor((event.treasure_value || 50) * 2);
|
|
778
|
-
event.strategic_explorer = true;
|
|
779
923
|
}
|
|
780
924
|
}
|
|
781
925
|
|
|
782
|
-
// Hook
|
|
926
|
+
// Hook 7: TIMED RELEASE — Legendary owners get 90% combat
|
|
927
|
+
// wins + 0.6x dungeon time.
|
|
783
928
|
if (hasLegendaryWeapon) {
|
|
784
|
-
if (event.event === "combat completed") {
|
|
785
|
-
|
|
786
|
-
if (chance.bool({ likelihood: 90 })) {
|
|
787
|
-
event.outcome = "Victory";
|
|
788
|
-
event.legendary_weapon_equipped = true;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
929
|
+
if (event.event === "combat completed" && event.outcome !== "Victory" && chance.bool({ likelihood: 90 })) {
|
|
930
|
+
event.outcome = "Victory";
|
|
791
931
|
}
|
|
792
|
-
|
|
793
932
|
if (event.event === "exit dungeon") {
|
|
794
933
|
event.completion_status = "completed";
|
|
795
934
|
event.time_spent_mins = Math.floor((event.time_spent_mins || 60) * 0.6);
|
|
796
|
-
event.legendary_weapon_equipped = true;
|
|
797
935
|
}
|
|
798
936
|
}
|
|
799
937
|
|
|
800
|
-
// Hook
|
|
938
|
+
// Hook 8: SUBSCRIPTION TIER — Premium/Elite get win+reward+
|
|
939
|
+
// completion+treasure boosts. Reads tier from profile.
|
|
801
940
|
if (subscriptionTier === "Premium" || subscriptionTier === "Elite") {
|
|
802
941
|
const isElite = subscriptionTier === "Elite";
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
if (
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
event.outcome = "Victory";
|
|
809
|
-
event.loot_gained = true;
|
|
810
|
-
event.subscriber_advantage = subscriptionTier;
|
|
811
|
-
}
|
|
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;
|
|
812
947
|
}
|
|
813
948
|
}
|
|
814
|
-
|
|
815
949
|
if (event.event === "quest turned in") {
|
|
816
950
|
const rewardMultiplier = isElite ? 1.8 : 1.4;
|
|
817
951
|
event.reward_gold = Math.floor((event.reward_gold || 100) * rewardMultiplier);
|
|
818
952
|
event.reward_xp = Math.floor((event.reward_xp || 500) * rewardMultiplier);
|
|
819
|
-
event.subscriber_advantage = subscriptionTier;
|
|
820
953
|
}
|
|
821
|
-
|
|
822
954
|
if (event.event === "exit dungeon") {
|
|
823
955
|
if (event.completion_status !== "completed") {
|
|
824
956
|
const completionBoost = isElite ? 65 : 45;
|
|
825
957
|
if (Math.random() * 100 < completionBoost) {
|
|
826
958
|
event.completion_status = "completed";
|
|
827
|
-
event.subscriber_advantage = subscriptionTier;
|
|
828
959
|
}
|
|
829
960
|
}
|
|
830
961
|
if (event.completion_status === "completed") {
|
|
@@ -832,46 +963,39 @@ const config = {
|
|
|
832
963
|
event.time_spent_mins = Math.floor((event.time_spent_mins || 60) * speedBoost);
|
|
833
964
|
}
|
|
834
965
|
}
|
|
835
|
-
|
|
836
966
|
if (event.event === "find treasure") {
|
|
837
967
|
const treasureBoost = isElite ? 2.0 : 1.5;
|
|
838
968
|
event.treasure_value = Math.floor((event.treasure_value || 50) * treasureBoost);
|
|
839
|
-
event.subscriber_advantage = subscriptionTier;
|
|
840
969
|
}
|
|
841
|
-
|
|
842
|
-
if (event.event === "player death" && !event.cursed_week) {
|
|
970
|
+
if (event.event === "player death") {
|
|
843
971
|
const survivalChance = isElite ? 50 : 30;
|
|
844
972
|
if (Math.random() * 100 < survivalChance) {
|
|
845
973
|
event.event = "combat completed";
|
|
846
974
|
event.outcome = "Victory";
|
|
847
975
|
event.loot_gained = true;
|
|
848
|
-
event.subscriber_advantage = subscriptionTier;
|
|
849
|
-
event.near_death_survival = true;
|
|
850
976
|
}
|
|
851
977
|
}
|
|
852
|
-
|
|
853
|
-
if (isElite && Math.random() * 100 < 15) {
|
|
978
|
+
if (isElite && Math.random() * 100 < 5) {
|
|
854
979
|
if (event.event === "quest turned in" || event.event === "exit dungeon") {
|
|
855
980
|
const treasureTemplate = userEvents.find(e => e.event === "find treasure");
|
|
856
981
|
if (treasureTemplate) {
|
|
857
982
|
const treasureTypes = ["Rare Artifact", "Gold", "Weapon", "Armor"];
|
|
858
|
-
|
|
983
|
+
userEvents.splice(idx + 1, 0, {
|
|
859
984
|
...treasureTemplate,
|
|
860
985
|
time: eventTime.add(Math.floor(Math.random() * 26) + 5, 'minutes').toISOString(),
|
|
861
986
|
user_id: event.user_id,
|
|
862
987
|
treasure_type: treasureTypes[Math.floor(Math.random() * treasureTypes.length)],
|
|
863
988
|
treasure_value: Math.floor(Math.random() * 601) + 200,
|
|
864
|
-
|
|
865
|
-
elite_bonus: true,
|
|
866
|
-
};
|
|
867
|
-
userEvents.splice(idx + 1, 0, bonusEvent);
|
|
989
|
+
});
|
|
868
990
|
}
|
|
869
991
|
}
|
|
870
992
|
}
|
|
871
993
|
}
|
|
872
994
|
});
|
|
873
995
|
|
|
874
|
-
// Hook
|
|
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.
|
|
875
999
|
if (firstEventTime) {
|
|
876
1000
|
const deathTemplate = userEvents.find(e => e.event === "player death");
|
|
877
1001
|
if (deathTemplate) {
|
|
@@ -884,7 +1008,7 @@ const config = {
|
|
|
884
1008
|
const deathsToInject = Math.floor(cursedEvents.length * 0.6);
|
|
885
1009
|
for (let d = 0; d < deathsToInject; d++) {
|
|
886
1010
|
const sourceEvent = cursedEvents[d % cursedEvents.length];
|
|
887
|
-
|
|
1011
|
+
userEvents.push({
|
|
888
1012
|
...deathTemplate,
|
|
889
1013
|
time: dayjs(sourceEvent.time).add(chance.integer({ min: 1, max: 30 }), 'minutes').toISOString(),
|
|
890
1014
|
user_id: sourceEvent.user_id,
|
|
@@ -892,41 +1016,64 @@ const config = {
|
|
|
892
1016
|
cause_of_death: "Curse",
|
|
893
1017
|
player_level: chance.integer({ min: 1, max: 50 }),
|
|
894
1018
|
resurrection_used: chance.bool({ likelihood: 80 }),
|
|
895
|
-
|
|
896
|
-
near_death_survival: false,
|
|
897
|
-
subscriber_advantage: "Free",
|
|
898
|
-
};
|
|
899
|
-
userEvents.push(injected);
|
|
1019
|
+
});
|
|
900
1020
|
}
|
|
901
1021
|
}
|
|
902
1022
|
}
|
|
903
1023
|
|
|
904
|
-
// Hook
|
|
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.
|
|
905
1027
|
const shouldChurn = (!joinedGuildEarly && earlyDeaths >= 3) || (earlyDeaths >= 5);
|
|
906
|
-
|
|
907
1028
|
if (shouldChurn) {
|
|
908
1029
|
const firstWeekEnd = firstEventTime ? firstEventTime.add(7, 'days') : null;
|
|
909
1030
|
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
if (chance.bool({ likelihood: 70 })) {
|
|
913
|
-
userEvents.splice(i, 1);
|
|
914
|
-
}
|
|
1031
|
+
if (firstWeekEnd && dayjs(userEvents[i].time).isAfter(firstWeekEnd) && chance.bool({ likelihood: 70 })) {
|
|
1032
|
+
userEvents.splice(i, 1);
|
|
915
1033
|
}
|
|
916
1034
|
}
|
|
917
1035
|
} else if (joinedGuildEarly) {
|
|
918
1036
|
const lastEvent = userEvents[userEvents.length - 1];
|
|
919
1037
|
const combatTemplate = userEvents.find(e => e.event === "combat completed");
|
|
920
1038
|
if (lastEvent && combatTemplate && chance.bool({ likelihood: 60 })) {
|
|
921
|
-
|
|
1039
|
+
userEvents.push({
|
|
922
1040
|
...combatTemplate,
|
|
923
1041
|
time: dayjs(lastEvent.time).add(chance.integer({ min: 1, max: 5 }), 'days').toISOString(),
|
|
924
1042
|
user_id: lastEvent.user_id,
|
|
925
1043
|
outcome: "Victory",
|
|
926
1044
|
loot_gained: true,
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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
|
+
}
|
|
930
1077
|
}
|
|
931
1078
|
}
|
|
932
1079
|
}
|