@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.
@@ -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