import { z } from 'zod' export const DirectionSchema = z.enum(['up', 'down', 'left', 'right']) export const PlayerCommandSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('move'), direction: DirectionSchema }), z.object({ type: z.literal('attack'), targetId: z.string().min(1).max(128) }), z.object({ type: z.literal('heal-self') }), z.object({ type: z.literal('pass') }) ]) export const CommandEnvelopeSchema = z.object({ requestId: z.string().min(1).max(128), runId: z.string().min(1), floorId: z.string().min(1), phaseId: z.string().min(1), command: PlayerCommandSchema }) export const SpawnMessageSchema = z.object({ type: z.literal('spawn-requested'), externalEventId: z.string().min(1), twitchUserId: z.string().min(1), displayName: z.string().min(1).max(40), broadcasterId: z.string().min(1), followerVerified: z.boolean() }) export const ChannelPointRedemptionSchema = z.object({ type: z.literal('channel-point-resurrection-redeemed'), externalEventId: z.string().min(1), twitchUserId: z.string().min(1), rewardId: z.string().min(1) }) export type PlayerCommand = z.infer export type CommandEnvelope = z.infer export type SpawnMessage = z.infer export type ChannelPointRedemption = z.infer export type RejectionReason = 'UNAUTHENTICATED' | 'IDENTITY_NOT_BOUND' | 'CHARACTER_NOT_FOUND' | 'CHARACTER_DEAD' | 'WRONG_PHASE' | 'STALE_RUN' | 'STALE_FLOOR' | 'STALE_PHASE' | 'DEADLINE_PASSED' | 'NO_AP' | 'INVALID_TARGET' | 'BLOCKED_TILE' | 'HEAL_USED' | 'DUPLICATE' export interface CommandResult { requestId: string accepted: boolean eventSequence: number reason: RejectionReason | null message: string }