@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-healthcare";
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 ──
@@ -247,7 +247,7 @@ const config = {
247
247
  token,
248
248
  seed: SEED,
249
249
  numDays: num_days,
250
- numEvents: num_users * avg_events_per_user,
250
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
251
251
  numUsers: num_users,
252
252
  hasAnonIds: false,
253
253
  hasSessionIds: true,
@@ -262,7 +262,6 @@ const config = {
262
262
  hasCampaigns: false,
263
263
  isAnonymous: false,
264
264
  hasAdSpend: false,
265
- percentUsersBornInDataset: 35,
266
265
  hasAvatar: true,
267
266
  concurrency: 1,
268
267
  writeToDisk: false,
@@ -2,7 +2,7 @@
2
2
  const SEED = "dm4-insurance";
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 ──
@@ -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
  hasAnonIds: false,
149
149
  hasSessionIds: true,
@@ -158,7 +158,6 @@ const config = {
158
158
  hasCampaigns: false,
159
159
  isAnonymous: false,
160
160
  hasAdSpend: false,
161
- percentUsersBornInDataset: 35,
162
161
  hasAvatar: true,
163
162
  concurrency: 1,
164
163
  writeToDisk: false,
@@ -2,7 +2,7 @@
2
2
  const SEED = "dm4-logistics";
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 ──
@@ -264,7 +264,7 @@ const config = {
264
264
  token,
265
265
  seed: SEED,
266
266
  numDays: num_days,
267
- numEvents: num_users * avg_events_per_user,
267
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
268
268
  numUsers: num_users,
269
269
  hasAnonIds: false,
270
270
  hasSessionIds: true,
@@ -279,7 +279,6 @@ const config = {
279
279
  hasCampaigns: false,
280
280
  isAnonymous: false,
281
281
  hasAdSpend: false,
282
- percentUsersBornInDataset: 35,
283
282
  hasAvatar: true,
284
283
  concurrency: 1,
285
284
  writeToDisk: false,
@@ -713,18 +712,13 @@ const config = {
713
712
  // no-op: conversion differentiation handled via event filtering below
714
713
  }
715
714
 
716
- // -- HOOK 1: MONTH-END REPORTING SURGE (event) ----------------
717
- // Reports on days 28-31 get 2x report_pages.
718
- if (type === "event") {
719
- if (record.event === "report generated") {
720
- const dayOfMonth = dayjs(record.time).date();
721
- if (dayOfMonth >= 28) {
722
- record.report_pages = Math.floor((record.report_pages || 20) * 2);
723
- }
724
- }
715
+ // -- HOOK 1: MONTH-END REPORTING SURGE ------------------------
716
+ // Moved to everything hook (after sessionization) so day-of-month
717
+ // tags match final timestamps. See everything hook below.
725
718
 
726
- // -- HOOK 2: RUSH ORDER PREMIUM (event) -------------------
727
- // Urgent purchase orders get 1.5x unit_cost.
719
+ // -- HOOK 2: RUSH ORDER PREMIUM (event) -------------------
720
+ // Urgent purchase orders get 1.5x unit_cost.
721
+ if (type === "event") {
728
722
  if (record.event === "purchase order created" && record.priority === "urgent") {
729
723
  record.unit_cost = Math.floor((record.unit_cost || 50) * 1.5);
730
724
  }
@@ -746,6 +740,18 @@ const config = {
746
740
  });
747
741
  }
748
742
 
743
+ // -- HOOK 1: MONTH-END REPORTING SURGE --------------------
744
+ // Reports on days 28-31 get 2x report_pages.
745
+ // Runs after sessionization so day-of-month matches final timestamps.
746
+ for (const e of record) {
747
+ if (e.event === 'report generated') {
748
+ const dayOfMonth = new Date(e.time).getUTCDate();
749
+ if (dayOfMonth >= 28) {
750
+ e.report_pages = Math.floor((e.report_pages || 20) * 2);
751
+ }
752
+ }
753
+ }
754
+
749
755
  // -- HOOK 8: SMALL-BUSINESS CONVERSION DROP ---------------
750
756
  // Small-business users lose ~35% of "alert configured" events
751
757
  // (last step of Integration Setup funnel), simulating lower
@@ -2,7 +2,7 @@
2
2
  const SEED = "dm4-marketplace";
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 ──
@@ -259,7 +259,7 @@ const config = {
259
259
  token,
260
260
  seed: SEED,
261
261
  numDays: num_days,
262
- numEvents: num_users * avg_events_per_user,
262
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
263
263
  numUsers: num_users,
264
264
  hasAnonIds: false,
265
265
  hasSessionIds: true,
@@ -274,7 +274,6 @@ const config = {
274
274
  hasCampaigns: false,
275
275
  isAnonymous: false,
276
276
  hasAdSpend: false,
277
- percentUsersBornInDataset: 35,
278
277
  hasAvatar: true,
279
278
  concurrency: 1,
280
279
  writeToDisk: false,
@@ -747,15 +746,9 @@ const config = {
747
746
  }
748
747
  }
749
748
 
750
- // ── HOOK 2: WEEKEND SHOPPING SURGE (event) ───────
751
- // Purchases on Sat/Sun get 1.2x total_amount.
752
- if (record.event === "purchase completed") {
753
- const dayOfWeek = dayjs(record.time).day();
754
- // Saturday=6, Sunday=0
755
- if (dayOfWeek === 0 || dayOfWeek === 6) {
756
- record.total_amount = Math.floor((record.total_amount || 60) * 1.2);
757
- }
758
- }
749
+ // ── HOOK 2: WEEKEND SHOPPING SURGE ───────────────
750
+ // Moved to the everything hook (after purchase cloning)
751
+ // so the 1.2x boost applies to both original AND cloned purchases.
759
752
  }
760
753
 
761
754
  // ── EVERYTHING HOOKS ─────────────────────────────────
@@ -831,11 +824,13 @@ const config = {
831
824
  }
832
825
 
833
826
  // ── HOOK 5: RESPONSE TIME → CONVERSION ───────────
834
- // Sellers with avg response_time < 2 hours get more offer_accepted.
827
+ // Sellers with avg response_time < 12 hours get more offer_accepted.
828
+ // (response_time_hours centers around ~6h via weighNumRange, so a
829
+ // threshold of 2h was too tight — almost no users qualified.)
835
830
  const messages = events.filter(e => e.event === "message sent" && e.response_time_hours);
836
831
  if (messages.length > 0) {
837
832
  const avgResponseTime = messages.reduce((sum, m) => sum + m.response_time_hours, 0) / messages.length;
838
- if (avgResponseTime < 2) {
833
+ if (avgResponseTime < 12) {
839
834
  const templateOffer = events.find(e => e.event === "offer accepted");
840
835
  if (templateOffer) {
841
836
  const offers = events.filter(e => e.event === "offer received");
@@ -865,6 +860,19 @@ const config = {
865
860
  }
866
861
  }
867
862
 
863
+ // ── HOOK 2: WEEKEND SHOPPING SURGE (everything) ─────
864
+ // Applied AFTER all purchase cloning (Hooks 3, 4) so the
865
+ // 1.2x boost hits both original and injected purchases.
866
+ events.forEach(e => {
867
+ if (e.event === "purchase completed") {
868
+ const dow = new Date(e.time).getUTCDay();
869
+ // Saturday=6, Sunday=0
870
+ if (dow === 0 || dow === 6) {
871
+ e.total_amount = Math.floor((e.total_amount || 60) * 1.2);
872
+ }
873
+ }
874
+ });
875
+
868
876
  return record;
869
877
  }
870
878
 
@@ -2,7 +2,7 @@
2
2
  const SEED = "harness-media";
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 ──
@@ -228,7 +228,7 @@ const config = {
228
228
  token,
229
229
  seed: SEED,
230
230
  numDays: num_days,
231
- numEvents: num_users * avg_events_per_user,
231
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
232
232
  numUsers: num_users,
233
233
  hasAnonIds: false,
234
234
  hasSessionIds: true,
@@ -243,7 +243,6 @@ const config = {
243
243
  hasCampaigns: false,
244
244
  isAnonymous: false,
245
245
  hasAdSpend: false,
246
- percentUsersBornInDataset: 50,
247
246
  hasAvatar: true,
248
247
  concurrency: 1,
249
248
  writeToDisk: false,
@@ -637,38 +636,23 @@ const config = {
637
636
  }
638
637
 
639
638
  // ─────────────────────────────────────────────────────────────
640
- // Hook #3: WEEKEND vs WEEKDAY PATTERNS (event)
641
- // Weekend: 1.5x watch duration. Weekday 6PM-11PM: prime_time tag
639
+ // Hook #3: WEEKEND vs WEEKDAY PATTERNS
640
+ // Moved to everything hook (after sessionization) so DOW/HOD
641
+ // tags match final timestamps. See everything hook below.
642
642
  // ─────────────────────────────────────────────────────────────
643
- if (type === "event") {
644
- const EVENT_TIME = dayjs(record.time);
645
- const dayOfWeek = EVENT_TIME.day(); // 0 = Sunday, 6 = Saturday
646
- const hour = EVENT_TIME.hour();
647
- const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
648
-
649
- if (isWeekend) {
650
- record.weekend_viewing = true;
651
- record.prime_time = false;
652
- if (record.event === "playback completed" && record.watch_duration_min) {
653
- record.watch_duration_min = Math.round(record.watch_duration_min * 1.5);
654
- }
655
- } else {
656
- record.weekend_viewing = false;
657
- // Weekday prime-time: 6PM to 11PM
658
- if (hour >= 18 && hour <= 23) {
659
- record.prime_time = true;
660
- } else {
661
- record.prime_time = false;
662
- }
663
- }
664
643
 
644
+ if (type === "event") {
665
645
  // ─────────────────────────────────────────────────────────────
666
646
  // Hook #5: NEW RELEASE SPIKE (event)
667
- // After day 50, blockbuster release drives content selection
647
+ // Days 50-65: blockbuster release drives content selection.
648
+ // Bounded window concentrates the spike instead of spreading
649
+ // it across the entire second half of the dataset.
668
650
  // ─────────────────────────────────────────────────────────────
669
- const BLOCKBUSTER_RELEASE = DATASET_START.add(50, 'days');
651
+ const BLOCKBUSTER_START = DATASET_START.add(50, 'days');
652
+ const BLOCKBUSTER_END = DATASET_START.add(65, 'days');
653
+ const EVENT_TIME = dayjs(record.time);
670
654
  if (record.event === "content selected" || record.event === "playback started") {
671
- if (EVENT_TIME.isAfter(BLOCKBUSTER_RELEASE) && chance.bool({ likelihood: 20 })) {
655
+ if (EVENT_TIME.isAfter(BLOCKBUSTER_START) && EVENT_TIME.isBefore(BLOCKBUSTER_END) && chance.bool({ likelihood: 20 })) {
672
656
  record.content_type = "movie";
673
657
  record.content_id = blockbusterId;
674
658
  record.blockbuster_release = true;
@@ -678,7 +662,7 @@ const config = {
678
662
  }
679
663
 
680
664
  if (record.event === "content rated") {
681
- if (EVENT_TIME.isAfter(BLOCKBUSTER_RELEASE) && chance.bool({ likelihood: 20 })) {
665
+ if (EVENT_TIME.isAfter(BLOCKBUSTER_START) && EVENT_TIME.isBefore(BLOCKBUSTER_END) && chance.bool({ likelihood: 20 })) {
682
666
  record.rating = chance.integer({ min: 4, max: 5 });
683
667
  record.content_id = blockbusterId;
684
668
  record.blockbuster_release = true;
@@ -727,6 +711,31 @@ const config = {
727
711
  });
728
712
  }
729
713
 
714
+ // ─── Hook #3: WEEKEND vs WEEKDAY PATTERNS ───
715
+ // Weekend: 1.5x watch duration. Weekday 6PM-11PM: prime_time tag.
716
+ // Runs after sessionization so DOW/HOD tags match final timestamps.
717
+ for (const e of userEvents) {
718
+ const eventDate = new Date(e.time);
719
+ const dayOfWeek = eventDate.getUTCDay(); // 0 = Sunday, 6 = Saturday
720
+ const hour = eventDate.getUTCHours();
721
+ const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
722
+
723
+ if (isWeekend) {
724
+ e.weekend_viewing = true;
725
+ e.prime_time = false;
726
+ if (e.event === "playback completed" && e.watch_duration_min) {
727
+ e.watch_duration_min = Math.round(e.watch_duration_min * 1.5);
728
+ }
729
+ } else {
730
+ e.weekend_viewing = false;
731
+ if (hour >= 18 && hour <= 23) {
732
+ e.prime_time = true;
733
+ } else {
734
+ e.prime_time = false;
735
+ }
736
+ }
737
+ }
738
+
730
739
  // ─── Bug 2 fix: Genre funnel conversion filtering ───
731
740
  // Drop ~25% of "playback completed" events for documentary genre
732
741
  // to simulate lower documentary funnel completion (was conversionRate * 0.7)