@ak--47/dungeon-master 1.2.2 → 1.3.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/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -2
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +2 -2
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +2 -2
- package/dungeons/vertical/ai-platform-schema.json +617 -0
- package/dungeons/vertical/ai-platform.js +799 -0
- package/dungeons/vertical/community.js +40 -26
- package/dungeons/vertical/crypto-schema.json +546 -0
- package/dungeons/vertical/crypto.js +721 -0
- package/dungeons/vertical/dating-schema.json +401 -0
- package/dungeons/vertical/dating.js +798 -0
- package/dungeons/vertical/devtools.js +13 -9
- package/dungeons/vertical/ecommerce.js +2 -3
- package/dungeons/vertical/education.js +4 -5
- package/dungeons/vertical/fintech.js +32 -29
- package/dungeons/vertical/fitness.js +2 -3
- package/dungeons/vertical/food-delivery.js +37 -43
- package/dungeons/vertical/gaming-schema.json +2495 -230
- package/dungeons/vertical/gaming.js +771 -388
- package/dungeons/vertical/healthcare.js +2 -3
- package/dungeons/vertical/insurance-application.js +2 -3
- package/dungeons/vertical/logistics.js +20 -14
- package/dungeons/vertical/marketplace.js +22 -14
- package/dungeons/vertical/media.js +39 -30
- package/dungeons/vertical/real-estate-schema.json +527 -0
- package/dungeons/vertical/real-estate.js +774 -0
- package/dungeons/vertical/sass.js +2 -3
- package/dungeons/vertical/social.js +15 -13
- package/dungeons/vertical/travel.js +59 -26
- package/lib/core/config-validator.js +71 -15
- package/lib/core/storage.js +14 -4
- package/lib/orchestrators/user-loop.js +39 -5
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/package.json +8 -2
- package/types.d.ts +219 -42
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// ── TWEAK THESE ──
|
|
2
|
-
const SEED = "
|
|
3
|
-
const num_days =
|
|
4
|
-
const num_users =
|
|
5
|
-
const
|
|
2
|
+
const SEED = "questforge";
|
|
3
|
+
const num_days = 100;
|
|
4
|
+
const num_users = 5_000;
|
|
5
|
+
const avg_events_per_user_per_day = 1.2;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
8
8
|
// ── env overrides ──
|
|
@@ -24,497 +24,553 @@ const chance = u.initChance(SEED);
|
|
|
24
24
|
* DATASET OVERVIEW
|
|
25
25
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
26
26
|
*
|
|
27
|
-
* QuestForge —
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* QuestForge — a D&D-inspired action RPG with a deep character system, party-
|
|
28
|
+
* based dungeon crawls, boss fights, a player-driven economy, guilds, and
|
|
29
|
+
* subscription tiers. Combines the strategic depth of a tabletop RPG with the
|
|
30
|
+
* monetization and engagement loops of a modern live-service game.
|
|
30
31
|
*
|
|
31
|
-
* CORE
|
|
32
|
-
*
|
|
33
|
-
* and
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
32
|
+
* CORE LOOP: Players create characters (12 classes, 9 races, 9 alignments,
|
|
33
|
+
* 13 backgrounds) and progress through quests, dungeon crawls, boss fights,
|
|
34
|
+
* and combat encounters. Strategic preparation (inspect, search for clues,
|
|
35
|
+
* Ancient Compass) creates skill gaps. Guild membership drives social
|
|
36
|
+
* retention. Monetization via premium currency, lucky charms, season pass,
|
|
37
|
+
* and subscription tiers (Free / Premium / Elite).
|
|
37
38
|
*
|
|
38
|
-
* SCALE:
|
|
39
|
+
* SCALE: 5,000 users × 120 events = 600K events over 100 days
|
|
40
|
+
* 24 event types, 7 funnels, guild group analytics, subscription tiers
|
|
39
41
|
*
|
|
40
|
-
* KEY
|
|
41
|
-
* -
|
|
42
|
-
* -
|
|
43
|
-
* -
|
|
44
|
-
* -
|
|
45
|
-
* -
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
42
|
+
* KEY SYSTEMS:
|
|
43
|
+
* - Character: 12 D&D classes, 9 races, 9 alignments, 13 backgrounds
|
|
44
|
+
* - Quests: accept → objective → turn in (5 quest types, gold/XP rewards)
|
|
45
|
+
* - Dungeons: enter → find treasure → exit (50 dungeons, party-based, 3 outcomes)
|
|
46
|
+
* - Combat: initiate → complete (6 enemy types), boss fights, attack/defend
|
|
47
|
+
* - Economy: item purchase/sell, real money purchases, vendor types
|
|
48
|
+
* - Progression: level up (1-50), stat points, level-scaled rewards
|
|
49
|
+
* - Social: guild join/leave (5-100 members, guild levels 1-20)
|
|
50
|
+
* - Monetization: premium currency, lucky charms, legendary chests, season pass
|
|
51
|
+
* - Subscriptions: Free / Premium ($9.99/mo) / Elite ($19.99/mo)
|
|
52
|
+
* - Experiments: A/B/C/Control variants across "fast leveling", "tension
|
|
53
|
+
* economy", "free trial"
|
|
50
54
|
*/
|
|
51
55
|
|
|
52
56
|
/*
|
|
53
57
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
54
|
-
* ANALYTICS HOOKS
|
|
58
|
+
* ANALYTICS HOOKS (11 architected patterns)
|
|
55
59
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
56
60
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
61
|
+
* 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
|
|
62
67
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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
|
|
66
72
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* - Breakdown: "level at end"
|
|
72
|
-
* - Expected: Linear increase — level 1 ≈ 55 gold, level 10 ≈ 110 gold,
|
|
73
|
-
* level 20 ≈ 165 gold. Clear positive correlation between level and reward.
|
|
73
|
+
* 3. RETENTION: Early Guild Joiners
|
|
74
|
+
* Players who join a guild within first 3 days have 80% D30 retention vs 20%.
|
|
75
|
+
* → Mixpanel: Cohort users who did "guild joined" within first 3 days
|
|
76
|
+
* → Compare D30 retention rate; look for guild_member_retained = true
|
|
74
77
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
+
* 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
|
|
78
82
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
83
|
+
* 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
|
|
81
87
|
*
|
|
82
|
-
*
|
|
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
|
|
83
92
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* - Expected: is_whale=true shows ~1.8x higher avg purchase amount than
|
|
90
|
-
* is_whale=false (e.g., ~18 vs ~10)
|
|
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
|
|
91
98
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
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"
|
|
98
105
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
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
|
|
102
111
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
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
|
|
105
117
|
*
|
|
106
|
-
*
|
|
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"
|
|
107
122
|
*
|
|
108
|
-
*
|
|
109
|
-
* - Report type: Insights
|
|
110
|
-
* - Event: Any event (e.g., "complete quest")
|
|
111
|
-
* - Measure: Total unique users
|
|
112
|
-
* - Breakdown: User profile "archetype"
|
|
113
|
-
* - Expected: Three segments — "hero" (~22%), "villain" (~22%), "neutral" (~56%)
|
|
114
|
-
* matching the 2/9, 2/9, 5/9 alignment split
|
|
115
|
-
*
|
|
116
|
-
* Report 2: Archetype Behavior Comparison
|
|
117
|
-
* - Report type: Insights
|
|
118
|
-
* - Event: "fight boss"
|
|
119
|
-
* - Measure: Total events per user
|
|
120
|
-
* - Breakdown: User profile "archetype"
|
|
121
|
-
* - Expected: All three archetypes should show similar event volumes
|
|
122
|
-
* (archetype doesn't affect gameplay — just a profiling hook)
|
|
123
|
-
*
|
|
124
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
123
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
125
124
|
* EXPECTED METRICS SUMMARY
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* Hook | Metric | Baseline | Hook Effect | Ratio
|
|
129
|
-
* ────────────────────────|─────────────────────────|──────────|──────────────|──────
|
|
130
|
-
* Gold Scaling | Avg gold at level 1 | ~55 | ~55 | 1.0x
|
|
131
|
-
* Gold Scaling | Avg gold at level 10 | ~55 | ~110 | 2.0x
|
|
132
|
-
* Gold Scaling | Avg gold at level 20 | ~55 | ~165 | 3.0x
|
|
133
|
-
* Whale Purchases | Avg purchase (whale) | ~10 | ~18 | 1.8x
|
|
134
|
-
* Whale Purchases | Revenue share (whale) | ~33% | >50% | disprop.
|
|
135
|
-
* Alignment Archetype | Hero users | — | ~22% | 2/9
|
|
136
|
-
* Alignment Archetype | Villain users | — | ~22% | 2/9
|
|
137
|
-
* Alignment Archetype | Neutral users | — | ~56% | 5/9
|
|
138
|
-
*
|
|
139
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
140
|
-
* ADVANCED ANALYSIS IDEAS
|
|
141
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
142
|
-
*
|
|
143
|
-
* 1. Gold Economy Analysis: Plot avg gold earned by level AND quest difficulty.
|
|
144
|
-
* Do Legendary quests at high levels create outsized gold rewards?
|
|
145
|
-
*
|
|
146
|
-
* 2. Whale Segmentation: Cross-reference is_whale with platform and game mode.
|
|
147
|
-
* Are whales concentrated on mobile? Do multiplayer whales spend more?
|
|
148
|
-
*
|
|
149
|
-
* 3. Archetype × Class Matrix: Do certain class/archetype combos correlate
|
|
150
|
-
* with higher boss fight rates or quest completion?
|
|
151
|
-
*
|
|
152
|
-
* 4. Experiment Impact: Compare "fast leveling" vs "tension economy" vs
|
|
153
|
-
* "free trial" on purchase amounts, level progression, and retention.
|
|
154
|
-
*
|
|
155
|
-
* 5. Quest Difficulty Funnel: How does quest difficulty affect completion
|
|
156
|
-
* rate, death count, and hint/boost usage? Is "Legendary" too hard?
|
|
125
|
+
* ───────────────────────────────────────────────────────────────────────────────
|
|
157
126
|
*
|
|
158
|
-
*
|
|
127
|
+
* 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
|
|
131
|
+
* Early Guild Join | D30 Retention | 20% | 80% | 4x
|
|
132
|
+
* 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
|
|
135
|
+
* 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
|
|
159
144
|
*/
|
|
160
145
|
|
|
146
|
+
// Generate consistent item/location IDs for lookup tables
|
|
147
|
+
const dungeonIds = v.range(1, 51).map(n => `dungeon_${v.uid(6)}`);
|
|
148
|
+
const questIds = v.range(1, 201).map(n => `quest_${v.uid(8)}`);
|
|
149
|
+
const itemIds = v.range(1, 301).map(n => `item_${v.uid(7)}`);
|
|
150
|
+
|
|
161
151
|
/** @type {Config} */
|
|
162
152
|
const config = {
|
|
163
153
|
token,
|
|
164
|
-
seed:
|
|
154
|
+
seed: SEED,
|
|
165
155
|
numDays: num_days,
|
|
166
|
-
|
|
156
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
167
157
|
numUsers: num_users,
|
|
168
158
|
hasAnonIds: false,
|
|
169
|
-
hasSessionIds:
|
|
159
|
+
hasSessionIds: true,
|
|
170
160
|
format: "json",
|
|
171
|
-
|
|
161
|
+
gzip: true,
|
|
162
|
+
alsoInferFunnels: false,
|
|
172
163
|
hasLocation: true,
|
|
173
|
-
hasAndroidDevices:
|
|
174
|
-
hasIOSDevices:
|
|
175
|
-
hasDesktopDevices:
|
|
164
|
+
hasAndroidDevices: true,
|
|
165
|
+
hasIOSDevices: true,
|
|
166
|
+
hasDesktopDevices: true,
|
|
176
167
|
hasBrowser: false,
|
|
177
168
|
hasCampaigns: false,
|
|
178
169
|
isAnonymous: false,
|
|
179
170
|
hasAdSpend: false,
|
|
180
|
-
percentUsersBornInDataset: 25,
|
|
181
171
|
|
|
182
172
|
hasAvatar: true,
|
|
183
173
|
|
|
184
174
|
concurrency: 1,
|
|
185
175
|
writeToDisk: false,
|
|
176
|
+
|
|
186
177
|
funnels: [
|
|
187
178
|
{
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
179
|
+
sequence: ["character created", "tutorial completed", "quest accepted"],
|
|
180
|
+
isFirstFunnel: true,
|
|
181
|
+
conversionRate: 75,
|
|
182
|
+
timeToConvert: 0.5,
|
|
192
183
|
},
|
|
193
184
|
{
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
185
|
+
// Core combat loop: most frequent player activity
|
|
186
|
+
sequence: ["combat initiated", "combat completed", "use item"],
|
|
187
|
+
conversionRate: 75,
|
|
188
|
+
timeToConvert: 0.5,
|
|
189
|
+
weight: 5,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
// Dungeon crawl: enter, explore, loot, exit
|
|
193
|
+
sequence: ["enter dungeon", "find treasure", "exit dungeon"],
|
|
194
|
+
conversionRate: 60,
|
|
195
|
+
timeToConvert: 2,
|
|
196
|
+
weight: 4,
|
|
197
197
|
props: {
|
|
198
|
-
"
|
|
199
|
-
|
|
200
|
-
"Retrieve",
|
|
201
|
-
"Explore",
|
|
202
|
-
"Destroy",
|
|
203
|
-
"Investigate",
|
|
204
|
-
],
|
|
205
|
-
"quest difficulty": [
|
|
206
|
-
"Easy",
|
|
207
|
-
"Medium",
|
|
208
|
-
"Hard",
|
|
209
|
-
"Legendary",
|
|
210
|
-
],
|
|
211
|
-
"quest location": [
|
|
212
|
-
"Forest",
|
|
213
|
-
"Dungeon",
|
|
214
|
-
"Mountain",
|
|
215
|
-
"City",
|
|
216
|
-
"Desert",
|
|
217
|
-
]
|
|
198
|
+
"dungeon_id": dungeonIds,
|
|
199
|
+
"difficulty": ["Easy", "Medium", "Hard", "Deadly"],
|
|
218
200
|
}
|
|
219
201
|
},
|
|
202
|
+
{
|
|
203
|
+
// Quest lifecycle
|
|
204
|
+
sequence: ["quest accepted", "quest objective completed", "quest turned in"],
|
|
205
|
+
conversionRate: 55,
|
|
206
|
+
timeToConvert: 3,
|
|
207
|
+
weight: 3,
|
|
208
|
+
props: { "quest_id": questIds },
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
// Preparation before dungeon: inspect + search for strategic explorer hook
|
|
212
|
+
sequence: ["inspect", "search for clues", "enter dungeon"],
|
|
213
|
+
conversionRate: 50,
|
|
214
|
+
timeToConvert: 1,
|
|
215
|
+
weight: 3,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
// Economy: buy gear, sell loot
|
|
219
|
+
sequence: ["item purchased", "use item", "item sold"],
|
|
220
|
+
conversionRate: 45,
|
|
221
|
+
timeToConvert: 6,
|
|
222
|
+
weight: 2,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
// Social and progression
|
|
226
|
+
sequence: ["guild joined", "level up", "real money purchase"],
|
|
227
|
+
conversionRate: 25,
|
|
228
|
+
timeToConvert: 24,
|
|
229
|
+
weight: 1,
|
|
230
|
+
},
|
|
220
231
|
],
|
|
232
|
+
|
|
221
233
|
events: [
|
|
222
|
-
{ event: "app install", weight: 10, isFirstEvent: true },
|
|
223
234
|
{
|
|
224
|
-
event: "character
|
|
235
|
+
event: "character created",
|
|
236
|
+
weight: 1,
|
|
237
|
+
isFirstEvent: true,
|
|
238
|
+
properties: {
|
|
239
|
+
"character_class": [
|
|
240
|
+
"Barbarian", "Bard", "Cleric", "Druid", "Fighter", "Monk",
|
|
241
|
+
"Paladin", "Ranger", "Rogue", "Sorcerer", "Warlock", "Wizard"
|
|
242
|
+
],
|
|
243
|
+
"starting_race": [
|
|
244
|
+
"Human", "Elf", "Dwarf", "Halfling", "Dragonborn",
|
|
245
|
+
"Gnome", "Half-Elf", "Half-Orc", "Tiefling"
|
|
246
|
+
],
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
event: "tutorial completed",
|
|
225
251
|
weight: 2,
|
|
226
252
|
properties: {
|
|
227
|
-
|
|
228
|
-
|
|
253
|
+
"completion_time_mins": u.weighNumRange(3, 25, 0.8, 10),
|
|
254
|
+
"skipped": [false, false, false, false, false, false, true],
|
|
255
|
+
}
|
|
229
256
|
},
|
|
230
257
|
{
|
|
231
|
-
event: "
|
|
232
|
-
weight:
|
|
258
|
+
event: "quest accepted",
|
|
259
|
+
weight: 15,
|
|
233
260
|
properties: {
|
|
234
|
-
"
|
|
235
|
-
"
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
"Fighter",
|
|
239
|
-
"Rogue",
|
|
240
|
-
"Wizard",
|
|
241
|
-
"Paladin",
|
|
242
|
-
"Ranger",
|
|
243
|
-
"Sorcerer",
|
|
244
|
-
"Warlock",
|
|
245
|
-
],
|
|
246
|
-
},
|
|
261
|
+
"quest_id": questIds,
|
|
262
|
+
"quest_type": ["Main Story", "Side Quest", "Bounty", "Exploration", "Escort"],
|
|
263
|
+
"recommended_level": u.weighNumRange(1, 50),
|
|
264
|
+
}
|
|
247
265
|
},
|
|
248
266
|
{
|
|
249
|
-
event: "
|
|
267
|
+
event: "quest objective completed",
|
|
250
268
|
weight: 12,
|
|
251
269
|
properties: {
|
|
252
|
-
"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
"Explore",
|
|
256
|
-
"Destroy",
|
|
257
|
-
"Investigate",
|
|
258
|
-
],
|
|
259
|
-
"quest difficulty": [
|
|
260
|
-
"Easy",
|
|
261
|
-
"Medium",
|
|
262
|
-
"Hard",
|
|
263
|
-
"Legendary",
|
|
264
|
-
],
|
|
265
|
-
"quest location": [
|
|
266
|
-
"Forest",
|
|
267
|
-
"Dungeon",
|
|
268
|
-
"Mountain",
|
|
269
|
-
"City",
|
|
270
|
-
"Desert",
|
|
271
|
-
],
|
|
272
|
-
"party size": u.weighNumRange(1, 6),
|
|
273
|
-
"used hint": ["no", "no", "yes"],
|
|
274
|
-
"used boost": ["no", "no", "no", "no", "yes"],
|
|
275
|
-
"level at start": u.weighNumRange(1, 20),
|
|
276
|
-
},
|
|
270
|
+
"quest_id": questIds,
|
|
271
|
+
"objective_number": u.weighNumRange(1, 5),
|
|
272
|
+
}
|
|
277
273
|
},
|
|
278
274
|
{
|
|
279
|
-
event: "
|
|
280
|
-
weight:
|
|
275
|
+
event: "quest turned in",
|
|
276
|
+
weight: 10,
|
|
277
|
+
properties: {
|
|
278
|
+
"quest_id": questIds,
|
|
279
|
+
"reward_gold": u.weighNumRange(10, 500, 0.5, 100),
|
|
280
|
+
"reward_xp": u.weighNumRange(50, 2000, 0.5, 500),
|
|
281
|
+
"compass_user": [false],
|
|
282
|
+
"subscriber_advantage": ["Free"],
|
|
283
|
+
"level_scaled": [false],
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
event: "enter dungeon",
|
|
288
|
+
weight: 18,
|
|
281
289
|
properties: {
|
|
282
|
-
"
|
|
283
|
-
"
|
|
284
|
-
"
|
|
285
|
-
|
|
286
|
-
"used boost": ["no", "no", "no", "no", "yes"],
|
|
287
|
-
"number of deaths": u.weighNumRange(0, 5, 1, 2),
|
|
288
|
-
"level at start": u.weighNumRange(1, 20),
|
|
289
|
-
"level at end": u.weighNumRange(1, 20),
|
|
290
|
-
},
|
|
290
|
+
"dungeon_id": dungeonIds,
|
|
291
|
+
"difficulty": ["Easy", "Medium", "Hard", "Deadly"],
|
|
292
|
+
"party_size": u.weighNumRange(1, 5),
|
|
293
|
+
}
|
|
291
294
|
},
|
|
292
295
|
{
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
"
|
|
297
|
-
"
|
|
298
|
-
"
|
|
299
|
-
"
|
|
296
|
+
event: "exit dungeon",
|
|
297
|
+
weight: 14,
|
|
298
|
+
properties: {
|
|
299
|
+
"dungeon_id": dungeonIds,
|
|
300
|
+
"time_spent_mins": u.weighNumRange(5, 120, 0.6, 30),
|
|
301
|
+
"completion_status": ["completed", "abandoned", "died"],
|
|
302
|
+
"strategic_explorer": [false],
|
|
303
|
+
"legendary_weapon_equipped": [false],
|
|
304
|
+
"subscriber_advantage": ["Free"],
|
|
300
305
|
}
|
|
301
306
|
},
|
|
302
307
|
{
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
"
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
"Items: Scrolls",
|
|
313
|
-
"Boosts",
|
|
314
|
-
"Currency",
|
|
315
|
-
],
|
|
316
|
-
"purchase amount": u.weighNumRange(1, 50, 1, 20),
|
|
317
|
-
is_whale: [false],
|
|
308
|
+
event: "find treasure",
|
|
309
|
+
weight: 16,
|
|
310
|
+
properties: {
|
|
311
|
+
"treasure_type": ["Gold", "Weapon", "Armor", "Potion", "Scroll", "Rare Artifact"],
|
|
312
|
+
"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],
|
|
318
317
|
}
|
|
319
318
|
},
|
|
320
319
|
{
|
|
321
|
-
event: "
|
|
320
|
+
event: "player death",
|
|
321
|
+
weight: 8,
|
|
322
|
+
properties: {
|
|
323
|
+
"cause_of_death": ["Monster", "Trap", "Fall Damage", "Poison", "Friendly Fire"],
|
|
324
|
+
"player_level": u.weighNumRange(1, 50),
|
|
325
|
+
"resurrection_used": [false, false, false, true],
|
|
326
|
+
"cursed_week": [false],
|
|
327
|
+
"near_death_survival": [false],
|
|
328
|
+
"subscriber_advantage": ["Free"],
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
event: "level up",
|
|
333
|
+
weight: 5,
|
|
334
|
+
properties: {
|
|
335
|
+
"new_level": u.weighNumRange(2, 50),
|
|
336
|
+
"stat_points_gained": u.weighNumRange(1, 5),
|
|
337
|
+
"new_abilities": ["Attack", "Spell", "Feat", "Skill"],
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
event: "item purchased",
|
|
342
|
+
weight: 11,
|
|
343
|
+
properties: {
|
|
344
|
+
"item_id": itemIds,
|
|
345
|
+
"item_type": ["Weapon", "Armor", "Potion", "Scroll", "Mount", "Cosmetic"],
|
|
346
|
+
"price_gold": u.weighNumRange(10, 500, 0.8, 100),
|
|
347
|
+
"vendor_type": ["Town", "Dungeon", "Special Event"],
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
event: "item sold",
|
|
352
|
+
weight: 7,
|
|
353
|
+
properties: {
|
|
354
|
+
"item_id": itemIds,
|
|
355
|
+
"item_type": ["Weapon", "Armor", "Potion", "Scroll", "Junk"],
|
|
356
|
+
"sell_price": u.weighNumRange(5, 250, 0.5, 50),
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
event: "real money purchase",
|
|
322
361
|
weight: 3,
|
|
323
362
|
properties: {
|
|
324
|
-
"
|
|
325
|
-
"
|
|
326
|
-
"
|
|
327
|
-
"
|
|
328
|
-
"
|
|
329
|
-
"
|
|
330
|
-
|
|
331
|
-
"boss level": u.weighNumRange(10, 20),
|
|
332
|
-
"boss difficulty": [
|
|
333
|
-
"Hard",
|
|
334
|
-
"Legendary",
|
|
335
|
-
"Impossible",
|
|
363
|
+
"product": [
|
|
364
|
+
"Premium Currency (1000)",
|
|
365
|
+
"Premium Currency (5000)",
|
|
366
|
+
"Lucky Charm Pack",
|
|
367
|
+
"Legendary Weapon Chest",
|
|
368
|
+
"Cosmetic Bundle",
|
|
369
|
+
"Season Pass"
|
|
336
370
|
],
|
|
337
|
-
"
|
|
338
|
-
|
|
339
|
-
|
|
371
|
+
"price_usd": [4.99, 9.99, 19.99, 49.99, 99.99],
|
|
372
|
+
"payment_method": ["Credit Card", "PayPal", "Apple Pay", "Google Pay"],
|
|
373
|
+
"lucky_charm_effect": [false],
|
|
374
|
+
"is_whale": [false],
|
|
375
|
+
}
|
|
340
376
|
},
|
|
341
377
|
{
|
|
342
|
-
event: "
|
|
378
|
+
event: "guild joined",
|
|
379
|
+
weight: 4,
|
|
380
|
+
properties: {
|
|
381
|
+
"guild_size": u.weighNumRange(5, 100),
|
|
382
|
+
"guild_level": u.weighNumRange(1, 20),
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
event: "guild left",
|
|
343
387
|
weight: 1,
|
|
344
388
|
properties: {
|
|
345
|
-
"
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
389
|
+
"reason": ["Inactive", "Found Better Guild", "Conflict", "Disbanded"],
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
event: "inspect",
|
|
394
|
+
weight: 9,
|
|
395
|
+
properties: {
|
|
396
|
+
"inspect_target": ["NPC", "Monster", "Treasure Chest", "Door", "Statue", "Bookshelf"],
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
event: "search for clues",
|
|
401
|
+
weight: 8,
|
|
402
|
+
properties: {
|
|
403
|
+
"location_type": ["Dungeon Entrance", "Hidden Room", "Quest Location", "Town Square"],
|
|
404
|
+
"clue_found": [false, false, true, true, true],
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
event: "use item",
|
|
409
|
+
weight: 14,
|
|
410
|
+
properties: {
|
|
411
|
+
"item_id": itemIds,
|
|
412
|
+
"item_type": [
|
|
413
|
+
"Health Potion", "Mana Potion", "Buff Scroll",
|
|
414
|
+
"Ancient Compass", "Lucky Charm", "Resurrection Stone"
|
|
350
415
|
],
|
|
351
|
-
|
|
416
|
+
"context": ["Combat", "Exploration", "Boss Fight", "Casual"],
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
event: "combat initiated",
|
|
421
|
+
weight: 20,
|
|
422
|
+
properties: {
|
|
423
|
+
"enemy_type": ["Goblin", "Skeleton", "Dragon", "Demon", "Undead", "Beast"],
|
|
424
|
+
"enemy_level": u.weighNumRange(1, 50),
|
|
425
|
+
"combat_duration_sec": u.weighNumRange(10, 300, 0.7, 60),
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
event: "combat completed",
|
|
430
|
+
weight: 18,
|
|
431
|
+
properties: {
|
|
432
|
+
"outcome": ["Victory", "Defeat", "Fled"],
|
|
433
|
+
"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
|
+
}
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
event: "fight boss",
|
|
442
|
+
weight: 3,
|
|
443
|
+
properties: {
|
|
444
|
+
"boss_type": ["Dragon", "Demon", "Lich", "Vampire", "Beholder"],
|
|
445
|
+
"boss_level": u.weighNumRange(10, 50),
|
|
446
|
+
"boss_difficulty": ["Hard", "Legendary", "Impossible"],
|
|
447
|
+
"fight_duration_mins": u.weighNumRange(1, 60),
|
|
448
|
+
"victory": [false, true, true],
|
|
449
|
+
}
|
|
352
450
|
},
|
|
353
451
|
{
|
|
354
452
|
event: "attack",
|
|
355
453
|
weight: 5,
|
|
356
454
|
properties: {
|
|
357
|
-
|
|
455
|
+
"attack_type": ["Melee", "Ranged", "Spell", "Special"],
|
|
456
|
+
"damage_dealt": u.weighNumRange(1, 200, 0.7, 30),
|
|
358
457
|
}
|
|
359
458
|
},
|
|
360
459
|
{
|
|
361
460
|
event: "defend",
|
|
362
461
|
weight: 3,
|
|
363
462
|
properties: {
|
|
364
|
-
|
|
463
|
+
"defense_type": ["Block", "Parry", "Dodge", "Shield"],
|
|
464
|
+
"damage_blocked": u.weighNumRange(0, 150, 0.7, 20),
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
event: "gameplay summary",
|
|
469
|
+
weight: 6,
|
|
470
|
+
properties: {
|
|
471
|
+
"enemies_defeated": u.weighNumRange(0, 100),
|
|
472
|
+
"respawns": u.weighNumRange(0, 10, 5),
|
|
473
|
+
"total_attacks": u.weighNumRange(0, 100, 6, 12),
|
|
474
|
+
"gold_found": u.weighNumRange(0, 1000),
|
|
365
475
|
}
|
|
366
476
|
},
|
|
367
477
|
],
|
|
368
|
-
superProps: {
|
|
369
|
-
Platform: [
|
|
370
|
-
"Mobile",
|
|
371
|
-
"Xbox",
|
|
372
|
-
"Playstation",
|
|
373
|
-
"Switch",
|
|
374
|
-
"Web"
|
|
375
|
-
],
|
|
376
|
-
"game mode": u.pickAWinner([
|
|
377
|
-
"Single Player",
|
|
378
|
-
"Multiplayer",
|
|
379
|
-
], 1),
|
|
380
|
-
language: [
|
|
381
|
-
"English",
|
|
382
|
-
"Spanish",
|
|
383
|
-
"French",
|
|
384
|
-
"German",
|
|
385
|
-
"Japanese",
|
|
386
|
-
"Korean",
|
|
387
|
-
"Chinese",
|
|
388
|
-
],
|
|
389
478
|
|
|
479
|
+
superProps: {
|
|
480
|
+
Platform: ["PC", "Mac", "PlayStation", "Xbox", "Switch"],
|
|
481
|
+
graphics_quality: ["Low", "Medium", "High", "Ultra"],
|
|
482
|
+
subscription_tier: ["Free", "Free", "Free", "Premium", "Elite"],
|
|
390
483
|
},
|
|
484
|
+
|
|
391
485
|
scdProps: {
|
|
392
|
-
"subscription MRR" : {
|
|
393
|
-
frequency: "week",
|
|
394
|
-
values: u.weighNumRange(0, 250, 1, 200),
|
|
395
|
-
timing: "fixed",
|
|
396
|
-
max: 250,
|
|
397
|
-
},
|
|
398
486
|
player_rank: {
|
|
399
|
-
values: ["
|
|
487
|
+
values: ["recruit", "veteran", "elite", "legend"],
|
|
400
488
|
frequency: "week",
|
|
401
489
|
timing: "fuzzy",
|
|
402
|
-
max:
|
|
403
|
-
},
|
|
404
|
-
guild_rank: {
|
|
405
|
-
values: ["bronze", "silver", "gold", "legendary"],
|
|
406
|
-
frequency: "month",
|
|
407
|
-
timing: "fixed",
|
|
408
|
-
max: 6,
|
|
409
|
-
type: "guild_id"
|
|
490
|
+
max: 20
|
|
410
491
|
}
|
|
411
492
|
},
|
|
493
|
+
|
|
412
494
|
userProps: {
|
|
413
|
-
|
|
414
|
-
"
|
|
415
|
-
"
|
|
416
|
-
"Playstation",
|
|
417
|
-
"Switch",
|
|
418
|
-
"Web"
|
|
495
|
+
"preferred_playstyle": [
|
|
496
|
+
"Solo Explorer", "Group Raider", "PvP Fighter",
|
|
497
|
+
"Quest Completionist", "Treasure Hunter"
|
|
419
498
|
],
|
|
420
|
-
"
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
language: [
|
|
425
|
-
"English",
|
|
426
|
-
"Spanish",
|
|
427
|
-
"French",
|
|
428
|
-
"German",
|
|
429
|
-
"Japanese",
|
|
430
|
-
"Korean",
|
|
431
|
-
"Chinese",
|
|
499
|
+
"total_playtime_hours": u.weighNumRange(1, 500, 1.5, 50),
|
|
500
|
+
"achievement_points": u.weighNumRange(0, 5000, 0.8, 500),
|
|
501
|
+
"favorite_class": [
|
|
502
|
+
"Warrior", "Mage", "Rogue", "Cleric", "Ranger", "Paladin"
|
|
432
503
|
],
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
"free trial",
|
|
437
|
-
],
|
|
438
|
-
variant: ["A", "B", "C", "Control"],
|
|
504
|
+
Platform: ["PC", "Mac", "PlayStation", "Xbox", "Switch"],
|
|
505
|
+
graphics_quality: ["Low", "Medium", "High", "Ultra"],
|
|
506
|
+
subscription_tier: ["Free", "Free", "Free", "Premium", "Elite"],
|
|
439
507
|
|
|
508
|
+
// D&D character identity (from gaming dungeon)
|
|
440
509
|
race: [
|
|
441
|
-
"Human",
|
|
442
|
-
"Elf",
|
|
443
|
-
"Dwarf",
|
|
444
|
-
"Halfling",
|
|
445
|
-
"Dragonborn",
|
|
446
|
-
"Gnome",
|
|
447
|
-
"Half-Elf",
|
|
448
|
-
"Half-Orc",
|
|
449
|
-
"Tiefling",
|
|
510
|
+
"Human", "Elf", "Dwarf", "Halfling", "Dragonborn",
|
|
511
|
+
"Gnome", "Half-Elf", "Half-Orc", "Tiefling"
|
|
450
512
|
],
|
|
451
513
|
class: [
|
|
452
|
-
"Barbarian",
|
|
453
|
-
"
|
|
454
|
-
"Cleric",
|
|
455
|
-
"Druid",
|
|
456
|
-
"Fighter",
|
|
457
|
-
"Monk",
|
|
458
|
-
"Paladin",
|
|
459
|
-
"Ranger",
|
|
460
|
-
"Rogue",
|
|
461
|
-
"Sorcerer",
|
|
462
|
-
"Warlock",
|
|
463
|
-
"Wizard",
|
|
514
|
+
"Barbarian", "Bard", "Cleric", "Druid", "Fighter", "Monk",
|
|
515
|
+
"Paladin", "Ranger", "Rogue", "Sorcerer", "Warlock", "Wizard"
|
|
464
516
|
],
|
|
465
517
|
alignment: [
|
|
466
|
-
"Lawful Good",
|
|
467
|
-
"Neutral
|
|
468
|
-
"Chaotic
|
|
469
|
-
"Lawful Neutral",
|
|
470
|
-
"True Neutral",
|
|
471
|
-
"Chaotic Neutral",
|
|
472
|
-
"Lawful Evil",
|
|
473
|
-
"Neutral Evil",
|
|
474
|
-
"Chaotic Evil",
|
|
518
|
+
"Lawful Good", "Neutral Good", "Chaotic Good",
|
|
519
|
+
"Lawful Neutral", "True Neutral", "Chaotic Neutral",
|
|
520
|
+
"Lawful Evil", "Neutral Evil", "Chaotic Evil"
|
|
475
521
|
],
|
|
476
|
-
level: u.weighNumRange(1, 20),
|
|
477
522
|
background: [
|
|
478
|
-
"Acolyte",
|
|
479
|
-
"
|
|
480
|
-
"
|
|
481
|
-
"Entertainer",
|
|
482
|
-
"Folk Hero",
|
|
483
|
-
"Guild Artisan",
|
|
484
|
-
"Hermit",
|
|
485
|
-
"Noble",
|
|
486
|
-
"Outlander",
|
|
487
|
-
"Sage",
|
|
488
|
-
"Sailor",
|
|
489
|
-
"Soldier",
|
|
490
|
-
"Urchin",
|
|
523
|
+
"Acolyte", "Charlatan", "Criminal", "Entertainer", "Folk Hero",
|
|
524
|
+
"Guild Artisan", "Hermit", "Noble", "Outlander", "Sage",
|
|
525
|
+
"Sailor", "Soldier", "Urchin"
|
|
491
526
|
],
|
|
492
|
-
|
|
527
|
+
level: u.weighNumRange(1, 20),
|
|
493
528
|
archetype: ["neutral"],
|
|
529
|
+
|
|
530
|
+
// A/B/C experiment scaffolding
|
|
531
|
+
experiment: ["fast leveling", "tension economy", "free trial"],
|
|
532
|
+
variant: ["A", "B", "C", "Control"],
|
|
494
533
|
},
|
|
495
|
-
// HOOK: 3 patterns — gold scaling by level, whale purchases, alignment archetype
|
|
496
|
-
hook: function (record, type, meta) {
|
|
497
534
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const level = record["level at end"] || 1;
|
|
502
|
-
record["quest reward (gold)"] = Math.round((record["quest reward (gold)"] || 50) * (1 + level * 0.1));
|
|
503
|
-
}
|
|
535
|
+
groupKeys: [
|
|
536
|
+
["guild_id", 500, ["guild joined", "guild left", "quest turned in", "combat completed"]],
|
|
537
|
+
],
|
|
504
538
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
record.is_whale = true;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
539
|
+
groupProps: {
|
|
540
|
+
guild_id: {
|
|
541
|
+
"name": () => `${chance.word()} ${chance.pickone(["Knights", "Dragons", "Warriors", "Seekers", "Legends"])}`,
|
|
542
|
+
"member_count": u.weighNumRange(5, 100),
|
|
543
|
+
"guild_level": u.weighNumRange(1, 20),
|
|
544
|
+
"total_wealth": u.weighNumRange(1000, 1000000, 0.5, 50000),
|
|
514
545
|
}
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
lookupTables: [],
|
|
515
549
|
|
|
550
|
+
/**
|
|
551
|
+
* 🎯 ARCHITECTED ANALYTICS HOOKS — 11 patterns
|
|
552
|
+
*
|
|
553
|
+
* 1. CONVERSION: Ancient Compass users have 3x quest completion + 1.5x rewards
|
|
554
|
+
* 2. TIME-BASED: "Cursed Week" (days 40-47) has 5x death rates
|
|
555
|
+
* 3. RETENTION: Early guild joiners (first 3 days) have 80% D30 retention vs 20%
|
|
556
|
+
* 4. CHURN: Players with 3+ deaths in first week have 70% churn rate
|
|
557
|
+
* 5. PURCHASE VALUE: Lucky Charm buyers spend 5x more (LTV pattern)
|
|
558
|
+
* 6. BEHAVIORS TOGETHER: inspect + search before dungeon = 85% completion vs 45%
|
|
559
|
+
* 7. TIMED RELEASE: Legendary weapon released day 45, early adopters dominate
|
|
560
|
+
* 8. SUBSCRIPTION TIER: Premium/Elite users have higher engagement and success
|
|
561
|
+
* 9. PROGRESSION SCALING: Quest gold scales with player level (1 + level * 0.1)
|
|
562
|
+
* 10. WHALE PURCHASES: ~33% of users via deterministic hash spend 1.8x more
|
|
563
|
+
* 11. ALIGNMENT ARCHETYPE: Good=hero, Evil=villain, other=neutral (user hook)
|
|
564
|
+
*/
|
|
565
|
+
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
|
+
// Hook #11: ALIGNMENT ARCHETYPE — derive archetype on user profile
|
|
516
573
|
if (type === "user") {
|
|
517
|
-
// Pattern 3: Chaotic Evil players get a "villain" tag; Lawful Good get "hero"
|
|
518
574
|
if (record.alignment === "Chaotic Evil" || record.alignment === "Neutral Evil") {
|
|
519
575
|
record.archetype = "villain";
|
|
520
576
|
} else if (record.alignment === "Lawful Good" || record.alignment === "Neutral Good") {
|
|
@@ -524,28 +580,355 @@ const config = {
|
|
|
524
580
|
}
|
|
525
581
|
}
|
|
526
582
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (type === "funnel-pre") {
|
|
532
|
-
|
|
533
|
-
}
|
|
583
|
+
// Hook #2 event-level part removed — cursed week now handled in everything hook
|
|
534
584
|
|
|
535
|
-
|
|
585
|
+
// Hook #7: TIMED RELEASE — Legendary Weapon
|
|
586
|
+
if (type === "event") {
|
|
587
|
+
const EVENT_TIME = dayjs(record.time);
|
|
536
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;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
537
598
|
}
|
|
538
599
|
|
|
600
|
+
// Hooks #1, #3, #4, #5, #6, #8, #9, #10: per-user behavioral patterns
|
|
539
601
|
if (type === "everything") {
|
|
540
|
-
|
|
602
|
+
const userEvents = record;
|
|
541
603
|
const profile = meta.profile;
|
|
542
|
-
|
|
604
|
+
|
|
605
|
+
// Stamp superProps from profile for consistency
|
|
606
|
+
userEvents.forEach(e => {
|
|
543
607
|
e.Platform = profile.Platform;
|
|
544
|
-
e
|
|
545
|
-
e.
|
|
608
|
+
e.graphics_quality = profile.graphics_quality;
|
|
609
|
+
e.subscription_tier = profile.subscription_tier;
|
|
546
610
|
});
|
|
547
611
|
|
|
548
|
-
|
|
612
|
+
const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
|
|
613
|
+
const userLevel = profile.level || 1;
|
|
614
|
+
|
|
615
|
+
// Track user behaviors
|
|
616
|
+
let usedAncientCompass = false;
|
|
617
|
+
let boughtLuckyCharm = false;
|
|
618
|
+
let joinedGuildEarly = false;
|
|
619
|
+
let earlyDeaths = 0;
|
|
620
|
+
let hasLegendaryWeapon = false;
|
|
621
|
+
let inspectedBeforeDungeon = false;
|
|
622
|
+
let searchedBeforeDungeon = false;
|
|
623
|
+
let subscriptionTier = "Free";
|
|
624
|
+
|
|
625
|
+
// Hook #10: Whale segmentation — deterministic via user_id hash (~33%)
|
|
626
|
+
const userId = userEvents.length > 0 ? (userEvents[0].user_id || userEvents[0].distinct_id || "") : "";
|
|
627
|
+
const isWhale = userId.length > 0 && userId.charCodeAt(0) % 3 === 0;
|
|
628
|
+
|
|
629
|
+
// First pass: identify user patterns
|
|
630
|
+
userEvents.forEach((event) => {
|
|
631
|
+
const eventTime = dayjs(event.time);
|
|
632
|
+
const daysSinceStart = firstEventTime ? eventTime.diff(firstEventTime, 'days', true) : 0;
|
|
633
|
+
|
|
634
|
+
if (event.subscription_tier) {
|
|
635
|
+
subscriptionTier = event.subscription_tier;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (event.event === "use item" && event.item_type === "Ancient Compass") {
|
|
639
|
+
usedAncientCompass = true;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (event.event === "real money purchase" && event.product === "Lucky Charm Pack") {
|
|
643
|
+
boughtLuckyCharm = true;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (event.event === "guild joined" && daysSinceStart < 3) {
|
|
647
|
+
joinedGuildEarly = true;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (event.event === "player death" && daysSinceStart < 7) {
|
|
651
|
+
earlyDeaths++;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (event.event === "find treasure" && event.legendary_drop) {
|
|
655
|
+
hasLegendaryWeapon = true;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (event.event === "inspect") {
|
|
659
|
+
inspectedBeforeDungeon = true;
|
|
660
|
+
}
|
|
661
|
+
if (event.event === "search for clues") {
|
|
662
|
+
searchedBeforeDungeon = true;
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// Second pass: modify events based on patterns
|
|
667
|
+
userEvents.forEach((event, idx) => {
|
|
668
|
+
const eventTime = dayjs(event.time);
|
|
669
|
+
|
|
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
|
|
699
|
+
if (event.event === "quest turned in") {
|
|
700
|
+
const baseGold = event.reward_gold || 100;
|
|
701
|
+
event.reward_gold = Math.floor(baseGold * (1 + userLevel * 0.1));
|
|
702
|
+
event.level_scaled = true;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Hook #1: CONVERSION — Ancient Compass users complete more quests
|
|
706
|
+
if (usedAncientCompass && event.event === "quest turned in") {
|
|
707
|
+
event.reward_gold = Math.floor((event.reward_gold || 100) * 1.5);
|
|
708
|
+
event.reward_xp = Math.floor((event.reward_xp || 500) * 1.5);
|
|
709
|
+
event.compass_user = true;
|
|
710
|
+
|
|
711
|
+
if (chance.bool({ likelihood: 40 })) {
|
|
712
|
+
const extraQuest = {
|
|
713
|
+
...event,
|
|
714
|
+
time: eventTime.add(chance.integer({ min: 10, max: 120 }), 'minutes').toISOString(),
|
|
715
|
+
quest_id: chance.pickone(questIds),
|
|
716
|
+
reward_gold: chance.integer({ min: 100, max: 500 }),
|
|
717
|
+
reward_xp: chance.integer({ min: 500, max: 2000 }),
|
|
718
|
+
compass_user: true,
|
|
719
|
+
};
|
|
720
|
+
userEvents.splice(idx + 1, 0, extraQuest);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Hook #5: PURCHASE VALUE — Lucky charm buyers spend 5x more
|
|
725
|
+
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
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (event.event === "item purchased" && chance.bool({ likelihood: 35 })) {
|
|
737
|
+
const purchaseTemplate = userEvents.find(e => e.event === "real money purchase");
|
|
738
|
+
if (purchaseTemplate) {
|
|
739
|
+
const extraPurchase = {
|
|
740
|
+
...purchaseTemplate,
|
|
741
|
+
time: eventTime.add(chance.integer({ min: 1, max: 3 }), 'days').toISOString(),
|
|
742
|
+
user_id: event.user_id,
|
|
743
|
+
product: chance.pickone([
|
|
744
|
+
"Premium Currency (5000)",
|
|
745
|
+
"Legendary Weapon Chest",
|
|
746
|
+
"Season Pass"
|
|
747
|
+
]),
|
|
748
|
+
price_usd: chance.pickone([19.99, 49.99, 99.99]),
|
|
749
|
+
payment_method: chance.pickone(["Credit Card", "PayPal"]),
|
|
750
|
+
lucky_charm_effect: true,
|
|
751
|
+
};
|
|
752
|
+
userEvents.splice(idx + 1, 0, extraPurchase);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
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;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Hook #6: BEHAVIORS TOGETHER — Inspect + Search before dungeon
|
|
766
|
+
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
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
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
|
+
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
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (event.event === "exit dungeon") {
|
|
794
|
+
event.completion_status = "completed";
|
|
795
|
+
event.time_spent_mins = Math.floor((event.time_spent_mins || 60) * 0.6);
|
|
796
|
+
event.legendary_weapon_equipped = true;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Hook #8: SUBSCRIPTION TIER — Premium/Elite advantages
|
|
801
|
+
if (subscriptionTier === "Premium" || subscriptionTier === "Elite") {
|
|
802
|
+
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
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (event.event === "quest turned in") {
|
|
816
|
+
const rewardMultiplier = isElite ? 1.8 : 1.4;
|
|
817
|
+
event.reward_gold = Math.floor((event.reward_gold || 100) * rewardMultiplier);
|
|
818
|
+
event.reward_xp = Math.floor((event.reward_xp || 500) * rewardMultiplier);
|
|
819
|
+
event.subscriber_advantage = subscriptionTier;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (event.event === "exit dungeon") {
|
|
823
|
+
if (event.completion_status !== "completed") {
|
|
824
|
+
const completionBoost = isElite ? 65 : 45;
|
|
825
|
+
if (Math.random() * 100 < completionBoost) {
|
|
826
|
+
event.completion_status = "completed";
|
|
827
|
+
event.subscriber_advantage = subscriptionTier;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (event.completion_status === "completed") {
|
|
831
|
+
const speedBoost = isElite ? 0.7 : 0.85;
|
|
832
|
+
event.time_spent_mins = Math.floor((event.time_spent_mins || 60) * speedBoost);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (event.event === "find treasure") {
|
|
837
|
+
const treasureBoost = isElite ? 2.0 : 1.5;
|
|
838
|
+
event.treasure_value = Math.floor((event.treasure_value || 50) * treasureBoost);
|
|
839
|
+
event.subscriber_advantage = subscriptionTier;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (event.event === "player death" && !event.cursed_week) {
|
|
843
|
+
const survivalChance = isElite ? 50 : 30;
|
|
844
|
+
if (Math.random() * 100 < survivalChance) {
|
|
845
|
+
event.event = "combat completed";
|
|
846
|
+
event.outcome = "Victory";
|
|
847
|
+
event.loot_gained = true;
|
|
848
|
+
event.subscriber_advantage = subscriptionTier;
|
|
849
|
+
event.near_death_survival = true;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if (isElite && Math.random() * 100 < 15) {
|
|
854
|
+
if (event.event === "quest turned in" || event.event === "exit dungeon") {
|
|
855
|
+
const treasureTemplate = userEvents.find(e => e.event === "find treasure");
|
|
856
|
+
if (treasureTemplate) {
|
|
857
|
+
const treasureTypes = ["Rare Artifact", "Gold", "Weapon", "Armor"];
|
|
858
|
+
const bonusEvent = {
|
|
859
|
+
...treasureTemplate,
|
|
860
|
+
time: eventTime.add(Math.floor(Math.random() * 26) + 5, 'minutes').toISOString(),
|
|
861
|
+
user_id: event.user_id,
|
|
862
|
+
treasure_type: treasureTypes[Math.floor(Math.random() * treasureTypes.length)],
|
|
863
|
+
treasure_value: Math.floor(Math.random() * 601) + 200,
|
|
864
|
+
subscriber_advantage: "Elite",
|
|
865
|
+
elite_bonus: true,
|
|
866
|
+
};
|
|
867
|
+
userEvents.splice(idx + 1, 0, bonusEvent);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
// Hook #2: CURSED WEEK — inject death events for days 40-47 of each user's timeline
|
|
875
|
+
if (firstEventTime) {
|
|
876
|
+
const deathTemplate = userEvents.find(e => e.event === "player death");
|
|
877
|
+
if (deathTemplate) {
|
|
878
|
+
const cursedStart = firstEventTime.add(40, 'days');
|
|
879
|
+
const cursedEnd = firstEventTime.add(47, 'days');
|
|
880
|
+
const cursedEvents = userEvents.filter(e => {
|
|
881
|
+
const t = dayjs(e.time);
|
|
882
|
+
return t.isAfter(cursedStart) && t.isBefore(cursedEnd);
|
|
883
|
+
});
|
|
884
|
+
const deathsToInject = Math.floor(cursedEvents.length * 0.6);
|
|
885
|
+
for (let d = 0; d < deathsToInject; d++) {
|
|
886
|
+
const sourceEvent = cursedEvents[d % cursedEvents.length];
|
|
887
|
+
const injected = {
|
|
888
|
+
...deathTemplate,
|
|
889
|
+
time: dayjs(sourceEvent.time).add(chance.integer({ min: 1, max: 30 }), 'minutes').toISOString(),
|
|
890
|
+
user_id: sourceEvent.user_id,
|
|
891
|
+
event: "player death",
|
|
892
|
+
cause_of_death: "Curse",
|
|
893
|
+
player_level: chance.integer({ min: 1, max: 50 }),
|
|
894
|
+
resurrection_used: chance.bool({ likelihood: 80 }),
|
|
895
|
+
cursed_week: true,
|
|
896
|
+
near_death_survival: false,
|
|
897
|
+
subscriber_advantage: "Free",
|
|
898
|
+
};
|
|
899
|
+
userEvents.push(injected);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Hook #3 RETENTION + Hook #4 CHURN
|
|
905
|
+
const shouldChurn = (!joinedGuildEarly && earlyDeaths >= 3) || (earlyDeaths >= 5);
|
|
906
|
+
|
|
907
|
+
if (shouldChurn) {
|
|
908
|
+
const firstWeekEnd = firstEventTime ? firstEventTime.add(7, 'days') : null;
|
|
909
|
+
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
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
} else if (joinedGuildEarly) {
|
|
918
|
+
const lastEvent = userEvents[userEvents.length - 1];
|
|
919
|
+
const combatTemplate = userEvents.find(e => e.event === "combat completed");
|
|
920
|
+
if (lastEvent && combatTemplate && chance.bool({ likelihood: 60 })) {
|
|
921
|
+
const retentionEvent = {
|
|
922
|
+
...combatTemplate,
|
|
923
|
+
time: dayjs(lastEvent.time).add(chance.integer({ min: 1, max: 5 }), 'days').toISOString(),
|
|
924
|
+
user_id: lastEvent.user_id,
|
|
925
|
+
outcome: "Victory",
|
|
926
|
+
loot_gained: true,
|
|
927
|
+
guild_member_retained: true,
|
|
928
|
+
};
|
|
929
|
+
userEvents.push(retentionEvent);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
549
932
|
}
|
|
550
933
|
|
|
551
934
|
return record;
|