@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +51 -13
  3. package/dungeons/technical/ad-spend.js +2 -2
  4. package/dungeons/technical/anonymous-users.js +2 -2
  5. package/dungeons/technical/array-of-object-lookup.js +2 -2
  6. package/dungeons/technical/experiments.js +2 -2
  7. package/dungeons/technical/foobar.js +2 -2
  8. package/dungeons/technical/group-analytics.js +2 -2
  9. package/dungeons/technical/mirror-strategies.js +2 -2
  10. package/dungeons/technical/nested-objects.js +2 -2
  11. package/dungeons/technical/retention-cadence.js +2 -3
  12. package/dungeons/technical/sanity.js +2 -2
  13. package/dungeons/technical/scale-test.js +2 -2
  14. package/dungeons/technical/scd.js +2 -2
  15. package/dungeons/technical/simple.js +2 -2
  16. package/dungeons/technical/simplest.js +2 -2
  17. package/dungeons/technical/text-generation.js +2 -2
  18. package/dungeons/vertical/ai-platform-schema.json +617 -0
  19. package/dungeons/vertical/ai-platform.js +799 -0
  20. package/dungeons/vertical/community.js +40 -26
  21. package/dungeons/vertical/crypto-schema.json +546 -0
  22. package/dungeons/vertical/crypto.js +721 -0
  23. package/dungeons/vertical/dating-schema.json +401 -0
  24. package/dungeons/vertical/dating.js +798 -0
  25. package/dungeons/vertical/devtools.js +13 -9
  26. package/dungeons/vertical/ecommerce.js +2 -3
  27. package/dungeons/vertical/education.js +4 -5
  28. package/dungeons/vertical/fintech.js +32 -29
  29. package/dungeons/vertical/fitness.js +2 -3
  30. package/dungeons/vertical/food-delivery.js +37 -43
  31. package/dungeons/vertical/gaming-schema.json +2495 -230
  32. package/dungeons/vertical/gaming.js +771 -388
  33. package/dungeons/vertical/healthcare.js +2 -3
  34. package/dungeons/vertical/insurance-application.js +2 -3
  35. package/dungeons/vertical/logistics.js +20 -14
  36. package/dungeons/vertical/marketplace.js +22 -14
  37. package/dungeons/vertical/media.js +39 -30
  38. package/dungeons/vertical/real-estate-schema.json +527 -0
  39. package/dungeons/vertical/real-estate.js +774 -0
  40. package/dungeons/vertical/sass.js +2 -3
  41. package/dungeons/vertical/social.js +15 -13
  42. package/dungeons/vertical/travel.js +59 -26
  43. package/lib/core/config-validator.js +71 -15
  44. package/lib/core/storage.js +14 -4
  45. package/lib/orchestrators/user-loop.js +39 -5
  46. package/lib/templates/macro-presets.js +111 -0
  47. package/lib/templates/soup-presets.js +19 -36
  48. package/package.json +8 -2
  49. package/types.d.ts +219 -42
  50. package/dungeons/user/.gitkeep +0 -0
  51. package/dungeons/vertical/rpg-schema.json +0 -2491
  52. package/dungeons/vertical/rpg.js +0 -976
@@ -2,7 +2,7 @@
2
2
  const SEED = "dm4-devtools";
3
3
  const num_days = 100;
4
4
  const num_users = 5_000;
