@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.
- package/README.md +7 -7
- package/dungeons/technical/ad-spend-schema.json +128 -0
- package/dungeons/technical/ad-spend.js +116 -0
- package/dungeons/technical/anonymous-users-schema.json +92 -0
- package/dungeons/technical/anonymous-users.js +102 -0
- package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
- package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +12 -5
- package/dungeons/technical/experiments-schema.json +203 -0
- package/dungeons/technical/experiments.js +168 -0
- package/dungeons/technical/foobar-schema.json +362 -0
- package/dungeons/{foobar.js → technical/foobar.js} +3 -3
- package/dungeons/technical/group-analytics-schema.json +241 -0
- package/dungeons/technical/group-analytics.js +151 -0
- package/dungeons/technical/mirror-strategies-schema.json +84 -0
- package/dungeons/technical/mirror-strategies.js +126 -0
- package/dungeons/technical/nested-objects-schema.json +145 -0
- package/dungeons/technical/nested-objects.js +207 -0
- package/dungeons/technical/retention-cadence-schema.json +37 -0
- package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +2 -3
- package/dungeons/technical/sanity-schema.json +185 -0
- package/dungeons/{sanity.js → technical/sanity.js} +4 -2
- package/dungeons/technical/scale-test-schema.json +70 -0
- package/dungeons/technical/scale-test.js +90 -0
- package/dungeons/technical/scd-schema.json +467 -0
- package/dungeons/{scd.js → technical/scd.js} +20 -10
- package/dungeons/technical/simple-schema.json +362 -0
- package/dungeons/{simple.js → technical/simple.js} +7 -7
- package/dungeons/technical/simplest-schema.json +724 -0
- package/dungeons/{simplest.js → technical/simplest.js} +7 -7
- package/dungeons/technical/text-generation-schema.json +1062 -0
- package/dungeons/{text-generation.js → technical/text-generation.js} +24 -11
- package/dungeons/vertical/community-schema.json +579 -0
- package/dungeons/vertical/community.js +794 -0
- package/dungeons/vertical/devtools-schema.json +601 -0
- package/dungeons/vertical/devtools.js +843 -0
- package/dungeons/vertical/ecommerce-schema.json +604 -0
- package/dungeons/{ecommerce.js → vertical/ecommerce.js} +43 -5
- package/dungeons/vertical/education-schema.json +5686 -0
- package/dungeons/{education.js → vertical/education.js} +70 -39
- package/dungeons/vertical/fintech-schema.json +630 -0
- package/dungeons/{fintech.js → vertical/fintech.js} +107 -56
- package/dungeons/vertical/fitness-schema.json +530 -0
- package/dungeons/vertical/fitness.js +808 -0
- package/dungeons/vertical/food-delivery-schema.json +36728 -0
- package/dungeons/{food.js → vertical/food-delivery.js} +67 -26
- package/dungeons/vertical/gaming-schema.json +438 -0
- package/dungeons/{gaming.js → vertical/gaming.js} +29 -16
- package/dungeons/vertical/healthcare-schema.json +549 -0
- package/dungeons/vertical/healthcare.js +775 -0
- package/dungeons/vertical/insurance-application-schema.json +485 -0
- package/dungeons/{insurance-application.js → vertical/insurance-application.js} +41 -31
- package/dungeons/vertical/logistics-schema.json +574 -0
- package/dungeons/vertical/logistics.js +799 -0
- package/dungeons/vertical/marketplace-schema.json +533 -0
- package/dungeons/vertical/marketplace.js +850 -0
- package/dungeons/vertical/media-schema.json +4749 -0
- package/dungeons/{media.js → vertical/media.js} +103 -19
- package/dungeons/vertical/rpg-schema.json +2491 -0
- package/dungeons/{rpg.js → vertical/rpg.js} +87 -60
- package/dungeons/vertical/sass-schema.json +3128 -0
- package/dungeons/{sass.js → vertical/sass.js} +65 -26
- package/dungeons/vertical/social-schema.json +620 -0
- package/dungeons/{social.js → vertical/social.js} +76 -17
- package/dungeons/vertical/travel-schema.json +580 -0
- package/dungeons/vertical/travel.js +742 -0
- package/index.js +20 -16
- package/lib/core/config-validator.js +286 -1
- package/lib/core/context.js +16 -5
- package/lib/core/storage.js +2 -0
- package/lib/generators/adspend.js +3 -3
- package/lib/generators/events.js +141 -14
- package/lib/generators/funnels.js +67 -7
- package/lib/generators/text.js +2 -2
- package/lib/orchestrators/user-loop.js +444 -22
- package/lib/templates/soup-presets.js +2 -2
- package/lib/utils/utils.js +4 -5
- package/package.json +4 -3
- package/scripts/dungeon-to-json.mjs +163 -0
- package/scripts/extract-dungeon-schema.mjs +343 -0
- package/scripts/json-to-dungeon.mjs +200 -0
- package/scripts/run-dungeon.mjs +76 -0
- package/scripts/run-many.mjs +118 -0
- package/scripts/verify-runner.mjs +42 -0
- package/types.d.ts +316 -1
- package/dungeons/array-of-object-lookup-schema.json +0 -327
- package/dungeons/ecommerce-schema.json +0 -462
- package/dungeons/education-schema.json +0 -2409
- package/dungeons/fintech-schema.json +0 -14034
- package/dungeons/foobar-schema.json +0 -403
- package/dungeons/food-delivery-schema.json +0 -192
- package/dungeons/food-delivery.js +0 -602
- package/dungeons/food-schema.json +0 -1152
- package/dungeons/gaming-schema.json +0 -1270
- package/dungeons/insurance-application-schema.json +0 -204
- package/dungeons/media-schema.json +0 -906
- package/dungeons/retention-cadence-schema.json +0 -78
- package/dungeons/rpg-schema.json +0 -4526
- package/dungeons/sanity-schema.json +0 -255
- package/dungeons/sass-schema.json +0 -1291
- package/dungeons/scd-schema.json +0 -919
- package/dungeons/simple-schema.json +0 -608
- package/dungeons/simplest-schema.json +0 -1418
- package/dungeons/social-schema.json +0 -1118
- 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
|
-
|
|
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
|
-
|
|
150
|
+
conversionStepCount,
|
|
113
151
|
chance
|
|
114
152
|
);
|
|
115
153
|
|
|
116
154
|
// Get steps user will actually take
|
|
117
|
-
|
|
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) {
|
package/lib/generators/text.js
CHANGED
|
@@ -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
|
|
547
|
+
this._rng = seedrandom(this.config.seed);
|
|
548
548
|
}
|
|
549
549
|
|
|
550
550
|
// Initialize subsystems
|