@ak--47/dungeon-master 1.0.0 → 1.2.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 (104) hide show
  1. package/README.md +7 -7
  2. package/dungeons/technical/ad-spend-schema.json +128 -0
  3. package/dungeons/technical/ad-spend.js +116 -0
  4. package/dungeons/technical/anonymous-users-schema.json +92 -0
  5. package/dungeons/technical/anonymous-users.js +102 -0
  6. package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
  7. package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +12 -5
  8. package/dungeons/technical/experiments-schema.json +203 -0
  9. package/dungeons/technical/experiments.js +168 -0
  10. package/dungeons/technical/foobar-schema.json +362 -0
  11. package/dungeons/{foobar.js → technical/foobar.js} +3 -3
  12. package/dungeons/technical/group-analytics-schema.json +241 -0
  13. package/dungeons/technical/group-analytics.js +151 -0
  14. package/dungeons/technical/mirror-strategies-schema.json +84 -0
  15. package/dungeons/technical/mirror-strategies.js +126 -0
  16. package/dungeons/technical/nested-objects-schema.json +145 -0
  17. package/dungeons/technical/nested-objects.js +207 -0
  18. package/dungeons/technical/retention-cadence-schema.json +37 -0
  19. package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +2 -3
  20. package/dungeons/technical/sanity-schema.json +185 -0
  21. package/dungeons/{sanity.js → technical/sanity.js} +4 -2
  22. package/dungeons/technical/scale-test-schema.json +70 -0
  23. package/dungeons/technical/scale-test.js +90 -0
  24. package/dungeons/technical/scd-schema.json +467 -0
  25. package/dungeons/{scd.js → technical/scd.js} +20 -10
  26. package/dungeons/technical/simple-schema.json +362 -0
  27. package/dungeons/{simple.js → technical/simple.js} +7 -7
  28. package/dungeons/technical/simplest-schema.json +724 -0
  29. package/dungeons/{simplest.js → technical/simplest.js} +7 -7
  30. package/dungeons/technical/text-generation-schema.json +1062 -0
  31. package/dungeons/{text-generation.js → technical/text-generation.js} +24 -11
  32. package/dungeons/vertical/community-schema.json +579 -0
  33. package/dungeons/vertical/community.js +794 -0
  34. package/dungeons/vertical/devtools-schema.json +601 -0
  35. package/dungeons/vertical/devtools.js +843 -0
  36. package/dungeons/vertical/ecommerce-schema.json +604 -0
  37. package/dungeons/{ecommerce.js → vertical/ecommerce.js} +43 -5
  38. package/dungeons/vertical/education-schema.json +5686 -0
  39. package/dungeons/{education.js → vertical/education.js} +70 -39
  40. package/dungeons/vertical/fintech-schema.json +630 -0
  41. package/dungeons/{fintech.js → vertical/fintech.js} +107 -56
  42. package/dungeons/vertical/fitness-schema.json +530 -0
  43. package/dungeons/vertical/fitness.js +808 -0
  44. package/dungeons/vertical/food-delivery-schema.json +36728 -0
  45. package/dungeons/{food.js → vertical/food-delivery.js} +67 -26
  46. package/dungeons/vertical/gaming-schema.json +438 -0
  47. package/dungeons/{gaming.js → vertical/gaming.js} +29 -16
  48. package/dungeons/vertical/healthcare-schema.json +549 -0
  49. package/dungeons/vertical/healthcare.js +775 -0
  50. package/dungeons/vertical/insurance-application-schema.json +485 -0
  51. package/dungeons/{insurance-application.js → vertical/insurance-application.js} +41 -31
  52. package/dungeons/vertical/logistics-schema.json +574 -0
  53. package/dungeons/vertical/logistics.js +799 -0
  54. package/dungeons/vertical/marketplace-schema.json +533 -0
  55. package/dungeons/vertical/marketplace.js +850 -0
  56. package/dungeons/vertical/media-schema.json +4749 -0
  57. package/dungeons/{media.js → vertical/media.js} +103 -19
  58. package/dungeons/vertical/rpg-schema.json +2491 -0
  59. package/dungeons/{rpg.js → vertical/rpg.js} +87 -60
  60. package/dungeons/vertical/sass-schema.json +3128 -0
  61. package/dungeons/{sass.js → vertical/sass.js} +65 -26
  62. package/dungeons/vertical/social-schema.json +620 -0
  63. package/dungeons/{social.js → vertical/social.js} +76 -17
  64. package/dungeons/vertical/travel-schema.json +580 -0
  65. package/dungeons/vertical/travel.js +742 -0
  66. package/index.js +20 -16
  67. package/lib/core/config-validator.js +286 -1
  68. package/lib/core/context.js +16 -5
  69. package/lib/core/storage.js +2 -0
  70. package/lib/generators/adspend.js +3 -3
  71. package/lib/generators/events.js +141 -14
  72. package/lib/generators/funnels.js +67 -7
  73. package/lib/generators/text.js +2 -2
  74. package/lib/orchestrators/user-loop.js +444 -22
  75. package/lib/templates/soup-presets.js +2 -2
  76. package/lib/utils/utils.js +4 -5
  77. package/package.json +4 -3
  78. package/scripts/dungeon-to-json.mjs +163 -0
  79. package/scripts/extract-dungeon-schema.mjs +343 -0
  80. package/scripts/json-to-dungeon.mjs +200 -0
  81. package/scripts/run-dungeon.mjs +76 -0
  82. package/scripts/run-many.mjs +118 -0
  83. package/scripts/verify-runner.mjs +42 -0
  84. package/types.d.ts +316 -1
  85. package/dungeons/array-of-object-lookup-schema.json +0 -327
  86. package/dungeons/ecommerce-schema.json +0 -462
  87. package/dungeons/education-schema.json +0 -2409
  88. package/dungeons/fintech-schema.json +0 -14034
  89. package/dungeons/foobar-schema.json +0 -403
  90. package/dungeons/food-delivery-schema.json +0 -192
  91. package/dungeons/food-delivery.js +0 -602
  92. package/dungeons/food-schema.json +0 -1152
  93. package/dungeons/gaming-schema.json +0 -1270
  94. package/dungeons/insurance-application-schema.json +0 -204
  95. package/dungeons/media-schema.json +0 -906
  96. package/dungeons/retention-cadence-schema.json +0 -78
  97. package/dungeons/rpg-schema.json +0 -4526
  98. package/dungeons/sanity-schema.json +0 -255
  99. package/dungeons/sass-schema.json +0 -1291
  100. package/dungeons/scd-schema.json +0 -919
  101. package/dungeons/simple-schema.json +0 -608
  102. package/dungeons/simplest-schema.json +0 -1418
  103. package/dungeons/social-schema.json +0 -1118
  104. package/dungeons/text-generation-schema.json +0 -3096
