Build MVP: campaigns, characters, combat, dice, compendium
- Rules abstraction (5e + pf2e) behind one interface, fully tested - Pure combat engine: turn-order safe across add/remove/reorder/init-change - Dexie storage with real cascade deletes + Zod validation on write - Seedable dice engine with notation parser - Lazy SRD compendium (code-split), bestiary -> combat - Strict TS, 54 unit tests, Playwright e2e smoke (all green) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { z } from 'zod';
|
||||
import { conditionSchema, hpSchema, int } from './common';
|
||||
|
||||
export const combatantKindSchema = z.enum(['pc', 'npc', 'monster']);
|
||||
|
||||
export const combatantSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().min(1).max(120),
|
||||
kind: combatantKindSchema,
|
||||
/** link to a Character, if this combatant is a tracked PC/NPC */
|
||||
characterId: z.string().optional(),
|
||||
/** ref into the bestiary, if spawned from a monster */
|
||||
monsterRef: z.string().optional(),
|
||||
|
||||
initiative: int,
|
||||
ac: int,
|
||||
hp: hpSchema,
|
||||
conditions: z.array(conditionSchema).default([]),
|
||||
notes: z.string().max(2000).default(''),
|
||||
});
|
||||
|
||||
export type Combatant = z.infer<typeof combatantSchema>;
|
||||
|
||||
export const encounterStatusSchema = z.enum(['planning', 'active', 'ended']);
|
||||
|
||||
export const encounterSchema = z.object({
|
||||
id: z.string(),
|
||||
campaignId: z.string(),
|
||||
name: z.string().min(1).max(120),
|
||||
status: encounterStatusSchema.default('planning'),
|
||||
round: int.nonnegative().default(0),
|
||||
/** index into combatants of whose turn it is */
|
||||
turnIndex: int.nonnegative().default(0),
|
||||
combatants: z.array(combatantSchema).default([]),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
});
|
||||
|
||||
export type Encounter = z.infer<typeof encounterSchema>;
|
||||
Reference in New Issue
Block a user