@ak--47/dungeon-master 1.0.0
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/README.md +518 -0
- package/dungeons/array-of-object-lookup-schema.json +327 -0
- package/dungeons/array-of-object-lookup.js +220 -0
- package/dungeons/ecommerce-schema.json +462 -0
- package/dungeons/ecommerce.js +447 -0
- package/dungeons/education-schema.json +2409 -0
- package/dungeons/education.js +768 -0
- package/dungeons/fintech-schema.json +14034 -0
- package/dungeons/fintech.js +696 -0
- package/dungeons/foobar-schema.json +403 -0
- package/dungeons/foobar.js +296 -0
- package/dungeons/food-delivery-schema.json +192 -0
- package/dungeons/food-delivery.js +602 -0
- package/dungeons/food-schema.json +1152 -0
- package/dungeons/food.js +754 -0
- package/dungeons/gaming-schema.json +1270 -0
- package/dungeons/gaming.js +508 -0
- package/dungeons/insurance-application-schema.json +204 -0
- package/dungeons/insurance-application.js +605 -0
- package/dungeons/media-schema.json +906 -0
- package/dungeons/media.js +790 -0
- package/dungeons/retention-cadence-schema.json +78 -0
- package/dungeons/retention-cadence.js +244 -0
- package/dungeons/rpg-schema.json +4526 -0
- package/dungeons/rpg.js +919 -0
- package/dungeons/sanity-schema.json +255 -0
- package/dungeons/sanity.js +152 -0
- package/dungeons/sass-schema.json +1291 -0
- package/dungeons/sass.js +795 -0
- package/dungeons/scd-schema.json +919 -0
- package/dungeons/scd.js +277 -0
- package/dungeons/simple-schema.json +608 -0
- package/dungeons/simple.js +285 -0
- package/dungeons/simplest-schema.json +1418 -0
- package/dungeons/simplest.js +392 -0
- package/dungeons/social-schema.json +1118 -0
- package/dungeons/social.js +686 -0
- package/dungeons/text-generation-schema.json +3096 -0
- package/dungeons/text-generation.js +812 -0
- package/index.js +567 -0
- package/lib/core/config-validator.js +395 -0
- package/lib/core/context.js +204 -0
- package/lib/core/dungeon-loader.js +337 -0
- package/lib/core/storage.js +379 -0
- package/lib/generators/adspend.js +132 -0
- package/lib/generators/events.js +271 -0
- package/lib/generators/funnels.js +407 -0
- package/lib/generators/mirror.js +167 -0
- package/lib/generators/product-lookup.js +262 -0
- package/lib/generators/product-names.js +195 -0
- package/lib/generators/profiles.js +93 -0
- package/lib/generators/scd.js +124 -0
- package/lib/generators/text.js +1192 -0
- package/lib/orchestrators/mixpanel-sender.js +266 -0
- package/lib/orchestrators/user-loop.js +335 -0
- package/lib/templates/abbreviated.d.ts +169 -0
- package/lib/templates/defaults.js +1405 -0
- package/lib/templates/phrases.js +2526 -0
- package/lib/templates/schema.d.ts +173 -0
- package/lib/templates/soup-presets.js +188 -0
- package/lib/utils/function-registry.js +302 -0
- package/lib/utils/json-evaluator.js +172 -0
- package/lib/utils/logger.js +34 -0
- package/lib/utils/utils.js +1490 -0
- package/package.json +89 -0
- package/types.d.ts +865 -0
package/dungeons/rpg.js
ADDED
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import utc from "dayjs/plugin/utc.js";
|
|
3
|
+
import "dotenv/config";
|
|
4
|
+
import * as u from "../lib/utils/utils.js";
|
|
5
|
+
import * as v from "ak-tools";
|
|
6
|
+
|
|
7
|
+
const SEED = "harness-gaming";
|
|
8
|
+
dayjs.extend(utc);
|
|
9
|
+
const chance = u.initChance(SEED);
|
|
10
|
+
const num_users = 5_000;
|
|
11
|
+
const days = 100;
|
|
12
|
+
|
|
13
|
+
/** @typedef {import("../types.d.ts").Dungeon} Config */
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
17
|
+
* DATASET OVERVIEW
|
|
18
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
19
|
+
*
|
|
20
|
+
* D&D-Style Action RPG — a fantasy adventure game with deep strategic gameplay
|
|
21
|
+
* and a player-driven economy.
|
|
22
|
+
*
|
|
23
|
+
* CORE LOOP: Players create characters (6 classes, 5 races) and progress through
|
|
24
|
+
* quests, dungeon crawls, and combat encounters. Strategic preparation (inspect,
|
|
25
|
+
* search for clues, Ancient Compass) creates skill gaps. Guild membership drives
|
|
26
|
+
* social retention. Monetization via premium currency, lucky charms, season pass,
|
|
27
|
+
* and subscription tiers (Free / Premium / Elite).
|
|
28
|
+
*
|
|
29
|
+
* SCALE: 5,000 users × 120 events = 600K events over 100 days
|
|
30
|
+
* 20 event types, 7 funnels, guild group analytics, subscription tiers
|
|
31
|
+
*
|
|
32
|
+
* KEY SYSTEMS:
|
|
33
|
+
* - Quests: accept → objective → turn in (5 quest types, gold/XP rewards)
|
|
34
|
+
* - Dungeons: enter → find treasure → exit (50 dungeons, party-based, 3 outcomes)
|
|
35
|
+
* - Combat: initiate → complete (6 enemy types, level scaling, loot)
|
|
36
|
+
* - Economy: item purchase/sell, real money purchases, vendor types
|
|
37
|
+
* - Progression: level up (1-50), character classes, stat points
|
|
38
|
+
* - Social: guild join/leave (5-100 members, guild levels 1-20)
|
|
39
|
+
* - Monetization: premium currency, lucky charms, legendary chests, season pass
|
|
40
|
+
* - Subscriptions: Free / Premium ($9.99/mo) / Elite ($19.99/mo)
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
45
|
+
* ANALYTICS HOOKS (8 architected patterns)
|
|
46
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
47
|
+
*
|
|
48
|
+
* 1. CONVERSION: Ancient Compass Effect
|
|
49
|
+
* Players who use the "Ancient Compass" item have 3x quest completion rate
|
|
50
|
+
* and earn 1.5x more rewards.
|
|
51
|
+
* → Mixpanel: Segment users by "use item" where item_type = "Ancient Compass"
|
|
52
|
+
* → Compare quest completion rate and average reward_gold / reward_xp
|
|
53
|
+
* → Look for compass_user = true on quest turned in events
|
|
54
|
+
*
|
|
55
|
+
* 2. TIME-BASED: Cursed Week (days 40-47)
|
|
56
|
+
* Death rates spike 5x, dungeon completion plummets, resurrection usage 4x.
|
|
57
|
+
* → Mixpanel: Chart "player death" count by day — clear spike days 40-47
|
|
58
|
+
* → Filter cause_of_death = "Curse" and cursed_week = true
|
|
59
|
+
* → Compare completion_status distribution before/during/after
|
|
60
|
+
*
|
|
61
|
+
* 3. RETENTION: Early Guild Joiners
|
|
62
|
+
* Players who join a guild within first 3 days have 80% D30 retention vs 20%.
|
|
63
|
+
* → Mixpanel: Create cohort of users who did "guild joined" within first 3 days
|
|
64
|
+
* → Compare D30 retention rate and average events per user
|
|
65
|
+
* → Look for guild_member_retained = true on late engagement events
|
|
66
|
+
*
|
|
67
|
+
* 4. CHURN: Death Spiral
|
|
68
|
+
* Players with 3+ deaths in first week have 70% churn rate (events removed).
|
|
69
|
+
* → Mixpanel: Segment users by count of "player death" in first 7 days
|
|
70
|
+
* → Bucket: 0-2, 3-4, 5+ deaths — compare events after day 7
|
|
71
|
+
* → Users with 3+ early deaths have 70% fewer post-week-1 events
|
|
72
|
+
*
|
|
73
|
+
* 5. PURCHASE VALUE: Lucky Charm LTV
|
|
74
|
+
* Lucky Charm Pack buyers become 5x higher LTV customers — higher purchase
|
|
75
|
+
* frequency, upgraded tiers, more total spend.
|
|
76
|
+
* → Mixpanel: Segment by "real money purchase" where product contains "Lucky Charm"
|
|
77
|
+
* → Compare total revenue per user, purchase frequency, average order value
|
|
78
|
+
* → Look for lucky_charm_effect = true on subsequent purchases
|
|
79
|
+
*
|
|
80
|
+
* 6. BEHAVIORS TOGETHER: Strategic Explorers
|
|
81
|
+
* Players who both "inspect" AND "search for clues" before dungeons have
|
|
82
|
+
* 85% dungeon completion (vs 45%) and 2x treasure value.
|
|
83
|
+
* → Mixpanel: Segment users who did BOTH "inspect" AND "search for clues"
|
|
84
|
+
* → Filter dungeon funnel: enter → exit, compare completion_status rates
|
|
85
|
+
* → Look for strategic_explorer = true on exit dungeon / find treasure events
|
|
86
|
+
*
|
|
87
|
+
* 7. TIMED RELEASE: Shadowmourne Legendary Weapon (day 45)
|
|
88
|
+
* 2% of players find the legendary weapon after release. They get 90% combat
|
|
89
|
+
* win rate (vs 60%) and complete dungeons 40% faster.
|
|
90
|
+
* → Mixpanel: Filter "find treasure" where treasure_type = "Shadowmourne Legendary"
|
|
91
|
+
* → Compare combat win rate and dungeon time_spent_mins before/after day 45
|
|
92
|
+
* → Look for legendary_weapon_equipped = true
|
|
93
|
+
*
|
|
94
|
+
* 8. SUBSCRIPTION TIER: Premium/Elite Advantage
|
|
95
|
+
* Premium: 50% better combat wins, 1.4x rewards, 45% higher dungeon completion
|
|
96
|
+
* Elite: 70% better combat wins, 1.8x rewards, 65% higher dungeon completion,
|
|
97
|
+
* bonus treasure events, reduced death rates.
|
|
98
|
+
* → Mixpanel: Segment by subscription_tier super property
|
|
99
|
+
* → Compare combat win rates, reward_gold, dungeon completion, death rates
|
|
100
|
+
* → Look for subscriber_advantage = "Premium" or "Elite"
|
|
101
|
+
*
|
|
102
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
* EXPECTED METRICS SUMMARY
|
|
104
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
105
|
+
*
|
|
106
|
+
* Hook | Metric | Baseline | Hook Effect | Ratio
|
|
107
|
+
* ──────────────────────|──────────────────────|──────────|─────────────|──────
|
|
108
|
+
* Ancient Compass | Quest completion | 55% | 85-90% | ~1.6x
|
|
109
|
+
* Cursed Week | Death rate | 8% | 40% | 5x
|
|
110
|
+
* Early Guild Join | D30 Retention | 20% | 80% | 4x
|
|
111
|
+
* Death Spiral | Retention (3+ deaths)| 100% | 30% | 0.3x
|
|
112
|
+
* Lucky Charm | LTV | $15 | $75 | 5x
|
|
113
|
+
* Strategic Explorer | Dungeon completion | 45% | 85% | ~1.9x
|
|
114
|
+
* Legendary Weapon | Combat win rate | 60% | 90% | 1.5x
|
|
115
|
+
* Premium Tier | Combat win rate | 60% | 90% | 1.5x
|
|
116
|
+
* Elite Tier | Combat win rate | 60% | 102% | 1.7x
|
|
117
|
+
*
|
|
118
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
119
|
+
* ADVANCED ANALYSIS IDEAS
|
|
120
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
121
|
+
*
|
|
122
|
+
* CROSS-HOOK PATTERNS:
|
|
123
|
+
*
|
|
124
|
+
* 1. The Ultimate Player: Users who use Ancient Compass (Hook #1), join guild
|
|
125
|
+
* early (Hook #3), buy Lucky Charm (Hook #5), get legendary weapon (Hook #7),
|
|
126
|
+
* and have Elite subscription (Hook #8) — exceptional metrics across all dims.
|
|
127
|
+
*
|
|
128
|
+
* 2. The Cursed Compass: Do Ancient Compass users survive the Cursed Week
|
|
129
|
+
* better than others?
|
|
130
|
+
*
|
|
131
|
+
* 3. Guild Churn Prevention: Does early guild joining (Hook #3) prevent
|
|
132
|
+
* death-spiral churn (Hook #4)?
|
|
133
|
+
*
|
|
134
|
+
* 4. LTV + Retention: Compare Lucky Charm buyers who joined guilds early vs.
|
|
135
|
+
* those who didn't.
|
|
136
|
+
*
|
|
137
|
+
* 5. Subscription Impact on Churn: Do Premium/Elite subscribers avoid the
|
|
138
|
+
* death spiral churn pattern?
|
|
139
|
+
*
|
|
140
|
+
* COHORT ANALYSIS:
|
|
141
|
+
*
|
|
142
|
+
* - Cohort by starting week: Users who started during Cursed Week (days 40-47)
|
|
143
|
+
* should show different patterns
|
|
144
|
+
* - Cohort by character class: Do certain classes show different hook patterns?
|
|
145
|
+
* - Cohort by platform: Do PC users vs. console users exhibit different
|
|
146
|
+
* strategic explorer behavior?
|
|
147
|
+
* - Cohort by subscription tier: Track retention, engagement, and monetization
|
|
148
|
+
* differences
|
|
149
|
+
*
|
|
150
|
+
* FUNNEL ANALYSIS:
|
|
151
|
+
*
|
|
152
|
+
* - Onboarding Funnel: How does Ancient Compass usage affect tutorial → first
|
|
153
|
+
* quest conversion?
|
|
154
|
+
* - Dungeon Funnel: Compare enter → treasure → exit completion by strategic
|
|
155
|
+
* explorers and subscription tier
|
|
156
|
+
* - Quest Funnel: Compare quest accepted → completed rates before and during
|
|
157
|
+
* Cursed Week
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
// Generate consistent item/location IDs for lookup tables
|
|
161
|
+
const dungeonIds = v.range(1, 51).map(n => `dungeon_${v.uid(6)}`);
|
|
162
|
+
const questIds = v.range(1, 201).map(n => `quest_${v.uid(8)}`);
|
|
163
|
+
const itemIds = v.range(1, 301).map(n => `item_${v.uid(7)}`);
|
|
164
|
+
|
|
165
|
+
/** @type {Config} */
|
|
166
|
+
const config = {
|
|
167
|
+
token: "",
|
|
168
|
+
seed: SEED,
|
|
169
|
+
numDays: days,
|
|
170
|
+
numEvents: num_users * 120,
|
|
171
|
+
numUsers: num_users,
|
|
172
|
+
hasAnonIds: false,
|
|
173
|
+
hasSessionIds: true,
|
|
174
|
+
format: "json",
|
|
175
|
+
gzip: true,
|
|
176
|
+
alsoInferFunnels: false,
|
|
177
|
+
hasLocation: true,
|
|
178
|
+
hasAndroidDevices: true,
|
|
179
|
+
hasIOSDevices: true,
|
|
180
|
+
hasDesktopDevices: true,
|
|
181
|
+
hasBrowser: false,
|
|
182
|
+
hasCampaigns: false,
|
|
183
|
+
isAnonymous: false,
|
|
184
|
+
hasAdSpend: false,
|
|
185
|
+
percentUsersBornInDataset: 50,
|
|
186
|
+
|
|
187
|
+
hasAvatar: true,
|
|
188
|
+
|
|
189
|
+
batchSize: 2_500_000,
|
|
190
|
+
concurrency: 1,
|
|
191
|
+
writeToDisk: false,
|
|
192
|
+
|
|
193
|
+
funnels: [
|
|
194
|
+
{
|
|
195
|
+
sequence: ["character created", "tutorial completed", "quest accepted"],
|
|
196
|
+
isFirstFunnel: true,
|
|
197
|
+
conversionRate: 75,
|
|
198
|
+
timeToConvert: 0.5,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
// Core combat loop: most frequent player activity
|
|
202
|
+
sequence: ["combat initiated", "combat completed", "use item"],
|
|
203
|
+
conversionRate: 75,
|
|
204
|
+
timeToConvert: 0.5,
|
|
205
|
+
weight: 5,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
// Dungeon crawl: enter, explore, loot, exit
|
|
209
|
+
sequence: ["enter dungeon", "find treasure", "exit dungeon"],
|
|
210
|
+
conversionRate: 60,
|
|
211
|
+
timeToConvert: 2,
|
|
212
|
+
weight: 4,
|
|
213
|
+
props: {
|
|
214
|
+
"dungeon_id": u.pickAWinner(dungeonIds),
|
|
215
|
+
"difficulty": ["Easy", "Medium", "Hard", "Deadly"],
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
// Quest lifecycle
|
|
220
|
+
sequence: ["quest accepted", "quest objective completed", "quest turned in"],
|
|
221
|
+
conversionRate: 55,
|
|
222
|
+
timeToConvert: 3,
|
|
223
|
+
weight: 3,
|
|
224
|
+
props: { "quest_id": u.pickAWinner(questIds) },
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
// Preparation before dungeon: inspect + search for strategic explorer hook
|
|
228
|
+
sequence: ["inspect", "search for clues", "enter dungeon"],
|
|
229
|
+
conversionRate: 50,
|
|
230
|
+
timeToConvert: 1,
|
|
231
|
+
weight: 3,
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
// Economy: buy gear, sell loot
|
|
235
|
+
sequence: ["item purchased", "use item", "item sold"],
|
|
236
|
+
conversionRate: 45,
|
|
237
|
+
timeToConvert: 6,
|
|
238
|
+
weight: 2,
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
// Social and progression
|
|
242
|
+
sequence: ["guild joined", "level up", "real money purchase"],
|
|
243
|
+
conversionRate: 25,
|
|
244
|
+
timeToConvert: 24,
|
|
245
|
+
weight: 1,
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
|
|
249
|
+
events: [
|
|
250
|
+
{
|
|
251
|
+
event: "character created",
|
|
252
|
+
weight: 1,
|
|
253
|
+
isFirstEvent: true,
|
|
254
|
+
properties: {
|
|
255
|
+
"character_class": [
|
|
256
|
+
"Warrior",
|
|
257
|
+
"Mage",
|
|
258
|
+
"Rogue",
|
|
259
|
+
"Cleric",
|
|
260
|
+
"Ranger",
|
|
261
|
+
"Paladin"
|
|
262
|
+
],
|
|
263
|
+
"starting_race": [
|
|
264
|
+
"Human",
|
|
265
|
+
"Elf",
|
|
266
|
+
"Dwarf",
|
|
267
|
+
"Halfling",
|
|
268
|
+
"Orc"
|
|
269
|
+
],
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
event: "tutorial completed",
|
|
274
|
+
weight: 2,
|
|
275
|
+
properties: {
|
|
276
|
+
"completion_time_mins": u.weighNumRange(3, 25, 0.8, 10),
|
|
277
|
+
"skipped": u.pickAWinner([true, false], 0.15),
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
event: "quest accepted",
|
|
282
|
+
weight: 15,
|
|
283
|
+
properties: {
|
|
284
|
+
"quest_id": u.pickAWinner(questIds),
|
|
285
|
+
"quest_type": [
|
|
286
|
+
"Main Story",
|
|
287
|
+
"Side Quest",
|
|
288
|
+
"Bounty",
|
|
289
|
+
"Exploration",
|
|
290
|
+
"Escort"
|
|
291
|
+
],
|
|
292
|
+
"recommended_level": u.weighNumRange(1, 50),
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
event: "quest objective completed",
|
|
297
|
+
weight: 12,
|
|
298
|
+
properties: {
|
|
299
|
+
"quest_id": u.pickAWinner(questIds),
|
|
300
|
+
"objective_number": u.weighNumRange(1, 5),
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
event: "quest turned in",
|
|
305
|
+
weight: 10,
|
|
306
|
+
properties: {
|
|
307
|
+
"quest_id": u.pickAWinner(questIds),
|
|
308
|
+
"reward_gold": u.weighNumRange(10, 500, 0.5, 100),
|
|
309
|
+
"reward_xp": u.weighNumRange(50, 2000, 0.5, 500),
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
event: "enter dungeon",
|
|
314
|
+
weight: 18,
|
|
315
|
+
properties: {
|
|
316
|
+
"dungeon_id": u.pickAWinner(dungeonIds),
|
|
317
|
+
"difficulty": ["Easy", "Medium", "Hard", "Deadly"],
|
|
318
|
+
"party_size": u.weighNumRange(1, 5),
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
event: "exit dungeon",
|
|
323
|
+
weight: 14,
|
|
324
|
+
properties: {
|
|
325
|
+
"dungeon_id": u.pickAWinner(dungeonIds),
|
|
326
|
+
"time_spent_mins": u.weighNumRange(5, 120, 0.6, 30),
|
|
327
|
+
"completion_status": ["completed", "abandoned", "died"],
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
event: "find treasure",
|
|
332
|
+
weight: 16,
|
|
333
|
+
properties: {
|
|
334
|
+
"treasure_type": [
|
|
335
|
+
"Gold",
|
|
336
|
+
"Weapon",
|
|
337
|
+
"Armor",
|
|
338
|
+
"Potion",
|
|
339
|
+
"Scroll",
|
|
340
|
+
"Rare Artifact"
|
|
341
|
+
],
|
|
342
|
+
"treasure_value": u.weighNumRange(5, 1000, 1.2, 50),
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
event: "player death",
|
|
347
|
+
weight: 8,
|
|
348
|
+
properties: {
|
|
349
|
+
"cause_of_death": [
|
|
350
|
+
"Monster",
|
|
351
|
+
"Trap",
|
|
352
|
+
"Fall Damage",
|
|
353
|
+
"Poison",
|
|
354
|
+
"Friendly Fire"
|
|
355
|
+
],
|
|
356
|
+
"player_level": u.weighNumRange(1, 50),
|
|
357
|
+
"resurrection_used": u.pickAWinner([true, false], 0.25),
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
event: "level up",
|
|
362
|
+
weight: 5,
|
|
363
|
+
properties: {
|
|
364
|
+
"new_level": u.weighNumRange(2, 50),
|
|
365
|
+
"stat_points_gained": u.weighNumRange(1, 5),
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
event: "item purchased",
|
|
370
|
+
weight: 11,
|
|
371
|
+
properties: {
|
|
372
|
+
"item_id": u.pickAWinner(itemIds),
|
|
373
|
+
"item_type": [
|
|
374
|
+
"Weapon",
|
|
375
|
+
"Armor",
|
|
376
|
+
"Potion",
|
|
377
|
+
"Scroll",
|
|
378
|
+
"Mount",
|
|
379
|
+
"Cosmetic"
|
|
380
|
+
],
|
|
381
|
+
"price_gold": u.weighNumRange(10, 500, 0.8, 100),
|
|
382
|
+
"vendor_type": ["Town", "Dungeon", "Special Event"],
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
event: "item sold",
|
|
387
|
+
weight: 7,
|
|
388
|
+
properties: {
|
|
389
|
+
"item_id": u.pickAWinner(itemIds),
|
|
390
|
+
"item_type": [
|
|
391
|
+
"Weapon",
|
|
392
|
+
"Armor",
|
|
393
|
+
"Potion",
|
|
394
|
+
"Scroll",
|
|
395
|
+
"Junk"
|
|
396
|
+
],
|
|
397
|
+
"sell_price": u.weighNumRange(5, 250, 0.5, 50),
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
event: "real money purchase",
|
|
402
|
+
weight: 3,
|
|
403
|
+
properties: {
|
|
404
|
+
"product": [
|
|
405
|
+
"Premium Currency (1000)",
|
|
406
|
+
"Premium Currency (5000)",
|
|
407
|
+
"Lucky Charm Pack",
|
|
408
|
+
"Legendary Weapon Chest",
|
|
409
|
+
"Cosmetic Bundle",
|
|
410
|
+
"Season Pass"
|
|
411
|
+
],
|
|
412
|
+
"price_usd": u.pickAWinner([4.99, 9.99, 19.99, 49.99, 99.99]),
|
|
413
|
+
"payment_method": ["Credit Card", "PayPal", "Apple Pay", "Google Pay"],
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
event: "guild joined",
|
|
418
|
+
weight: 4,
|
|
419
|
+
properties: {
|
|
420
|
+
"guild_size": u.weighNumRange(5, 100),
|
|
421
|
+
"guild_level": u.weighNumRange(1, 20),
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
event: "guild left",
|
|
426
|
+
weight: 1,
|
|
427
|
+
properties: {
|
|
428
|
+
"reason": [
|
|
429
|
+
"Inactive",
|
|
430
|
+
"Found Better Guild",
|
|
431
|
+
"Conflict",
|
|
432
|
+
"Disbanded"
|
|
433
|
+
],
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
event: "inspect",
|
|
438
|
+
weight: 9,
|
|
439
|
+
properties: {
|
|
440
|
+
"inspect_target": [
|
|
441
|
+
"NPC",
|
|
442
|
+
"Monster",
|
|
443
|
+
"Treasure Chest",
|
|
444
|
+
"Door",
|
|
445
|
+
"Statue",
|
|
446
|
+
"Bookshelf"
|
|
447
|
+
],
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
event: "search for clues",
|
|
452
|
+
weight: 8,
|
|
453
|
+
properties: {
|
|
454
|
+
"location_type": [
|
|
455
|
+
"Dungeon Entrance",
|
|
456
|
+
"Hidden Room",
|
|
457
|
+
"Quest Location",
|
|
458
|
+
"Town Square"
|
|
459
|
+
],
|
|
460
|
+
"clue_found": u.pickAWinner([true, false], 0.6),
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
event: "use item",
|
|
465
|
+
weight: 14,
|
|
466
|
+
properties: {
|
|
467
|
+
"item_id": u.pickAWinner(itemIds),
|
|
468
|
+
"item_type": [
|
|
469
|
+
"Health Potion",
|
|
470
|
+
"Mana Potion",
|
|
471
|
+
"Buff Scroll",
|
|
472
|
+
"Ancient Compass",
|
|
473
|
+
"Lucky Charm",
|
|
474
|
+
"Resurrection Stone"
|
|
475
|
+
],
|
|
476
|
+
"context": ["Combat", "Exploration", "Boss Fight", "Casual"],
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
event: "combat initiated",
|
|
481
|
+
weight: 20,
|
|
482
|
+
properties: {
|
|
483
|
+
"enemy_type": [
|
|
484
|
+
"Goblin",
|
|
485
|
+
"Skeleton",
|
|
486
|
+
"Dragon",
|
|
487
|
+
"Demon",
|
|
488
|
+
"Undead",
|
|
489
|
+
"Beast"
|
|
490
|
+
],
|
|
491
|
+
"enemy_level": u.weighNumRange(1, 50),
|
|
492
|
+
"combat_duration_sec": u.weighNumRange(10, 300, 0.7, 60),
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
event: "combat completed",
|
|
497
|
+
weight: 18,
|
|
498
|
+
properties: {
|
|
499
|
+
"outcome": ["Victory", "Defeat", "Fled"],
|
|
500
|
+
"loot_gained": u.pickAWinner([true, false], 0.7),
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
],
|
|
504
|
+
|
|
505
|
+
superProps: {
|
|
506
|
+
platform: [
|
|
507
|
+
"PC",
|
|
508
|
+
"Mac",
|
|
509
|
+
"PlayStation",
|
|
510
|
+
"Xbox",
|
|
511
|
+
"Switch"
|
|
512
|
+
],
|
|
513
|
+
graphics_quality: [
|
|
514
|
+
"Low",
|
|
515
|
+
"Medium",
|
|
516
|
+
"High",
|
|
517
|
+
"Ultra"
|
|
518
|
+
],
|
|
519
|
+
subscription_tier: u.pickAWinner(["Free", "Free", "Free", "Premium", "Elite"]),
|
|
520
|
+
},
|
|
521
|
+
|
|
522
|
+
scdProps: {},
|
|
523
|
+
|
|
524
|
+
userProps: {
|
|
525
|
+
"preferred_playstyle": [
|
|
526
|
+
"Solo Explorer",
|
|
527
|
+
"Group Raider",
|
|
528
|
+
"PvP Fighter",
|
|
529
|
+
"Quest Completionist",
|
|
530
|
+
"Treasure Hunter"
|
|
531
|
+
],
|
|
532
|
+
"total_playtime_hours": u.weighNumRange(1, 500, 1.5, 50),
|
|
533
|
+
"achievement_points": u.weighNumRange(0, 5000, 0.8, 500),
|
|
534
|
+
"favorite_class": [
|
|
535
|
+
"Warrior",
|
|
536
|
+
"Mage",
|
|
537
|
+
"Rogue",
|
|
538
|
+
"Cleric",
|
|
539
|
+
"Ranger",
|
|
540
|
+
"Paladin"
|
|
541
|
+
],
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
groupKeys: [
|
|
545
|
+
["guild_id", 500, ["guild joined", "guild left", "quest turned in", "combat completed"]],
|
|
546
|
+
],
|
|
547
|
+
|
|
548
|
+
groupProps: {
|
|
549
|
+
guild_id: {
|
|
550
|
+
"name": () => `${chance.word()} ${chance.pickone(["Knights", "Dragons", "Warriors", "Seekers", "Legends"])}`,
|
|
551
|
+
"member_count": u.weighNumRange(5, 100),
|
|
552
|
+
"guild_level": u.weighNumRange(1, 20),
|
|
553
|
+
"total_wealth": u.weighNumRange(1000, 1000000, 0.5, 50000),
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
lookupTables: [],
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* 🎯 ARCHITECTED ANALYTICS HOOKS
|
|
561
|
+
*
|
|
562
|
+
* This hook function creates 8 deliberate patterns in the data:
|
|
563
|
+
*
|
|
564
|
+
* 1. CONVERSION: Ancient Compass users have 3x quest completion rate
|
|
565
|
+
* 2. TIME-BASED: "Cursed Week" (days 40-47) has 5x death rates & low completion
|
|
566
|
+
* 3. RETENTION: Early guild joiners (first 3 days) have 80% 30-day retention vs 20%
|
|
567
|
+
* 4. CHURN: Players with 3+ deaths in first week have 70% churn rate
|
|
568
|
+
* 5. PURCHASE VALUE: Lucky Charm buyers spend 5x more (LTV pattern)
|
|
569
|
+
* 6. BEHAVIORS TOGETHER: inspect + search before dungeon = 85% completion vs 45%
|
|
570
|
+
* 7. TIMED RELEASE: Legendary weapon released day 45, early adopters dominate
|
|
571
|
+
* 8. SUBSCRIPTION TIER: Premium/Elite users have higher engagement and success rates
|
|
572
|
+
*/
|
|
573
|
+
hook: function (record, type, meta) {
|
|
574
|
+
const NOW = dayjs();
|
|
575
|
+
const DATASET_START = NOW.subtract(days, 'days');
|
|
576
|
+
const CURSED_WEEK_START = DATASET_START.add(40, 'days');
|
|
577
|
+
const CURSED_WEEK_END = DATASET_START.add(47, 'days');
|
|
578
|
+
const LEGENDARY_WEAPON_RELEASE = DATASET_START.add(45, 'days');
|
|
579
|
+
|
|
580
|
+
// Hook #2: TIME-BASED TREND - Cursed Week
|
|
581
|
+
if (type === "event") {
|
|
582
|
+
const EVENT_TIME = dayjs(record.time);
|
|
583
|
+
|
|
584
|
+
// During cursed week, dramatically increase death rates
|
|
585
|
+
if (EVENT_TIME.isAfter(CURSED_WEEK_START) && EVENT_TIME.isBefore(CURSED_WEEK_END)) {
|
|
586
|
+
// 50% chance to inject a death event
|
|
587
|
+
if (chance.bool({ likelihood: 30 })) {
|
|
588
|
+
const deathEvent = {
|
|
589
|
+
event: "player death",
|
|
590
|
+
time: record.time,
|
|
591
|
+
user_id: record.user_id,
|
|
592
|
+
cause_of_death: "Curse",
|
|
593
|
+
player_level: chance.integer({ min: 1, max: 50 }),
|
|
594
|
+
resurrection_used: chance.bool({ likelihood: 80 }), // More res usage during curse
|
|
595
|
+
cursed_week: true,
|
|
596
|
+
};
|
|
597
|
+
// Return death event instead sometimes
|
|
598
|
+
if (chance.bool({ likelihood: 50 })) {
|
|
599
|
+
return deathEvent;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Hook #7: TIMED RELEASE - Legendary Weapon
|
|
606
|
+
if (type === "event") {
|
|
607
|
+
const EVENT_TIME = dayjs(record.time);
|
|
608
|
+
|
|
609
|
+
if (record.event === "find treasure") {
|
|
610
|
+
// After legendary weapon release, some lucky players get it
|
|
611
|
+
if (EVENT_TIME.isAfter(LEGENDARY_WEAPON_RELEASE) && chance.bool({ likelihood: 2 })) {
|
|
612
|
+
record.treasure_type = "Shadowmourne Legendary";
|
|
613
|
+
record.treasure_value = 50000;
|
|
614
|
+
record.legendary_drop = true;
|
|
615
|
+
} else {
|
|
616
|
+
record.legendary_drop = false;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Hook #3, #4, #5, #6, #1: EVERYTHING - Complex behavioral patterns
|
|
622
|
+
if (type === "everything") {
|
|
623
|
+
const userEvents = record;
|
|
624
|
+
const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
|
|
625
|
+
|
|
626
|
+
// Track user behaviors
|
|
627
|
+
let usedAncientCompass = false;
|
|
628
|
+
let boughtLuckyCharm = false;
|
|
629
|
+
let joinedGuildEarly = false;
|
|
630
|
+
let earlyDeaths = 0;
|
|
631
|
+
let hasLegendaryWeapon = false;
|
|
632
|
+
let inspectedBeforeDungeon = false;
|
|
633
|
+
let searchedBeforeDungeon = false;
|
|
634
|
+
let subscriptionTier = "Free"; // Hook #8: Track subscription tier
|
|
635
|
+
|
|
636
|
+
// First pass: identify user patterns
|
|
637
|
+
userEvents.forEach((event, idx) => {
|
|
638
|
+
const eventTime = dayjs(event.time);
|
|
639
|
+
const daysSinceStart = firstEventTime ? eventTime.diff(firstEventTime, 'days', true) : 0;
|
|
640
|
+
|
|
641
|
+
// Hook #8: Capture subscription tier from any event that has it
|
|
642
|
+
if (event.subscription_tier) {
|
|
643
|
+
subscriptionTier = event.subscription_tier;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Hook #1: Track Ancient Compass usage
|
|
647
|
+
if (event.event === "use item" && event.item_type === "Ancient Compass") {
|
|
648
|
+
usedAncientCompass = true;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Hook #5: Track Lucky Charm purchases
|
|
652
|
+
if (event.event === "real money purchase" && event.product === "Lucky Charm Pack") {
|
|
653
|
+
boughtLuckyCharm = true;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Hook #3: Track early guild joining
|
|
657
|
+
if (event.event === "guild joined" && daysSinceStart < 3) {
|
|
658
|
+
joinedGuildEarly = true;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Hook #4: Track early deaths (first 7 days)
|
|
662
|
+
if (event.event === "player death" && daysSinceStart < 7) {
|
|
663
|
+
earlyDeaths++;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Hook #7: Track legendary weapon ownership
|
|
667
|
+
if (event.event === "find treasure" && event.legendary_drop) {
|
|
668
|
+
hasLegendaryWeapon = true;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Hook #6: Track inspect + search patterns
|
|
672
|
+
if (event.event === "inspect") {
|
|
673
|
+
inspectedBeforeDungeon = true;
|
|
674
|
+
}
|
|
675
|
+
if (event.event === "search for clues") {
|
|
676
|
+
searchedBeforeDungeon = true;
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// Second pass: modify events based on patterns
|
|
681
|
+
userEvents.forEach((event, idx) => {
|
|
682
|
+
const eventTime = dayjs(event.time);
|
|
683
|
+
|
|
684
|
+
// Set schema defaults for conditional properties
|
|
685
|
+
if (event.event === "quest turned in") {
|
|
686
|
+
event.compass_user = false;
|
|
687
|
+
event.subscriber_advantage = "Free";
|
|
688
|
+
}
|
|
689
|
+
if (event.event === "exit dungeon") {
|
|
690
|
+
event.strategic_explorer = false;
|
|
691
|
+
event.legendary_weapon_equipped = false;
|
|
692
|
+
event.subscriber_advantage = "Free";
|
|
693
|
+
}
|
|
694
|
+
if (event.event === "find treasure") {
|
|
695
|
+
event.strategic_explorer = false;
|
|
696
|
+
event.subscriber_advantage = "Free";
|
|
697
|
+
}
|
|
698
|
+
if (event.event === "combat completed") {
|
|
699
|
+
event.legendary_weapon_equipped = false;
|
|
700
|
+
event.subscriber_advantage = "Free";
|
|
701
|
+
event.near_death_survival = false;
|
|
702
|
+
}
|
|
703
|
+
if (event.event === "player death") {
|
|
704
|
+
event.near_death_survival = false;
|
|
705
|
+
event.subscriber_advantage = "Free";
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Hook #1: CONVERSION - Ancient Compass users complete more quests
|
|
709
|
+
if (usedAncientCompass && event.event === "quest turned in") {
|
|
710
|
+
// Triple the rewards for compass users (they're more successful)
|
|
711
|
+
event.reward_gold = Math.floor((event.reward_gold || 100) * 1.5);
|
|
712
|
+
event.reward_xp = Math.floor((event.reward_xp || 500) * 1.5);
|
|
713
|
+
event.compass_user = true;
|
|
714
|
+
|
|
715
|
+
// Add extra quest completions for compass users
|
|
716
|
+
if (chance.bool({ likelihood: 40 })) {
|
|
717
|
+
const extraQuest = {
|
|
718
|
+
event: "quest turned in",
|
|
719
|
+
time: eventTime.add(chance.integer({ min: 10, max: 120 }), 'minutes').toISOString(),
|
|
720
|
+
user_id: event.user_id,
|
|
721
|
+
quest_id: chance.pickone(questIds),
|
|
722
|
+
reward_gold: chance.integer({ min: 100, max: 500 }),
|
|
723
|
+
reward_xp: chance.integer({ min: 500, max: 2000 }),
|
|
724
|
+
compass_user: true,
|
|
725
|
+
};
|
|
726
|
+
userEvents.splice(idx + 1, 0, extraQuest);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Hook #5: PURCHASE VALUE - Lucky charm buyers spend 5x more
|
|
731
|
+
if (boughtLuckyCharm) {
|
|
732
|
+
if (event.event === "real money purchase") {
|
|
733
|
+
// Increase purchase amounts
|
|
734
|
+
if (event.price_usd) {
|
|
735
|
+
const currentPrice = event.price_usd;
|
|
736
|
+
if (currentPrice < 49.99) {
|
|
737
|
+
event.price_usd = currentPrice * 2; // Upgrade tier
|
|
738
|
+
}
|
|
739
|
+
event.lucky_charm_effect = true;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Add additional purchase events (higher LTV)
|
|
744
|
+
if (event.event === "item purchased" && chance.bool({ likelihood: 35 })) {
|
|
745
|
+
const extraPurchase = {
|
|
746
|
+
event: "real money purchase",
|
|
747
|
+
time: eventTime.add(chance.integer({ min: 1, max: 3 }), 'days').toISOString(),
|
|
748
|
+
user_id: event.user_id,
|
|
749
|
+
product: chance.pickone([
|
|
750
|
+
"Premium Currency (5000)",
|
|
751
|
+
"Legendary Weapon Chest",
|
|
752
|
+
"Season Pass"
|
|
753
|
+
]),
|
|
754
|
+
price_usd: chance.pickone([19.99, 49.99, 99.99]),
|
|
755
|
+
payment_method: chance.pickone(["Credit Card", "PayPal"]),
|
|
756
|
+
lucky_charm_effect: true,
|
|
757
|
+
};
|
|
758
|
+
userEvents.splice(idx + 1, 0, extraPurchase);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Hook #6: BEHAVIORS TOGETHER - Inspect + Search before dungeon
|
|
763
|
+
if (inspectedBeforeDungeon && searchedBeforeDungeon) {
|
|
764
|
+
if (event.event === "exit dungeon") {
|
|
765
|
+
// Much higher completion rate
|
|
766
|
+
if (event.completion_status !== "completed") {
|
|
767
|
+
// Convert 85% of non-completions to completions
|
|
768
|
+
if (chance.bool({ likelihood: 85 })) {
|
|
769
|
+
event.completion_status = "completed";
|
|
770
|
+
event.strategic_explorer = true;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Find more treasure
|
|
776
|
+
if (event.event === "find treasure") {
|
|
777
|
+
event.treasure_value = Math.floor((event.treasure_value || 50) * 2);
|
|
778
|
+
event.strategic_explorer = true;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Hook #7: TIMED RELEASE - Legendary weapon owners dominate
|
|
783
|
+
if (hasLegendaryWeapon) {
|
|
784
|
+
// Higher combat success
|
|
785
|
+
if (event.event === "combat completed") {
|
|
786
|
+
if (event.outcome !== "Victory") {
|
|
787
|
+
// Convert 90% of losses to victories
|
|
788
|
+
if (chance.bool({ likelihood: 90 })) {
|
|
789
|
+
event.outcome = "Victory";
|
|
790
|
+
event.legendary_weapon_equipped = true;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Complete dungeons faster and more successfully
|
|
796
|
+
if (event.event === "exit dungeon") {
|
|
797
|
+
event.completion_status = "completed";
|
|
798
|
+
event.time_spent_mins = Math.floor((event.time_spent_mins || 60) * 0.6);
|
|
799
|
+
event.legendary_weapon_equipped = true;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Hook #8: SUBSCRIPTION TIER - Premium/Elite users have better outcomes
|
|
804
|
+
if (subscriptionTier === "Premium" || subscriptionTier === "Elite") {
|
|
805
|
+
const isElite = subscriptionTier === "Elite";
|
|
806
|
+
|
|
807
|
+
// Better combat outcomes
|
|
808
|
+
if (event.event === "combat completed") {
|
|
809
|
+
if (event.outcome !== "Victory") {
|
|
810
|
+
const winBoost = isElite ? 70 : 50; // Elite: 70%, Premium: 50%
|
|
811
|
+
if (Math.random() * 100 < winBoost) {
|
|
812
|
+
event.outcome = "Victory";
|
|
813
|
+
event.loot_gained = true;
|
|
814
|
+
event.subscriber_advantage = subscriptionTier;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Higher quest completion and rewards
|
|
820
|
+
if (event.event === "quest turned in") {
|
|
821
|
+
const rewardMultiplier = isElite ? 1.8 : 1.4; // Elite: 1.8x, Premium: 1.4x
|
|
822
|
+
event.reward_gold = Math.floor((event.reward_gold || 100) * rewardMultiplier);
|
|
823
|
+
event.reward_xp = Math.floor((event.reward_xp || 500) * rewardMultiplier);
|
|
824
|
+
event.subscriber_advantage = subscriptionTier;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Higher dungeon completion rates
|
|
828
|
+
if (event.event === "exit dungeon") {
|
|
829
|
+
if (event.completion_status !== "completed") {
|
|
830
|
+
const completionBoost = isElite ? 65 : 45; // Elite: 65%, Premium: 45%
|
|
831
|
+
if (Math.random() * 100 < completionBoost) {
|
|
832
|
+
event.completion_status = "completed";
|
|
833
|
+
event.subscriber_advantage = subscriptionTier;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
// Faster completion times
|
|
837
|
+
if (event.completion_status === "completed") {
|
|
838
|
+
const speedBoost = isElite ? 0.7 : 0.85; // Elite: 30% faster, Premium: 15% faster
|
|
839
|
+
event.time_spent_mins = Math.floor((event.time_spent_mins || 60) * speedBoost);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Better treasure finds
|
|
844
|
+
if (event.event === "find treasure") {
|
|
845
|
+
const treasureBoost = isElite ? 2.0 : 1.5; // Elite: 2x, Premium: 1.5x
|
|
846
|
+
event.treasure_value = Math.floor((event.treasure_value || 50) * treasureBoost);
|
|
847
|
+
event.subscriber_advantage = subscriptionTier;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Reduced death rates (subscribers have advantages)
|
|
851
|
+
if (event.event === "player death") {
|
|
852
|
+
const survivalChance = isElite ? 50 : 30; // Elite: 50% avoid death, Premium: 30%
|
|
853
|
+
if (Math.random() * 100 < survivalChance) {
|
|
854
|
+
// Convert death to combat completed with victory
|
|
855
|
+
event.event = "combat completed";
|
|
856
|
+
event.outcome = "Victory";
|
|
857
|
+
event.loot_gained = true;
|
|
858
|
+
event.subscriber_advantage = subscriptionTier;
|
|
859
|
+
event.near_death_survival = true;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Elite users get bonus engagement events
|
|
864
|
+
if (isElite && Math.random() * 100 < 15) {
|
|
865
|
+
if (event.event === "quest turned in" || event.event === "exit dungeon") {
|
|
866
|
+
const treasureTypes = ["Rare Artifact", "Gold", "Weapon", "Armor"];
|
|
867
|
+
const bonusEvent = {
|
|
868
|
+
event: "find treasure",
|
|
869
|
+
time: eventTime.add(Math.floor(Math.random() * 26) + 5, 'minutes').toISOString(),
|
|
870
|
+
user_id: event.user_id,
|
|
871
|
+
treasure_type: treasureTypes[Math.floor(Math.random() * treasureTypes.length)],
|
|
872
|
+
treasure_value: Math.floor(Math.random() * 601) + 200,
|
|
873
|
+
subscriber_advantage: "Elite",
|
|
874
|
+
elite_bonus: true,
|
|
875
|
+
};
|
|
876
|
+
userEvents.splice(idx + 1, 0, bonusEvent);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// Hook #3: RETENTION - Early guild joiners retained
|
|
883
|
+
// Hook #4: CHURN - High early death = churn
|
|
884
|
+
const shouldChurn = (!joinedGuildEarly && earlyDeaths >= 3) ||
|
|
885
|
+
(earlyDeaths >= 5);
|
|
886
|
+
|
|
887
|
+
if (shouldChurn) {
|
|
888
|
+
// Remove 70% of events after first week (churn)
|
|
889
|
+
const firstWeekEnd = firstEventTime ? firstEventTime.add(7, 'days') : null;
|
|
890
|
+
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
891
|
+
const evt = userEvents[i];
|
|
892
|
+
if (firstWeekEnd && dayjs(evt.time).isAfter(firstWeekEnd)) {
|
|
893
|
+
if (chance.bool({ likelihood: 70 })) {
|
|
894
|
+
userEvents.splice(i, 1);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
} else if (joinedGuildEarly) {
|
|
899
|
+
// Add extra engagement events for retained users
|
|
900
|
+
const lastEvent = userEvents[userEvents.length - 1];
|
|
901
|
+
if (lastEvent && chance.bool({ likelihood: 60 })) {
|
|
902
|
+
const retentionEvent = {
|
|
903
|
+
event: "combat completed",
|
|
904
|
+
time: dayjs(lastEvent.time).add(chance.integer({ min: 1, max: 5 }), 'days').toISOString(),
|
|
905
|
+
user_id: lastEvent.user_id,
|
|
906
|
+
outcome: "Victory",
|
|
907
|
+
loot_gained: true,
|
|
908
|
+
guild_member_retained: true,
|
|
909
|
+
};
|
|
910
|
+
userEvents.push(retentionEvent);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
return record;
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
export default config;
|