5
- const avg_events_per_user = 120;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -256,7 +256,7 @@ const config = {
256
256
  token,
257
257
  seed: SEED,
258
258
  numDays: num_days,
259
- numEvents: num_users * avg_events_per_user,
259
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
260
260
  numUsers: num_users,
261
261
  hasAnonIds: false,
262
262
  hasSessionIds: true,
@@ -271,7 +271,6 @@ const config = {
271
271
  hasCampaigns: false,
272
272
  isAnonymous: false,
273
273
  hasAdSpend: false,
274
- percentUsersBornInDataset: 35,
275
274
  hasAvatar: true,
276
275
  concurrency: 1,
277
276
  writeToDisk: false,
@@ -834,19 +833,24 @@ const config = {
834
833
  }
835
834
 
836
835
  // -- HOOK 6: POST-OUTAGE RECOVERY ---------------------
837
- // After outage ends (day 42.25), deployment events get cloned.
838
- const OUTAGE_END = DATASET_START.add(42.25, "days");
839
- const RECOVERY_END = DATASET_START.add(46, "days");
836
+ // After the outage volume rebound (d44-48), deployment events get
837
+ // aggressively cloned to produce a visible spike above baseline.
838
+ // Shifted later than outage end (d42.25) so natural volume has
839
+ // recovered from the 0.05x suppression before cloning kicks in.
840
+ const RECOVERY_START = DATASET_START.add(44, "days");
841
+ const RECOVERY_END = DATASET_START.add(48, "days");
840
842
  const deployEvents = events.filter(e => {
841
843
  if (e.event !== "deployment completed") return false;
842
844
  const t = dayjs(e.time);
843
- return t.isAfter(OUTAGE_END) && t.isBefore(RECOVERY_END);
845
+ return t.isAfter(RECOVERY_START) && t.isBefore(RECOVERY_END);
844
846
  });
845
847
  deployEvents.forEach(dep => {
846
- if (chance.bool({ likelihood: 50 })) {
848
+ // 100% clone rate with 3 copies per event to clearly
849
+ // exceed baseline deploy volume (d35-41)
850
+ for (let c = 0; c < 3; c++) {
847
851
  events.push({
848
852
  ...dep,
849
- time: dayjs(dep.time).add(chance.integer({ min: 1, max: 6 }), "hours").toISOString(),
853
+ time: dayjs(dep.time).add(chance.integer({ min: 1, max: 8 }), "hours").toISOString(),
850
854
  user_id: dep.user_id,
851
855
  deploy_status: chance.pickone(["success", "success", "rolled_back"]),
852
856
  environment: "production",
@@ -2,7 +2,7 @@
2
2
  const SEED = "simple is best";
3
3
  const num_days = 108;
4
4
  const num_users = 50_000;
5
- const avg_events_per_user = 40;
5
+ const avg_events_per_user_per_day = 0.37;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -143,7 +143,7 @@ const config = {
143
143
  token,
144
144
  seed: SEED,
145
145
  numDays: num_days,
146
- numEvents: num_users * avg_events_per_user,
146
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
147
147
  numUsers: num_users,
148
148
  format: 'json', //csv or json
149
149
  region: "US",
@@ -159,7 +159,6 @@ const config = {
159
159
  isAnonymous: false,
160
160
  alsoInferFunnels: false,
161
161
  concurrency: 1,
162
- percentUsersBornInDataset: 25,
163
162
  events: [
164
163
  {
165
164
  event: "checkout",
@@ -2,7 +2,7 @@
2
2
  const SEED = "harness-education";
3
3
  const num_days = 100;
4
4
  const num_users = 5_000;
5
- const avg_events_per_user = 120;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -193,7 +193,7 @@ const config = {
193
193
  token,
194
194
  seed: SEED,
195
195
  numDays: num_days,
196
- numEvents: num_users * avg_events_per_user,
196
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
197
197
  numUsers: num_users,
198
198
  hasAnonIds: false,
199
199
  hasSessionIds: true,
@@ -208,7 +208,6 @@ const config = {
208
208
  hasCampaigns: false,
209
209
  isAnonymous: false,
210
210
  hasAdSpend: false,
211
- percentUsersBornInDataset: 50,
212
211
  hasAvatar: true,
213
212
  concurrency: 1,
214
213
  writeToDisk: false,
@@ -512,7 +511,7 @@ const config = {
512
511
  * 8. PLAYBACK SPEED CORRELATION: Speed learners paradoxically score higher; thorough learners get extended time
513
512
  */
514
513
  hook: function (record, type, meta) {
515
- const NOW = dayjs();
514
+ const NOW = dayjs.utc();
516
515
  const DATASET_START = NOW.subtract(num_days, 'days');
517
516
 
518
517
  // ═══════════════════════════════════════════════════════════════════
@@ -580,7 +579,7 @@ const config = {
580
579
  // ═══════════════════════════════════════════════════════════════════
581
580
  if (type === "event") {
582
581
  if (record.time) {
583
- const eventTime = dayjs(record.time);
582
+ const eventTime = dayjs.utc(record.time);
584
583
  const dayInDataset = eventTime.diff(DATASET_START, 'days', true);
585
584
 
586
585
  const spikableEvents = ["quiz started", "quiz completed", "assignment submitted"];
@@ -2,7 +2,7 @@
2
2
  const SEED = "harness-fintech";
3
3
  const num_days = 100;
4
4
  const num_users = 5_000;
5
- const avg_events_per_user = 120;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -139,7 +139,7 @@ const config = {
139
139
  token,
140
140
  seed: SEED,
141
141
  numDays: num_days,
142
- numEvents: num_users * avg_events_per_user,
142
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
143
143
  numUsers: num_users,
144
144
  hasAnonIds: false,
145
145
  hasSessionIds: true,
@@ -154,7 +154,6 @@ const config = {
154
154
  hasCampaigns: false,
155
155
  isAnonymous: false,
156
156
  hasAdSpend: false,
157
- percentUsersBornInDataset: 50,
158
157
 
159
158
  hasAvatar: true,
160
159
 
@@ -534,32 +533,6 @@ const config = {
534
533
  }
535
534
  }
536
535
 
537
- // ===============================================================
538
- // Hook #7: PREMIUM TIER VALUE (event)
539
- // Premium tier users get 3x reward values and 2x investment sell
540
- // returns. Plus tier users get 1.5x rewards.
541
- // ===============================================================
542
- if (record.event === "reward redeemed") {
543
- if (record.account_tier === "premium") {
544
- record.value = Math.floor((record.value || 10) * 3);
545
- record.premium_reward = true;
546
- } else if (record.account_tier === "plus") {
547
- record.value = Math.floor((record.value || 10) * 1.5);
548
- record.premium_reward = false;
549
- } else {
550
- record.premium_reward = false;
551
- }
552
- }
553
-
554
- if (record.event === "investment made" && record.action === "sell") {
555
- if (record.account_tier === "premium") {
556
- record.amount = Math.floor((record.amount || 250) * 2);
557
- record.premium_returns = true;
558
- } else {
559
- record.premium_returns = false;
560
- }
561
- }
562
-
563
536
  // ===============================================================
564
537
  // Hook #8: MONTH-END ANXIETY (event)
565
538
  // Last 3 days of month (day >= 28) see 40% longer app sessions
@@ -600,6 +573,36 @@ const config = {
600
573
  e.low_balance_churn = profile.low_balance_churn;
601
574
  });
602
575
 
576
+ // -----------------------------------------------------------
577
+ // Hook #7: PREMIUM TIER VALUE (moved from event hook)
578
+ // Uses profile.account_tier which is authoritative here.
579
+ // Premium: 3x rewards, 2x investment sell returns.
580
+ // Plus: 1.5x rewards.
581
+ // -----------------------------------------------------------
582
+ const tier = profile.account_tier;
583
+ userEvents.forEach(e => {
584
+ if (e.event === "reward redeemed") {
585
+ if (tier === "premium") {
586
+ e.value = Math.floor((e.value || 10) * 3);
587
+ e.premium_reward = true;
588
+ } else if (tier === "plus") {
589
+ e.value = Math.floor((e.value || 10) * 1.5);
590
+ e.premium_reward = false;
591
+ } else {
592
+ e.premium_reward = false;
593
+ }
594
+ }
595
+
596
+ if (e.event === "investment made" && e.action === "sell") {
597
+ if (tier === "premium") {
598
+ e.amount = Math.floor((e.amount || 250) * 2);
599
+ e.premium_returns = true;
600
+ } else {
601
+ e.premium_returns = false;
602
+ }
603
+ }
604
+ });
605
+
603
606
  const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
604
607
 
605
608
  // -----------------------------------------------------------
@@ -2,7 +2,7 @@
2
2
  const SEED = "dm4-fitness";
3
3
  const num_days = 100;
4
4
  const num_users = 5_000;
5
- const avg_events_per_user = 120;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -265,7 +265,7 @@ const config = {
265
265
  token,
266
266
  seed: SEED,
267
267
  numDays: num_days,
268
- numEvents: num_users * avg_events_per_user,
268
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
269
269
  numUsers: num_users,
270
270
  hasAnonIds: false,
271
271
  hasSessionIds: true,
@@ -280,7 +280,6 @@ const config = {
280
280
  hasCampaigns: false,
281
281
  isAnonymous: false,
282
282
  hasAdSpend: false,
283
- percentUsersBornInDataset: 35,
284
283
  hasAvatar: true,
285
284
  concurrency: 1,
286
285
  writeToDisk: false,
@@ -2,7 +2,7 @@
2
2
  const SEED = "harness-food";
3
3
  const num_days = 100;
4
4
  const num_users = 5_000;
5
- const avg_events_per_user = 120;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -166,7 +166,7 @@ const config = {
166
166
  token,
167
167
  seed: SEED,
168
168
  numDays: num_days,
169
- numEvents: num_users * avg_events_per_user,
169
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
170
170
  numUsers: num_users,
171
171
  hasAnonIds: false,
172
172
  hasSessionIds: true,
@@ -181,7 +181,6 @@ const config = {
181
181
  hasCampaigns: false,
182
182
  isAnonymous: false,
183
183
  hasAdSpend: false,
184
- percentUsersBornInDataset: 50,
185
184
  hasAvatar: true,
186
185
  concurrency: 1,
187
186
  writeToDisk: false,
@@ -586,46 +585,8 @@ const config = {
586
585
  }
587
586
  }
588
587
 
589
- // ═══════════════════════════════════════════════════════════════════
590
- // HOOK 2: COUPON INJECTION (funnel-post)
591
- // Free-tier users get coupon_applied events spliced into their
592
- // funnel sequences 30% of the time, simulating promotional nudges
593
- // that push non-subscribers toward conversion.
594
- // ═══════════════════════════════════════════════════════════════════
595
- if (type === "funnel-post") {
596
- if (Array.isArray(record) && record.length >= 2) {
597
- // Check if user is Free tier from first event's super props
598
- const firstEvent = record[0];
599
- const isFreeUser = firstEvent && firstEvent.subscription_tier === "Free";
600
-
601
- if (isFreeUser && chance.bool({ likelihood: 30 })) {
602
- // Pick a random insertion point between funnel steps
603
- const insertIdx = chance.integer({ min: 1, max: record.length - 1 });
604
- const prevEvent = record[insertIdx - 1];
605
- const nextEvent = record[insertIdx];
606
- const midTime = dayjs(prevEvent.time).add(
607
- dayjs(nextEvent.time).diff(dayjs(prevEvent.time)) / 2,
608
- 'milliseconds'
609
- ).toISOString();
610
-
611
- const couponTemplate = record.find(e => e.event === "coupon applied");
612
- const couponEvent = {
613
- ...(couponTemplate || firstEvent),
614
- event: "coupon applied",
615
- time: midTime,
616
- user_id: firstEvent.user_id,
617
- subscription_tier: firstEvent.subscription_tier,
618
- Platform: firstEvent.Platform,
619
- city: firstEvent.city,
620
- coupon_code: chance.pickone(couponCodes),
621
- discount_type: chance.pickone(["percent", "flat", "free_delivery"]),
622
- discount_value: chance.integer({ min: 10, max: 30 }),
623
- coupon_injected: true,
624
- };
625
- record.splice(insertIdx, 0, couponEvent);
626
- }
627
- }
628
- }
588
+ // (Hook #2 coupon injection moved to everything hook where
589
+ // meta.profile.subscription_tier is authoritative)
629
590
 
630
591
  // ═══════════════════════════════════════════════════════════════════
631
592
  // HOOK 3: LATE NIGHT MUNCHIES (event)
@@ -694,6 +655,39 @@ const config = {
694
655
  });
695
656
  }
696
657
 
658
+ // ── Hook #2: COUPON INJECTION (moved from funnel-post) ──
659
+ // Free-tier users get coupon_applied events injected 30% of the
660
+ // time near checkout events. Uses meta.profile.subscription_tier
661
+ // which is authoritative here.
662
+ if (profile && profile.subscription_tier === "Free") {
663
+ for (let i = userEvents.length - 1; i >= 1; i--) {
664
+ const evt = userEvents[i];
665
+ if (evt.event === "checkout started" && chance.bool({ likelihood: 30 })) {
666
+ const prevEvent = userEvents[i - 1];
667
+ const midTime = dayjs(prevEvent.time).add(
668
+ dayjs(evt.time).diff(dayjs(prevEvent.time)) / 2,
669
+ 'milliseconds'
670
+ ).toISOString();
671
+
672
+ const couponTemplate = userEvents.find(e => e.event === "coupon applied");
673
+ const couponEvent = {
674
+ ...(couponTemplate || evt),
675
+ event: "coupon applied",
676
+ time: midTime,
677
+ user_id: evt.user_id,
678
+ subscription_tier: profile.subscription_tier,
679
+ Platform: profile.Platform,
680
+ city: profile.city,
681
+ coupon_code: chance.pickone(couponCodes),
682
+ discount_type: chance.pickone(["percent", "flat", "free_delivery"]),
683
+ discount_value: chance.integer({ min: 10, max: 30 }),
684
+ coupon_injected: true,
685
+ };
686
+ userEvents.splice(i, 0, couponEvent);
687
+ }
688
+ }
689
+ }
690
+
697
691
  // ── Bug 2 fix: Conversion filtering for lunch rush + first order ──
698
692
  // Hook 1: Non-lunch/dinner-hour events → drop ~30% of final funnel step
699
693
  // Hook 8: Returning users (no first_order_bonus) → drop ~30% of final funnel step