@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.
Files changed (74) hide show
  1. package/dungeons/technical/array-of-object-lookup.js +0 -2
  2. package/dungeons/technical/simple.js +3 -4
  3. package/dungeons/technical/simplest-schema.json +5 -0
  4. package/dungeons/technical/text-generation.js +1 -1
  5. package/dungeons/vertical/ai-platform.js +192 -133
  6. package/dungeons/vertical/community.js +64 -34
  7. package/dungeons/vertical/crypto.js +333 -224
  8. package/dungeons/vertical/dating.js +228 -282
  9. package/dungeons/vertical/devtools.js +164 -62
  10. package/dungeons/vertical/ecommerce.js +240 -91
  11. package/dungeons/vertical/education.js +328 -310
  12. package/dungeons/vertical/fintech.js +437 -311
  13. package/dungeons/vertical/fitness.js +141 -58
  14. package/dungeons/vertical/food-delivery.js +298 -318
  15. package/dungeons/vertical/gaming.js +378 -231
  16. package/dungeons/vertical/healthcare.js +140 -60
  17. package/dungeons/vertical/insurance-application.js +168 -73
  18. package/dungeons/vertical/logistics.js +95 -6
  19. package/dungeons/vertical/marketplace.js +137 -51
  20. package/dungeons/vertical/media.js +241 -386
  21. package/dungeons/vertical/real-estate.js +322 -315
  22. package/dungeons/vertical/sass.js +253 -263
  23. package/dungeons/vertical/social.js +262 -206
  24. package/dungeons/vertical/travel.js +59 -16
  25. package/index.js +17 -17
  26. package/lib/core/config-validator.js +93 -21
  27. package/lib/core/context.js +10 -24
  28. package/lib/core/storage.js +6 -1
  29. package/lib/generators/events.js +11 -14
  30. package/lib/generators/funnels.js +12 -4
  31. package/lib/generators/mirror.js +3 -2
  32. package/lib/generators/product-names.js +1 -1
  33. package/lib/generators/scd.js +2 -1
  34. package/lib/generators/text.js +1 -1
  35. package/lib/orchestrators/user-loop.js +47 -47
  36. package/lib/utils/utils.js +71 -39
  37. package/package.json +1 -1
  38. package/scripts/smoke-test-all.mjs +162 -0
  39. package/scripts/verify-runner.mjs +72 -24
  40. package/types.d.ts +38 -15
  41. package/dungeons/technical/ad-spend-schema.json +0 -128
  42. package/dungeons/technical/anonymous-users-schema.json +0 -92
  43. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  44. package/dungeons/technical/experiments-schema.json +0 -203
  45. package/dungeons/technical/foobar-schema.json +0 -362
  46. package/dungeons/technical/group-analytics-schema.json +0 -241
  47. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  48. package/dungeons/technical/nested-objects-schema.json +0 -145
  49. package/dungeons/technical/retention-cadence-schema.json +0 -37
  50. package/dungeons/technical/sanity-schema.json +0 -185
  51. package/dungeons/technical/scale-test-schema.json +0 -70
  52. package/dungeons/technical/scd-schema.json +0 -467
  53. package/dungeons/technical/simple-schema.json +0 -362
  54. package/dungeons/technical/text-generation-schema.json +0 -1062
  55. package/dungeons/vertical/ai-platform-schema.json +0 -617
  56. package/dungeons/vertical/community-schema.json +0 -579
  57. package/dungeons/vertical/crypto-schema.json +0 -546
  58. package/dungeons/vertical/dating-schema.json +0 -401
  59. package/dungeons/vertical/devtools-schema.json +0 -601
  60. package/dungeons/vertical/ecommerce-schema.json +0 -604
  61. package/dungeons/vertical/education-schema.json +0 -5686
  62. package/dungeons/vertical/fintech-schema.json +0 -630
  63. package/dungeons/vertical/fitness-schema.json +0 -530
  64. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  65. package/dungeons/vertical/gaming-schema.json +0 -2703
  66. package/dungeons/vertical/healthcare-schema.json +0 -549
  67. package/dungeons/vertical/insurance-application-schema.json +0 -485
  68. package/dungeons/vertical/logistics-schema.json +0 -574
  69. package/dungeons/vertical/marketplace-schema.json +0 -533
  70. package/dungeons/vertical/media-schema.json +0 -4749
  71. package/dungeons/vertical/real-estate-schema.json +0 -527
  72. package/dungeons/vertical/sass-schema.json +0 -3128
  73. package/dungeons/vertical/social-schema.json +0 -620
  74. 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 (11 architected patterns)
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 have 3x quest completion rate
63
- * and earn 1.5x more rewards.
64
- * → Mixpanel: Segment users by "use item" where item_type = "Ancient Compass"
65
- * → Compare quest completion rate and average reward_gold / reward_xp
66
- * → Look for compass_user = true on quest turned in events
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 5x, dungeon completion plummets, resurrection usage 4x.
70
- * Mixpanel: Chart "player death" count by day — clear spike days 40-47
71
- * → Filter cause_of_death = "Curse" and cursed_week = true
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 join a guild within first 3 days have 80% D30 retention vs 20%.
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 rate; look for guild_member_retained = true
81
+ * → Compare D30 retention vs other users early joiners retain higher
77
82
  *
78
83
  * 4. CHURN: Death Spiral