@@ -18,9 +18,10 @@ import { dataLogger as logger } from "../utils/logger.js";
18
18
  * @param {number} firstEventTime - Unix timestamp for first event
19
19
  * @param {Object} profile - User profile object
20
20
  * @param {Object} scd - Slowly changing dimensions object
21
+ * @param {Object} [persona] - User's assigned persona
21
22
  * @returns {Promise<[Array, boolean]>} Tuple of [events, didConvert]
22
23
  */
23
- export async function makeFunnel(context, funnel, user, firstEventTime, profile = {}, scd = {}) {
24
+ export async function makeFunnel(context, funnel, user, firstEventTime, profile = {}, scd = {}, persona = null, featureCtx = {}) {
24
25
  if (!funnel) throw new Error("no funnel");
25
26
  if (!user) throw new Error("no user");
26
27
 
@@ -99,22 +100,65 @@ export async function makeFunnel(context, funnel, user, firstEventTime, profile
99
100
  const funnelPossibleEvents = buildFunnelEvents(context, sequence, chosenFunnelProps, bindPropsIndex, expName, expVariant);
100
101
 
101
102
  // Handle repeat logic and conversion rate adjustment
102
- const { processedEvents, adjustedConversionRate } = processEventRepeats(
103
+ let { processedEvents, adjustedConversionRate } = processEventRepeats(
103
104
  funnelPossibleEvents,
104
105
  requireRepeats,
105
106
  conversionRate,
106
107
  chance
107
108
  );
108
109
 
110
+ // Apply persona conversion modifier (before hook, so hook can override via funnel-pre)
111
+ if (persona && persona.conversionModifier) {
112
+ adjustedConversionRate = Math.min(100, Math.max(0, Math.round(adjustedConversionRate * persona.conversionModifier)));
113
+ }
114
+
115
+ // Apply world event conversion modifier if active at firstEventTime
116
+ const resolvedWorldEvents = /** @type {import('../../types').ResolvedWorldEvent[]} */ (config.worldEvents);
117
+ if (resolvedWorldEvents && firstEventTime) {
118
+ for (const we of resolvedWorldEvents) {
119
+ if (firstEventTime >= we.startUnix && firstEventTime < we.endUnix && we.conversionModifier !== 1.0) {
120
+ const affects = we.affectsEvents;
121
+ if (affects === "*" || (Array.isArray(affects) && sequence.some(s => affects.includes(s)))) {
122
+ adjustedConversionRate = Math.min(100, Math.max(0, Math.round(adjustedConversionRate * we.conversionModifier)));
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ // Apply feature conversion lifts
129
+ const resolvedFeatures = config.features;
130
+ if (resolvedFeatures && firstEventTime) {
131
+ const daysSinceBegin = (firstEventTime - context.FIXED_BEGIN) / 86400;
132
+ for (const feat of resolvedFeatures) {
133
+ if (feat.conversionLift && daysSinceBegin >= feat.launchDay) {
134
+ const daysSinceLaunch = daysSinceBegin - feat.launchDay;
135
+ const { k, midpoint } = feat._resolvedCurve || { k: 0.08, midpoint: 30 };
136
+ const adoptionProb = 1 / (1 + Math.exp(-k * (daysSinceLaunch - midpoint)));
137
+ if (chance.bool({ likelihood: Math.min(100, adoptionProb * 100) })) {
138
+ adjustedConversionRate = Math.min(100, Math.max(0, Math.round(adjustedConversionRate * feat.conversionLift)));
139
+ }
140
+ }
141
+ }
142
+ }
143
+
109
144
  // Determine if user converts and how many steps they'll take
145
+ // When experiment mode is active, $experiment_started is prepended to sequence
146
+ // but should not count as a funnel step for conversion purposes
147
+ const conversionStepCount = expName ? sequence.length - 1 : sequence.length;
110
148
  const { doesUserConvert, numStepsUserWillTake } = determineConversion(
111
149
  adjustedConversionRate,
112
- sequence.length,
150
+ conversionStepCount,
113
151
  chance
114
152
  );
115
153
 
116
154
  // Get steps user will actually take
117
- const funnelStepsUserWillTake = processedEvents.slice(0, numStepsUserWillTake);
155
+ let funnelStepsUserWillTake;
156
+ if (expName) {
157
+ // $experiment_started always fires; conversion only applies to actual funnel steps
158
+ funnelStepsUserWillTake = [processedEvents[0], ...processedEvents.slice(1, 1 + numStepsUserWillTake)];
159
+ } else {
160
+ funnelStepsUserWillTake = processedEvents.slice(0, numStepsUserWillTake);
161
+ }
118
162
 
119
163
  // Apply ordering strategy
120
164
  const funnelActualOrder = applyOrderingStrategy(
@@ -137,6 +181,18 @@ export async function makeFunnel(context, funnel, user, firstEventTime, profile
137
181
  funnelEventsWithTiming.push(sessionStartEvent);
138
182
  }
139
183
 
184
+ // Build complete feature context: merge passed-in featureCtx with config fallbacks
185
+ const funnelFeatureCtx = {
186
+ persona: featureCtx.persona || persona || null,
187
+ userCampaign: featureCtx.userCampaign || null,
188
+ userLocation: featureCtx.userLocation || null,
189
+ worldEventsTimeline: featureCtx.worldEventsTimeline || context.config.worldEvents || null,
190
+ resolvedFeatures: featureCtx.resolvedFeatures || context.config.features || null,
191
+ resolvedAnomalies: featureCtx.resolvedAnomalies || context.config.anomalies || null,
192
+ dataQuality: featureCtx.dataQuality || context.config.dataQuality || null,
193
+ geo: featureCtx.geo || context.config.geo || null,
194
+ };
195
+
140
196
  // Generate actual events with timing
141
197
  const finalEvents = await generateFunnelEvents(
142
198
  context,
@@ -145,7 +201,8 @@ export async function makeFunnel(context, funnel, user, firstEventTime, profile
145
201
  firstEventTime || dayjs(created).unix(),
146
202
  anonymousIds,
147
203
  sessionIds,
148
- groupKeys
204
+ groupKeys,
205
+ funnelFeatureCtx
149
206
  );
150
207
 
151
208
  // Call post-funnel hook
@@ -353,7 +410,8 @@ async function generateFunnelEvents(
353
410
  earliestTime,
354
411
  anonymousIds,
355
412
  sessionIds,
356
- groupKeys
413
+ groupKeys,
414
+ featureCtx = {}
357
415
  ) {
358
416
  let funnelStartTime;
359
417
 
@@ -367,7 +425,9 @@ async function generateFunnelEvents(
367
425
  sessionIds,
368
426
  {},
369
427
  groupKeys,
370
- false // Let all funnel events use TimeSoup for proper time distribution
428
+ false, // Let all funnel events use TimeSoup for proper time distribution
429
+ false,
430
+ featureCtx
371
431
  );
372
432
 
373
433
  if (index === 0) {
@@ -542,9 +542,9 @@ class OrganicTextGenerator {
542
542
  ...config
543
543
  };
544
544
 
545
- // Initialize seed if provided
545
+ // Initialize seed if provided (use local RNG, not global, to avoid poisoning Math.random)
546
546
  if (this.config.seed) {
547
- seedrandom(this.config.seed, { global: true });
547
+ this._rng = seedrandom(this.config.seed);
548
548
  }
549
549
 
550
550
  // Initialize subsystems