@ak--47/dungeon-master 1.0.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 +518 -0
- package/dungeons/array-of-object-lookup-schema.json +327 -0
- package/dungeons/array-of-object-lookup.js +220 -0
- package/dungeons/ecommerce-schema.json +462 -0
- package/dungeons/ecommerce.js +447 -0
- package/dungeons/education-schema.json +2409 -0
- package/dungeons/education.js +768 -0
- package/dungeons/fintech-schema.json +14034 -0
- package/dungeons/fintech.js +696 -0
- package/dungeons/foobar-schema.json +403 -0
- package/dungeons/foobar.js +296 -0
- package/dungeons/food-delivery-schema.json +192 -0
- package/dungeons/food-delivery.js +602 -0
- package/dungeons/food-schema.json +1152 -0
- package/dungeons/food.js +754 -0
- package/dungeons/gaming-schema.json +1270 -0
- package/dungeons/gaming.js +508 -0
- package/dungeons/insurance-application-schema.json +204 -0
- package/dungeons/insurance-application.js +605 -0
- package/dungeons/media-schema.json +906 -0
- package/dungeons/media.js +790 -0
- package/dungeons/retention-cadence-schema.json +78 -0
- package/dungeons/retention-cadence.js +244 -0
- package/dungeons/rpg-schema.json +4526 -0
- package/dungeons/rpg.js +919 -0
- package/dungeons/sanity-schema.json +255 -0
- package/dungeons/sanity.js +152 -0
- package/dungeons/sass-schema.json +1291 -0
- package/dungeons/sass.js +795 -0
- package/dungeons/scd-schema.json +919 -0
- package/dungeons/scd.js +277 -0
- package/dungeons/simple-schema.json +608 -0
- package/dungeons/simple.js +285 -0
- package/dungeons/simplest-schema.json +1418 -0
- package/dungeons/simplest.js +392 -0
- package/dungeons/social-schema.json +1118 -0
- package/dungeons/social.js +686 -0
- package/dungeons/text-generation-schema.json +3096 -0
- package/dungeons/text-generation.js +812 -0
- package/index.js +567 -0
- package/lib/core/config-validator.js +395 -0
- package/lib/core/context.js +204 -0
- package/lib/core/dungeon-loader.js +337 -0
- package/lib/core/storage.js +379 -0
- package/lib/generators/adspend.js +132 -0
- package/lib/generators/events.js +271 -0
- package/lib/generators/funnels.js +407 -0
- package/lib/generators/mirror.js +167 -0
- package/lib/generators/product-lookup.js +262 -0
- package/lib/generators/product-names.js +195 -0
- package/lib/generators/profiles.js +93 -0
- package/lib/generators/scd.js +124 -0
- package/lib/generators/text.js +1192 -0
- package/lib/orchestrators/mixpanel-sender.js +266 -0
- package/lib/orchestrators/user-loop.js +335 -0
- package/lib/templates/abbreviated.d.ts +169 -0
- package/lib/templates/defaults.js +1405 -0
- package/lib/templates/phrases.js +2526 -0
- package/lib/templates/schema.d.ts +173 -0
- package/lib/templates/soup-presets.js +188 -0
- package/lib/utils/function-registry.js +302 -0
- package/lib/utils/json-evaluator.js +172 -0
- package/lib/utils/logger.js +34 -0
- package/lib/utils/utils.js +1490 -0
- package/package.json +89 -0
- package/types.d.ts +865 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A "ValueValid" can be:
|
|
3
|
+
* - A primitive value (string, number, boolean)
|
|
4
|
+
* - An array of primitives (the system picks one randomly)
|
|
5
|
+
* - A function call object: { "functionName": "...", "args": [...] }
|
|
6
|
+
* - An arrow function object: { "functionName": "arrow", "body": "..." }
|
|
7
|
+
*
|
|
8
|
+
* This is the building block for all property values in the dungeon.
|
|
9
|
+
*/
|
|
10
|
+
type Primitives = string | number | boolean;
|
|
11
|
+
type FunctionCall = { functionName: string; args?: any[]; body?: string };
|
|
12
|
+
type ValueValid = Primitives | Primitives[] | FunctionCall;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The main configuration object for the entire data generation spec, known as a "Dungeon".
|
|
17
|
+
* This is the high-level object you will be constructing.
|
|
18
|
+
*
|
|
19
|
+
* REQUIRED fields: events, funnels, superProps, userProps
|
|
20
|
+
* OPTIONAL fields: scdProps, groupKeys, groupProps, groupEvents
|
|
21
|
+
*/
|
|
22
|
+
export interface Dungeon {
|
|
23
|
+
/** REQUIRED: A list of all possible events that can occur in the simulation. */
|
|
24
|
+
events: EventConfig[];
|
|
25
|
+
|
|
26
|
+
/** REQUIRED: A list of event sequences that represent user journeys (e.g., sign-up, purchase). */
|
|
27
|
+
funnels: Funnel[];
|
|
28
|
+
|
|
29
|
+
/** REQUIRED: Properties that are attached to every event for all users. */
|
|
30
|
+
superProps: Record<string, ValueValid>;
|
|
31
|
+
|
|
32
|
+
/** REQUIRED: Properties that define the characteristics of individual users. */
|
|
33
|
+
userProps: Record<string, ValueValid>;
|
|
34
|
+
|
|
35
|
+
/** OPTIONAL: Properties that change for users or groups over time (Slowly Changing Dimensions). Only include when properties explicitly change over time. */
|
|
36
|
+
scdProps?: Record<string, SCDProp>;
|
|
37
|
+
|
|
38
|
+
/** OPTIONAL: Defines group entities (companies, teams). Format: [["group_key", count], ...]. ONLY for B2B/SaaS scenarios. */
|
|
39
|
+
groupKeys?: [string, number][];
|
|
40
|
+
|
|
41
|
+
/** OPTIONAL: Properties for groups defined in groupKeys. ONLY include if groupKeys is defined. */
|
|
42
|
+
groupProps?: Record<string, Record<string, ValueValid>>;
|
|
43
|
+
|
|
44
|
+
/** OPTIONAL: Events attributed to groups on a schedule (e.g., monthly billing). Rarely needed. */
|
|
45
|
+
groupEvents?: GroupEventConfig[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Defines a single event, its properties, and its likelihood of occurring.
|
|
51
|
+
*
|
|
52
|
+
* The "weight" determines relative frequency - an event with weight 10 occurs
|
|
53
|
+
* roughly 10x more often than an event with weight 1.
|
|
54
|
+
*/
|
|
55
|
+
interface EventConfig {
|
|
56
|
+
/** REQUIRED: The name of the event (e.g., "Page View", "Add to Cart", "checkout"). */
|
|
57
|
+
event: string;
|
|
58
|
+
|
|
59
|
+
/** OPTIONAL: The relative frequency of this event. Higher numbers = more frequent. Default: 1 */
|
|
60
|
+
weight?: number;
|
|
61
|
+
|
|
62
|
+
/** OPTIONAL: Properties associated with this event. Each property can be a value or array. */
|
|
63
|
+
properties?: Record<string, ValueValid>;
|
|
64
|
+
|
|
65
|
+
/** OPTIONAL: If true, this event will be the first event for a new user (e.g., "sign up"). Only one event should have this. */
|
|
66
|
+
isFirstEvent?: boolean;
|
|
67
|
+
|
|
68
|
+
/** OPTIONAL: If true, this event signifies that a user has churned (e.g., "account deleted"). */
|
|
69
|
+
isChurnEvent?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Defines a sequence of events that represents a meaningful user journey or workflow.
|
|
75
|
+
*
|
|
76
|
+
* Funnels model how users progress through your product - from sign-up to purchase,
|
|
77
|
+
* from onboarding to activation, etc. The conversionRate determines what percentage
|
|
78
|
+
* of users who start the funnel will complete it.
|
|
79
|
+
*/
|
|
80
|
+
interface Funnel {
|
|
81
|
+
/** REQUIRED: Event names that make up this journey. Must match event names in the events array. */
|
|
82
|
+
sequence: string[];
|
|
83
|
+
|
|
84
|
+
/** REQUIRED: Percentage (0-100) of users who complete the funnel. 15 means 15% conversion. */
|
|
85
|
+
conversionRate: number;
|
|
86
|
+
|
|
87
|
+
/** OPTIONAL: The name of the funnel (e.g., "Purchase Funnel", "Onboarding Flow"). */
|
|
88
|
+
name?: string;
|
|
89
|
+
|
|
90
|
+
/** OPTIONAL: The likelihood that a user will attempt this funnel vs others. Default: 1 */
|
|
91
|
+
weight?: number;
|
|
92
|
+
|
|
93
|
+
/** OPTIONAL: If true, this is an initial user experience funnel (e.g., onboarding). */
|
|
94
|
+
isFirstFunnel?: boolean;
|
|
95
|
+
|
|
96
|
+
/** OPTIONAL: Average hours to complete the funnel. Default: 1 */
|
|
97
|
+
timeToConvert?: number;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* OPTIONAL: How events are ordered within the funnel.
|
|
101
|
+
* - "sequential" (default): Events happen in exact order
|
|
102
|
+
* - "random": Events can happen in any order
|
|
103
|
+
* - "first-fixed": First event is fixed, rest are random
|
|
104
|
+
* - "last-fixed": Last event is fixed, rest are random
|
|
105
|
+
* - "first-and-last-fixed": First and last are fixed, middle is random
|
|
106
|
+
*/
|
|
107
|
+
order?: "sequential" | "random" | "first-fixed" | "last-fixed" | "first-and-last-fixed";
|
|
108
|
+
|
|
109
|
+
/** OPTIONAL: Properties attached to every event in this funnel (e.g., experiment_variant, traffic_source). */
|
|
110
|
+
props?: Record<string, ValueValid>;
|
|
111
|
+
|
|
112
|
+
/** OPTIONAL: User property conditions for eligibility. Only users matching these values run this funnel. */
|
|
113
|
+
conditions?: Record<string, ValueValid>;
|
|
114
|
+
|
|
115
|
+
/** OPTIONAL: If true, generates 3 variants with different conversion rates for A/B testing analysis. */
|
|
116
|
+
experiment?: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Defines a "Slowly Changing Dimension" - a property of a user or group
|
|
122
|
+
* that changes periodically over time (e.g., subscription plan, user role).
|
|
123
|
+
*
|
|
124
|
+
* ONLY include SCDs when properties explicitly need to change over time.
|
|
125
|
+
* For static properties, just use userProps or groupProps.
|
|
126
|
+
*/
|
|
127
|
+
interface SCDProp {
|
|
128
|
+
/** OPTIONAL: The entity type - 'user' or a group key like 'company_id'. Default: 'user' */
|
|
129
|
+
type?: "user" | string;
|
|
130
|
+
|
|
131
|
+
/** REQUIRED: How often this property can change. */
|
|
132
|
+
frequency: "day" | "week" | "month" | "year";
|
|
133
|
+
|
|
134
|
+
/** REQUIRED: Possible values for this property. */
|
|
135
|
+
values: ValueValid;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* REQUIRED: When changes occur.
|
|
139
|
+
* - "fixed": Changes occur exactly on the frequency interval
|
|
140
|
+
* - "fuzzy": Changes occur randomly around the interval
|
|
141
|
+
*/
|
|
142
|
+
timing: "fixed" | "fuzzy";
|
|
143
|
+
|
|
144
|
+
/** OPTIONAL: Maximum number of times this property can change per entity. Default: 100 */
|
|
145
|
+
max?: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Defines an event attributed to a group on a regular schedule.
|
|
151
|
+
* Example: monthly subscription charges, weekly reports, etc.
|
|
152
|
+
*
|
|
153
|
+
* This is rarely needed - only use for B2B scenarios with recurring group-level events.
|
|
154
|
+
*/
|
|
155
|
+
interface GroupEventConfig {
|
|
156
|
+
/** REQUIRED: The name of the event. */
|
|
157
|
+
event: string;
|
|
158
|
+
|
|
159
|
+
/** REQUIRED: How often the event occurs (in days). e.g., 30 for monthly. */
|
|
160
|
+
frequency: number;
|
|
161
|
+
|
|
162
|
+
/** REQUIRED: The group key this event belongs to (e.g., "company_id"). */
|
|
163
|
+
group_key: string;
|
|
164
|
+
|
|
165
|
+
/** OPTIONAL: If true, a random user in the group is also attributed to the event. */
|
|
166
|
+
attribute_to_user?: boolean;
|
|
167
|
+
|
|
168
|
+
/** OPTIONAL: Properties for this event. */
|
|
169
|
+
properties?: Record<string, ValueValid>;
|
|
170
|
+
|
|
171
|
+
/** OPTIONAL: Relative frequency of this event. */
|
|
172
|
+
weight?: number;
|
|
173
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimeSoup preset configurations
|
|
3
|
+
* Each preset defines time distribution parameters that produce distinct patterns.
|
|
4
|
+
*
|
|
5
|
+
* Parameters:
|
|
6
|
+
* - peaks(numDays): function returning number of Gaussian clusters
|
|
7
|
+
* - deviation: controls peak width (higher = tighter)
|
|
8
|
+
* - mean: offset from chunk center (0 = centered)
|
|
9
|
+
* - dayOfWeekWeights: 7-element array [Sun..Sat], max=1.0, null to disable
|
|
10
|
+
* - hourOfDayWeights: 24-element array [0h..23h UTC], max=1.0, null to disable
|
|
11
|
+
*
|
|
12
|
+
* Some presets also suggest bornRecentBias and percentUsersBornInDataset,
|
|
13
|
+
* but those are top-level dungeon config — presets only set them if not already specified.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Real-world Mixpanel DOW pattern: weekday-heavy, Saturday valley
|
|
17
|
+
export const REAL_DOW = [0.637, 1.0, 0.999, 0.998, 0.966, 0.802, 0.528];
|
|
18
|
+
|
|
19
|
+
// Real-world Mixpanel HOD pattern: early-morning peak (UTC), afternoon valley
|
|
20
|
+
export const REAL_HOD = [
|
|
21
|
+
0.949, 0.992, 0.998, 0.946, 0.895, 0.938, 1.0, 0.997,
|
|
22
|
+
0.938, 0.894, 0.827, 0.786, 0.726, 0.699, 0.688, 0.643,
|
|
23
|
+
0.584, 0.574, 0.554, 0.576, 0.604, 0.655, 0.722, 0.816
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Flat weights (no cyclical pattern)
|
|
27
|
+
export const FLAT_DOW = [1, 1, 1, 1, 1, 1, 1];
|
|
28
|
+
export const FLAT_HOD = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
|
|
29
|
+
|
|
30
|
+
/** @type {Record<string, {peaks: (numDays: number) => number, deviation: number, mean: number, dayOfWeekWeights: number[]|null, hourOfDayWeights: number[]|null, bornRecentBias?: number, percentUsersBornInDataset?: number}>} */
|
|
31
|
+
export const SOUP_PRESETS = {
|
|
32
|
+
/**
|
|
33
|
+
* steady — Mature SaaS / Stable Product
|
|
34
|
+
* Nearly flat day-over-day, slight weekly pattern, minimal growth trend.
|
|
35
|
+
*/
|
|
36
|
+
steady: {
|
|
37
|
+
peaks: (numDays) => Math.max(5, numDays * 2),
|
|
38
|
+
deviation: 1.5,
|
|
39
|
+
mean: 0,
|
|
40
|
+
dayOfWeekWeights: REAL_DOW,
|
|
41
|
+
hourOfDayWeights: REAL_HOD,
|
|
42
|
+
bornRecentBias: 0.1,
|
|
43
|
+
percentUsersBornInDataset: 10,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* growth — Growing Startup (DEFAULT)
|
|
48
|
+
* Gradual uptrend with visible weekly peaks. This is the default behavior.
|
|
49
|
+
*/
|
|
50
|
+
growth: {
|
|
51
|
+
peaks: (numDays) => Math.max(5, numDays * 2),
|
|
52
|
+
deviation: 2,
|
|
53
|
+
mean: 0,
|
|
54
|
+
dayOfWeekWeights: REAL_DOW,
|
|
55
|
+
hourOfDayWeights: REAL_HOD,
|
|
56
|
+
bornRecentBias: 0.3,
|
|
57
|
+
percentUsersBornInDataset: 15,
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* spiky — Event-Driven / Bursty
|
|
62
|
+
* Clear peaks and valleys, dramatic variation. Fewer Gaussian clusters + tight deviation.
|
|
63
|
+
*/
|
|
64
|
+
spiky: {
|
|
65
|
+
peaks: (numDays) => Math.max(5, Math.ceil(numDays / 10)),
|
|
66
|
+
deviation: 3.5,
|
|
67
|
+
mean: 0,
|
|
68
|
+
dayOfWeekWeights: REAL_DOW,
|
|
69
|
+
hourOfDayWeights: REAL_HOD,
|
|
70
|
+
bornRecentBias: 0.3,
|
|
71
|
+
percentUsersBornInDataset: 20,
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* seasonal — Strong Cyclical Patterns
|
|
76
|
+
* 3-4 major waves across the dataset. Very few peaks create dramatic macro trends.
|
|
77
|
+
*/
|
|
78
|
+
seasonal: {
|
|
79
|
+
peaks: () => 4,
|
|
80
|
+
deviation: 2.5,
|
|
81
|
+
mean: 0,
|
|
82
|
+
dayOfWeekWeights: REAL_DOW,
|
|
83
|
+
hourOfDayWeights: REAL_HOD,
|
|
84
|
+
bornRecentBias: 0.2,
|
|
85
|
+
percentUsersBornInDataset: 25,
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* global — Distributed Users Across Timezones
|
|
90
|
+
* Very flat hourly + daily distribution. No cyclical patterns.
|
|
91
|
+
*/
|
|
92
|
+
global: {
|
|
93
|
+
peaks: (numDays) => Math.max(5, numDays * 2),
|
|
94
|
+
deviation: 1,
|
|
95
|
+
mean: 0,
|
|
96
|
+
dayOfWeekWeights: FLAT_DOW,
|
|
97
|
+
hourOfDayWeights: FLAT_HOD,
|
|
98
|
+
bornRecentBias: 0,
|
|
99
|
+
percentUsersBornInDataset: 10,
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* churny — High Churn / Declining Product
|
|
104
|
+
* Flat distribution (no growth trend). All users pre-exist the dataset,
|
|
105
|
+
* so there's no acceleration. Combine with an "everything" hook that
|
|
106
|
+
* filters late events to create a true declining shape.
|
|
107
|
+
*/
|
|
108
|
+
churny: {
|
|
109
|
+
peaks: (numDays) => Math.max(5, numDays * 2),
|
|
110
|
+
deviation: 2,
|
|
111
|
+
mean: 0,
|
|
112
|
+
dayOfWeekWeights: REAL_DOW,
|
|
113
|
+
hourOfDayWeights: REAL_HOD,
|
|
114
|
+
bornRecentBias: 0,
|
|
115
|
+
percentUsersBornInDataset: 5,
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* chaotic — Unpredictable / Irregular Patterns
|
|
120
|
+
* Few peaks + very tight clustering = dramatic bursts separated by quiet stretches.
|
|
121
|
+
*/
|
|
122
|
+
chaotic: {
|
|
123
|
+
peaks: (numDays) => Math.max(3, Math.ceil(numDays / 20)),
|
|
124
|
+
deviation: 4,
|
|
125
|
+
mean: 0,
|
|
126
|
+
dayOfWeekWeights: REAL_DOW,
|
|
127
|
+
hourOfDayWeights: REAL_HOD,
|
|
128
|
+
bornRecentBias: 0.5,
|
|
129
|
+
percentUsersBornInDataset: 40,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** @type {string[]} */
|
|
134
|
+
export const PRESET_NAMES = Object.keys(SOUP_PRESETS);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Resolves a soup config — handles string presets, preset+overrides, and raw objects.
|
|
138
|
+
* @param {string | object} soup - Soup config from dungeon
|
|
139
|
+
* @param {number} numDays - Number of days in the dataset
|
|
140
|
+
* @returns {{ soup: object, suggestedBornRecentBias?: number, suggestedPercentUsersBornInDataset?: number }}
|
|
141
|
+
*/
|
|
142
|
+
export function resolveSoup(soup, numDays) {
|
|
143
|
+
if (!soup) return { soup: {} };
|
|
144
|
+
|
|
145
|
+
// String preset: "growth", "spiky", etc.
|
|
146
|
+
if (typeof soup === 'string') {
|
|
147
|
+
const preset = SOUP_PRESETS[soup];
|
|
148
|
+
if (!preset) {
|
|
149
|
+
throw new Error(`Unknown soup preset: "${soup}". Valid presets: ${PRESET_NAMES.join(', ')}`);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
soup: {
|
|
153
|
+
peaks: preset.peaks(numDays),
|
|
154
|
+
deviation: preset.deviation,
|
|
155
|
+
mean: preset.mean,
|
|
156
|
+
dayOfWeekWeights: preset.dayOfWeekWeights,
|
|
157
|
+
hourOfDayWeights: preset.hourOfDayWeights,
|
|
158
|
+
},
|
|
159
|
+
suggestedBornRecentBias: preset.bornRecentBias,
|
|
160
|
+
suggestedPercentUsersBornInDataset: preset.percentUsersBornInDataset,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Object with preset key: { preset: "growth", deviation: 3 }
|
|
165
|
+
if (typeof soup === 'object' && soup.preset) {
|
|
166
|
+
const preset = SOUP_PRESETS[soup.preset];
|
|
167
|
+
if (!preset) {
|
|
168
|
+
throw new Error(`Unknown soup preset: "${soup.preset}". Valid presets: ${PRESET_NAMES.join(', ')}`);
|
|
169
|
+
}
|
|
170
|
+
const base = {
|
|
171
|
+
peaks: preset.peaks(numDays),
|
|
172
|
+
deviation: preset.deviation,
|
|
173
|
+
mean: preset.mean,
|
|
174
|
+
dayOfWeekWeights: preset.dayOfWeekWeights,
|
|
175
|
+
hourOfDayWeights: preset.hourOfDayWeights,
|
|
176
|
+
};
|
|
177
|
+
// Apply overrides (excluding the 'preset' key itself)
|
|
178
|
+
const { preset: _, ...overrides } = soup;
|
|
179
|
+
return {
|
|
180
|
+
soup: { ...base, ...overrides },
|
|
181
|
+
suggestedBornRecentBias: preset.bornRecentBias,
|
|
182
|
+
suggestedPercentUsersBornInDataset: preset.percentUsersBornInDataset,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Raw object: { peaks: 10, deviation: 2 } — pass through unchanged
|
|
187
|
+
return { soup };
|
|
188
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of valid functions that can be called in dungeon configurations
|
|
3
|
+
* This replaces the old string-based function parsing with a clean JSON structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { dataLogger as logger } from './logger.js';
|
|
7
|
+
|
|
8
|
+
export const FUNCTION_REGISTRY = {
|
|
9
|
+
// Utility functions from utils.js
|
|
10
|
+
weighNumRange: {
|
|
11
|
+
minArgs: 2,
|
|
12
|
+
maxArgs: 3,
|
|
13
|
+
description: 'Generate weighted random number in range'
|
|
14
|
+
},
|
|
15
|
+
range: {
|
|
16
|
+
minArgs: 2,
|
|
17
|
+
maxArgs: 2,
|
|
18
|
+
description: 'Generate array of numbers in range'
|
|
19
|
+
},
|
|
20
|
+
date: {
|
|
21
|
+
minArgs: 1,
|
|
22
|
+
maxArgs: 3,
|
|
23
|
+
description: 'Generate date string'
|
|
24
|
+
},
|
|
25
|
+
choose: {
|
|
26
|
+
minArgs: 1,
|
|
27
|
+
maxArgs: 1,
|
|
28
|
+
description: 'Choose from array'
|
|
29
|
+
},
|
|
30
|
+
integer: {
|
|
31
|
+
minArgs: 1,
|
|
32
|
+
maxArgs: 2,
|
|
33
|
+
description: 'Generate random integer'
|
|
34
|
+
},
|
|
35
|
+
exhaust: {
|
|
36
|
+
minArgs: 1,
|
|
37
|
+
maxArgs: 1,
|
|
38
|
+
description: 'Exhaust values from array'
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Additional utility functions
|
|
42
|
+
maybe: {
|
|
43
|
+
minArgs: 1,
|
|
44
|
+
maxArgs: 2,
|
|
45
|
+
description: 'Return value or null based on probability'
|
|
46
|
+
},
|
|
47
|
+
takeSome: {
|
|
48
|
+
minArgs: 1,
|
|
49
|
+
maxArgs: 3,
|
|
50
|
+
description: 'Take random subset from array'
|
|
51
|
+
},
|
|
52
|
+
randomElement: {
|
|
53
|
+
minArgs: 1,
|
|
54
|
+
maxArgs: 1,
|
|
55
|
+
description: 'Return random element from array'
|
|
56
|
+
},
|
|
57
|
+
randomInt: {
|
|
58
|
+
minArgs: 2,
|
|
59
|
+
maxArgs: 2,
|
|
60
|
+
description: 'Generate random integer'
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Text generation functions
|
|
64
|
+
createTextGenerator: {
|
|
65
|
+
minArgs: 1,
|
|
66
|
+
maxArgs: 1,
|
|
67
|
+
description: 'Create text generator with config'
|
|
68
|
+
},
|
|
69
|
+
generateBatch: {
|
|
70
|
+
minArgs: 2,
|
|
71
|
+
maxArgs: 2,
|
|
72
|
+
description: 'Generate batch of text'
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Chance.js functions (using dot notation)
|
|
76
|
+
'chance.word': {
|
|
77
|
+
minArgs: 0,
|
|
78
|
+
maxArgs: 1,
|
|
79
|
+
description: 'Generate random word'
|
|
80
|
+
},
|
|
81
|
+
'chance.sentence': {
|
|
82
|
+
minArgs: 0,
|
|
83
|
+
maxArgs: 1,
|
|
84
|
+
description: 'Generate random sentence'
|
|
85
|
+
},
|
|
86
|
+
'chance.paragraph': {
|
|
87
|
+
minArgs: 0,
|
|
88
|
+
maxArgs: 1,
|
|
89
|
+
description: 'Generate random paragraph'
|
|
90
|
+
},
|
|
91
|
+
'chance.name': {
|
|
92
|
+
minArgs: 0,
|
|
93
|
+
maxArgs: 1,
|
|
94
|
+
description: 'Generate random name'
|
|
95
|
+
},
|
|
96
|
+
'chance.first': {
|
|
97
|
+
minArgs: 0,
|
|
98
|
+
maxArgs: 1,
|
|
99
|
+
description: 'Generate random first name'
|
|
100
|
+
},
|
|
101
|
+
'chance.last': {
|
|
102
|
+
minArgs: 0,
|
|
103
|
+
maxArgs: 1,
|
|
104
|
+
description: 'Generate random last name'
|
|
105
|
+
},
|
|
106
|
+
'chance.email': {
|
|
107
|
+
minArgs: 0,
|
|
108
|
+
maxArgs: 1,
|
|
109
|
+
description: 'Generate random email'
|
|
110
|
+
},
|
|
111
|
+
'chance.company': {
|
|
112
|
+
minArgs: 0,
|
|
113
|
+
maxArgs: 0,
|
|
114
|
+
description: 'Generate random company name'
|
|
115
|
+
},
|
|
116
|
+
'chance.profession': {
|
|
117
|
+
minArgs: 0,
|
|
118
|
+
maxArgs: 0,
|
|
119
|
+
description: 'Generate random profession'
|
|
120
|
+
},
|
|
121
|
+
'chance.industry': {
|
|
122
|
+
minArgs: 0,
|
|
123
|
+
maxArgs: 0,
|
|
124
|
+
description: 'Generate random industry'
|
|
125
|
+
},
|
|
126
|
+
'chance.country': {
|
|
127
|
+
minArgs: 0,
|
|
128
|
+
maxArgs: 1,
|
|
129
|
+
description: 'Generate random country'
|
|
130
|
+
},
|
|
131
|
+
'chance.city': {
|
|
132
|
+
minArgs: 0,
|
|
133
|
+
maxArgs: 0,
|
|
134
|
+
description: 'Generate random city'
|
|
135
|
+
},
|
|
136
|
+
'chance.state': {
|
|
137
|
+
minArgs: 0,
|
|
138
|
+
maxArgs: 1,
|
|
139
|
+
description: 'Generate random state'
|
|
140
|
+
},
|
|
141
|
+
'chance.address': {
|
|
142
|
+
minArgs: 0,
|
|
143
|
+
maxArgs: 1,
|
|
144
|
+
description: 'Generate random address'
|
|
145
|
+
},
|
|
146
|
+
'chance.phone': {
|
|
147
|
+
minArgs: 0,
|
|
148
|
+
maxArgs: 1,
|
|
149
|
+
description: 'Generate random phone number'
|
|
150
|
+
},
|
|
151
|
+
'chance.url': {
|
|
152
|
+
minArgs: 0,
|
|
153
|
+
maxArgs: 1,
|
|
154
|
+
description: 'Generate random URL'
|
|
155
|
+
},
|
|
156
|
+
'chance.domain': {
|
|
157
|
+
minArgs: 0,
|
|
158
|
+
maxArgs: 1,
|
|
159
|
+
description: 'Generate random domain'
|
|
160
|
+
},
|
|
161
|
+
'chance.ip': {
|
|
162
|
+
minArgs: 0,
|
|
163
|
+
maxArgs: 0,
|
|
164
|
+
description: 'Generate random IP address'
|
|
165
|
+
},
|
|
166
|
+
'chance.guid': {
|
|
167
|
+
minArgs: 0,
|
|
168
|
+
maxArgs: 1,
|
|
169
|
+
description: 'Generate random GUID'
|
|
170
|
+
},
|
|
171
|
+
'chance.hash': {
|
|
172
|
+
minArgs: 0,
|
|
173
|
+
maxArgs: 1,
|
|
174
|
+
description: 'Generate random hash'
|
|
175
|
+
},
|
|
176
|
+
'chance.integer': {
|
|
177
|
+
minArgs: 0,
|
|
178
|
+
maxArgs: 1,
|
|
179
|
+
description: 'Generate random integer with options'
|
|
180
|
+
},
|
|
181
|
+
'chance.floating': {
|
|
182
|
+
minArgs: 0,
|
|
183
|
+
maxArgs: 1,
|
|
184
|
+
description: 'Generate random float'
|
|
185
|
+
},
|
|
186
|
+
'chance.bool': {
|
|
187
|
+
minArgs: 0,
|
|
188
|
+
maxArgs: 1,
|
|
189
|
+
description: 'Generate random boolean'
|
|
190
|
+
},
|
|
191
|
+
'chance.character': {
|
|
192
|
+
minArgs: 0,
|
|
193
|
+
maxArgs: 1,
|
|
194
|
+
description: 'Generate random character'
|
|
195
|
+
},
|
|
196
|
+
'chance.string': {
|
|
197
|
+
minArgs: 0,
|
|
198
|
+
maxArgs: 1,
|
|
199
|
+
description: 'Generate random string'
|
|
200
|
+
},
|
|
201
|
+
'chance.pick': {
|
|
202
|
+
minArgs: 1,
|
|
203
|
+
maxArgs: 2,
|
|
204
|
+
description: 'Pick random element from array'
|
|
205
|
+
},
|
|
206
|
+
'chance.pickone': {
|
|
207
|
+
minArgs: 1,
|
|
208
|
+
maxArgs: 1,
|
|
209
|
+
description: 'Pick one random element from array'
|
|
210
|
+
},
|
|
211
|
+
'chance.pickset': {
|
|
212
|
+
minArgs: 2,
|
|
213
|
+
maxArgs: 2,
|
|
214
|
+
description: 'Pick set of random elements from array'
|
|
215
|
+
},
|
|
216
|
+
'chance.cc': {
|
|
217
|
+
minArgs: 0,
|
|
218
|
+
maxArgs: 1,
|
|
219
|
+
description: 'Generate credit card number'
|
|
220
|
+
},
|
|
221
|
+
'chance.android_id': {
|
|
222
|
+
minArgs: 0,
|
|
223
|
+
maxArgs: 0,
|
|
224
|
+
description: 'Generate Android device ID'
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
// Commonly used utility functions from dungeons
|
|
228
|
+
pickAWinner: {
|
|
229
|
+
minArgs: 1,
|
|
230
|
+
maxArgs: 2,
|
|
231
|
+
description: 'Pick from array with power-law weighting (most common values first)'
|
|
232
|
+
},
|
|
233
|
+
weighChoices: {
|
|
234
|
+
minArgs: 1,
|
|
235
|
+
maxArgs: 1,
|
|
236
|
+
description: 'Weight choices by frequency in array (more duplicates = higher weight)'
|
|
237
|
+
},
|
|
238
|
+
decimal: {
|
|
239
|
+
minArgs: 0,
|
|
240
|
+
maxArgs: 3,
|
|
241
|
+
description: 'Generate random decimal (min, max, fixed decimal places)'
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// Special function for arrow functions
|
|
245
|
+
arrow: {
|
|
246
|
+
minArgs: 1,
|
|
247
|
+
maxArgs: 1,
|
|
248
|
+
description: 'Raw arrow function with body',
|
|
249
|
+
special: true
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Validate a function call structure
|
|
255
|
+
* @param {Object} funcCall - The function call object with functionName and args
|
|
256
|
+
* @returns {boolean} - Whether the function call is valid
|
|
257
|
+
*/
|
|
258
|
+
export function validateFunctionCall(funcCall) {
|
|
259
|
+
if (!funcCall || typeof funcCall !== 'object') {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const { functionName, args, body } = funcCall;
|
|
264
|
+
|
|
265
|
+
if (!functionName || typeof functionName !== 'string') {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Special handling for arrow functions
|
|
270
|
+
if (functionName === 'arrow') {
|
|
271
|
+
return typeof body === 'string' && body.length > 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const funcDef = FUNCTION_REGISTRY[functionName];
|
|
275
|
+
if (!funcDef) {
|
|
276
|
+
logger.warn({ functionName }, `Unknown function: ${functionName}`);
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check args
|
|
281
|
+
if (!Array.isArray(args)) {
|
|
282
|
+
if (funcDef.minArgs === 0 && !args) {
|
|
283
|
+
return true; // No args required and none provided
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (args.length < funcDef.minArgs || args.length > funcDef.maxArgs) {
|
|
289
|
+
logger.warn({ functionName, expected: `${funcDef.minArgs}-${funcDef.maxArgs}`, actual: args.length }, `Function ${functionName} expects ${funcDef.minArgs}-${funcDef.maxArgs} args, got ${args.length}`);
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get list of all valid function names
|
|
298
|
+
* @returns {string[]} Array of function names
|
|
299
|
+
*/
|
|
300
|
+
export function getValidFunctionNames() {
|
|
301
|
+
return Object.keys(FUNCTION_REGISTRY);
|
|
302
|
+
}
|