@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
@@ -1,42 +1,90 @@
1
1
  /**
2
- * Verify Runner — runs a dungeon at constrained params for hook verification.
2
+ * Verify Runner — runs a dungeon for hook verification.
3
3
  *
4
- * Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name]
4
+ * Default mode: FULL FIDELITY — runs the dungeon with its own configured
5
+ * numUsers / avgEventsPerUserPerDay / numDays. This is the only way to
6
+ * verify the trends a benchmark consumer will actually see.
5
7
  *
6
- * Example: node scripts/verify-runner.mjs dungeons/streaming.js verify-streaming
8
+ * --small mode: ~1K users with avgEventsPerUserPerDay scaled proportionally
9
+ * so total events stay near 100K. Use only for fast smoke checks; do NOT
10
+ * ship verification verdicts based on --small runs.
11
+ *
12
+ * Usage:
13
+ * node scripts/verify-runner.mjs <dungeon-path> [run-name] [--small]
14
+ *
15
+ * Examples:
16
+ * node scripts/verify-runner.mjs dungeons/vertical/gaming.js verify-gaming
17
+ * node scripts/verify-runner.mjs dungeons/vertical/gaming.js verify-gaming --small
7
18
  */
8
19
  import generate from '../index.js';
9
20
  import path from 'path';
10
21
 
11
- const dungeonPath = process.argv[2];
22
+ const args = process.argv.slice(2);
23
+ const flags = new Set(args.filter(a => a.startsWith('--')));
24
+ const positional = args.filter(a => !a.startsWith('--'));
25
+
26
+ const dungeonPath = positional[0];
12
27
  if (!dungeonPath) {
13
- console.error('Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name]');
28
+ console.error('Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name] [--small]');
14
29
  process.exit(1);
15
30
  }
16
31
 
17
- const runName = process.argv[3] || 'verify-hooks';
32
+ const runName = positional[1] || 'verify-hooks';
33
+ const isSmall = flags.has('--small');
18
34
  const absolutePath = path.isAbsolute(dungeonPath)
19
- ? dungeonPath
20
- : path.resolve(process.cwd(), dungeonPath);
35
+ ? dungeonPath
36
+ : path.resolve(process.cwd(), dungeonPath);
21
37
 
22
38
  const { default: config } = await import(absolutePath);
23
39
 
24
- const results = await generate({
25
- ...config,
26
- token: "",
27
- numUsers: 1000,
28
- numEvents: 100_000,
29
- format: "json",
30
- gzip: false,
31
- writeToDisk: true,
32
- name: runName,
33
- concurrency: 1,
34
- verbose: false,
35
- });
40
+ let override;
41
+
42
+ if (isSmall) {
43
+ // SMALL mode: 1K users, scaled per-day rate so total events ≈ 100K.
44
+ // Preserves the dungeon's per-user-per-day shape while shrinking the dataset.
45
+ // Use only for quick smoke checks — verdicts must come from full fidelity.
46
+ const numUsers = 1000;
47
+ const numDays = config.numDays || 100;
48
+ const targetTotal = 100_000;
49
+ const scaledRate = targetTotal / (numUsers * numDays);
50
+ override = {
51
+ ...config,
52
+ token: '',
53
+ numUsers,
54
+ numDays,
55
+ avgEventsPerUserPerDay: scaledRate,
56
+ numEvents: undefined,
57
+ format: 'json',
58
+ gzip: false,
59
+ writeToDisk: true,
60
+ name: runName,
61
+ concurrency: 1,
62
+ verbose: false,
63
+ };
64
+ } else {
65
+ // FULL FIDELITY: use the dungeon's own scale settings as-shipped.
66
+ // Full-scale runs can take minutes for 50K-user dungeons; that's expected.
67
+ override = {
68
+ ...config,
69
+ token: '',
70
+ format: 'json',
71
+ gzip: false,
72
+ writeToDisk: true,
73
+ name: runName,
74
+ concurrency: config.concurrency || 1,
75
+ verbose: false,
76
+ };
77
+ }
78
+
79
+ const t0 = Date.now();
80
+ const results = await generate(override);
81
+ const wallMs = Date.now() - t0;
36
82
 
37
83
  console.log(JSON.stringify({
38
- eventCount: results.eventCount,
39
- userCount: results.userCount,
40
- files: results.files,
41
- duration: results.time?.human || results.time?.delta + 'ms'
84
+ mode: isSmall ? 'small' : 'full',
85
+ eventCount: results.eventCount,
86
+ userCount: results.userCount,
87
+ files: results.files,
88
+ duration: results.time?.human || `${wallMs}ms`,
89
+ wallMs,
42
90
  }));
