@ak--47/dungeon-master 1.2.3 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) 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 -4
  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 +5 -6
  16. package/dungeons/technical/simplest-schema.json +5 -0
  17. package/dungeons/technical/simplest.js +2 -2
  18. package/dungeons/technical/text-generation.js +3 -3
  19. package/dungeons/vertical/ai-platform.js +858 -0
  20. package/dungeons/vertical/community.js +84 -40
  21. package/dungeons/vertical/crypto.js +830 -0
  22. package/dungeons/vertical/dating.js +744 -0
  23. package/dungeons/vertical/devtools.js +175 -69
  24. package/dungeons/vertical/ecommerce.js +242 -94
  25. package/dungeons/vertical/education.js +330 -313
  26. package/dungeons/vertical/fintech.js +442 -313
  27. package/dungeons/vertical/fitness.js +143 -61
  28. package/dungeons/vertical/food-delivery.js +327 -353
  29. package/dungeons/vertical/gaming.js +912 -382
  30. package/dungeons/vertical/healthcare.js +142 -63
  31. package/dungeons/vertical/insurance-application.js +170 -76
  32. package/dungeons/vertical/logistics.js +115 -20
  33. package/dungeons/vertical/marketplace.js +152 -58
  34. package/dungeons/vertical/media.js +248 -384
  35. package/dungeons/vertical/real-estate.js +781 -0
  36. package/dungeons/vertical/sass.js +255 -266
  37. package/dungeons/vertical/social.js +264 -206
  38. package/dungeons/vertical/travel.js +117 -41
  39. package/index.js +17 -17
  40. package/lib/core/config-validator.js +159 -31
  41. package/lib/core/context.js +10 -24
  42. package/lib/core/storage.js +6 -1
  43. package/lib/generators/events.js +11 -14
  44. package/lib/generators/funnels.js +12 -4
  45. package/lib/generators/mirror.js +3 -2
  46. package/lib/generators/product-names.js +1 -1
  47. package/lib/generators/scd.js +2 -1
  48. package/lib/generators/text.js +1 -1
  49. package/lib/orchestrators/user-loop.js +81 -47
  50. package/lib/templates/macro-presets.js +111 -0
  51. package/lib/templates/soup-presets.js +19 -36
  52. package/lib/utils/utils.js +71 -39
  53. package/package.json +8 -2
  54. package/scripts/smoke-test-all.mjs +162 -0
  55. package/scripts/verify-runner.mjs +72 -24
  56. package/types.d.ts +251 -51
  57. package/dungeons/technical/ad-spend-schema.json +0 -128
  58. package/dungeons/technical/anonymous-users-schema.json +0 -92
  59. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  60. package/dungeons/technical/experiments-schema.json +0 -203
  61. package/dungeons/technical/foobar-schema.json +0 -362
  62. package/dungeons/technical/group-analytics-schema.json +0 -241
  63. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  64. package/dungeons/technical/nested-objects-schema.json +0 -145
  65. package/dungeons/technical/retention-cadence-schema.json +0 -37
  66. package/dungeons/technical/sanity-schema.json +0 -185
  67. package/dungeons/technical/scale-test-schema.json +0 -70
  68. package/dungeons/technical/scd-schema.json +0 -467
  69. package/dungeons/technical/simple-schema.json +0 -362
  70. package/dungeons/technical/text-generation-schema.json +0 -1062
  71. package/dungeons/user/.gitkeep +0 -0
  72. package/dungeons/vertical/community-schema.json +0 -579
  73. package/dungeons/vertical/devtools-schema.json +0 -601
  74. package/dungeons/vertical/ecommerce-schema.json +0 -604
  75. package/dungeons/vertical/education-schema.json +0 -5686
  76. package/dungeons/vertical/fintech-schema.json +0 -630
  77. package/dungeons/vertical/fitness-schema.json +0 -530
  78. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  79. package/dungeons/vertical/gaming-schema.json +0 -438
  80. package/dungeons/vertical/healthcare-schema.json +0 -549
  81. package/dungeons/vertical/insurance-application-schema.json +0 -485
  82. package/dungeons/vertical/logistics-schema.json +0 -574
  83. package/dungeons/vertical/marketplace-schema.json +0 -533
  84. package/dungeons/vertical/media-schema.json +0 -4749
  85. package/dungeons/vertical/rpg-schema.json +0 -2491
  86. package/dungeons/vertical/rpg.js +0 -976
  87. package/dungeons/vertical/sass-schema.json +0 -3128
  88. package/dungeons/vertical/social-schema.json +0 -620
  89. package/dungeons/vertical/travel-schema.json +0 -580
