@ak--47/dungeon-master 1.5.0 → 1.5.2

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 (66) hide show
  1. package/.claude/skills/create-dungeon/SKILL.md +139 -46
  2. package/.claude/skills/verify-dungeon/references/counting-semantics.md +31 -6
  3. package/.claude/skills/verify-dungeon/references/sql-recipes.md +44 -25
  4. package/.claude/skills/write-hooks/SKILL.md +31 -3
  5. package/CHANGELOG.md +85 -0
  6. package/HOOKS.md +13 -0
  7. package/dungeons/technical/ad-spend.js +41 -49
  8. package/dungeons/technical/anonymous-users.js +38 -36
  9. package/dungeons/technical/array-of-object-lookup.js +136 -153
  10. package/dungeons/technical/datagen-v15-verify.js +24 -11
  11. package/dungeons/technical/experiments.js +42 -40
  12. package/dungeons/technical/foobar.js +114 -118
  13. package/dungeons/technical/group-analytics.js +42 -40
  14. package/dungeons/technical/hook-helpers-verify.js +69 -50
  15. package/dungeons/technical/identity-model-verify.js +22 -12
  16. package/dungeons/technical/mirror-strategies.js +37 -39
  17. package/dungeons/technical/nested-objects.js +119 -118
  18. package/dungeons/technical/pattern-aggregate-by-bin.js +21 -8
  19. package/dungeons/technical/pattern-attributed-by-source.js +23 -9
  20. package/dungeons/technical/pattern-frequency-by-frequency.js +21 -8
  21. package/dungeons/technical/pattern-funnel-frequency.js +30 -15
  22. package/dungeons/technical/pattern-ttc-by-segment.js +21 -8
  23. package/dungeons/technical/retention-cadence.js +115 -112
  24. package/dungeons/technical/sanity.js +86 -80
  25. package/dungeons/technical/scale-test.js +34 -38
  26. package/dungeons/technical/scd.js +111 -128
  27. package/dungeons/technical/simple.js +134 -141
  28. package/dungeons/technical/simplest.js +54 -62
  29. package/dungeons/technical/text-generation.js +110 -146
  30. package/dungeons/vertical/ai-platform.js +296 -333
  31. package/dungeons/vertical/community.js +284 -255
  32. package/dungeons/vertical/crypto.js +395 -391
  33. package/dungeons/vertical/dating.js +411 -378
  34. package/dungeons/vertical/devtools.js +336 -298
  35. package/dungeons/vertical/ecommerce.js +316 -394
  36. package/dungeons/vertical/education.js +369 -325
  37. package/dungeons/vertical/fintech.js +358 -325
  38. package/dungeons/vertical/fitness.js +335 -291
  39. package/dungeons/vertical/food-delivery.js +343 -307
  40. package/dungeons/vertical/gaming.js +480 -444
  41. package/dungeons/vertical/healthcare.js +306 -262
  42. package/dungeons/vertical/insurance-application.js +427 -409
  43. package/dungeons/vertical/logistics.js +271 -252
  44. package/dungeons/vertical/marketplace.js +333 -323
  45. package/dungeons/vertical/media.js +382 -335
  46. package/dungeons/vertical/real-estate.js +395 -346
  47. package/dungeons/vertical/sass.js +319 -333
  48. package/dungeons/vertical/social.js +368 -316
  49. package/dungeons/vertical/travel.js +297 -295
  50. package/index.js +46 -4
  51. package/lib/core/config-validator.js +126 -28
  52. package/lib/generators/funnels.js +4 -1
  53. package/lib/orchestrators/mixpanel-sender.js +7 -0
  54. package/lib/orchestrators/user-loop.js +132 -31
  55. package/lib/templates/defaults.js +59 -59
  56. package/lib/templates/macro-presets.js +14 -2
  57. package/lib/utils/dataset-context.js +103 -0
  58. package/lib/utils/retention-curve.js +140 -0
  59. package/lib/utils/utils.js +149 -38
  60. package/lib/verify/counting.js +40 -0
  61. package/lib/verify/emulate-breakdown.js +20 -1
  62. package/lib/verify/index.js +1 -0
  63. package/lib/verify/schema-validator.js +3 -1
  64. package/package.json +11 -2
  65. package/scripts/run-dungeon.mjs +12 -1
  66. package/types.d.ts +117 -1