79
- * Players with 3+ deaths in first week have 70% churn rate (events removed).
80
- * Mixpanel: Segment users by count of "player death" in first 7 days
81
- * → Bucket: 0-2, 3-4, 5+ deaths compare events after day 7
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 buyers become 5x higher LTV customers.
85
- * Mixpanel: Segment by "real money purchase" where product = "Lucky Charm Pack"
86
- * → Compare total revenue, purchase frequency; look for lucky_charm_effect = true
87
- *
88
- * 6. BEHAVIORS TOGETHER: Strategic Explorers
89
- * Players who both "inspect" AND "search for clues" before dungeons have
90
- * 85% dungeon completion (vs 45%) and 2x treasure value.
91
- * → Look for strategic_explorer = true on exit dungeon / find treasure events
92
- *
93
- * 7. TIMED RELEASE: Shadowmourne Legendary Weapon (day 45)
94
- * 2% of players find the legendary weapon after release. They get 90% combat
95
- * win rate (vs 60%) and complete dungeons 40% faster.
96
- * Filter "find treasure" where treasure_type = "Shadowmourne Legendary"
97
- * → Look for legendary_weapon_equipped = true
98
- *
99
- * 8. SUBSCRIPTION TIER: Premium/Elite Advantage
100
- * Premium: 50% better combat wins, 1.4x rewards, 45% higher dungeon completion
101
- * Elite: 70% better combat wins, 1.8x rewards, 65% higher dungeon completion,
102
- * bonus treasure events, reduced death rates.
103
- * Segment by subscription_tier super property
104
- * → Look for subscriber_advantage = "Premium" or "Elite"
105
- *
106
- * 9. PROGRESSION SCALING: Gold Reward by Level
107
- * Quest gold reward scales with player level: reward_gold *= (1 + level * 0.1).
108
- * A level-10 player earns 2x gold and a level-20 player earns 3x gold vs level-1.
109
- * Mixpanel: Insights on "quest turned in", avg of reward_gold, breakdown by level
110
- * → Expected: linear positive correlation between level and avg reward_gold
111
- *
112
- * 10. WHALE PURCHASES: Top-Spender Cohort
113
- * ~33% of users (deterministic via user_id hash) are "whales" who spend 1.8x
114
- * on real money purchases and are tagged is_whale: true.
115
- * → Mixpanel: Insights on "real money purchase", avg of price_usd, breakdown by is_whale
116
- * → Expected: is_whale=true ~1.8x higher avg purchase amount than is_whale=false
117
- *
118
- * 11. ALIGNMENT ARCHETYPE: Hero / Villain / Neutral
119
- * User profiles are enriched with an "archetype" derived from alignment:
120
- * Good "hero" (~22%), Evil → "villain" (~22%), other → "neutral" (~56%).
121
- * → Mixpanel: Insights, total unique users, breakdown by user profile "archetype"
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 completion | 55% | 85-90% | ~1.6x
130
- * Cursed Week | Death rate | 8% | 40% | 5x
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 | LTV | $15 | $75 | 5x
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 | Combat win rate | 60% | 90% | 1.5x
137
- * Elite Tier | Combat win rate | 60% | 102% | 1.7x
138
- * Gold Scaling (lvl 10) | Avg quest gold | ~100 | ~200 | 2.0x
139
- * Gold Scaling (lvl 20) | Avg quest gold | ~100 | ~300 | 3.0x
140
- * Whale Purchases | Avg real money spend | ~$15 | ~$27 | 1.8x
141
- * Hero archetype | User share | | ~22% | 2/9
142
- * Villain archetype | User share | | ~22% | 2/9
143
- * Neutral archetype | User share | | ~56% | 5/9
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
- numDays: num_days,
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
- if (type === "event") {
587
- const EVENT_TIME = dayjs(record.time);
588
-
589
- if (record.event === "find treasure") {
590
- if (EVENT_TIME.isAfter(LEGENDARY_WEAPON_RELEASE) && chance.bool({ likelihood: 2 })) {
591
- record.treasure_type = "Shadowmourne Legendary";
592
- record.treasure_value = 50000;
593
- record.legendary_drop = true;
594
- } else {
595
- record.legendary_drop = false;
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.legendary_drop) {
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: modify events based on patterns
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
- // Set schema defaults for conditional properties
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 #1: CONVERSION — Ancient Compass users complete more quests
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
- const extraQuest = {
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
- compass_user: true,
719
- };
720
- userEvents.splice(idx + 1, 0, extraQuest);
885
+ });
721
886
  }
722
887
  }
723
888
 
724
- // Hook #5: PURCHASE VALUE — Lucky charm buyers spend 5x more
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
- if (event.price_usd) {
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
- const extraPurchase = {
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
- lucky_charm_effect: true,
751
- };
752
- userEvents.splice(idx + 1, 0, extraPurchase);
905
+ });
753
906
  }
754
907
  }
755
908
  }
756
909
 
757
- // Hook #10: WHALE PURCHASES — boost real money purchase amounts
758
- if (isWhale && event.event === "real money purchase") {
759
- if (event.price_usd) {
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 #6: BEHAVIORS TOGETHER — Inspect + Search before dungeon
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
- if (event.completion_status !== "completed") {
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 #7: TIMED RELEASE — Legendary weapon owners dominate
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
- if (event.outcome !== "Victory") {
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 #8: SUBSCRIPTION TIER — Premium/Elite advantages
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
- if (event.event === "combat completed") {
805
- if (event.outcome !== "Victory") {
806
- const winBoost = isElite ? 70 : 50;
807
- if (Math.random() * 100 < winBoost) {
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
- const bonusEvent = {
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
- subscriber_advantage: "Elite",
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 #2: CURSED WEEK — inject death events for days 40-47 of each user's timeline
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
- const injected = {
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
- cursed_week: true,
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 #3 RETENTION + Hook #4 CHURN
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
- const evt = userEvents[i];
911
- if (firstWeekEnd && dayjs(evt.time).isAfter(firstWeekEnd)) {
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
- const retentionEvent = {
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
- guild_member_retained: true,
928
- };
929
- userEvents.push(retentionEvent);
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
  }