@@ -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 ──
@@ -15,9 +15,6 @@ import * as u from "../../lib/utils/utils.js";
15
15
 
16
16
  dayjs.extend(utc);
17
17
  const chance = u.initChance(SEED);
18
- const NOW = dayjs();
19
- const DATASET_START = NOW.subtract(num_days, "days");
20
-
21
18
  /** @typedef {import("../../types").Dungeon} Config */
22
19
 
23
20
  /**
@@ -50,29 +47,28 @@ const DATASET_START = NOW.subtract(num_days, "days");
50
47
 
51
48
  /**
52
49
  * ═══════════════════════════════════════════════════════════════
53
- * ANALYTICS HOOKS (8 hooks)
50
+ * ANALYTICS HOOKS (10 hooks)
54
51
  * ═══════════════════════════════════════════════════════════════
55
52
  *
53
+ * NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable
54
+ * via raw-prop breakdowns (HOD, day, segment) or behavioral cohorts.
55
+ *
56
56
  * ───────────────────────────────────────────────────────────────
57
- * 1. MORNING WORKOUT BOOST (event hook)
57
+ * 1. MORNING WORKOUT BOOST (everything)
58
58
  * ───────────────────────────────────────────────────────────────
59
59
  *
60
- * PATTERN: Workouts completed between 5AM-9AM get 1.3x
61
- * calories_burned. Simulates the metabolic boost from fasted
62
- * morning exercise.
60
+ * PATTERN: Workouts in 5-9 UTC get calories_burned 1.3x. Mutates
61
+ * raw prop. No flag.
63
62
  *
64
63
  * HOW TO FIND IT IN MIXPANEL:
65
64
  *
66
- * Report 1: Morning vs Non-Morning Calorie Burn
67
- * Report type: Insights
68
- * Event: "workout completed"
69
- * Measure: Average of "calories_burned"
70
- * Breakdown: "is_morning_workout"
71
- * • Expected: is_morning_workout=true should show ~1.3x avg
72
- * calories vs false (true ≈ 390, false ≈ 300)
65
+ * Report 1: Avg Calories Burned by Hour of Day
66
+ * - Event: "workout completed"
67
+ * - Measure: Average of "calories_burned"
68
+ * - Breakdown: hour of day
69
+ * - Expected: 5-9 hours show ~ 1.3x baseline
73
70
  *
74
- * REAL-WORLD ANALOGUE: Fitness apps promote morning workouts
75
- * as more effective for fat loss, driving engagement.
71
+ * REAL-WORLD ANALOGUE: Morning workouts get a metabolic boost.
76
72
  *
77
73
  * ───────────────────────────────────────────────────────────────
78
74
  * 2. POST-LAUNCH AI COACHING LIFT (event hook)
@@ -244,28 +240,80 @@ const DATASET_START = NOW.subtract(num_days, "days");
244
240
  * REAL-WORLD ANALOGUE: Annual gym memberships have higher
245
241
  * utilization — sunk cost + commitment drives consistency.
246
242
  *
243
+ * ───────────────────────────────────────────────────────────────
244
+ * 9. WORKOUT LOOP TIME-TO-CONVERT (funnel-post)
245
+ * ───────────────────────────────────────────────────────────────
246
+ *
247
+ * PATTERN: Annual + family subscribers complete the Workout Loop
248
+ * funnel 1.3x faster (factor 0.77); Free users 1.25x slower (factor 1.25).
249
+ *
250
+ * HOW TO FIND IT IN MIXPANEL:
251
+ *
252
+ * Report 1: Workout Loop Median Time-to-Convert by Subscription
253
+ * - Funnels > "workout planned" -> "workout completed" -> "progress checked"
254
+ * - Measure: Median time to convert
255
+ * - Breakdown: subscription_plan
256
+ * - Expected: annual ~ 0.77x; free ~ 1.25x
257
+ *
258
+ * NOTE (funnel-post measurement): visible only via Mixpanel funnel
259
+ * median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
260
+ * show this — funnel-post adjusts gaps within funnel instances, not
261
+ * across the user's full event history.
262
+ *
263
+ * ───────────────────────────────────────────────────────────────
264
+ * 10. WORKOUT-COUNT MAGIC NUMBER (everything)
265
+ * ───────────────────────────────────────────────────────────────
266
+ *
267
+ * PATTERN: Sweet 12-20 workouts/user → +35% on workout
268
+ * duration_minutes (peak progression). Over 21+ → drop 30% of
269
+ * post-day-30 events (overtraining churn). No flag.
270
+ *
271
+ * HOW TO FIND IT IN MIXPANEL:
272
+ *
273
+ * Report 1: Avg Workout Duration by Workout-Count Bucket
274
+ * - Cohort A: users with 12-20 "workout completed"
275
+ * - Cohort B: users with 0-11
276
+ * - Event: "workout completed"
277
+ * - Measure: Average of "duration_minutes"
278
+ * - Expected: A ~ 1.35x B
279
+ *
280
+ * Report 2: D30+ Activity on Heavy Workout Cohort
281
+ * - Cohort C: users with >= 21 "workout completed"
282
+ * - Cohort A: users with 12-20
283
+ * - Event: any event
284
+ * - Measure: Total per user, post-day-30
285
+ * - Expected: C ~ 30% fewer post-day-30 events per user
286
+ *
287
+ * REAL-WORLD ANALOGUE: Sweet-spot training drives progression;
288
+ * over-training causes injury and burnout.
289
+ *
247
290
  * ═══════════════════════════════════════════════════════════════
248
291
  * EXPECTED METRICS SUMMARY
249
292
  * ═══════════════════════════════════════════════════════════════
250
293
  *
251
- * Hook | Metric | Baseline | Effect | Ratio
252
- * ────────────────────────────|─────────────────────|──────────|─────────|──────
253
- * Morning Workout Boost | calories_burned | 300 | 390 | 1.3x
254
- * AI Coaching Lift | duration_minutes | 40 | 48 | 1.2x
255
- * Streak Retention | achievements/user | 1 | 2-3 | 2-3x
256
- * Social Challenge Completion | challenges/user | 1 | 1.5 | 1.5x
257
- * Resolver Churn Cliff | events after day 14 | 100% | 30% | 0.3x
258
- * Coach Session Quality | satisfaction_score | 3.0 | 4.3 | 1.4x
259
- * Coach Profile Enrichment | total_workouts | 0 | 350 | N/A
260
- * Annual Funnel Lift | funnel conversion | 45% | 63% | 1.4x
294
+ * Hook | Metric | Baseline | Effect | Ratio
295
+ * ----------------------------|---------------------|----------|-----------|------
296
+ * Morning Workout Boost | calories_burned 5-9 | 1x | 1.3x | 1.3x
297
+ * AI Coaching Lift | duration_minutes | 1x | 1.2x | 1.2x
298
+ * Streak Retention | achievements/user | 1x | 2-3x | 2-3x
299
+ * Social Challenge Completion | challenges/user | 1x | 1.5x | 1.5x
300
+ * Resolver Churn Cliff | events after day 14 | 1x | 0.3x | -70%
301
+ * Coach Session Quality | satisfaction_score | 3.0 | 4.3 | 1.4x
302
+ * Coach Profile Enrichment | total_workouts | 0 | 350 | n/a
303
+ * Annual Funnel Lift | funnel conversion | 45% | 63% | 1.4x
304
+ * Workout Loop T2C | median min by tier | 1x | 0.77/1.25x| ~ 1.6x range
305
+ * Workout Magic Number | sweet duration_min | 1x | 1.35x | 1.35x
306
+ * Workout Magic Number | over D30+ activity | 1x | 0.7x | -30%
261
307
  */