@@ -128,7 +128,7 @@ const iosDevices = [
128
128
  screen_height: '2868',
129
129
  screen_width: '1320',
130
130
  os: 'iOS',
131
- Platform: 'iOS',
131
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
132
132
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'visible'],
133
133
  radio: ['4G', 'LTE', '5G', '5G mmWave', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
134
134
  }, {
@@ -136,7 +136,7 @@ const iosDevices = [
136
136
  screen_height: '2868',
137
137
  screen_width: '1320',
138
138
  os: 'iOS',
139
- Platform: 'iOS',
139
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
140
140
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'visible'],
141
141
  radio: ['4G', 'LTE', '5G', '5G mmWave', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
142
142
  }, {
@@ -144,7 +144,7 @@ const iosDevices = [
144
144
  screen_height: '2796',
145
145
  screen_width: '1290',
146
146
  os: 'iOS',
147
- Platform: 'iOS',
147
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
148
148
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'visible'],
149
149
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
150
150
  }, {
@@ -152,7 +152,7 @@ const iosDevices = [
152
152
  screen_height: '2556',
153
153
  screen_width: '1179',
154
154
  os: 'iOS',
155
- Platform: 'iOS',
155
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
156
156
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'visible'],
157
157
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
158
158
  },
@@ -163,7 +163,7 @@ const iosDevices = [
163
163
  screen_height: '2796',
164
164
  screen_width: '1290',
165
165
  os: 'iOS',
166
- Platform: 'iOS',
166
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
167
167
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile'],
168
168
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E']
169
169
  }, {
@@ -171,7 +171,7 @@ const iosDevices = [
171
171
  screen_height: '2556',
172
172
  screen_width: '1179',
173
173
  os: 'iOS',
174
- Platform: 'iOS',
174
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
175
175
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile'],
176
176
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E']
177
177
  }, {
@@ -179,7 +179,7 @@ const iosDevices = [
179
179
  screen_height: '2796',
180
180
  screen_width: '1290',
181
181
  os: 'iOS',
182
- Platform: 'iOS',
182
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
183
183
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
184
184
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6']
185
185
  }, {
@@ -187,7 +187,7 @@ const iosDevices = [
187
187
  screen_height: '2556',
188
188
  screen_width: '1179',
189
189
  os: 'iOS',
190
- Platform: 'iOS',
190
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
191
191
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
192
192
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6']
193
193
  },
@@ -198,7 +198,7 @@ const iosDevices = [
198
198
  screen_height: '2532',
199
199
  screen_width: '1170',
200
200
  os: 'iOS',
201
- Platform: 'iOS',
201
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
202
202
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
203
203
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6']
204
204
  }, {
@@ -206,7 +206,7 @@ const iosDevices = [
206
206
  screen_height: '1334',
207
207
  screen_width: '750',
208
208
  os: 'iOS',
209
- Platform: 'iOS',
209
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
210
210
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
211
211
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6']
212
212
  },
@@ -217,7 +217,7 @@ const iosDevices = [
217
217
  screen_height: '2420',
218
218
  screen_width: '1668',
219
219
  os: 'iPadOS',
220
- Platform: 'iOS',
220
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
221
221
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
222
222
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
223
223
  }, {
@@ -225,7 +225,7 @@ const iosDevices = [
225
225
  screen_height: '2752',
226
226
  screen_width: '2064',
227
227
  os: 'iPadOS',
228
- Platform: 'iOS',
228
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
229
229
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
230
230
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
231
231
  },
@@ -236,7 +236,7 @@ const iosDevices = [
236
236
  screen_height: '2360',
237
237
  screen_width: '1640',
238
238
  os: 'iPadOS',
239
- Platform: 'iOS',
239
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
240
240
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
241
241
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E']
242
242
  }, {
@@ -244,7 +244,7 @@ const iosDevices = [
244
244
  screen_height: '2752',
245
245
  screen_width: '2064',
246
246
  os: 'iPadOS',
247
- Platform: 'iOS',
247
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
248
248
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
249
249
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E']
250
250
  },
@@ -255,7 +255,7 @@ const iosDevices = [
255
255
  screen_height: '2360',
256
256
  screen_width: '1640',
257
257
  os: 'iPadOS',
258
- Platform: 'iOS',
258
+ // Platform: 'iOS', // Removed in 1.5.1 — see TODO #11 (collided with dungeon-defined Platform props)
259
259
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
260
260
  radio: ['4G', 'LTE', 'Wifi', 'WiFi6']
261
261
  }
@@ -268,7 +268,7 @@ const androidDevices = [
268
268
  screen_height: '2992',
269
269
  screen_width: '1344',
270
270
  os: 'Android',
271
- Platform: 'Android',
271
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
272
272
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'visible', 'google fi'],
273
273
  radio: ['4G', 'LTE', '5G', '5G mmWave', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
274
274
  }, {
@@ -276,7 +276,7 @@ const androidDevices = [
276
276
  screen_height: '2856',
277
277
  screen_width: '1280',
278
278
  os: 'Android',
279
- Platform: 'Android',
279
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
280
280
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'visible', 'google fi'],
281
281
  radio: ['4G', 'LTE', '5G', '5G mmWave', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
282
282
  }, {
@@ -284,7 +284,7 @@ const androidDevices = [
284
284
  screen_height: '2424',
285
285
  screen_width: '1080',
286
286
  os: 'Android',
287
- Platform: 'Android',
287
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
288
288
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'visible', 'google fi'],
289
289
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
290
290
  }, {
@@ -292,7 +292,7 @@ const androidDevices = [
292
292
  screen_height: '2400',
293
293
  screen_width: '1080',
294
294
  os: 'Android',
295
- Platform: 'Android',
295
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
296
296
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'google fi'],
297
297
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E']
298
298
  },
@@ -303,7 +303,7 @@ const androidDevices = [
303
303
  screen_height: '3120',
304
304
  screen_width: '1440',
305
305
  os: 'Android',
306
- Platform: 'Android',
306
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
307
307
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'visible'],
308
308
  radio: ['4G', 'LTE', '5G', '5G mmWave', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
309
309
  }, {
@@ -311,7 +311,7 @@ const androidDevices = [
311
311
  screen_height: '3120',
312
312
  screen_width: '1440',
313
313
  os: 'Android',
314
- Platform: 'Android',
314
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
315
315
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile', 'visible'],
316
316
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
317
317
  }, {
@@ -319,7 +319,7 @@ const androidDevices = [
319
319
  screen_height: '2340',
320
320
  screen_width: '1080',
321
321
  os: 'Android',
322
- Platform: 'Android',
322
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
323
323
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile'],
324
324
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
325
325
  },
@@ -330,7 +330,7 @@ const androidDevices = [
330
330
  screen_height: '2160',
331
331
  screen_width: '1856',
332
332
  os: 'Android',
333
- Platform: 'Android',
333
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
334
334
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
335
335
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
336
336
  }, {
@@ -338,7 +338,7 @@ const androidDevices = [
338
338
  screen_height: '2640',
339
339
  screen_width: '1080',
340
340
  os: 'Android',
341
- Platform: 'Android',
341
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
342
342
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
343
343
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
344
344
  },
@@ -349,7 +349,7 @@ const androidDevices = [
349
349
  screen_height: '3168',
350
350
  screen_width: '1440',
351
351
  os: 'Android',
352
- Platform: 'Android',
352
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
353
353
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile'],
354
354
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
355
355
  }, {
@@ -357,7 +357,7 @@ const androidDevices = [
357
357
  screen_height: '3168',
358
358
  screen_width: '1440',
359
359
  os: 'Android',
360
- Platform: 'Android',
360
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
361
361
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular', 'mint mobile'],
362
362
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E']
363
363
  },
@@ -368,7 +368,7 @@ const androidDevices = [
368
368
  screen_height: '2412',
369
369
  screen_width: '1084',
370
370
  os: 'Android',
371
- Platform: 'Android',
371
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
372
372
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
373
373
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6']
374
374
  },
@@ -379,7 +379,7 @@ const androidDevices = [
379
379
  screen_height: '3200',
380
380
  screen_width: '1440',
381
381
  os: 'Android',
382
- Platform: 'Android',
382
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
383
383
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
384
384
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E', 'WiFi7']
385
385
  }, {
@@ -387,7 +387,7 @@ const androidDevices = [
387
387
  screen_height: '2712',
388
388
  screen_width: '1220',
389
389
  os: 'Android',
390
- Platform: 'Android',
390
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
391
391
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
392
392
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6']
393
393
  },
@@ -398,7 +398,7 @@ const androidDevices = [
398
398
  screen_height: '3840',
399
399
  screen_width: '1644',
400
400
  os: 'Android',
401
- Platform: 'Android',
401
+ // Platform: 'Android', // Removed in 1.5.1 — see TODO #11
402
402
  carrier: ['verizon', 'at&t', 't-mobile', 'cricket', 'us cellular'],
403
403
  radio: ['4G', 'LTE', '5G', 'Wifi', 'WiFi6', 'WiFi6E']
404
404
  }
@@ -411,49 +411,49 @@ const desktopDevices = [
411
411
  screen_height: '2520',
412
412
  screen_width: '4480',
413
413
  os: 'macOS',
414
- Platform: 'Desktop'
414
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
415
415
  }, {
416
416
  model: 'Mac Studio (M4 Ultra, 2024)',
417
417
  screen_height: '2880',
418
418
  screen_width: '5120',
419
419
  os: 'macOS',
420
- Platform: 'Desktop'
420
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
421
421
  }, {
422
422
  model: 'Mac Pro (M3 Ultra, 2024)',
423
423
  screen_height: '2160',
424
424
  screen_width: '3840',
425
425
  os: 'macOS',
426
- Platform: 'Desktop'
426
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
427
427
  }, {
428
428
  model: 'Mac Mini (M4, 2024)',
429
429
  screen_height: '2160',
430
430
  screen_width: '3840',
431
431
  os: 'macOS',
432
- Platform: 'Desktop'
432
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
433
433
  }, {
434
434
  model: 'MacBook Air 15-inch (M3, 2024)',
435
435
  screen_height: '1964',
436
436
  screen_width: '2880',
437
437
  os: 'macOS',
438
- Platform: 'Desktop'
438
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
439
439
  }, {
440
440
  model: 'MacBook Air 13-inch (M3, 2024)',
441
441
  screen_height: '1664',
442
442
  screen_width: '2560',
443
443
  os: 'macOS',
444
- Platform: 'Desktop'
444
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
445
445
  }, {
446
446
  model: 'MacBook Pro 14-inch (M4 Pro, 2024)',
447
447
  screen_height: '1964',
448
448
  screen_width: '3024',
449
449
  os: 'macOS',
450
- Platform: 'Desktop'
450
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
451
451
  }, {
452
452
  model: 'MacBook Pro 16-inch (M4 Max, 2024)',
453
453
  screen_height: '2234',
454
454
  screen_width: '3456',
455
455
  os: 'macOS',
456
- Platform: 'Desktop'
456
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
457
457
  },
458
458
 
459
459
  // Windows Laptops (2024-2025)
@@ -462,67 +462,67 @@ const desktopDevices = [
462
462
  screen_height: '2160',
463
463
  screen_width: '2880',
464
464
  os: 'Windows',
465
- Platform: 'Desktop'
465
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
466
466
  }, {
467
467
  model: 'Dell XPS 15 (Intel Core Ultra 9)',
468
468
  screen_height: '2160',
469
469
  screen_width: '3840',
470
470
  os: 'Windows',
471
- Platform: 'Desktop'
471
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
472
472
  }, {
473
473
  model: 'Dell XPS 17 (RTX 4080)',
474
474
  screen_height: '2400',
475
475
  screen_width: '3840',
476
476
  os: 'Windows',
477
- Platform: 'Desktop'
477
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
478
478
  }, {
479
479
  model: 'Microsoft Surface Laptop Studio 2',
480
480
  screen_height: '1504',
481
481
  screen_width: '2256',
482
482
  os: 'Windows',
483
- Platform: 'Desktop'
483
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
484
484
  }, {
485
485
  model: 'Microsoft Surface Pro 11',
486
486
  screen_height: '1944',
487
487
  screen_width: '2880',
488
488
  os: 'Windows',
489
- Platform: 'Desktop'
489
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
490
490
  }, {
491
491
  model: 'HP Spectre x360 16 (Intel Core Ultra)',
492
492
  screen_height: '2160',
493
493
  screen_width: '3840',
494
494
  os: 'Windows',
495
- Platform: 'Desktop'
495
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
496
496
  }, {
497
497
  model: 'HP EliteBook 840 G11',
498
498
  screen_height: '1200',
499
499
  screen_width: '1920',
500
500
  os: 'Windows',
501
- Platform: 'Desktop'
501
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
502
502
  }, {
503
503
  model: 'Lenovo ThinkPad X1 Carbon Gen 12',
504
504
  screen_height: '1200',
505
505
  screen_width: '1920',
506
506
  os: 'Windows',
507
- Platform: 'Desktop'
507
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
508
508
  }, {
509
509
  model: 'Lenovo Yoga Pro 9i (RTX 4070)',
510
510
  screen_height: '1600',
511
511
  screen_width: '2560',
512
512
  os: 'Windows',
513
- Platform: 'Desktop'
513
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
514
514
  }, {
515
515
  model: 'ASUS ZenBook Pro 16X OLED',
516
516
  screen_height: '2160',
517
517
  screen_width: '3840',
518
518
  os: 'Windows',
519
- Platform: 'Desktop'
519
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
520
520
  }, {
521
521
  model: 'ASUS ROG Zephyrus G16',
522
522
  screen_height: '1200',
523
523
  screen_width: '1920',
524
524
  os: 'Windows',
525
- Platform: 'Desktop'
525
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
526
526
  },
527
527
 
528
528
  // Gaming Desktops (2024-2025)
@@ -531,25 +531,25 @@ const desktopDevices = [
531
531
  screen_height: '2160',
532
532
  screen_width: '3840',
533
533
  os: 'Windows',
534
- Platform: 'Desktop'
534
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
535
535
  }, {
536
536
  model: 'HP OMEN 45L (RTX 4080 Super)',
537
537
  screen_height: '1440',
538
538
  screen_width: '2560',
539
539
  os: 'Windows',
540
- Platform: 'Desktop'
540
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
541
541
  }, {
542
542
  model: 'Corsair One i500 (RTX 4090)',
543
543
  screen_height: '2160',
544
544
  screen_width: '3840',
545
545
  os: 'Windows',
546
- Platform: 'Desktop'
546
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
547
547
  }, {
548
548
  model: 'NZXT BLD H5 Elite',
549
549
  screen_height: '1440',
550
550
  screen_width: '2560',
551
551
  os: 'Windows',
552
- Platform: 'Desktop'
552
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
553
553
  },
554
554
 
555
555
  // Business/Office Desktops
@@ -558,19 +558,19 @@ const desktopDevices = [
558
558
  screen_height: '1080',
559
559
  screen_width: '1920',
560
560
  os: 'Windows',
561
- Platform: 'Desktop'
561
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
562
562
  }, {
563
563
  model: 'HP EliteDesk 805 G8',
564
564
  screen_height: '1440',
565
565
  screen_width: '2560',
566
566
  os: 'Windows',
567
- Platform: 'Desktop'
567
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
568
568
  }, {
569
569
  model: 'Lenovo ThinkCentre M90a Pro',
570
570
  screen_height: '1440',
571
571
  screen_width: '2560',
572
572
  os: 'Windows',
573
- Platform: 'Desktop'
573
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
574
574
  },
575
575
 
576
576
  // Linux/Alternative OS (growing presence)
@@ -579,19 +579,19 @@ const desktopDevices = [
579
579
  screen_height: '2160',
580
580
  screen_width: '3840',
581
581
  os: 'Pop!_OS',
582
- Platform: 'Desktop'
582
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
583
583
  }, {
584
584
  model: 'Framework Laptop 16',
585
585
  screen_height: '1600',
586
586
  screen_width: '2560',
587
587
  os: 'Linux',
588
- Platform: 'Desktop'
588
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
589
589
  }, {
590
590
  model: 'Purism Librem 14',
591
591
  screen_height: '1080',
592
592
  screen_width: '1920',
593
593
  os: 'PureOS',
594
- Platform: 'Desktop'
594
+ // Platform: 'Desktop' // Removed in 1.5.1 — see TODO #11
595
595
  }
596
596
  ];
597
597
 
@@ -38,7 +38,15 @@
38
38
  * achievable with the v1.5 engine fix's per-cluster trapezoidal shape.
39
39
  */
40
40
 
41
- /** @type {Record<string, {bornRecentBias: number, percentUsersBornInDataset: number, preExistingSpread: 'pinned'|'uniform'}>} */
41
+ /**
42
+ * v1.5.1: macro presets carry an optional `avgActiveDaysPerUser` default. The
43
+ * validator merges this default only when the dungeon doesn't set the field
44
+ * explicitly. `flat` deliberately omits it (legacy behavior — every window-day
45
+ * potentially active). Other macros ship sensible-default concentrations so
46
+ * new dungeons get realistic user-activity patterns out of the box.
47
+ *
48
+ * @type {Record<string, {bornRecentBias: number, percentUsersBornInDataset: number, preExistingSpread: 'pinned'|'uniform', avgActiveDaysPerUser?: number}>}
49
+ */
42
50
  export const MACRO_PRESETS = {
43
51
  /**
44
52
  * flat (DEFAULT) — Mature product, no growth narrative.
@@ -62,6 +70,7 @@ export const MACRO_PRESETS = {
62
70
  bornRecentBias: 0.1,
63
71
  percentUsersBornInDataset: 12,
64
72
  preExistingSpread: 'uniform',
73
+ avgActiveDaysPerUser: 15,
65
74
  },
66
75
 
67
76
  /**
@@ -73,6 +82,7 @@ export const MACRO_PRESETS = {
73
82
  bornRecentBias: 0.3,
74
83
  percentUsersBornInDataset: 30,
75
84
  preExistingSpread: 'pinned',
85
+ avgActiveDaysPerUser: 10,
76
86
  },
77
87
 
78
88
  /**
@@ -84,6 +94,7 @@ export const MACRO_PRESETS = {
84
94
  bornRecentBias: 0.6,
85
95
  percentUsersBornInDataset: 55,
86
96
  preExistingSpread: 'pinned',
97
+ avgActiveDaysPerUser: 20,
87
98
  },
88
99
 
89
100
  /**
@@ -103,6 +114,7 @@ export const MACRO_PRESETS = {
103
114
  bornRecentBias: -0.3,
104
115
  percentUsersBornInDataset: 5,
105
116
  preExistingSpread: 'uniform',
117
+ avgActiveDaysPerUser: 5,
106
118
  },
107
119
  };
108
120
 
@@ -114,7 +126,7 @@ export const MACRO_PRESET_NAMES = Object.keys(MACRO_PRESETS);
114
126
  * Defaults to "flat" if nothing is provided.
115
127
  *
116
128
  * @param {string | object | undefined} macro - Macro config from dungeon
117
- * @returns {{bornRecentBias: number, percentUsersBornInDataset: number, preExistingSpread: 'pinned' | 'uniform'}}
129
+ * @returns {{bornRecentBias: number, percentUsersBornInDataset: number, preExistingSpread: 'pinned' | 'uniform', avgActiveDaysPerUser?: number}}
118
130
  */
119
131
  export function resolveMacro(macro) {
120
132
  if (!macro) return { ...MACRO_PRESETS.flat };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * v1.5.1: AsyncLocalStorage-scoped dataset-window context.
3
+ *
4
+ * Replaces the legacy module-scoped `DATASET_NOW` / `DATASET_BEGIN` mutable
5
+ * state in `lib/utils/utils.js`. Each `generate()` call wraps the pipeline in
6
+ * `runWithDataset(begin, now, fn)`, and the factory thunks (`date`, `day`,
7
+ * `dateRange`, `TimeSoup`, `validTime`) read from the ALS store via
8
+ * `getDatasetNow()` / `getDatasetBegin()` instead of module globals.
9
+ *
10
+ * This makes concurrent in-process `generate()` calls safe with respect to
11
+ * the dataset-window dimension. RNG scoping is a SEPARATE concurrency hole
12
+ * — see TODOs.md "Considered but punted" for the follow-up plan.
13
+ *
14
+ * Trip-ups (see plans/archived/globals-killplan-1.5.1/kill-globals.md §7):
15
+ * - ALS does NOT propagate across child_process.fork / worker_threads.
16
+ * - Tests that import dungeon configs at top-level evaluate thunks at
17
+ * import time — those thunks fall back to wall-clock if invoked outside
18
+ * a `runWithDataset` scope.
19
+ * - Callbacks scheduled via setImmediate / setTimeout / native event
20
+ * emitters MAY lose context. Audit at the orchestrator level.
21
+ */
22
+
23
+ import { AsyncLocalStorage } from 'node:async_hooks';
24
+ import dayjs from 'dayjs';
25
+ import utc from 'dayjs/plugin/utc.js';
26
+ dayjs.extend(utc);
27
+
28
+ /** @type {AsyncLocalStorage<{ begin: number, now: number }>} */
29
+ const datasetALS = new AsyncLocalStorage();
30
+
31
+ /**
32
+ * Legacy fallback — written by the deprecated `setDatasetNow` / `setDatasetBegin`
33
+ * shims in `lib/utils/utils.js`. Used ONLY when no ALS scope is active. This
34
+ * keeps existing tests that call the setters directly (instead of wrapping in
35
+ * `runWithDataset`) functional through v1.5.1. v1.6 may remove the shims.
36
+ *
37
+ * @type {{ begin: number|null, now: number|null }}
38
+ */
39
+ const legacyFallback = { begin: null, now: null };
40
+
41
+ /** @internal — used by `setDatasetNow` / `setDatasetBegin` shims. */
42
+ export function _setLegacyDatasetNow(unixSeconds) {
43
+ if (typeof unixSeconds === 'number' && Number.isFinite(unixSeconds)) {
44
+ legacyFallback.now = unixSeconds;
45
+ }
46
+ }
47
+
48
+ /** @internal — used by `setDatasetNow` / `setDatasetBegin` shims. */
49
+ export function _setLegacyDatasetBegin(unixSeconds) {
50
+ if (typeof unixSeconds === 'number' && Number.isFinite(unixSeconds)) {
51
+ legacyFallback.begin = unixSeconds;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Run `fn` inside a dataset-window scope. Factory thunks invoked anywhere in
57
+ * the async call chain (sync or via `await`) will read the scoped window
58
+ * instead of the module fallback.
59
+ *
60
+ * @template T
61
+ * @param {number} datasetBeginUnix
62
+ * @param {number} datasetNowUnix
63
+ * @param {() => T} fn
64
+ * @returns {T}
65
+ */
66
+ export function runWithDataset(datasetBeginUnix, datasetNowUnix, fn) {
67
+ return datasetALS.run({ begin: datasetBeginUnix, now: datasetNowUnix }, fn);
68
+ }
69
+
70
+ /**
71
+ * Read the scoped dataset-now value. Resolution order:
72
+ * 1. ALS store (active `runWithDataset` scope) — production pipeline path.
73
+ * 2. Legacy fallback (set via deprecated `setDatasetNow` shim) — for tests.
74
+ * 3. Wall-clock `dayjs.utc()` — last-resort default.
75
+ *
76
+ * @returns {dayjs.Dayjs}
77
+ */
78
+ export function getDatasetNow() {
79
+ const store = datasetALS.getStore();
80
+ if (store) return dayjs.unix(store.now).utc();
81
+ if (legacyFallback.now !== null) return dayjs.unix(legacyFallback.now).utc();
82
+ return dayjs.utc();
83
+ }
84
+
85
+ /**
86
+ * Read the scoped dataset-begin value. Same resolution as `getDatasetNow`,
87
+ * falling back to wall-clock minus 30 days.
88
+ *
89
+ * @returns {dayjs.Dayjs}
90
+ */
91
+ export function getDatasetBegin() {
92
+ const store = datasetALS.getStore();
93
+ if (store) return dayjs.unix(store.begin).utc();
94
+ if (legacyFallback.begin !== null) return dayjs.unix(legacyFallback.begin).utc();
95
+ return dayjs.utc().subtract(30, 'day');
96
+ }
97
+
98
+ /**
99
+ * @returns {boolean} true when called inside a runWithDataset scope.
100
+ */
101
+ export function hasDatasetScope() {
102
+ return datasetALS.getStore() !== undefined;
103
+ }