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