feat(gameplay): enforce core SRD d20 rules
All checks were successful
CI / validate (push) Successful in 21m53s
CI / validate (pull_request) Successful in 15m15s

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-08-23 18:50:55 +05:00
parent ace1e47a6d
commit 4e0c38c925
13 changed files with 230 additions and 27 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { PlayerIntentSchema } from './index'
import { CheckRequestSchema, PlayerIntentSchema } from './index'
describe('shared database contracts', () => {
it('accepts PostgREST timestamptz values with an explicit UTC offset', () => {
@@ -16,4 +16,16 @@ describe('shared database contracts', () => {
expect(intent.ready).toBe(true)
})
it('accepts typed SRD d20 tests', () => {
expect(CheckRequestSchema.parse({
actorId: 'hero', kind: 'skill', skill: 'perception', proficient: true,
difficulty: 15, mode: 'advantage', reason: 'Search the trapped hall',
})).toMatchObject({ kind: 'skill', skill: 'perception', proficient: true })
expect(CheckRequestSchema.parse({
actorId: 'hero', kind: 'savingThrow', ability: 'wis', proficient: false,
difficulty: 13, mode: 'normal', reason: 'Resist the whisper',
})).toMatchObject({ kind: 'savingThrow', ability: 'wis' })
})
})

View File

@@ -10,6 +10,14 @@ export const abilityKeys = ['str', 'dex', 'con', 'int', 'wis', 'cha'] as const
export const AbilityKeySchema = z.enum(abilityKeys)
export type AbilityKey = z.infer<typeof AbilityKeySchema>
export const skillKeys = [
'acrobatics', 'animalHandling', 'arcana', 'athletics', 'deception', 'history',
'insight', 'intimidation', 'investigation', 'medicine', 'nature', 'perception',
'performance', 'persuasion', 'religion', 'sleightOfHand', 'stealth', 'survival',
] as const
export const SkillKeySchema = z.enum(skillKeys)
export type SkillKey = z.infer<typeof SkillKeySchema>
export const AbilityScoresSchema = z.object({
str: z.number().int().min(1).max(30),
dex: z.number().int().min(1).max(30),
@@ -121,8 +129,10 @@ export type RoundContext = z.infer<typeof RoundContextSchema>
export const CheckRequestSchema = z.object({
actorId: z.string().min(1),
kind: z.enum(['ability', 'attack', 'initiative', 'damage', 'healing']),
kind: z.enum(['ability', 'skill', 'savingThrow', 'attack', 'initiative', 'damage', 'healing']),
ability: AbilityKeySchema.optional(),
skill: SkillKeySchema.optional(),
proficient: z.boolean().optional(),
difficulty: z.number().int().min(1).max(40).optional(),
targetId: z.string().optional(),
mode: z.enum(['normal', 'advantage', 'disadvantage']).default('normal'),