Important Fixes, New mechanics, and many more
Some checks failed
CI / validate (push) Failing after 9m21s
Some checks failed
CI / validate (push) Failing after 9m21s
This commit is contained in:
@@ -20,6 +20,7 @@ describe('game engine', () => {
|
||||
expect(roll.rolls).toEqual([12])
|
||||
expect(roll.total).toBe(17)
|
||||
expect(roll.success).toBe(true)
|
||||
expect(roll.id).toMatch(/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i)
|
||||
})
|
||||
|
||||
it('labels disadvantage rolls correctly', () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { randomInt } from 'node:crypto'
|
||||
import { randomInt, randomUUID } from 'node:crypto'
|
||||
import type { AbilityKey, Character, CheckRequest, DiceRoll, ProposedEvent } from '@dng/shared'
|
||||
import { makeId } from '@dng/shared'
|
||||
|
||||
export interface RandomSource {
|
||||
integer(min: number, max: number): number
|
||||
@@ -62,7 +61,9 @@ export function resolveCheck(request: CheckRequest, actor: Character, random: Ra
|
||||
if (request.kind === 'damage' || request.kind === 'healing') {
|
||||
const rolled = rollDice(request.dice ?? '1d6', random)
|
||||
return {
|
||||
id: makeId('roll'),
|
||||
// dice_rolls.id is a native PostgreSQL uuid. Keep persistence IDs free
|
||||
// of the human-readable prefixes used for logs and worker identities.
|
||||
id: randomUUID(),
|
||||
checkKind: request.kind,
|
||||
formula: request.dice ?? '1d6',
|
||||
rolls: rolled.rolls,
|
||||
@@ -85,7 +86,7 @@ export function resolveCheck(request: CheckRequest, actor: Character, random: Ra
|
||||
const total = keptValue + modifier
|
||||
const difficulty = request.difficulty ?? null
|
||||
return {
|
||||
id: makeId('roll'),
|
||||
id: randomUUID(),
|
||||
checkKind: request.kind,
|
||||
formula: request.mode === 'normal'
|
||||
? `1d20${modifier >= 0 ? '+' : ''}${modifier}`
|
||||
|
||||
19
packages/shared/src/index.test.ts
Normal file
19
packages/shared/src/index.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { PlayerIntentSchema } from './index'
|
||||
|
||||
describe('shared database contracts', () => {
|
||||
it('accepts PostgREST timestamptz values with an explicit UTC offset', () => {
|
||||
const intent = PlayerIntentSchema.parse({
|
||||
id: '2c321216-ef17-48a2-9aa3-6250bb4354f5',
|
||||
roundId: '940fc00f-76e1-45dc-b401-55a2084e3e0e',
|
||||
memberId: '85701d65-0b2c-4c7e-aa30-d67fc77f1f54',
|
||||
characterId: 'bfc4b38f-bf3c-4ee1-b875-aa98b2b3a756',
|
||||
action: 'Inspect the access panel.',
|
||||
ready: true,
|
||||
createdAt: '2026-08-15T08:42:10.123456+00:00',
|
||||
updatedAt: '2026-08-15T08:42:11+00:00',
|
||||
})
|
||||
|
||||
expect(intent.ready).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,11 @@
|
||||
import { z } from 'zod'
|
||||
export * from './moderation'
|
||||
|
||||
// PostgreSQL/PostgREST serializes timestamptz values with an explicit offset
|
||||
// (for example `+00:00`). Browser-created timestamps normally use `Z`.
|
||||
// Both are valid ISO-8601 timestamps and must be accepted at the API boundary.
|
||||
const IsoDatetimeSchema = z.string().datetime({ offset: true })
|
||||
|
||||
export const abilityKeys = ['str', 'dex', 'con', 'int', 'wis', 'cha'] as const
|
||||
export const AbilityKeySchema = z.enum(abilityKeys)
|
||||
export type AbilityKey = z.infer<typeof AbilityKeySchema>
|
||||
@@ -53,6 +58,7 @@ export const CharacterSchema = z.object({
|
||||
proficiency: z.number().int().min(1).max(10),
|
||||
inventory: z.array(z.string().max(120)).max(50).default([]),
|
||||
statuses: z.array(z.string().max(80)).max(12).default([]),
|
||||
persona: z.record(z.string(), z.unknown()).optional(),
|
||||
})
|
||||
export type Character = z.infer<typeof CharacterSchema>
|
||||
|
||||
@@ -63,11 +69,31 @@ export const PlayerIntentSchema = z.object({
|
||||
characterId: z.string().min(1),
|
||||
action: z.string().trim().min(1).max(2000),
|
||||
ready: z.boolean().default(false),
|
||||
createdAt: z.string().datetime(),
|
||||
updatedAt: z.string().datetime(),
|
||||
createdAt: IsoDatetimeSchema,
|
||||
updatedAt: IsoDatetimeSchema,
|
||||
})
|
||||
export type PlayerIntent = z.infer<typeof PlayerIntentSchema>
|
||||
|
||||
export const RoundContextSchema = z.object({
|
||||
scene: z.string().max(6000),
|
||||
storySummary: z.string().max(6000).nullable(),
|
||||
recentRounds: z.array(z.object({
|
||||
number: z.number().int().positive(),
|
||||
narration: z.string().max(6000),
|
||||
nextPrompt: z.string().max(500).nullable().optional(),
|
||||
})).max(2),
|
||||
humanIntents: z.array(PlayerIntentSchema).max(8),
|
||||
aiCharacters: z.array(CharacterSchema).max(8),
|
||||
activeCharacters: z.array(CharacterSchema).max(16),
|
||||
memories: z.array(z.object({
|
||||
summary: z.string().min(1).max(1200),
|
||||
importance: z.number().int().min(1).max(5).optional(),
|
||||
tags: z.array(z.string().max(40)).max(12).optional(),
|
||||
entityIds: z.array(z.string()).max(20).optional(),
|
||||
})).max(8),
|
||||
})
|
||||
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']),
|
||||
@@ -81,7 +107,7 @@ export const CheckRequestSchema = z.object({
|
||||
export type CheckRequest = z.infer<typeof CheckRequestSchema>
|
||||
|
||||
export const DiceRollSchema = z.object({
|
||||
id: z.string(),
|
||||
id: z.string().uuid(),
|
||||
checkKind: CheckRequestSchema.shape.kind,
|
||||
formula: z.string(),
|
||||
rolls: z.array(z.number().int()),
|
||||
@@ -92,7 +118,7 @@ export const DiceRollSchema = z.object({
|
||||
success: z.boolean().nullable(),
|
||||
actorId: z.string(),
|
||||
targetId: z.string().nullable(),
|
||||
createdAt: z.string().datetime(),
|
||||
createdAt: IsoDatetimeSchema,
|
||||
})
|
||||
export type DiceRoll = z.infer<typeof DiceRollSchema>
|
||||
|
||||
@@ -130,6 +156,11 @@ export const RoundResolutionSchema = z.object({
|
||||
})
|
||||
export type RoundResolution = z.infer<typeof RoundResolutionSchema>
|
||||
|
||||
export const StorySummarySchema = z.object({
|
||||
summary: z.string().min(20).max(6000),
|
||||
})
|
||||
export type StorySummary = z.infer<typeof StorySummarySchema>
|
||||
|
||||
export const CoauthorMessageSchema = z.object({
|
||||
role: z.enum(['user', 'assistant']),
|
||||
content: z.string().trim().min(1).max(5000),
|
||||
|
||||
Reference in New Issue
Block a user