import { type AnimationLevel } from '../../utils/animationLevel' export type FeedbackCueKey = | 'session_started' | 'session_finished' | 'hint:changed' | 'control_completed:start' | 'control_completed:control' | 'control_completed:finish' | 'punch_feedback:warning' | 'guidance:searching' | 'guidance:distant' | 'guidance:approaching' | 'guidance:ready' export type HapticPattern = 'short' | 'long' export type UiPunchFeedbackMotion = 'none' | 'pop' | 'success' | 'warning' export type UiContentCardMotion = 'none' | 'pop' | 'finish' export type UiPunchButtonMotion = 'none' | 'ready' | 'warning' export type UiMapPulseMotion = 'none' | 'ready' | 'control' | 'finish' export type UiStageMotion = 'none' | 'control' | 'finish' export type UiHudProgressMotion = 'none' | 'success' | 'finish' export type UiHudDistanceMotion = 'none' | 'success' export interface HapticCueConfig { enabled: boolean pattern: HapticPattern } export interface UiCueConfig { enabled: boolean punchFeedbackMotion: UiPunchFeedbackMotion contentCardMotion: UiContentCardMotion punchButtonMotion: UiPunchButtonMotion mapPulseMotion: UiMapPulseMotion stageMotion: UiStageMotion hudProgressMotion: UiHudProgressMotion hudDistanceMotion: UiHudDistanceMotion durationMs: number } export interface GameHapticsConfig { enabled: boolean cues: Record } export interface GameUiEffectsConfig { enabled: boolean cues: Record } export interface ResolvedGameUiEffectsConfig extends GameUiEffectsConfig { animationLevel: AnimationLevel } export interface PartialHapticCueConfig { enabled?: boolean pattern?: HapticPattern } export interface PartialUiCueConfig { enabled?: boolean punchFeedbackMotion?: UiPunchFeedbackMotion contentCardMotion?: UiContentCardMotion punchButtonMotion?: UiPunchButtonMotion mapPulseMotion?: UiMapPulseMotion stageMotion?: UiStageMotion hudProgressMotion?: UiHudProgressMotion hudDistanceMotion?: UiHudDistanceMotion durationMs?: number } export interface GameHapticsConfigOverrides { enabled?: boolean cues?: Partial> } export interface GameUiEffectsConfigOverrides { enabled?: boolean cues?: Partial> } export const DEFAULT_GAME_HAPTICS_CONFIG: GameHapticsConfig = { enabled: true, cues: { session_started: { enabled: false, pattern: 'short' }, session_finished: { enabled: true, pattern: 'long' }, 'hint:changed': { enabled: true, pattern: 'short' }, 'control_completed:start': { enabled: true, pattern: 'short' }, 'control_completed:control': { enabled: true, pattern: 'short' }, 'control_completed:finish': { enabled: true, pattern: 'long' }, 'punch_feedback:warning': { enabled: true, pattern: 'short' }, 'guidance:searching': { enabled: false, pattern: 'short' }, 'guidance:distant': { enabled: true, pattern: 'short' }, 'guidance:approaching': { enabled: true, pattern: 'short' }, 'guidance:ready': { enabled: true, pattern: 'short' }, }, } export const DEFAULT_GAME_UI_EFFECTS_CONFIG: GameUiEffectsConfig = { enabled: true, cues: { session_started: { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', hudProgressMotion: 'none', hudDistanceMotion: 'none', durationMs: 0 }, session_finished: { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', hudProgressMotion: 'none', hudDistanceMotion: 'none', durationMs: 0 }, 'hint:changed': { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', hudProgressMotion: 'none', hudDistanceMotion: 'none', durationMs: 0 }, 'control_completed:start': { enabled: true, punchFeedbackMotion: 'success', contentCardMotion: 'pop', punchButtonMotion: 'none', mapPulseMotion: 'control', stageMotion: 'control', hudProgressMotion: 'success', hudDistanceMotion: 'success', durationMs: 560 }, 'control_completed:control': { enabled: true, punchFeedbackMotion: 'success', contentCardMotion: 'pop', punchButtonMotion: 'none', mapPulseMotion: 'control', stageMotion: 'control', hudProgressMotion: 'success', hudDistanceMotion: 'success', durationMs: 560 }, 'control_completed:finish': { enabled: true, punchFeedbackMotion: 'success', contentCardMotion: 'finish', punchButtonMotion: 'none', mapPulseMotion: 'finish', stageMotion: 'finish', hudProgressMotion: 'finish', hudDistanceMotion: 'success', durationMs: 680 }, 'punch_feedback:warning': { enabled: true, punchFeedbackMotion: 'warning', contentCardMotion: 'none', punchButtonMotion: 'warning', mapPulseMotion: 'none', stageMotion: 'none', hudProgressMotion: 'none', hudDistanceMotion: 'none', durationMs: 560 }, 'guidance:searching': { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', hudProgressMotion: 'none', hudDistanceMotion: 'none', durationMs: 0 }, 'guidance:distant': { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', hudProgressMotion: 'none', hudDistanceMotion: 'none', durationMs: 0 }, 'guidance:approaching': { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', hudProgressMotion: 'none', hudDistanceMotion: 'none', durationMs: 0 }, 'guidance:ready': { enabled: true, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'ready', mapPulseMotion: 'ready', stageMotion: 'none', hudProgressMotion: 'none', hudDistanceMotion: 'none', durationMs: 900 }, }, } function clampDuration(value: number, fallback: number): number { return Number.isFinite(value) && value >= 0 ? value : fallback } function mergeHapticCue(baseCue: HapticCueConfig, override?: PartialHapticCueConfig): HapticCueConfig { return { enabled: override && override.enabled !== undefined ? !!override.enabled : baseCue.enabled, pattern: override && override.pattern ? override.pattern : baseCue.pattern, } } function mergeUiCue(baseCue: UiCueConfig, override?: PartialUiCueConfig): UiCueConfig { return { enabled: override && override.enabled !== undefined ? !!override.enabled : baseCue.enabled, punchFeedbackMotion: override && override.punchFeedbackMotion ? override.punchFeedbackMotion : baseCue.punchFeedbackMotion, contentCardMotion: override && override.contentCardMotion ? override.contentCardMotion : baseCue.contentCardMotion, punchButtonMotion: override && override.punchButtonMotion ? override.punchButtonMotion : baseCue.punchButtonMotion, mapPulseMotion: override && override.mapPulseMotion ? override.mapPulseMotion : baseCue.mapPulseMotion, stageMotion: override && override.stageMotion ? override.stageMotion : baseCue.stageMotion, hudProgressMotion: override && override.hudProgressMotion ? override.hudProgressMotion : baseCue.hudProgressMotion, hudDistanceMotion: override && override.hudDistanceMotion ? override.hudDistanceMotion : baseCue.hudDistanceMotion, durationMs: clampDuration(Number(override && override.durationMs), baseCue.durationMs), } } export function mergeGameHapticsConfig(overrides?: GameHapticsConfigOverrides | null): GameHapticsConfig { const cues: GameHapticsConfig['cues'] = { session_started: mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues.session_started, overrides && overrides.cues ? overrides.cues.session_started : undefined), session_finished: mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues.session_finished, overrides && overrides.cues ? overrides.cues.session_finished : undefined), 'hint:changed': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['hint:changed'], overrides && overrides.cues ? overrides.cues['hint:changed'] : undefined), 'control_completed:start': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['control_completed:start'], overrides && overrides.cues ? overrides.cues['control_completed:start'] : undefined), 'control_completed:control': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['control_completed:control'], overrides && overrides.cues ? overrides.cues['control_completed:control'] : undefined), 'control_completed:finish': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['control_completed:finish'], overrides && overrides.cues ? overrides.cues['control_completed:finish'] : undefined), 'punch_feedback:warning': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['punch_feedback:warning'], overrides && overrides.cues ? overrides.cues['punch_feedback:warning'] : undefined), 'guidance:searching': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['guidance:searching'], overrides && overrides.cues ? overrides.cues['guidance:searching'] : undefined), 'guidance:distant': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['guidance:distant'], overrides && overrides.cues ? overrides.cues['guidance:distant'] : undefined), 'guidance:approaching': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['guidance:approaching'], overrides && overrides.cues ? overrides.cues['guidance:approaching'] : undefined), 'guidance:ready': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['guidance:ready'], overrides && overrides.cues ? overrides.cues['guidance:ready'] : undefined), } return { enabled: overrides && overrides.enabled !== undefined ? !!overrides.enabled : DEFAULT_GAME_HAPTICS_CONFIG.enabled, cues, } } export function mergeGameUiEffectsConfig(overrides?: GameUiEffectsConfigOverrides | null): GameUiEffectsConfig { const cues: GameUiEffectsConfig['cues'] = { session_started: mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues.session_started, overrides && overrides.cues ? overrides.cues.session_started : undefined), session_finished: mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues.session_finished, overrides && overrides.cues ? overrides.cues.session_finished : undefined), 'hint:changed': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['hint:changed'], overrides && overrides.cues ? overrides.cues['hint:changed'] : undefined), 'control_completed:start': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['control_completed:start'], overrides && overrides.cues ? overrides.cues['control_completed:start'] : undefined), 'control_completed:control': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['control_completed:control'], overrides && overrides.cues ? overrides.cues['control_completed:control'] : undefined), 'control_completed:finish': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['control_completed:finish'], overrides && overrides.cues ? overrides.cues['control_completed:finish'] : undefined), 'punch_feedback:warning': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['punch_feedback:warning'], overrides && overrides.cues ? overrides.cues['punch_feedback:warning'] : undefined), 'guidance:searching': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['guidance:searching'], overrides && overrides.cues ? overrides.cues['guidance:searching'] : undefined), 'guidance:distant': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['guidance:distant'], overrides && overrides.cues ? overrides.cues['guidance:distant'] : undefined), 'guidance:approaching': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['guidance:approaching'], overrides && overrides.cues ? overrides.cues['guidance:approaching'] : undefined), 'guidance:ready': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['guidance:ready'], overrides && overrides.cues ? overrides.cues['guidance:ready'] : undefined), } return { enabled: overrides && overrides.enabled !== undefined ? !!overrides.enabled : DEFAULT_GAME_UI_EFFECTS_CONFIG.enabled, cues, } }