262
308
 
263
309
  /** @type {Config} */
264
310
  const config = {
265
311
  token,
266
312
  seed: SEED,
267
- numDays: num_days,
268
- numEvents: num_users * avg_events_per_user,
313
+ datasetStart: "2026-01-01T00:00:00Z",
314
+ datasetEnd: "2026-04-28T23:59:59Z",
315
+ // numDays: num_days,
316
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
269
317
  numUsers: num_users,
270
318
  hasAnonIds: false,
271
319
  hasSessionIds: true,
@@ -280,7 +328,6 @@ const config = {
280
328
  hasCampaigns: false,
281
329
  isAnonymous: false,
282
330
  hasAdSpend: false,
283
- percentUsersBornInDataset: 35,
284
331
  hasAvatar: true,
285
332
  concurrency: 1,
286
333
  writeToDisk: false,
@@ -312,7 +359,6 @@ const config = {
312
359
  duration_minutes: u.weighNumRange(10, 90, 0.5, 40),
313
360
  calories_burned: u.weighNumRange(50, 800, 0.4, 300),
314
361
  heart_rate_avg: u.weighNumRange(80, 185, 0.5, 130),
315
- is_morning_workout: [false],
316
362
  satisfaction_score: u.weighNumRange(1, 5, 0.7, 3),
317
363
  coaching_mode: ["self_guided"],
318
364
  challenge_mode: ["solo"],
@@ -592,8 +638,8 @@ const config = {
592
638
  { name: "family", price: 149.99 },
593
639
  ],
594
640
  lifecycle: {
595
- trialToPayRate: 0.30,
596
- upgradeRate: 0.08,
641
+ trialToPayRate: 0.65,
642
+ upgradeRate: 0.65,
597
643
  downgradeRate: 0.03,
598
644
  churnRate: 0.05,
599
645
  winBackRate: 0.10,
@@ -678,8 +724,8 @@ const config = {
678
724
 
679
725
  // ── Hook Function ──────────────────────────────────────
680
726
  hook: function (record, type, meta) {
681
- // ── HOOK 7: COACH PROFILE ENRICHMENT (user) ──────────
682
- // Coach segment gets boosted total_workouts and streak_days.
727
+ // HOOK 7: COACH PROFILE ENRICHMENT (user) — coach segment users
728
+ // get high total_workouts + streak_days.
683
729
  if (type === "user") {
684
730
  if (record.segment === "coach") {
685
731
  record.total_workouts = chance.integer({ min: 200, max: 500 });
@@ -687,29 +733,35 @@ const config = {
687
733
  }
688
734
  }
689
735
 
690
- // ── HOOK 8: ANNUAL SUBSCRIBER WORKOUT FUNNEL LIFT (funnel-pre)
691
- // (conversionRate filtering moved to everything hook)
692
- if (type === "funnel-pre") {
693
- // no-op: conversion filtering handled via event dropping in everything hook
694
- }
695
-
696
- // ── HOOK 1: MORNING WORKOUT BOOST (event) ────────────
697
- // Workouts between 5AM-9AM get 1.3x calories_burned.
698
- if (type === "event") {
699
- if (record.event === "workout completed") {
700
- const hour = dayjs(record.time).hour();
701
- if (hour >= 5 && hour < 9) {
702
- record.is_morning_workout = true;
703
- if (record.calories_burned) {
704
- record.calories_burned = Math.floor(record.calories_burned * 1.3);
736
+ // HOOK 9 (T2C): WORKOUT LOOP TIME-TO-CONVERT (funnel-post)
737
+ // Annual subscribers complete the Workout Loop funnel 1.3x faster
738
+ // (factor 0.77); Free users 1.25x slower (factor 1.25).
739
+ if (type === "funnel-post") {
740
+ const segment = meta?.profile?.subscription_plan;
741
+ if (Array.isArray(record) && record.length > 1) {
742
+ const factor = (
743
+ segment === "annual" || segment === "family" ? 0.77 :
744
+ segment === "free" ? 1.25 :
745
+ 1.0
746
+ );
747
+ if (factor !== 1.0) {
748
+ for (let i = 1; i < record.length; i++) {
749
+ const prev = dayjs(record[i - 1].time);
750
+ const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
751
+ record[i].time = prev.add(newGap, "milliseconds").toISOString();
705
752
  }
706
753
  }
707
754
  }
755
+ }
708
756
 
757
+ // (HOOK 1: MORNING WORKOUT BOOST moved to everything hook — hour checks
758
+ // must run after bunchIntoSessions redistributes timestamps)
759
+ if (type === "event") {
760
+ const datasetStart = dayjs.unix(meta.datasetStart);
709
761
  // ── HOOK 2: POST-LAUNCH AI COACHING LIFT (event) ────
710
762
  // After day 35, ai_assisted workouts get 1.2x duration.
711
763
  if (record.event === "workout completed" || record.event === "workout planned") {
712
- const AI_LAUNCH = DATASET_START.add(35, "days");
764
+ const AI_LAUNCH = datasetStart.add(35, "days");
713
765
  const eventTime = dayjs(record.time);
714
766
  if (eventTime.isAfter(AI_LAUNCH) && record.coaching_mode === "ai_assisted") {
715
767
  if (record.duration_minutes) {
@@ -724,6 +776,7 @@ const config = {
724
776
 
725
777
  // ── EVERYTHING HOOKS ─────────────────────────────────
726
778
  if (type === "everything") {
779
+ const datasetStart = dayjs.unix(meta.datasetStart);
727
780
  let events = record;
728
781
  if (!events.length) return record;
729
782
 
@@ -737,16 +790,26 @@ const config = {
737
790
  });
738
791
  }
739
792
 
793
+ // HOOK 1: MORNING WORKOUT BOOST — 5AM-9AM UTC workouts get
794
+ // calories_burned 1.3x. No flag — analyst breaks down by HOD.
795
+ events.forEach(e => {
796
+ if (e.event === "workout completed") {
797
+ const hour = new Date(e.time).getUTCHours();
798
+ if (hour >= 5 && hour < 9 && e.calories_burned) {
799
+ e.calories_burned = Math.floor(e.calories_burned * 1.3);
800
+ }
801
+ }
802
+ });
803
+
740
804
  // ── HOOK 8: ANNUAL SUBSCRIBER CONVERSION FILTER ─
741
805
  // Free/monthly-tier users drop ~30% of "progress checked"
742
806
  // (last step of Workout Loop funnel) to simulate lower conversion.
807
+ // NOTE: subscription field is named `subscription_plan` (set by the
808
+ // subscription feature in user-loop.js), NOT `subscription_tier`.
743
809
  if (meta && meta.profile) {
744
- const tier = meta.profile.subscription_tier;
745
- if (tier !== "annual" && tier !== "family") {
746
- record = record.filter(e => {
747
- if (e.event === "progress checked" && chance.bool({ likelihood: 30 })) return false;
748
- return true;
749
- });
810
+ const plan = meta.profile.subscription_plan;
811
+ if (plan !== "annual" && plan !== "family" && chance.bool({ likelihood: 30 })) {
812
+ record = record.filter(e => e.event !== "progress checked");
750
813
  events = record;
751
814
  }
752
815
  }
@@ -804,7 +867,7 @@ const config = {
804
867
  // ── HOOK 5: RESOLVER CHURN CLIFF ─────────────────
805
868
  // Resolver segment users with <8 events lose 70% after day 14.
806
869
  if (meta && meta.profile && meta.profile.segment === "resolver" && events.length < 8) {
807
- const CHURN_CLIFF = DATASET_START.add(14, "days");
870
+ const CHURN_CLIFF = datasetStart.add(14, "days");
808
871
  for (let i = events.length - 1; i >= 0; i--) {
809
872
  const eventTime = dayjs(events[i].time);
810
873
  if (eventTime.isAfter(CHURN_CLIFF) && chance.bool({ likelihood: 70 })) {
@@ -813,8 +876,7 @@ const config = {
813
876
  }
814
877
  }
815
878
 
816
- // ── HOOK 6: COACH SESSION QUALITY ────────────────
817
- // Users with coach_session events get higher satisfaction.
879
+ // HOOK 6: COACH SESSION QUALITY — coach-session satisfaction 4-5.
818
880
  const hasCoachSessions = events.some(e => e.event === "coach session");
819
881
  if (hasCoachSessions) {
820
882
  events.forEach(e => {
@@ -824,6 +886,26 @@ const config = {
824
886
  });
825
887
  }
826
888
 
889
+ // HOOK 10: WORKOUT-COUNT MAGIC NUMBER (no flags)
890
+ // Sweet 12-20 workouts → +35% on workout duration_minutes (peak
891
+ // progression). Over 21+ → drop 30% of post-day-30 events
892
+ // (overtraining → churn).
893
+ const workoutCount = events.filter(e => e.event === "workout completed").length;
894
+ if (workoutCount >= 12 && workoutCount <= 20) {
895
+ events.forEach(e => {
896
+ if (e.event === "workout completed" && typeof e.duration_minutes === "number") {
897
+ e.duration_minutes = Math.round(e.duration_minutes * 1.35);
898
+ }
899
+ });
900
+ } else if (workoutCount >= 21) {
901
+ const day30 = datasetStart.add(30, "days");
902
+ for (let i = events.length - 1; i >= 0; i--) {
903
+ if (dayjs(events[i].time).isAfter(day30) && chance.bool({ likelihood: 30 })) {
904
+ events.splice(i, 1);
905
+ }
906
+ }
907
+ }
908
+
827
909
  return record;
828
910
  }
829
911