package/types.d.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  /**
2
- * most of the time, the value of a property is a primitive
2
+ * Primitive scalar types that property values resolve to. Property values may
3
+ * also be plain object literals (nested records) — `Record<string, any>` is the
4
+ * narrowest TS can express here without making `Primitives` self-recursive
5
+ * (which it can't be, because `ValueValid` is built on top of it).
3
6
  */
4
7
  type Primitives = string | number | boolean | Date | Record<string, any>;
5
8
 
6
9
  /**
7
- * a "validValue" can be a primitive, an array of primitives, or a function that returns a primitive
10
+ * A "validValue" can be a primitive, an array of valid values, or a thunk that
11
+ * returns one. Configs use this everywhere properties are user-defined.
8
12
  */
9
13
  export type ValueValid = Primitives | ValueValid[] | (() => ValueValid);
10
14
 
@@ -17,16 +21,29 @@ export interface Dungeon {
17
21
  token?: string;
18
22
  /** RNG seed for reproducible output. Same seed + concurrency=1 = identical data. */
19
23
  seed?: string;
20
- /** Number of days the dataset spans (from "now" looking backward). Default: 30 */
24
+ /** Number of days the dataset spans. Used as fallback when datasetStart/datasetEnd are NOT both set — window becomes (today_start - numDays, today_start). Default: 30. When datasetStart/datasetEnd ARE both set, numDays is recomputed from the window and any user-supplied value is ignored (with a warning). */
21
25
  numDays?: number;
22
- /** Explicit start of dataset window (unix seconds). Alternative to numDays. */
26
+ /**
27
+ * Explicit start of the dataset window. Pin BOTH `datasetStart` and `datasetEnd` for
28
+ * bit-exact deterministic runs. Accepts ISO string ("2026-01-01T00:00:00Z"), unix
29
+ * seconds (1735689600), unix milliseconds (1735689600000), or anything `dayjs()`
30
+ * can parse. Setting only one of datasetStart/datasetEnd throws.
31
+ */
32
+ datasetStart?: string | number;
33
+ /**
34
+ * Explicit end of the dataset window. See `datasetStart` — both must be set together.
35
+ */
36
+ datasetEnd?: string | number;
37
+ /** @deprecated Legacy alias internally aliased to datasetStart on validated config. Prefer `datasetStart`. */
23
38
  epochStart?: number;
24
- /** Explicit end of dataset window (unix seconds). Defaults to FIXED_NOW. */
39
+ /** @deprecated Legacy alias internally aliased to datasetEnd on validated config. Prefer `datasetEnd`. */
25
40
  epochEnd?: number;
26
- /** Target total number of events to generate across all users. */
41
+ /** Target total number of events to generate across all users. Fallback when avgEventsPerUserPerDay is not set; otherwise derived from rate × numUsers × numDays. */
27
42
  numEvents?: number;
28
43
  /** Number of unique users to generate. */
29
44
  numUsers?: number;
45
+ /** Average events per user per active day. The canonical event-volume primitive — born-late users get this rate × their remaining window, so per-day density stays constant. If both this and numEvents are set, this wins. */
46
+ avgEventsPerUserPerDay?: number;
30
47
  /** Output format for files written to disk. */
31
48
  format?: "csv" | "json" | "parquet" | string;
32
49
  /** Mixpanel data residency region. */
@@ -106,9 +123,11 @@ export interface Dungeon {
106
123
  groupEvents?: GroupEventConfig[];
107
124
  /** Lookup table definitions for dimension tables. */
108
125
  lookupTables?: LookupTableSchema[];
109
- /** TimeSoup configuration: controls the temporal distribution of events (peaks, deviation, mean). */
126
+ /** TimeSoup configuration: shapes intra-week and intra-day rhythm (peaks, deviation, DOW/HOD weights). Pair with `macro` for big-picture trend control. */
110
127
  soup?: soup;
111
- /** Hook function called on every data point. The primary mechanism for engineering deliberate trends and patterns. */
128
+ /** Macro trend shape across the full dataset window: birth distribution + per-user event allocation. Default: "flat". Use "growth"/"viral"/"steady"/"decline" or a custom object. */
129
+ macro?: macro;
130
+ /** Hook function called on every data point. The primary mechanism for engineering deliberate trends and patterns. The `any` is intentional — `record` and `meta` shapes vary by hook type; narrow inside the function (see `HookMeta*` types). */
112
131
  hook?: Hook<any>;
113
132
 
114
133
  // ── Advanced Features ──
@@ -135,10 +154,14 @@ export interface Dungeon {
135
154
  [key: string]: any;
136
155
 
137
156
  // ── Distribution Controls ──
138
- /** Percentage of users whose account creation falls within the dataset window (vs. pre-existing). Default: 15 */
157
+ // These three knobs are normally set by the `macro` preset (default "flat").
158
+ // Setting them on the dungeon config directly overrides the preset's value.
159
+ /** Percentage of users whose account creation falls within the dataset window (vs. pre-existing). Default (from macro: "flat"): 15 */
139
160
  percentUsersBornInDataset?: number;
140
- /** Bias toward recent birth dates for users born in dataset (0 = uniform, 1 = heavily recent). Default: 0.3 */
161
+ /** Bias for birth dates of users born in dataset. -1..1; negative = early skew, positive = recent skew, 0 = uniform. Default (from macro: "flat"): 0 */
141
162
  bornRecentBias?: number;
163
+ /** How pre-existing users' first event time is placed. "pinned" stacks them all at FIXED_BEGIN; "uniform" spreads across [FIXED_BEGIN-30d, FIXED_BEGIN]. Default (from macro: "flat"): "uniform" */
164
+ preExistingSpread?: "pinned" | "uniform";
142
165
  }
143
166
 
144
167
  export type SCDProp = {
@@ -183,6 +206,43 @@ export type SoupConfig = {
183
206
  */
184
207
  type soup = SoupPreset | SoupConfig;
185
208
 
209
+ /**
210
+ * Macro preset names for big-picture trend shape across the dataset window.
211
+ * Macro is orthogonal to soup: macro shapes the whole-window trend (births,
212
+ * growth, decline); soup shapes the intra-week and intra-day rhythm.
213
+ */
214
+ export type MacroPreset = "flat" | "steady" | "growth" | "viral" | "decline";
215
+
216
+ /**
217
+ * Macro configuration object — fine-grained big-picture trend control.
218
+ */
219
+ export type MacroConfig = {
220
+ /** Use a named macro preset as the base, then override individual fields. */
221
+ preset?: MacroPreset;
222
+ /** Bias for birth dates. -1..1; negative = early skew, positive = recent skew, 0 = uniform. */
223
+ bornRecentBias?: number;
224
+ /** Percentage of users born in dataset window (0..100). */
225
+ percentUsersBornInDataset?: number;
226
+ /** "pinned" = pre-existing users stack at FIXED_BEGIN; "uniform" = spread across [FIXED_BEGIN-30d, FIXED_BEGIN]. */
227
+ preExistingSpread?: "pinned" | "uniform";
228
+ };
229
+
230
+ /** Big-picture trend shape: preset string, config object, or preset+overrides. */
231
+ type macro = MacroPreset | MacroConfig;
232
+
233
+ /** Public alias for the `soup` config union (preset string or config object). */
234
+ export type Soup = soup;
235
+
236
+ /** Public alias for the `macro` config union (preset string or config object). */
237
+ export type Macro = macro;
238
+
239
+ /** Resolved macro values after preset + override resolution. Used internally. */
240
+ export interface ResolvedMacro {
241
+ bornRecentBias: number;
242
+ percentUsersBornInDataset: number;
243
+ preExistingSpread: "pinned" | "uniform";
244
+ }
245
+
186
246
  /**
187
247
  * Hook types and when they fire (in order per user):
188
248
  * - "user" — user profile object (mutate in-place, return ignored)
@@ -213,31 +273,141 @@ export type hookTypes =
213
273
 
214
274
  /**
215
275
  * A hook function that receives every piece of data as it flows through the pipeline.
216
- * @param record - The data being processed (event, profile, array of events, etc.)
217
- * @param type - Which hook type is firing
218
- * @param meta - Contextual metadata (varies by type; "everything" includes meta.profile and meta.scd)
276
+ *
277
+ * The runtime signature is intentionally permissive (`any`) because `record` and `meta`
278
+ * vary by `type`. Use the `HookMeta*` interfaces below as convenience types when narrowing
279
+ * inside your hook (e.g. `if (type === "event") { const m = meta as HookMetaEvent; ... }`).
280
+ *
281
+ * Return-value semantics:
282
+ * - "event": return value REPLACES the event (must be the event object).
283
+ * - "everything": return an array to REPLACE the user's event list (filter/inject/dedupe).
284
+ * - "user", "scd-pre", "funnel-pre", "funnel-post": return value is IGNORED — mutate in place.
285
+ * - storage-only ("ad-spend", "group", "mirror", "lookup"): return value is IGNORED.
286
+ *
287
+ * @param record - The data being processed (event, profile, array of events, funnel config, etc.).
288
+ * @param type - Which hook type is firing — see `hookTypes`.
289
+ * @param meta - Contextual metadata. Shape depends on `type` — see `HookMeta*` interfaces.
219
290
  */
220
291
  export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
221
292
 
293
+ /**
294
+ * Time-window anchors present on every hook's `meta`. Use these to derive relative
295
+ * dates inside hooks (e.g. `dayjs.unix(meta.datasetStart).add(45, 'days')`). NEVER
296
+ * read wall-clock `dayjs()` inside a hook — it makes hooks non-deterministic.
297
+ */
298
+ export interface HookMetaTimeAnchors {
299
+ /** Start of the dataset window (unix seconds). Same value the engine uses to bound event generation. */
300
+ datasetStart: number;
301
+ /** End of the dataset window (unix seconds). Same value the engine uses to bound event generation. */
302
+ datasetEnd: number;
303
+ }
304
+
305
+ /** Meta passed to the "event" hook. */
306
+ export interface HookMetaEvent extends HookMetaTimeAnchors {
307
+ /** The user this event belongs to (only `distinct_id` is guaranteed). */
308
+ user: { distinct_id: string };
309
+ /** The fully-resolved dungeon config. */
310
+ config: Dungeon;
311
+ }
312
+
313
+ /** Meta passed to the "user" hook (fires when a user profile is created). */
314
+ export interface HookMetaUser extends HookMetaTimeAnchors {
315
+ /** The user object being constructed (mutate in place). */
316
+ user: UserProfile;
317
+ /** The fully-resolved dungeon config. */
318
+ config: Dungeon;
319
+ /** True if the user's account creation falls inside the dataset window. */
320
+ userIsBornInDataset: boolean;
321
+ }
322
+
323
+ /** Meta passed to the "scd-pre" hook (fires per SCD prop, before insertion). */
324
+ export interface HookMetaScdPre extends HookMetaTimeAnchors {
325
+ /** The user profile that owns these SCD entries. */
326
+ profile: UserProfile;
327
+ /** The SCD prop key being generated (e.g. "plan", "tier"). */
328
+ type: string;
329
+ /** The full SCD entry list for this prop (mutate in place). */
330
+ scd: SCDSchema[];
331
+ /** The fully-resolved dungeon config. */
332
+ config: Dungeon;
333
+ /** All SCD prop arrays generated so far for this user, keyed by prop name. */
334
+ allSCDs: Record<string, SCDSchema[]>;
335
+ }
336
+
337
+ /** Meta passed to the "funnel-pre" hook (mutate funnel before generating events). */
338
+ export interface HookMetaFunnelPre extends HookMetaTimeAnchors {
339
+ user: { distinct_id: string };
340
+ profile: UserProfile;
341
+ scd: Record<string, SCDSchema[]>;
342
+ funnel: Funnel;
343
+ config: Dungeon;
344
+ /** Unix seconds — earliest possible event time for this funnel's first step. */
345
+ firstEventTime: number;
346
+ }
347
+
348
+ /** Meta passed to the "funnel-post" hook (mutate generated funnel events in place). */
349
+ export interface HookMetaFunnelPost extends HookMetaTimeAnchors {
350
+ user: { distinct_id: string };
351
+ profile: UserProfile;
352
+ scd: Record<string, SCDSchema[]>;
353
+ funnel: Funnel;
354
+ config: Dungeon;
355
+ }
356
+
357
+ /** Meta passed to the "everything" hook — most powerful hook (sees all events for one user). */
358
+ export interface HookMetaEverything extends HookMetaTimeAnchors {
359
+ /** The user's profile, including merged persona/region/attribution properties. */
360
+ profile: UserProfile;
361
+ /** All SCD entries for this user, keyed by prop name. */
362
+ scd: Record<string, SCDSchema[]>;
363
+ /** The fully-resolved dungeon config. */
364
+ config: Dungeon;
365
+ /** True if the user's account creation falls inside the dataset window. */
366
+ userIsBornInDataset: boolean;
367
+ }
368
+
222
369
  export interface hookArrayOptions<T> {
370
+ /** Transform/validate function applied to every record on push. */
223
371
  hook?: Hook<T>;
372
+ /** What this array stores — controls hook-firing semantics in the storage layer. */
224
373
  type?: hookTypes;
374
+ /** Output filename (no extension; format adds it). Used by storage's batch writer. */
225
375
  filename?: string;
376
+ /** Output filepath used when writing batches to disk. */
377
+ filepath?: string;
378
+ /** Output serialization format. */
226
379
  format?: "csv" | "json" | "parquet" | string;
380
+ /** Max parallel disk writes. */
227
381
  concurrency?: number;
382
+ /** Generation context (config, runtime, defaults). */
228
383
  context?: Context;
229
- [key: string]: any;
230
384
  }
231
385
 
232
386
  /**
233
- * an enriched array is an array that has a hookPush method that can be used to transform-then-push items into the array
387
+ * an enriched array is an array that has a hookPush method that can be used to transform-then-push items into the array.
388
+ *
389
+ * Storage callers also tag the array with a key identifying what it stores
390
+ * (e.g. SCD prop name, group key, lookup table key). The fields are optional
391
+ * because not every HookedArray needs them; mixpanel-sender / user-loop read
392
+ * them when present to route uploads correctly.
234
393
  */
235
394
  export interface HookedArray<T> extends Array<T> {
236
- hookPush: (item: T | T[], ...meta: any[]) => Promise<any>;
395
+ /** Transform-then-push. Resolves once the item (and any auto-flushed batch) is persisted. */
396
+ hookPush: (item: T | T[], ...meta: unknown[]) => Promise<void>;
397
+ /** Force-flush any pending batch to disk. */
237
398
  flush: () => Promise<void>;
399
+ /** Absolute path of the directory batches will be written to. */
238
400
  getWriteDir: () => string;
401
+ /** Absolute path (with extension) of the next batch file. */
239
402
  getWritePath: () => string;
240
- [key: string]: any;
403
+ /** SCD prop name this array carries (only set on SCD HookedArrays). */
404
+ scdKey?: string;
405
+ /** Entity type for SCDs ("user" or a group key). */
406
+ entityType?: string;
407
+ /** Group key this array carries (only set on group profile HookedArrays). */
408
+ groupKey?: string;
409
+ /** Lookup table key this array carries (only set on lookup table HookedArrays). */
410
+ lookupKey?: string;
241
411
  }
242
412
 
243
413
  export type AllData =
@@ -245,8 +415,7 @@ export type AllData =
245
415
  | HookedArray<UserProfile>
246
416
  | HookedArray<GroupProfileSchema>
247
417
  | HookedArray<LookupTableSchema>
248
- | HookedArray<SCDSchema>
249
- | any[];
418
+ | HookedArray<SCDSchema>;
250
419
 
251
420
  /**
252
421
  * the storage object is a key-value store that holds arrays of data
@@ -277,16 +446,33 @@ export interface RuntimeState {
277
446
  /**
278
447
  * Default data factories for generating realistic test data
279
448
  */
449
+ /**
450
+ * Default data factories — pre-resolved at context creation time so user-loop
451
+ * doesn't re-evaluate weighted picker arrays on every iteration.
452
+ */
280
453
  export interface Defaults {
281
- locationsUsers: () => any[];
282
- locationsEvents: () => any[];
283
- iOSDevices: () => any[];
284
- androidDevices: () => any[];
285
- desktopDevices: () => any[];
286
- browsers: () => any[];
287
- campaigns: () => any[];
288
- devicePools: { android: any[]; ios: any[]; desktop: any[] };
289
- allDevices: any[];
454
+ /** Location pools applied to user profiles (city, region, country, lat/lng). */
455
+ locationsUsers: () => Record<string, ValueValid>[];
456
+ /** Location pools applied to events. */
457
+ locationsEvents: () => Record<string, ValueValid>[];
458
+ /** iOS device pool (model, os version, etc.). */
459
+ iOSDevices: () => Record<string, ValueValid>[];
460
+ /** Android device pool. */
461
+ androidDevices: () => Record<string, ValueValid>[];
462
+ /** Desktop device pool (browser, screen resolution, etc.). */
463
+ desktopDevices: () => Record<string, ValueValid>[];
464
+ /** Browser/UA pool. */
465
+ browsers: () => Record<string, ValueValid>[];
466
+ /** UTM campaign pool used when `hasCampaigns: true`. */
467
+ campaigns: () => Record<string, ValueValid>[];
468
+ /** Pre-built per-platform device arrays selected once at context creation. */
469
+ devicePools: {
470
+ android: Record<string, ValueValid>[];
471
+ ios: Record<string, ValueValid>[];
472
+ desktop: Record<string, ValueValid>[];
473
+ };
474
+ /** Flat union of every device in `devicePools` — used when no platform filter applies. */
475
+ allDevices: Record<string, ValueValid>[];
290
476
  }
291
477
 
292
478
  /**
@@ -297,12 +483,17 @@ export interface Context {
297
483
  config: Dungeon;
298
484
  storage: Storage | null;
299
485
  defaults: Defaults;
300
- campaigns: any[];
486
+ /** Pre-built UTM campaign pool (used when `hasCampaigns: true`). */
487
+ campaigns: Record<string, ValueValid>[];
301
488
  runtime: RuntimeState;
489
+ /** End of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetEnd`, or fallback `today_start`. */
302
490
  FIXED_NOW: number;
491
+ /** Start of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetStart`, or fallback `today_start - numDays`. */
303
492
  FIXED_BEGIN?: number;
304
- TIME_SHIFT_SECONDS: number;
305
- MAX_TIME: number;
493
+ /** Alias of `FIXED_BEGIN` — surfaced on hook `meta.datasetStart`. */
494
+ DATASET_START_SECONDS: number;
495
+ /** Alias of `FIXED_NOW` — surfaced on hook `meta.datasetEnd`. */
496
+ DATASET_END_SECONDS: number;
306
497
 
307
498
  // State update methods
308
499
  incrementOperations(): void;
@@ -319,10 +510,6 @@ export interface Context {
319
510
  incrementUserCount(): void;
320
511
  incrementEventCount(): void;
321
512
  isBatchMode(): boolean;
322
-
323
- // Time helper methods
324
- getTimeShift(): number;
325
- getDaysShift(): number;
326
513
  }
327
514
 
328
515
  /**
@@ -494,7 +681,8 @@ export interface LookupTableSchema {
494
681
 
495
682
  export interface LookupTableData {
496
683
  key: string;
497
- data: any[];
684
+ /** Generated rows for this lookup table. Each row is a flat record keyed by attribute name. */
685
+ data: Record<string, ValueValid>[];
498
686
  }
499
687
 
500
688
  export interface SCDSchema {
@@ -506,7 +694,8 @@ export interface SCDSchema {
506
694
 
507
695
  export interface GroupProfileSchema {
508
696
  key: string;
509
- data: any[];
697
+ /** Generated group profile rows. Each row is a flat record keyed by group property name. */
698
+ data: Record<string, ValueValid>[];
510
699
  }
511
700
 
512
701
  /**
@@ -523,15 +712,25 @@ type ImportResult = import("mixpanel-import").ImportResults;
523
712
  * the end result of the data generation
524
713
  */
525
714
  export type Result = {
715
+ /** Generated events. */
526
716
  eventData: EventSchema[];
717
+ /** Mirror datasets (transformed copies of `eventData`). */
527
718
  mirrorEventData: EventSchema[];
528
- userProfilesData: any[];
529
- scdTableData: any[][];
719
+ /** User profiles. */
720
+ userProfilesData: UserProfile[];
721
+ /** SCD entries — one inner array per SCD prop. */
722
+ scdTableData: SCDSchema[][];
723
+ /** Ad-spend events (only populated when `hasAdSpend: true`). */
530
724
  adSpendData: EventSchema[];
725
+ /** Group profiles — one inner array per group key. */
531
726
  groupProfilesData: GroupProfileSchema[][];
727
+ /** Lookup tables — one inner array per table. */
532
728
  lookupTableData: LookupTableData[][];
729
+ /** Mixpanel import results (only populated when a token was provided). */
533
730
  importResults?: ImportResults;
731
+ /** Absolute paths of all files written to disk. */
534
732
  files?: string[];
733
+ /** Timing information. */
535
734
  time?: {
536
735
  start: number;
537
736
  end: number;
@@ -589,7 +788,7 @@ export interface WorldEvent {
589
788
  /** Conversion rate modifier during this event. */
590
789
  conversionModifier?: number;
591
790
  /** Properties injected into affected events. */
592
- injectProps?: Record<string, any>;
791
+ injectProps?: Record<string, ValueValid>;
593
792
  /** Which events are affected ("*" for all, or array of event names). */
594
793
  affectsEvents?: string[] | "*";
595
794
  /** Aftermath period after the event ends. */
@@ -752,7 +951,7 @@ export interface GeoRegion {
752
951
  /** UTC timezone offset for this region (e.g., -5 for EST). */
753
952
  timezoneOffset: number;
754
953
  /** Properties injected for users in this region. */
755
- properties?: Record<string, any>;
954
+ properties?: Record<string, ValueValid>;
756
955
  }
757
956
 
758
957
  /**
@@ -792,9 +991,9 @@ export interface FeatureConfig {
792
991
  /** Property name to inject on events. */
793
992
  property: string;
794
993
  /** Possible values for the property. First value is the "before" default if defaultBefore not set. */
795
- values: any[];
994
+ values: ValueValid[];
796
995
  /** Default value before the feature launches. If not set, property doesn't exist before launch. */
797
- defaultBefore?: any;
996
+ defaultBefore?: ValueValid;
798
997
  /** Which events are affected ("*" for all, or array of event names). */
799
998
  affectsEvents?: string[] | "*";
800
999
  /** Conversion rate lift for users who adopted the feature. */
@@ -802,7 +1001,7 @@ export interface FeatureConfig {
802
1001
  /** Resolved logistic curve params (set by config-validator). */
803
1002
  _resolvedCurve?: { k: number; midpoint: number };
804
1003
  /** Pre-computed adopted values (set by config-validator). */
805
- _adoptedValues?: any[];
1004
+ _adoptedValues?: ValueValid[];
806
1005
  }
807
1006
 
808
1007
  /**
@@ -830,7 +1029,7 @@ export interface AnomalyConfig {
830
1029
  /** For burst/coordinated: number of events to inject. */
831
1030
  count?: number;
832
1031
  /** Properties injected on anomalous events. */
833
- properties?: Record<string, any>;
1032
+ properties?: Record<string, ValueValid>;
834
1033
  /** Resolved absolute start time in unix seconds (set by config-validator). */
835
1034
  _startUnix?: number;
836
1035
  /** Resolved absolute end time in unix seconds (set by config-validator). */
@@ -869,8 +1068,8 @@ export declare function loadFromFile(filePath: string): Promise<Dungeon>;
869
1068
  export declare function loadFromText(code: string): Promise<Dungeon>;
870
1069
  /** Parse a JSON dungeon (UI schema format) into a runnable config */
871
1070
  export declare function parseJSONDungeon(json: object): Dungeon;
872
- /** Validate that an object has the minimum shape of a dungeon config */
873
- export declare function validateDungeonShape(config: any): void;
1071
+ /** Validate that an object has the minimum shape of a dungeon config. Throws on shape violations. */
1072
+ export declare function validateDungeonShape(config: unknown): void;
874
1073
 
875
1074
  // ============= Text Generator Types =============
876
1075
 
@@ -1049,7 +1248,7 @@ export interface TextMetadata {
1049
1248
  /** Keywords that were injected */
1050
1249
  injectedKeywords?: string[];
1051
1250
  /** User persona information */
1052
- persona?: Record<string, any>;
1251
+ persona?: Record<string, ValueValid>;
1053
1252
  /** Flesch reading ease score */
1054
1253
  readabilityScore?: number;
1055
1254
  /** Text style used */
@@ -1170,13 +1369,14 @@ export interface TimeSoupOptions {
1170
1369
  }
1171
1370
 
1172
1371
  /**
1173
- * Test context configuration for unit/integration tests
1372
+ * Test context configuration for unit/integration tests. Looser than `Context`
1373
+ * by design — tests routinely attach ad-hoc fixtures, so the index signature stays.
1174
1374
  */
1175
1375
  export interface TestContext {
1176
1376
  config: Dungeon;
1177
1377
  storage: Storage | null;
1178
1378
  defaults: Defaults;
1179
- campaigns: any[];
1379
+ campaigns: Record<string, ValueValid>[];
1180
1380
  runtime: RuntimeState;
1181
- [key: string]: any;
1381
+ [key: string]: unknown;
1182
1382
  }