export const POINTER_CONTRACT_SCHEMA_VERSION = 1 as const; export type PointerKind = "mouse" | "touch" | "pen"; export interface PointerObservation { schemaVersion: typeof POINTER_CONTRACT_SCHEMA_VERSION; pointerType: PointerKind; pointerId: number; pressure: number; tiltX: number; tiltY: number; button: number; buttons: number; cancelled: boolean; } function bounded(value: number, min: number, max: number, fallback: number): number { return Number.isFinite(value) ? Math.max(min, Math.min(max, value)) : fallback; } export function observePointerEvent(event: { pointerType?: string; pointerId?: number; pressure?: number; tiltX?: number; tiltY?: number; button?: number; buttons?: number; type?: string }): PointerObservation { const pointerType = event.pointerType === "touch" || event.pointerType === "pen" || event.pointerType === "mouse" ? event.pointerType : null; if (!pointerType) throw new Error("POINTER_TYPE_UNSUPPORTED"); if (!Number.isSafeInteger(event.pointerId) || event.pointerId! < 0) throw new Error("POINTER_ID_INVALID"); return { schemaVersion: POINTER_CONTRACT_SCHEMA_VERSION, pointerType, pointerId: event.pointerId!, pressure: bounded(event.pressure ?? (pointerType === "mouse" ? 0 : 0.5), 0, 1, 0), tiltX: bounded(event.tiltX ?? 0, -90, 90, 0), tiltY: bounded(event.tiltY ?? 0, -90, 90, 0), button: Number.isInteger(event.button) ? event.button! : -1, buttons: Number.isInteger(event.buttons) && event.buttons! >= 0 ? event.buttons! : 0, cancelled: event.type === "pointercancel", }; }