@mythxengine/worlds 0.1.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/LICENSE +21 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/schema/generation.d.ts +9288 -0
- package/dist/schema/generation.d.ts.map +1 -0
- package/dist/schema/generation.js +341 -0
- package/dist/schema/generation.js.map +1 -0
- package/dist/schema/index.d.ts +7 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +7 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/rules.d.ts +24925 -0
- package/dist/schema/rules.d.ts.map +1 -0
- package/dist/schema/rules.js +308 -0
- package/dist/schema/rules.js.map +1 -0
- package/dist/schema/world.d.ts +16451 -0
- package/dist/schema/world.d.ts.map +1 -0
- package/dist/schema/world.js +822 -0
- package/dist/schema/world.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* World Schema
|
|
3
|
+
*
|
|
4
|
+
* Defines the structure for complete world content packs.
|
|
5
|
+
* Adapted from content-creation-system for system-agnostic RPG generation.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { WorldRulesConfigSchema } from "./rules.js";
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// WORLD METADATA
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* World aesthetic configuration (for AI guidance)
|
|
14
|
+
*/
|
|
15
|
+
export const WorldAestheticSchema = z.object({
|
|
16
|
+
/** Visual style description */
|
|
17
|
+
visualStyle: z.string(),
|
|
18
|
+
/** Emotional tone */
|
|
19
|
+
tone: z.string(),
|
|
20
|
+
/** Core themes */
|
|
21
|
+
themes: z.array(z.string()),
|
|
22
|
+
/** Creative inspirations */
|
|
23
|
+
inspirations: z.array(z.string()),
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* World gameplay settings
|
|
27
|
+
*/
|
|
28
|
+
export const WorldSettingsSchema = z.object({
|
|
29
|
+
/** How deadly is combat? */
|
|
30
|
+
lethality: z.enum(["low", "medium", "high", "brutal"]),
|
|
31
|
+
/** How common is magic? */
|
|
32
|
+
magicLevel: z.enum(["none", "rare", "common", "high"]),
|
|
33
|
+
/** Technology era */
|
|
34
|
+
technologyLevel: z.enum(["primitive", "medieval", "renaissance", "industrial", "modern", "futuristic"]),
|
|
35
|
+
/** How present is the supernatural? */
|
|
36
|
+
supernaturalPresence: z.enum(["subtle", "common", "pervasive"]),
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* World content counts (for validation)
|
|
40
|
+
*/
|
|
41
|
+
export const WorldContentCountsSchema = z.object({
|
|
42
|
+
archetypes: z.number(),
|
|
43
|
+
items: z.number(),
|
|
44
|
+
monsters: z.number(),
|
|
45
|
+
encounters: z.number(),
|
|
46
|
+
conditions: z.number(),
|
|
47
|
+
locations: z.number(),
|
|
48
|
+
npcs: z.number().optional(),
|
|
49
|
+
factions: z.number().optional(),
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* World metadata
|
|
53
|
+
*/
|
|
54
|
+
export const WorldMetaSchema = z.object({
|
|
55
|
+
/** Unique identifier */
|
|
56
|
+
id: z.string(),
|
|
57
|
+
/** Display name */
|
|
58
|
+
name: z.string(),
|
|
59
|
+
/** One-line hook */
|
|
60
|
+
tagline: z.string(),
|
|
61
|
+
/** Semantic version */
|
|
62
|
+
version: z.string(),
|
|
63
|
+
/** Aesthetic guidance */
|
|
64
|
+
aesthetic: WorldAestheticSchema,
|
|
65
|
+
/** Gameplay tuning */
|
|
66
|
+
settings: WorldSettingsSchema,
|
|
67
|
+
/** Content counts */
|
|
68
|
+
contentCounts: WorldContentCountsSchema,
|
|
69
|
+
});
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// ABILITIES (re-export for convenience)
|
|
72
|
+
// ============================================================================
|
|
73
|
+
export const AbilitiesSchema = z.object({
|
|
74
|
+
STR: z.number().min(-3).max(3),
|
|
75
|
+
AGI: z.number().min(-3).max(3),
|
|
76
|
+
WIT: z.number().min(-3).max(3),
|
|
77
|
+
CON: z.number().min(-3).max(3),
|
|
78
|
+
});
|
|
79
|
+
export const AbilityNameSchema = z.enum(["STR", "AGI", "WIT", "CON"]);
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// ARCHETYPE
|
|
82
|
+
// ============================================================================
|
|
83
|
+
/**
|
|
84
|
+
* Starting stats for an archetype
|
|
85
|
+
*/
|
|
86
|
+
export const ArchetypeStartingStatsSchema = z.object({
|
|
87
|
+
abilities: AbilitiesSchema,
|
|
88
|
+
hp: z.number().min(1),
|
|
89
|
+
maxHp: z.number().min(1),
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* Archetype feature
|
|
93
|
+
*/
|
|
94
|
+
export const ArchetypeFeatureSchema = z.object({
|
|
95
|
+
id: z.string(),
|
|
96
|
+
name: z.string(),
|
|
97
|
+
description: z.string(),
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* A playable archetype in a world
|
|
101
|
+
*/
|
|
102
|
+
export const WorldArchetypeSchema = z.object({
|
|
103
|
+
id: z.string(),
|
|
104
|
+
name: z.string(),
|
|
105
|
+
tagline: z.string(),
|
|
106
|
+
description: z.string(),
|
|
107
|
+
/** Starting stats */
|
|
108
|
+
starting: ArchetypeStartingStatsSchema,
|
|
109
|
+
/** Starting equipment (item IDs) */
|
|
110
|
+
startingItems: z.array(z.string()),
|
|
111
|
+
/** Class features */
|
|
112
|
+
features: z.array(ArchetypeFeatureSchema),
|
|
113
|
+
/** Playstyle guidance */
|
|
114
|
+
playstyle: z.string(),
|
|
115
|
+
/** Background lore */
|
|
116
|
+
background: z.string(),
|
|
117
|
+
/** Flavor text */
|
|
118
|
+
flavor: z.string(),
|
|
119
|
+
});
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// ITEM
|
|
122
|
+
// ============================================================================
|
|
123
|
+
/**
|
|
124
|
+
* Item kind
|
|
125
|
+
*/
|
|
126
|
+
export const ItemKindSchema = z.enum(["weapon", "armor", "consumable", "special", "misc"]);
|
|
127
|
+
/**
|
|
128
|
+
* Weapon properties
|
|
129
|
+
*/
|
|
130
|
+
export const WeaponPropsSchema = z.object({
|
|
131
|
+
damage: z.string(),
|
|
132
|
+
ability: AbilityNameSchema,
|
|
133
|
+
properties: z.array(z.string()).optional(),
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* Armor properties
|
|
137
|
+
*/
|
|
138
|
+
export const ArmorPropsSchema = z.object({
|
|
139
|
+
damageReduction: z.number(),
|
|
140
|
+
properties: z.array(z.string()).optional(),
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* Consumable properties
|
|
144
|
+
*/
|
|
145
|
+
export const ConsumablePropsSchema = z.object({
|
|
146
|
+
uses: z.number(),
|
|
147
|
+
effect: z.string(),
|
|
148
|
+
effectDescription: z.string(),
|
|
149
|
+
});
|
|
150
|
+
/**
|
|
151
|
+
* An item in a world
|
|
152
|
+
*/
|
|
153
|
+
export const WorldItemSchema = z.object({
|
|
154
|
+
id: z.string(),
|
|
155
|
+
name: z.string(),
|
|
156
|
+
kind: ItemKindSchema,
|
|
157
|
+
description: z.string(),
|
|
158
|
+
/** Thematic flavor text */
|
|
159
|
+
flavor: z.string(),
|
|
160
|
+
/** Tags for filtering */
|
|
161
|
+
tags: z.array(z.string()),
|
|
162
|
+
/** Inventory slots consumed */
|
|
163
|
+
slots: z.number(),
|
|
164
|
+
/** Weapon properties (if weapon) */
|
|
165
|
+
weapon: WeaponPropsSchema.optional(),
|
|
166
|
+
/** Armor properties (if armor) */
|
|
167
|
+
armor: ArmorPropsSchema.optional(),
|
|
168
|
+
/** Consumable properties (if consumable) */
|
|
169
|
+
consumable: ConsumablePropsSchema.optional(),
|
|
170
|
+
});
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// MONSTER
|
|
173
|
+
// ============================================================================
|
|
174
|
+
/**
|
|
175
|
+
* Monster morale configuration
|
|
176
|
+
*/
|
|
177
|
+
export const MonsterMoraleSchema = z.object({
|
|
178
|
+
/** Morale threshold (1-10) */
|
|
179
|
+
threshold: z.number().min(1).max(10),
|
|
180
|
+
/** When to check morale */
|
|
181
|
+
checkWhen: z.enum(["belowHalfHP", "allyDies", "firstHit", "never"]),
|
|
182
|
+
/** Auto-flee HP threshold */
|
|
183
|
+
fleesBelowHP: z.number().optional(),
|
|
184
|
+
});
|
|
185
|
+
/**
|
|
186
|
+
* Monster tactics (AI guidance)
|
|
187
|
+
*/
|
|
188
|
+
export const MonsterTacticsSchema = z.object({
|
|
189
|
+
preferredRange: z.enum(["melee", "ranged", "any"]),
|
|
190
|
+
targetPriority: z.enum(["weakest", "strongest", "nearest", "random"]),
|
|
191
|
+
specialBehavior: z.string().optional(),
|
|
192
|
+
});
|
|
193
|
+
/**
|
|
194
|
+
* Monster attack
|
|
195
|
+
*/
|
|
196
|
+
export const MonsterAttackSchema = z.object({
|
|
197
|
+
name: z.string(),
|
|
198
|
+
ability: AbilityNameSchema,
|
|
199
|
+
damage: z.string(),
|
|
200
|
+
properties: z.array(z.string()).optional(),
|
|
201
|
+
flavor: z.string(),
|
|
202
|
+
});
|
|
203
|
+
/**
|
|
204
|
+
* Threat tier for monsters
|
|
205
|
+
*/
|
|
206
|
+
export const ThreatTierSchema = z.enum(["minion", "standard", "elite", "boss"]);
|
|
207
|
+
/**
|
|
208
|
+
* A monster in a world
|
|
209
|
+
*/
|
|
210
|
+
export const WorldMonsterSchema = z.object({
|
|
211
|
+
id: z.string(),
|
|
212
|
+
name: z.string(),
|
|
213
|
+
description: z.string(),
|
|
214
|
+
/** Base stats */
|
|
215
|
+
hp: z.number().min(1),
|
|
216
|
+
armor: z.number().min(0),
|
|
217
|
+
abilities: AbilitiesSchema,
|
|
218
|
+
/** Threat tier */
|
|
219
|
+
threat: ThreatTierSchema,
|
|
220
|
+
/** Combat attacks */
|
|
221
|
+
attacks: z.array(MonsterAttackSchema),
|
|
222
|
+
/** Special abilities (condition IDs or descriptions) */
|
|
223
|
+
specialAbilities: z.array(z.string()),
|
|
224
|
+
/** Morale and fleeing */
|
|
225
|
+
morale: MonsterMoraleSchema,
|
|
226
|
+
/** Combat AI guidance */
|
|
227
|
+
tactics: MonsterTacticsSchema,
|
|
228
|
+
/** World lore */
|
|
229
|
+
lore: z.string(),
|
|
230
|
+
/** Text when encountered */
|
|
231
|
+
encounterText: z.string(),
|
|
232
|
+
/** Text when defeated */
|
|
233
|
+
deathText: z.string(),
|
|
234
|
+
});
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// ENCOUNTER
|
|
237
|
+
// ============================================================================
|
|
238
|
+
/**
|
|
239
|
+
* Encounter type
|
|
240
|
+
*/
|
|
241
|
+
export const EncounterTypeSchema = z.enum(["combat", "event", "social"]);
|
|
242
|
+
/**
|
|
243
|
+
* Combat encounter setup
|
|
244
|
+
*/
|
|
245
|
+
export const CombatSetupSchema = z.object({
|
|
246
|
+
/** Monster spawns */
|
|
247
|
+
monsters: z.array(z.object({
|
|
248
|
+
monsterId: z.string(),
|
|
249
|
+
count: z.union([z.number(), z.string()]), // number or dice expression
|
|
250
|
+
})),
|
|
251
|
+
/** Starting conditions */
|
|
252
|
+
surprise: z.enum(["none", "enemies", "party"]).optional(),
|
|
253
|
+
/** Environment */
|
|
254
|
+
environment: z.object({
|
|
255
|
+
lighting: z.enum(["bright", "dim", "dark"]),
|
|
256
|
+
terrain: z.enum(["open", "cramped", "hazardous"]),
|
|
257
|
+
hazards: z.array(z.string()).optional(),
|
|
258
|
+
}).optional(),
|
|
259
|
+
});
|
|
260
|
+
/**
|
|
261
|
+
* Event encounter choice
|
|
262
|
+
*/
|
|
263
|
+
export const EventChoiceSchema = z.object({
|
|
264
|
+
text: z.string(),
|
|
265
|
+
test: z.object({
|
|
266
|
+
ability: AbilityNameSchema,
|
|
267
|
+
difficulty: z.number(),
|
|
268
|
+
}).optional(),
|
|
269
|
+
successOutcome: z.string(),
|
|
270
|
+
failureOutcome: z.string().optional(),
|
|
271
|
+
});
|
|
272
|
+
/**
|
|
273
|
+
* Event encounter setup
|
|
274
|
+
*/
|
|
275
|
+
export const EventSetupSchema = z.object({
|
|
276
|
+
choices: z.array(EventChoiceSchema),
|
|
277
|
+
});
|
|
278
|
+
/**
|
|
279
|
+
* Social encounter setup
|
|
280
|
+
*/
|
|
281
|
+
export const SocialSetupSchema = z.object({
|
|
282
|
+
npcIds: z.array(z.string()),
|
|
283
|
+
initialAttitude: z.enum(["friendly", "neutral", "hostile"]),
|
|
284
|
+
negotiable: z.boolean(),
|
|
285
|
+
});
|
|
286
|
+
/**
|
|
287
|
+
* An encounter in a world
|
|
288
|
+
*/
|
|
289
|
+
export const WorldEncounterSchema = z.object({
|
|
290
|
+
id: z.string(),
|
|
291
|
+
name: z.string(),
|
|
292
|
+
type: EncounterTypeSchema,
|
|
293
|
+
description: z.string(),
|
|
294
|
+
/** Narrative text when encounter begins */
|
|
295
|
+
text: z.string(),
|
|
296
|
+
/** GM guidance */
|
|
297
|
+
gmGuidance: z.string(),
|
|
298
|
+
/** Possible outcomes */
|
|
299
|
+
outcomes: z.array(z.string()),
|
|
300
|
+
/** Combat setup (if combat) */
|
|
301
|
+
combat: CombatSetupSchema.optional(),
|
|
302
|
+
/** Event setup (if event) */
|
|
303
|
+
event: EventSetupSchema.optional(),
|
|
304
|
+
/** Social setup (if social) */
|
|
305
|
+
social: SocialSetupSchema.optional(),
|
|
306
|
+
});
|
|
307
|
+
// ============================================================================
|
|
308
|
+
// LOCATION
|
|
309
|
+
// ============================================================================
|
|
310
|
+
/**
|
|
311
|
+
* A location in a world
|
|
312
|
+
*/
|
|
313
|
+
export const WorldLocationSchema = z.object({
|
|
314
|
+
id: z.string(),
|
|
315
|
+
name: z.string(),
|
|
316
|
+
description: z.string(),
|
|
317
|
+
/** Type of location */
|
|
318
|
+
type: z.enum(["settlement", "dungeon", "wilderness", "landmark", "building"]),
|
|
319
|
+
/** Atmospheric details */
|
|
320
|
+
atmosphere: z.string(),
|
|
321
|
+
/** What can be found here */
|
|
322
|
+
features: z.array(z.string()),
|
|
323
|
+
/** Connected location IDs */
|
|
324
|
+
connections: z.array(z.string()),
|
|
325
|
+
/** Encounter IDs that can occur here */
|
|
326
|
+
encounters: z.array(z.string()),
|
|
327
|
+
/** NPC IDs present at this location */
|
|
328
|
+
npcs: z.array(z.string()),
|
|
329
|
+
/** Secrets or hidden content */
|
|
330
|
+
secrets: z.array(z.string()).optional(),
|
|
331
|
+
/** GM notes */
|
|
332
|
+
gmNotes: z.string().optional(),
|
|
333
|
+
});
|
|
334
|
+
// ============================================================================
|
|
335
|
+
// NPC
|
|
336
|
+
// ============================================================================
|
|
337
|
+
/**
|
|
338
|
+
* Narrative role an NPC plays in the story
|
|
339
|
+
*/
|
|
340
|
+
export const NarrativeRoleSchema = z.enum([
|
|
341
|
+
"quest_giver",
|
|
342
|
+
"ally",
|
|
343
|
+
"obstacle",
|
|
344
|
+
"information",
|
|
345
|
+
"antagonist",
|
|
346
|
+
"merchant",
|
|
347
|
+
"background",
|
|
348
|
+
]);
|
|
349
|
+
/**
|
|
350
|
+
* An NPC in a world
|
|
351
|
+
*/
|
|
352
|
+
export const WorldNPCSchema = z.object({
|
|
353
|
+
id: z.string(),
|
|
354
|
+
name: z.string(),
|
|
355
|
+
description: z.string(),
|
|
356
|
+
/** Personality traits */
|
|
357
|
+
personality: z.string(),
|
|
358
|
+
/** What drives this NPC */
|
|
359
|
+
motivation: z.string(),
|
|
360
|
+
/** Initial attitude */
|
|
361
|
+
attitude: z.enum(["friendly", "neutral", "hostile", "unknown"]),
|
|
362
|
+
/** Example dialogue or speech patterns */
|
|
363
|
+
dialogueHints: z.array(z.string()),
|
|
364
|
+
/** Role this NPC plays in the narrative */
|
|
365
|
+
narrativeRole: NarrativeRoleSchema,
|
|
366
|
+
/** Location IDs where this NPC can be found */
|
|
367
|
+
locations: z.array(z.string()).optional(),
|
|
368
|
+
/** Relationships to other NPCs/characters */
|
|
369
|
+
relationships: z.record(z.string()).optional(),
|
|
370
|
+
/** Secrets this NPC knows or hides */
|
|
371
|
+
secrets: z.array(z.string()).optional(),
|
|
372
|
+
});
|
|
373
|
+
// ============================================================================
|
|
374
|
+
// FACTION
|
|
375
|
+
// ============================================================================
|
|
376
|
+
/**
|
|
377
|
+
* A faction in a world
|
|
378
|
+
*/
|
|
379
|
+
export const WorldFactionSchema = z.object({
|
|
380
|
+
id: z.string(),
|
|
381
|
+
name: z.string(),
|
|
382
|
+
description: z.string(),
|
|
383
|
+
/** Faction goals */
|
|
384
|
+
goals: z.array(z.string()),
|
|
385
|
+
/** Faction values/principles */
|
|
386
|
+
values: z.array(z.string()),
|
|
387
|
+
/** Member NPC IDs */
|
|
388
|
+
members: z.array(z.string()),
|
|
389
|
+
/** Relationships to other factions */
|
|
390
|
+
relationships: z.record(z.enum(["allied", "neutral", "hostile", "unknown"])),
|
|
391
|
+
/** Resources or power the faction commands */
|
|
392
|
+
resources: z.array(z.string()),
|
|
393
|
+
});
|
|
394
|
+
// ============================================================================
|
|
395
|
+
// CONDITION (World-specific)
|
|
396
|
+
// ============================================================================
|
|
397
|
+
/**
|
|
398
|
+
* A condition effect in a world
|
|
399
|
+
*/
|
|
400
|
+
export const WorldConditionSchema = z.object({
|
|
401
|
+
id: z.string(),
|
|
402
|
+
name: z.string(),
|
|
403
|
+
description: z.string(),
|
|
404
|
+
duration: z.union([z.number(), z.literal("permanent"), z.literal("until_rest")]),
|
|
405
|
+
effects: z.array(z.object({
|
|
406
|
+
type: z.string(),
|
|
407
|
+
target: z.string().optional(),
|
|
408
|
+
amount: z.number().optional(),
|
|
409
|
+
description: z.string().optional(),
|
|
410
|
+
})),
|
|
411
|
+
stackable: z.boolean(),
|
|
412
|
+
});
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// NARRATIVE GUIDANCE
|
|
415
|
+
// ============================================================================
|
|
416
|
+
/**
|
|
417
|
+
* Narrative guidance for AI/GM
|
|
418
|
+
*/
|
|
419
|
+
export const NarrativeGuidanceSchema = z.object({
|
|
420
|
+
/** Example session openers */
|
|
421
|
+
openingScenes: z.array(z.string()),
|
|
422
|
+
/** Story starter hooks */
|
|
423
|
+
plotHooks: z.array(z.string()),
|
|
424
|
+
/** Common conflict types */
|
|
425
|
+
commonConflicts: z.array(z.string()),
|
|
426
|
+
/** How stories typically resolve */
|
|
427
|
+
resolutionPatterns: z.array(z.string()),
|
|
428
|
+
});
|
|
429
|
+
// ============================================================================
|
|
430
|
+
// LEAD (The Edge) - Alexandrian Node-Based Design
|
|
431
|
+
// ============================================================================
|
|
432
|
+
/**
|
|
433
|
+
* A lead connects situations by providing discoverable information.
|
|
434
|
+
* Based on The Alexandrian's node-based scenario design.
|
|
435
|
+
*/
|
|
436
|
+
export const LeadSchema = z.object({
|
|
437
|
+
/** Unique identifier: lead:source-to-target-method */
|
|
438
|
+
id: z.string(),
|
|
439
|
+
/** What the lead reveals */
|
|
440
|
+
information: z.string(),
|
|
441
|
+
/** Situation this lead points to */
|
|
442
|
+
targetSituationId: z.string(),
|
|
443
|
+
/** How the lead is discovered */
|
|
444
|
+
discovery: z.object({
|
|
445
|
+
/** Method of discovery */
|
|
446
|
+
method: z.enum([
|
|
447
|
+
"location", // Found at a specific place
|
|
448
|
+
"npc", // Given by an NPC
|
|
449
|
+
"investigation", // Requires active searching
|
|
450
|
+
"observation", // Noticed passively
|
|
451
|
+
"document", // Found in written materials
|
|
452
|
+
"consequence", // Result of another action
|
|
453
|
+
"rumor", // Heard through gossip
|
|
454
|
+
"item", // Discovered via an item
|
|
455
|
+
]),
|
|
456
|
+
/** NPC/location/item providing the lead */
|
|
457
|
+
sourceId: z.string().optional(),
|
|
458
|
+
/** Description of how to find it */
|
|
459
|
+
description: z.string(),
|
|
460
|
+
/** Optional skill test to discover */
|
|
461
|
+
test: z.object({
|
|
462
|
+
ability: AbilityNameSchema,
|
|
463
|
+
difficulty: z.number(),
|
|
464
|
+
skill: z.string().optional(),
|
|
465
|
+
}).optional(),
|
|
466
|
+
}),
|
|
467
|
+
/** How obvious the lead is */
|
|
468
|
+
prominence: z.enum([
|
|
469
|
+
"obvious", // Cannot be missed
|
|
470
|
+
"available", // Easily found with basic effort
|
|
471
|
+
"hidden", // Requires specific action or skill
|
|
472
|
+
"obscured", // Actively concealed
|
|
473
|
+
]),
|
|
474
|
+
/** Conditions for the lead to be available */
|
|
475
|
+
prerequisites: z.object({
|
|
476
|
+
/** Flags that must be set */
|
|
477
|
+
requiredFlags: z.array(z.string()).optional(),
|
|
478
|
+
/** Flags that block this lead */
|
|
479
|
+
blockedByFlags: z.array(z.string()).optional(),
|
|
480
|
+
/** Time window when available */
|
|
481
|
+
timeWindow: z.object({
|
|
482
|
+
after: z.object({ day: z.number(), hour: z.number() }).optional(),
|
|
483
|
+
before: z.object({ day: z.number(), hour: z.number() }).optional(),
|
|
484
|
+
}).optional(),
|
|
485
|
+
}).optional(),
|
|
486
|
+
/** GM notes for running this lead */
|
|
487
|
+
gmNotes: z.string().optional(),
|
|
488
|
+
});
|
|
489
|
+
// ============================================================================
|
|
490
|
+
// SITUATION CLOCK (Proactive Timeline)
|
|
491
|
+
// ============================================================================
|
|
492
|
+
/**
|
|
493
|
+
* A stage in a situation clock - what happens at each tick
|
|
494
|
+
*/
|
|
495
|
+
export const ClockStageSchema = z.object({
|
|
496
|
+
/** Unique stage ID */
|
|
497
|
+
id: z.string(),
|
|
498
|
+
/** Stage name */
|
|
499
|
+
name: z.string(),
|
|
500
|
+
/** What happens at this stage */
|
|
501
|
+
description: z.string(),
|
|
502
|
+
/** What triggers this stage */
|
|
503
|
+
trigger: z.discriminatedUnion("type", [
|
|
504
|
+
z.object({
|
|
505
|
+
type: z.literal("time"),
|
|
506
|
+
/** Minutes from clock start */
|
|
507
|
+
minutesFromStart: z.number(),
|
|
508
|
+
}),
|
|
509
|
+
z.object({
|
|
510
|
+
type: z.literal("event"),
|
|
511
|
+
/** Description of triggering event */
|
|
512
|
+
eventDescription: z.string(),
|
|
513
|
+
/** Optional flag to check */
|
|
514
|
+
flag: z.string().optional(),
|
|
515
|
+
}),
|
|
516
|
+
z.object({
|
|
517
|
+
type: z.literal("playerInaction"),
|
|
518
|
+
/** Minutes of no player engagement */
|
|
519
|
+
minutesOfInaction: z.number(),
|
|
520
|
+
}),
|
|
521
|
+
]),
|
|
522
|
+
/** What changes when this stage triggers */
|
|
523
|
+
consequences: z.object({
|
|
524
|
+
/** Flags to set */
|
|
525
|
+
setFlags: z.array(z.string()).optional(),
|
|
526
|
+
/** Flags to remove */
|
|
527
|
+
removeFlags: z.array(z.string()).optional(),
|
|
528
|
+
/** Changes to NPCs */
|
|
529
|
+
npcChanges: z.array(z.object({
|
|
530
|
+
npcId: z.string(),
|
|
531
|
+
change: z.string(),
|
|
532
|
+
})).optional(),
|
|
533
|
+
/** Changes to locations */
|
|
534
|
+
locationChanges: z.array(z.object({
|
|
535
|
+
locationId: z.string(),
|
|
536
|
+
change: z.string(),
|
|
537
|
+
})).optional(),
|
|
538
|
+
/** Changes to lead availability */
|
|
539
|
+
leadChanges: z.array(z.object({
|
|
540
|
+
leadId: z.string(),
|
|
541
|
+
available: z.boolean(),
|
|
542
|
+
})).optional(),
|
|
543
|
+
/** Narrative text for this stage */
|
|
544
|
+
narrative: z.string(),
|
|
545
|
+
}),
|
|
546
|
+
/** Can this stage be undone? */
|
|
547
|
+
reversible: z.boolean(),
|
|
548
|
+
});
|
|
549
|
+
/**
|
|
550
|
+
* A clock tracking what happens if PCs don't intervene
|
|
551
|
+
*/
|
|
552
|
+
export const SituationClockSchema = z.object({
|
|
553
|
+
/** Unique clock ID */
|
|
554
|
+
id: z.string(),
|
|
555
|
+
/** Clock name */
|
|
556
|
+
name: z.string(),
|
|
557
|
+
/** What the clock counts toward (the doom) */
|
|
558
|
+
doom: z.string(),
|
|
559
|
+
/** Stages of the clock */
|
|
560
|
+
stages: z.array(ClockStageSchema),
|
|
561
|
+
/** Current stage index (runtime state, null if not started) */
|
|
562
|
+
currentStage: z.number().nullable(),
|
|
563
|
+
/** When the clock started (runtime state) */
|
|
564
|
+
startedAt: z.object({
|
|
565
|
+
day: z.number(),
|
|
566
|
+
hour: z.number(),
|
|
567
|
+
minute: z.number(),
|
|
568
|
+
}).nullable(),
|
|
569
|
+
/** Is the clock paused? */
|
|
570
|
+
paused: z.boolean(),
|
|
571
|
+
});
|
|
572
|
+
// ============================================================================
|
|
573
|
+
// WORLD SITUATION (The Node)
|
|
574
|
+
// ============================================================================
|
|
575
|
+
/**
|
|
576
|
+
* A situation is a set of circumstances that will change without PC intervention.
|
|
577
|
+
* Based on The Alexandrian's node-based scenario design.
|
|
578
|
+
*/
|
|
579
|
+
export const WorldSituationSchema = z.object({
|
|
580
|
+
// Core identification
|
|
581
|
+
/** Unique identifier: situation:slug-name */
|
|
582
|
+
id: z.string(),
|
|
583
|
+
/** Situation name */
|
|
584
|
+
name: z.string(),
|
|
585
|
+
/** Description of the situation */
|
|
586
|
+
description: z.string(),
|
|
587
|
+
/** Current status */
|
|
588
|
+
status: z.enum(["dormant", "brewing", "active", "resolved", "failed"]),
|
|
589
|
+
// Stakes - what's at risk
|
|
590
|
+
stakes: z.object({
|
|
591
|
+
/** What could go wrong */
|
|
592
|
+
risks: z.array(z.string()),
|
|
593
|
+
/** What could be gained */
|
|
594
|
+
opportunities: z.array(z.string()),
|
|
595
|
+
/** Who suffers most if ignored */
|
|
596
|
+
primaryVictim: z.string().optional(),
|
|
597
|
+
/** What happens if PCs don't act */
|
|
598
|
+
ifIgnored: z.string(),
|
|
599
|
+
}),
|
|
600
|
+
// Actors - who's involved
|
|
601
|
+
actors: z.array(z.object({
|
|
602
|
+
/** NPC or faction ID */
|
|
603
|
+
entityId: z.string(),
|
|
604
|
+
/** What they want from this situation */
|
|
605
|
+
agenda: z.string(),
|
|
606
|
+
/** What power they have */
|
|
607
|
+
leverage: z.string(),
|
|
608
|
+
/** What they do if not opposed */
|
|
609
|
+
defaultAction: z.string(),
|
|
610
|
+
/** Is this the main antagonist? */
|
|
611
|
+
isPrimaryAntagonist: z.boolean().optional(),
|
|
612
|
+
})),
|
|
613
|
+
// Locations - where it happens
|
|
614
|
+
locations: z.object({
|
|
615
|
+
/** Primary location IDs */
|
|
616
|
+
primary: z.array(z.string()),
|
|
617
|
+
/** Related location IDs */
|
|
618
|
+
related: z.array(z.string()).optional(),
|
|
619
|
+
/** Location-specific context */
|
|
620
|
+
details: z.record(z.string()).optional(),
|
|
621
|
+
}),
|
|
622
|
+
// Clock - what happens over time
|
|
623
|
+
clock: SituationClockSchema.optional(),
|
|
624
|
+
// Leads - outgoing edges to other situations
|
|
625
|
+
outgoingLeads: z.array(LeadSchema),
|
|
626
|
+
// Entry points - how PCs discover this situation
|
|
627
|
+
entryPoints: z.object({
|
|
628
|
+
/** Lead IDs from other situations pointing here */
|
|
629
|
+
incomingLeadIds: z.array(z.string()),
|
|
630
|
+
/** Ways to discover this directly */
|
|
631
|
+
directDiscovery: z.array(z.object({
|
|
632
|
+
method: z.string(),
|
|
633
|
+
description: z.string(),
|
|
634
|
+
locationId: z.string().optional(),
|
|
635
|
+
npcId: z.string().optional(),
|
|
636
|
+
})),
|
|
637
|
+
/** Target number of entry points (Three Clue Rule) */
|
|
638
|
+
minimumLeadsTarget: z.number().default(3),
|
|
639
|
+
}),
|
|
640
|
+
// Complications - what makes this hard
|
|
641
|
+
complications: z.array(z.object({
|
|
642
|
+
id: z.string(),
|
|
643
|
+
description: z.string(),
|
|
644
|
+
type: z.enum([
|
|
645
|
+
"obstacle", // Physical barrier
|
|
646
|
+
"opposition", // Active resistance
|
|
647
|
+
"moral", // Ethical dilemma
|
|
648
|
+
"resource", // Missing tools/info
|
|
649
|
+
"time", // Deadline pressure
|
|
650
|
+
"information", // Unknown factors
|
|
651
|
+
"relationship", // Social complications
|
|
652
|
+
]),
|
|
653
|
+
/** Possible ways to resolve */
|
|
654
|
+
resolutions: z.array(z.string()),
|
|
655
|
+
})),
|
|
656
|
+
// Outcomes - how this can end
|
|
657
|
+
outcomes: z.object({
|
|
658
|
+
/** Full success */
|
|
659
|
+
victory: z.object({
|
|
660
|
+
description: z.string(),
|
|
661
|
+
consequences: z.array(z.string()),
|
|
662
|
+
flagsSet: z.array(z.string()).optional(),
|
|
663
|
+
}),
|
|
664
|
+
/** Complete failure */
|
|
665
|
+
failure: z.object({
|
|
666
|
+
description: z.string(),
|
|
667
|
+
consequences: z.array(z.string()),
|
|
668
|
+
flagsSet: z.array(z.string()).optional(),
|
|
669
|
+
}),
|
|
670
|
+
/** Partial successes */
|
|
671
|
+
partial: z.array(z.object({
|
|
672
|
+
name: z.string(),
|
|
673
|
+
description: z.string(),
|
|
674
|
+
consequences: z.array(z.string()),
|
|
675
|
+
flagsSet: z.array(z.string()).optional(),
|
|
676
|
+
})).optional(),
|
|
677
|
+
/** Unexpected outcomes */
|
|
678
|
+
wildcards: z.array(z.string()).optional(),
|
|
679
|
+
}),
|
|
680
|
+
// GM Guidance
|
|
681
|
+
gmGuidance: z.object({
|
|
682
|
+
/** Thematic elements to emphasize */
|
|
683
|
+
themes: z.array(z.string()),
|
|
684
|
+
/** Tone notes for running this */
|
|
685
|
+
toneNotes: z.string(),
|
|
686
|
+
/** Common approaches and responses */
|
|
687
|
+
anticipatedApproaches: z.array(z.object({
|
|
688
|
+
approach: z.string(),
|
|
689
|
+
response: z.string(),
|
|
690
|
+
})),
|
|
691
|
+
/** Elements to hint at earlier */
|
|
692
|
+
foreshadowing: z.array(z.string()).optional(),
|
|
693
|
+
/** Connections to arcs */
|
|
694
|
+
arcConnections: z.array(z.string()).optional(),
|
|
695
|
+
}),
|
|
696
|
+
/** Tags for filtering/organization */
|
|
697
|
+
tags: z.array(z.string()),
|
|
698
|
+
/** Parent arc ID */
|
|
699
|
+
arcId: z.string().optional(),
|
|
700
|
+
/** Layer in layer-cake structures */
|
|
701
|
+
layer: z.number().optional(),
|
|
702
|
+
});
|
|
703
|
+
// ============================================================================
|
|
704
|
+
// WORLD ARC (The Cluster)
|
|
705
|
+
// ============================================================================
|
|
706
|
+
/**
|
|
707
|
+
* An arc groups related situations around a central tension.
|
|
708
|
+
* Provides structure for multi-session storylines.
|
|
709
|
+
*/
|
|
710
|
+
export const WorldArcSchema = z.object({
|
|
711
|
+
/** Unique identifier: arc:slug-name */
|
|
712
|
+
id: z.string(),
|
|
713
|
+
/** Arc name */
|
|
714
|
+
name: z.string(),
|
|
715
|
+
/** Arc description */
|
|
716
|
+
description: z.string(),
|
|
717
|
+
// Central tension
|
|
718
|
+
tension: z.object({
|
|
719
|
+
/** The core conflict */
|
|
720
|
+
centralConflict: z.string(),
|
|
721
|
+
/** Source of the conflict */
|
|
722
|
+
source: z.string(),
|
|
723
|
+
/** Opposing forces */
|
|
724
|
+
opposingForces: z.array(z.object({
|
|
725
|
+
name: z.string(),
|
|
726
|
+
goal: z.string(),
|
|
727
|
+
factionId: z.string().optional(),
|
|
728
|
+
})),
|
|
729
|
+
/** Why this matters now */
|
|
730
|
+
urgency: z.string().optional(),
|
|
731
|
+
}),
|
|
732
|
+
/** Situation IDs in this arc */
|
|
733
|
+
situationIds: z.array(z.string()),
|
|
734
|
+
// Structure for pacing
|
|
735
|
+
structure: z.object({
|
|
736
|
+
/** Arc structure type */
|
|
737
|
+
type: z.enum([
|
|
738
|
+
"funnel", // Multiple entry points narrow to climax
|
|
739
|
+
"layer_cake", // Situations unlock in layers
|
|
740
|
+
"hub_spoke", // Central hub with satellite situations
|
|
741
|
+
"chain", // Linear progression
|
|
742
|
+
"web", // Interconnected without clear structure
|
|
743
|
+
]),
|
|
744
|
+
/** Situation ID -> layer number mapping */
|
|
745
|
+
layers: z.record(z.number()).optional(),
|
|
746
|
+
/** Entry point situation IDs (for funnel) */
|
|
747
|
+
entryPoints: z.array(z.string()).optional(),
|
|
748
|
+
/** Climax situation ID (for funnel) */
|
|
749
|
+
climax: z.string().optional(),
|
|
750
|
+
/** Hub situation ID (for hub_spoke) */
|
|
751
|
+
hub: z.string().optional(),
|
|
752
|
+
/** Suggested play order */
|
|
753
|
+
suggestedOrder: z.array(z.string()).optional(),
|
|
754
|
+
}),
|
|
755
|
+
// Resolution
|
|
756
|
+
resolution: z.object({
|
|
757
|
+
/** How this arc can conclude */
|
|
758
|
+
patterns: z.array(z.object({
|
|
759
|
+
name: z.string(),
|
|
760
|
+
description: z.string(),
|
|
761
|
+
triggerConditions: z.array(z.string()),
|
|
762
|
+
})),
|
|
763
|
+
/** Arcs unlocked by completing this one */
|
|
764
|
+
unlocksArcs: z.array(z.string()).optional(),
|
|
765
|
+
/** Changes to the world on resolution */
|
|
766
|
+
worldChanges: z.array(z.string()),
|
|
767
|
+
}),
|
|
768
|
+
/** Thematic elements */
|
|
769
|
+
themes: z.array(z.string()),
|
|
770
|
+
// GM Guidance
|
|
771
|
+
gmGuidance: z.object({
|
|
772
|
+
/** How to introduce this arc */
|
|
773
|
+
introduction: z.string(),
|
|
774
|
+
/** Pacing advice */
|
|
775
|
+
pacing: z.string(),
|
|
776
|
+
/** Key NPCs to feature */
|
|
777
|
+
keyNpcs: z.array(z.string()),
|
|
778
|
+
/** Atmosphere notes */
|
|
779
|
+
atmosphere: z.string(),
|
|
780
|
+
}),
|
|
781
|
+
/** Arc status */
|
|
782
|
+
status: z.enum(["dormant", "foreshadowed", "active", "climax", "resolved"]),
|
|
783
|
+
});
|
|
784
|
+
// ============================================================================
|
|
785
|
+
// WORLD CONTENT PACK
|
|
786
|
+
// ============================================================================
|
|
787
|
+
/**
|
|
788
|
+
* A complete world content pack
|
|
789
|
+
*/
|
|
790
|
+
export const WorldContentPackSchema = z.object({
|
|
791
|
+
/** World metadata */
|
|
792
|
+
meta: WorldMetaSchema,
|
|
793
|
+
/** Playable archetypes */
|
|
794
|
+
archetypes: z.record(WorldArchetypeSchema),
|
|
795
|
+
/** Items */
|
|
796
|
+
items: z.record(WorldItemSchema),
|
|
797
|
+
/** Monsters */
|
|
798
|
+
monsters: z.record(WorldMonsterSchema),
|
|
799
|
+
/** Encounters */
|
|
800
|
+
encounters: z.record(WorldEncounterSchema),
|
|
801
|
+
/** Conditions (status effects) */
|
|
802
|
+
conditions: z.record(WorldConditionSchema),
|
|
803
|
+
/** Locations */
|
|
804
|
+
locations: z.record(WorldLocationSchema),
|
|
805
|
+
/** NPCs */
|
|
806
|
+
npcs: z.record(WorldNPCSchema),
|
|
807
|
+
/** Factions (optional) */
|
|
808
|
+
factions: z.record(WorldFactionSchema).optional(),
|
|
809
|
+
/** Situations - node-based scenario design (optional) */
|
|
810
|
+
situations: z.record(WorldSituationSchema).optional(),
|
|
811
|
+
/** Story arcs grouping situations (optional) */
|
|
812
|
+
arcs: z.record(WorldArcSchema).optional(),
|
|
813
|
+
/** Narrative guidance for AI/GM */
|
|
814
|
+
narrativeGuidance: NarrativeGuidanceSchema,
|
|
815
|
+
/**
|
|
816
|
+
* Rules configuration (optional)
|
|
817
|
+
* Allows world packs to extend or override base game rules.
|
|
818
|
+
* If not provided, default rules are used.
|
|
819
|
+
*/
|
|
820
|
+
rules: WorldRulesConfigSchema.optional(),
|
|
821
|
+
});
|
|
822
|
+
//# sourceMappingURL=world.js.map
|