blacktrigram 0.7.20 → 0.7.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/audio/AudioManager.js +3 -2
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/systems/ai/AdaptiveDifficulty.js +2 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/package.json +7 -7
|
@@ -52,11 +52,12 @@ var AudioManager = class {
|
|
|
52
52
|
this.assetLoader = new AudioAssetLoader();
|
|
53
53
|
this.audioPool = new AudioElementPool();
|
|
54
54
|
this.monitor = new AudioMonitor();
|
|
55
|
-
|
|
55
|
+
const cacheConfig = {
|
|
56
56
|
maxSizeBytes: 30 * 1024 * 1024,
|
|
57
57
|
criticalAssets: [...CRITICAL_AUDIO_ASSETS],
|
|
58
58
|
debug: typeof process !== "undefined" && process.env.NODE_ENV === "development"
|
|
59
|
-
}
|
|
59
|
+
};
|
|
60
|
+
this.audioCache = new AudioCache(cacheConfig);
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
63
|
* Helper method to release pooled audio back to the pool after playback
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioManager.js","names":[],"sources":["../../src/audio/AudioManager.ts"],"sourcesContent":["import { AudioAssetLoader, LoadOptions } from \"./AudioAssetLoader\";\nimport { AudioCache, AudioCacheConfig } from \"./AudioCache\";\nimport {\n AudioFPSImpact,\n AudioMemoryStats,\n AudioMonitor,\n AudioPerformanceStats,\n MemoryWarning,\n} from \"./AudioMonitor\";\nimport { AudioElementPool, PoolStatistics } from \"./AudioPool\";\nimport {\n AudioAsset,\n AudioConfig,\n IAudioManager,\n MusicTrackId,\n SoundEffectId,\n} from \"./types\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\n/**\n * Critical assets that should never be evicted from cache\n * 캐시에서 절대 제거되지 않아야 하는 중요한 자산\n */\nconst CRITICAL_AUDIO_ASSETS = [\n // Menu sounds - instant playback required\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n // Common combat sounds - instant playback required\n \"hit_impact\",\n \"hit_light\",\n \"hit_medium\",\n \"hit_heavy\",\n \"guard_block\",\n \"attack_whoosh\",\n \"attack_light\",\n \"stance_change\",\n] as const;\n\nexport class AudioManager implements IAudioManager {\n private _masterVolume: number = 1.0;\n private _musicVolume: number = 0.7;\n private _sfxVolume: number = 0.8;\n private _muted: boolean = false;\n private _currentMusicTrack: string | null = null;\n private _fallbackMode: boolean = false;\n private currentMusic: HTMLAudioElement | null = null;\n private soundCache: Map<string, HTMLAudioElement> = new Map();\n private _isInitialized: boolean = false;\n\n // Track music assets currently being loaded to prevent race conditions\n // 레이스 컨디션 방지를 위해 현재 로드 중인 음악 자산 추적\n private loadingMusic: Set<string> = new Set();\n\n // New optimized components\n private assetLoader: AudioAssetLoader;\n private audioPool: AudioElementPool;\n private monitor: AudioMonitor;\n private audioCache: AudioCache;\n private frequentSounds: Set<string> = new Set([\n \"hit_light\",\n \"attack_light\",\n \"stance_change\",\n ]);\n\n constructor(config?: Partial<AudioConfig>) {\n if (config) {\n this._masterVolume = config.masterVolume ?? 1.0;\n this._musicVolume = config.musicVolume ?? 0.7;\n this._sfxVolume = config.sfxVolume ?? 0.8;\n }\n\n // Initialize new components\n this.assetLoader = new AudioAssetLoader();\n this.audioPool = new AudioElementPool();\n this.monitor = new AudioMonitor();\n\n // Initialize AudioCache with 30MB limit and critical assets\n // 30MB 제한 및 중요 자산으로 AudioCache 초기화\n const cacheConfig: AudioCacheConfig = {\n maxSizeBytes: 30 * 1024 * 1024, // 30MB default\n criticalAssets: [...CRITICAL_AUDIO_ASSETS],\n debug: typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"development\",\n };\n this.audioCache = new AudioCache(cacheConfig);\n }\n\n /**\n * Helper method to release pooled audio back to the pool after playback\n * @param id - Asset ID\n * @param audio - Audio element to release\n */\n private releasePooledAudio(id: string, audio: HTMLAudioElement): void {\n if (!audio.paused) {\n audio.addEventListener(\n \"ended\",\n () => {\n this.audioPool.release(id, audio);\n },\n { once: true }\n );\n } else {\n this.audioPool.release(id, audio);\n }\n }\n\n // Interface getters\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get fallbackMode(): boolean {\n return this._fallbackMode;\n }\n\n get currentMusicTrack(): string | null {\n return this._currentMusicTrack;\n }\n\n get masterVolume(): number {\n return this._masterVolume;\n }\n\n get sfxVolume(): number {\n return this._sfxVolume;\n }\n\n get musicVolume(): number {\n return this._musicVolume;\n }\n\n get muted(): boolean {\n return this._muted;\n }\n\n async initialize(config?: AudioConfig): Promise<void> {\n try {\n // Create AudioContext using globalThis for cross-platform compatibility\n const AudioContextClass =\n globalThis.AudioContext ||\n (globalThis as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n new AudioContextClass();\n this._isInitialized = true;\n this._fallbackMode = false;\n\n if (config) {\n this._masterVolume = config.masterVolume ?? this._masterVolume;\n this._musicVolume = config.musicVolume ?? this._musicVolume;\n this._sfxVolume = config.sfxVolume ?? this._sfxVolume;\n }\n\n // Set memory threshold if configured\n if (config?.maxSimultaneousSounds) {\n const estimatedMemoryMB =\n config.maxSimultaneousSounds * ESTIMATED_ASSET_SIZE_MB;\n this.monitor.setMemoryThreshold(estimatedMemoryMB);\n }\n } catch (error) {\n console.warn(\n \"AudioContext initialization failed, using fallback mode:\",\n error\n );\n this._isInitialized = true;\n this._fallbackMode = true;\n }\n }\n\n async loadAsset(asset: AudioAsset, options?: LoadOptions): Promise<void> {\n const startTime = performance.now();\n\n try {\n // Check cache first - instant playback for cached assets\n // 캐시 먼저 확인 - 캐시된 자산의 즉시 재생\n const cachedAsset = this.audioCache.get(asset.id);\n if (cachedAsset) {\n // Asset already loaded in cache, use it\n const cached = this.soundCache.get(asset.id);\n if (cached) {\n return; // Already loaded and cached\n }\n }\n\n // Use faster timeouts for testing environment\n // Safely check for Node.js environment to avoid runtime errors in browsers\n const isTest =\n typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"test\";\n const loadOptions: LoadOptions = {\n timeout: isTest ? 100 : 10000,\n maxRetries: isTest ? 1 : 3,\n retryDelay: isTest ? 10 : 1000,\n ...options,\n };\n\n // Use AudioAssetLoader with retry and fallback\n const result = await this.assetLoader.loadAsset(asset, loadOptions);\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Estimate size for cache management\n // 캐시 관리를 위한 크기 추정\n const estimatedSize = this.estimateAudioSize(asset);\n\n // Add to LRU cache with size tracking\n // 크기 추적과 함께 LRU 캐시에 추가\n this.audioCache.set(asset.id, asset, estimatedSize);\n\n // Track performance and memory\n const loadTime = performance.now() - startTime;\n this.monitor.recordLoad(asset.id, loadTime, estimatedSize / (1024 * 1024));\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n } else {\n // Record failure\n const error = result.error ?? new Error(\"Unknown load error\");\n this.monitor.recordLoadFailure(asset.id, error);\n\n // Still cache the placeholder audio if available\n if (result.audio) {\n this.soundCache.set(asset.id, result.audio);\n\n // Add to cache even if failed (placeholder)\n const estimatedSize = this.estimateAudioSize(asset);\n this.audioCache.set(asset.id, asset, estimatedSize);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.warn(`Failed to load audio asset ${asset.id}:`, err);\n this.monitor.recordLoadFailure(asset.id, err);\n }\n }\n\n /**\n * Estimate audio asset size in bytes\n * 바이트 단위의 오디오 자산 크기 추정\n *\n * @returns Estimated size in bytes\n */\n private estimateAudioSize(_asset: AudioAsset): number {\n // Use ESTIMATED_ASSET_SIZE_MB constant for consistent estimation\n // Conservative estimate: ~0.5MB per audio file (compressed MP3/WebM)\n return ESTIMATED_ASSET_SIZE_MB * 1024 * 1024;\n }\n\n async playSoundEffect(id: SoundEffectId): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n // Alias for playSoundEffect to match interface\n async playSFX(id: SoundEffectId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n async playMusic(id: MusicTrackId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n this.stopMusic();\n\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n let audio = this.soundCache.get(id);\n \n // On-demand loading: If music not in cache, load it first\n // 온디맨드 로딩: 음악이 캐시에 없으면 먼저 로드\n if (!audio) {\n // Check if this music is already being loaded to prevent race conditions\n // JavaScript's single-threaded nature ensures atomic check-and-add operations\n // 레이스 컨디션 방지를 위해 이미 로드 중인지 확인\n // JavaScript의 싱글 스레드 특성으로 원자적 check-and-add 작업 보장\n if (this.loadingMusic.has(id)) {\n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" is already being loaded, waiting...`);\n }\n // Wait for the ongoing load to complete\n // 진행 중인 로드가 완료될 때까지 대기\n await this.waitForMusicLoad(id);\n audio = this.soundCache.get(id);\n } else {\n // Mark as loading before starting the load (atomic operation in single-threaded JS)\n // 로드 시작 전에 로딩 중으로 표시 (싱글 스레드 JS에서 원자적 작업)\n this.loadingMusic.add(id);\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" not in cache, loading on-demand...`);\n }\n \n try {\n const { audioAssetRegistry } = await import(\"./AudioAssetRegistry\");\n const musicAsset = audioAssetRegistry.getMusic(id);\n \n if (musicAsset) {\n try {\n await this.loadAsset(musicAsset);\n audio = this.soundCache.get(id);\n \n if (import.meta.env.DEV && audio) {\n console.log(`[AudioManager] Successfully loaded music \"${id}\" on-demand`);\n }\n } catch (error) {\n console.warn(`Failed to load music asset ${id} on-demand:`, error);\n return;\n }\n } else {\n console.warn(`Music asset not found in registry: ${id}`);\n return;\n }\n } finally {\n // Always remove from loading set when done\n // 완료 시 항상 로딩 세트에서 제거\n this.loadingMusic.delete(id);\n }\n }\n }\n\n if (audio) {\n try {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._musicVolume) * this._masterVolume;\n audio.loop = true;\n this.currentMusic = audio;\n this._currentMusicTrack = id;\n await audio.play();\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Playing music: ${id} (volume: ${audio.volume.toFixed(2)})`);\n }\n } catch (error) {\n console.warn(`Failed to play music ${id}:`, error);\n }\n }\n }\n\n /**\n * Wait for a music asset to finish loading\n * 음악 자산 로드가 완료될 때까지 대기\n * @param id - Music track ID\n * @param maxWaitMs - Maximum time to wait in milliseconds\n */\n private async waitForMusicLoad(id: string, maxWaitMs: number = 5000): Promise<void> {\n const startTime = Date.now();\n const checkInterval = 100; // Check every 100ms\n \n while (this.loadingMusic.has(id)) {\n if (Date.now() - startTime > maxWaitMs) {\n console.warn(`[AudioManager] Timeout waiting for music \"${id}\" to load`);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval));\n }\n }\n\n stopMusic(): void {\n if (this.currentMusic) {\n this.currentMusic.pause();\n this.currentMusic.currentTime = 0;\n this.currentMusic = null;\n this._currentMusicTrack = null;\n }\n }\n\n stopAll(): void {\n this.stopMusic();\n this.soundCache.forEach((audio) => {\n if (!audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n });\n }\n\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void {\n const clampedVolume = Math.max(0, Math.min(1, volume));\n\n switch (type) {\n case \"master\":\n this._masterVolume = clampedVolume;\n break;\n case \"sfx\":\n this._sfxVolume = clampedVolume;\n break;\n case \"music\":\n this._musicVolume = clampedVolume;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n break;\n case \"voice\":\n // Handle voice volume if needed\n break;\n }\n }\n\n mute(): void {\n this._muted = true;\n if (this.currentMusic) {\n this.currentMusic.volume = 0;\n }\n }\n\n unmute(): void {\n this._muted = false;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n }\n\n async fadeOut(duration: number = 1000): Promise<void> {\n if (!this.currentMusic) return;\n\n const musicElement = this.currentMusic;\n return new Promise((resolve) => {\n const startVolume = musicElement.volume;\n const fadeStep = startVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume > 0) {\n this.currentMusic.volume = Math.max(\n 0,\n this.currentMusic.volume - fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n this.stopMusic();\n resolve();\n }\n }, 50);\n });\n }\n\n async fadeIn(trackId: MusicTrackId, duration: number = 1000): Promise<void> {\n await this.playMusic(trackId, 0);\n\n if (!this.currentMusic) return;\n\n return new Promise((resolve) => {\n const targetVolume = this._musicVolume * this._masterVolume;\n const fadeStep = targetVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume < targetVolume) {\n this.currentMusic.volume = Math.min(\n targetVolume,\n this.currentMusic.volume + fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n resolve();\n }\n }, 50);\n });\n }\n\n async crossfade(\n fromTrackId: MusicTrackId,\n toTrackId: MusicTrackId,\n duration: number = 1000\n ): Promise<void> {\n // Fix: Remove unused fromTrackId parameter or use it properly\n const fadeOutPromise = this.fadeOut(duration);\n await fadeOutPromise;\n await this.fadeIn(toTrackId, duration);\n console.log(`Crossfaded from ${fromTrackId} to ${toTrackId}`);\n }\n\n getLoadedAssets(): ReadonlyMap<string, HTMLAudioElement> {\n return new Map(this.soundCache);\n }\n\n // Additional methods to match interface\n async playVoice(id: string): Promise<void> {\n return this.playSoundEffect(id);\n }\n\n async playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void> {\n const soundId = `${archetype}_${techniqueId}`;\n return this.playSoundEffect(soundId);\n }\n\n async playTrigramStanceSound(stance: string): Promise<void> {\n const soundId = `stance_${stance}`;\n return this.playSoundEffect(soundId);\n }\n\n async playVitalPointHitSound(severity: string): Promise<void> {\n const soundId = `vital_point_${severity}`;\n return this.playSoundEffect(soundId);\n }\n\n async playDojiangAmbience(): Promise<void> {\n return this.playMusic(\"dojang_ambience\");\n }\n\n // Legacy getters for backward compatibility\n getMasterVolume(): number {\n return this._masterVolume;\n }\n\n getMusicVolume(): number {\n return this._musicVolume;\n }\n\n getSfxVolume(): number {\n return this._sfxVolume;\n }\n\n get initialized(): boolean {\n return this._isInitialized;\n }\n\n // New optimized methods\n\n /**\n * Batch load multiple assets with progress tracking\n * @param assets - Array of audio assets to load\n * @param options - Optional load configuration (timeout, retries, etc.)\n * @param onProgress - Optional callback for progress updates with loaded and total counts\n * @returns Promise that resolves when all assets are processed\n */\n async batchLoadAssets(\n assets: readonly AudioAsset[],\n options?: LoadOptions,\n onProgress?: (loaded: number, total: number) => void\n ): Promise<void> {\n const results = await this.assetLoader.batchLoad(\n assets,\n options,\n (progress) => {\n onProgress?.(progress.loaded, progress.total);\n }\n );\n\n // Process results\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const asset = assets[i];\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n }\n }\n }\n\n /**\n * Unload an asset to free memory\n * @param assetId - ID of the asset to unload\n * @returns true if asset was unloaded, false if asset was not found\n */\n unloadAsset(assetId: string): boolean {\n // Remove from cache\n const audio = this.soundCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n this.soundCache.delete(assetId);\n }\n\n // Remove from AudioCache (LRU cache)\n // LRU 캐시에서 제거\n const removedFromCache = this.audioCache.remove(assetId);\n\n // Remove from loader cache\n const unloaded = this.assetLoader.unloadAsset(assetId);\n\n // Remove pool if exists\n if (this.audioPool.hasPool(assetId)) {\n this.audioPool.removePool(assetId);\n }\n\n // Unregister from monitor\n this.monitor.unregisterAsset(assetId);\n\n return unloaded || audio !== undefined || removedFromCache;\n }\n\n /**\n * Get cache statistics including LRU cache metrics\n * LRU 캐시 메트릭을 포함한 캐시 통계 가져오기\n *\n * @returns Cache statistics\n */\n getCacheStats(): {\n readonly lruCache: {\n readonly totalSize: number;\n readonly assetCount: number;\n readonly criticalCount: number;\n readonly utilizationPercent: number;\n readonly evictionCount: number;\n readonly hitCount: number;\n readonly missCount: number;\n readonly hitRate: number;\n };\n readonly soundCache: number;\n readonly poolStats: Map<string, PoolStatistics>;\n } {\n return {\n lruCache: this.audioCache.getStats(),\n soundCache: this.soundCache.size,\n poolStats: this.audioPool.getAllStatistics(),\n };\n }\n\n /**\n * Get memory statistics including total loaded MB, asset count, and warnings\n * @returns Memory statistics object\n */\n getMemoryStats(): AudioMemoryStats {\n return this.monitor.getMemoryStats();\n }\n\n /**\n * Get performance statistics including load times and playback latency\n * @returns Performance statistics object\n */\n getPerformanceStats(): AudioPerformanceStats {\n return this.monitor.getPerformanceStats();\n }\n\n /**\n * Get FPS impact analysis showing baseline vs current FPS and detected drops\n * @returns FPS impact analysis object\n */\n getFPSImpact(): AudioFPSImpact {\n return this.monitor.getFPSImpact();\n }\n\n /**\n * Update FPS measurement for monitoring\n * @param fps - Current frames per second measurement\n */\n updateFPS(fps: number): void {\n this.monitor.updateFPS(fps);\n }\n\n /**\n * Get comprehensive monitoring report including memory, performance, FPS, and warnings\n * @returns Comprehensive monitoring report object\n */\n getMonitoringReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return this.monitor.getReport();\n }\n\n /**\n * Get all memory warnings\n * @returns Array of memory warnings\n */\n getMemoryWarnings(): readonly MemoryWarning[] {\n return this.monitor.getWarnings();\n }\n\n /**\n * Clear memory warnings\n */\n clearMemoryWarnings(): void {\n this.monitor.clearWarnings();\n }\n\n /**\n * Get pool statistics for a specific asset or all pools\n * @param assetId - Optional asset ID to get specific pool stats\n * @returns Pool statistics for the specified asset or all pools\n */\n getPoolStatistics(\n assetId?: string\n ): PoolStatistics | Map<string, PoolStatistics> | undefined {\n if (assetId) {\n return this.audioPool.getPoolStatistics(assetId);\n }\n return this.audioPool.getAllStatistics();\n }\n\n /**\n * Get loader statistics including cached assets and loading state\n * @returns Loader statistics object\n */\n getLoaderStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return this.assetLoader.getStatistics();\n }\n}\n\nexport default AudioManager;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioManager.js","names":[],"sources":["../../src/audio/AudioManager.ts"],"sourcesContent":["import { AudioAssetLoader, LoadOptions } from \"./AudioAssetLoader\";\nimport { AudioCache, AudioCacheConfig } from \"./AudioCache\";\nimport {\n AudioFPSImpact,\n AudioMemoryStats,\n AudioMonitor,\n AudioPerformanceStats,\n MemoryWarning,\n} from \"./AudioMonitor\";\nimport { AudioElementPool, PoolStatistics } from \"./AudioPool\";\nimport {\n AudioAsset,\n AudioConfig,\n IAudioManager,\n MusicTrackId,\n SoundEffectId,\n} from \"./types\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\n/**\n * Critical assets that should never be evicted from cache\n * 캐시에서 절대 제거되지 않아야 하는 중요한 자산\n */\nconst CRITICAL_AUDIO_ASSETS = [\n // Menu sounds - instant playback required\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n // Common combat sounds - instant playback required\n \"hit_impact\",\n \"hit_light\",\n \"hit_medium\",\n \"hit_heavy\",\n \"guard_block\",\n \"attack_whoosh\",\n \"attack_light\",\n \"stance_change\",\n] as const;\n\nexport class AudioManager implements IAudioManager {\n private _masterVolume: number = 1.0;\n private _musicVolume: number = 0.7;\n private _sfxVolume: number = 0.8;\n private _muted: boolean = false;\n private _currentMusicTrack: string | null = null;\n private _fallbackMode: boolean = false;\n private currentMusic: HTMLAudioElement | null = null;\n private soundCache: Map<string, HTMLAudioElement> = new Map();\n private _isInitialized: boolean = false;\n\n // Track music assets currently being loaded to prevent race conditions\n // 레이스 컨디션 방지를 위해 현재 로드 중인 음악 자산 추적\n private loadingMusic: Set<string> = new Set();\n\n // New optimized components\n private assetLoader: AudioAssetLoader;\n private audioPool: AudioElementPool;\n private monitor: AudioMonitor;\n private audioCache: AudioCache;\n private frequentSounds: Set<string> = new Set([\n \"hit_light\",\n \"attack_light\",\n \"stance_change\",\n ]);\n\n constructor(config?: Partial<AudioConfig>) {\n if (config) {\n this._masterVolume = config.masterVolume ?? 1.0;\n this._musicVolume = config.musicVolume ?? 0.7;\n this._sfxVolume = config.sfxVolume ?? 0.8;\n }\n\n // Initialize new components\n this.assetLoader = new AudioAssetLoader();\n this.audioPool = new AudioElementPool();\n this.monitor = new AudioMonitor();\n\n // Initialize AudioCache with 30MB limit and critical assets\n // 30MB 제한 및 중요 자산으로 AudioCache 초기화\n const cacheConfig: AudioCacheConfig = {\n maxSizeBytes: 30 * 1024 * 1024, // 30MB default\n criticalAssets: [...CRITICAL_AUDIO_ASSETS],\n debug: typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"development\",\n };\n this.audioCache = new AudioCache(cacheConfig);\n }\n\n /**\n * Helper method to release pooled audio back to the pool after playback\n * @param id - Asset ID\n * @param audio - Audio element to release\n */\n private releasePooledAudio(id: string, audio: HTMLAudioElement): void {\n if (!audio.paused) {\n audio.addEventListener(\n \"ended\",\n () => {\n this.audioPool.release(id, audio);\n },\n { once: true }\n );\n } else {\n this.audioPool.release(id, audio);\n }\n }\n\n // Interface getters\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get fallbackMode(): boolean {\n return this._fallbackMode;\n }\n\n get currentMusicTrack(): string | null {\n return this._currentMusicTrack;\n }\n\n get masterVolume(): number {\n return this._masterVolume;\n }\n\n get sfxVolume(): number {\n return this._sfxVolume;\n }\n\n get musicVolume(): number {\n return this._musicVolume;\n }\n\n get muted(): boolean {\n return this._muted;\n }\n\n async initialize(config?: AudioConfig): Promise<void> {\n try {\n // Create AudioContext using globalThis for cross-platform compatibility\n const AudioContextClass =\n globalThis.AudioContext ||\n (globalThis as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n new AudioContextClass();\n this._isInitialized = true;\n this._fallbackMode = false;\n\n if (config) {\n this._masterVolume = config.masterVolume ?? this._masterVolume;\n this._musicVolume = config.musicVolume ?? this._musicVolume;\n this._sfxVolume = config.sfxVolume ?? this._sfxVolume;\n }\n\n // Set memory threshold if configured\n if (config?.maxSimultaneousSounds) {\n const estimatedMemoryMB =\n config.maxSimultaneousSounds * ESTIMATED_ASSET_SIZE_MB;\n this.monitor.setMemoryThreshold(estimatedMemoryMB);\n }\n } catch (error) {\n console.warn(\n \"AudioContext initialization failed, using fallback mode:\",\n error\n );\n this._isInitialized = true;\n this._fallbackMode = true;\n }\n }\n\n async loadAsset(asset: AudioAsset, options?: LoadOptions): Promise<void> {\n const startTime = performance.now();\n\n try {\n // Check cache first - instant playback for cached assets\n // 캐시 먼저 확인 - 캐시된 자산의 즉시 재생\n const cachedAsset = this.audioCache.get(asset.id);\n if (cachedAsset) {\n // Asset already loaded in cache, use it\n const cached = this.soundCache.get(asset.id);\n if (cached) {\n return; // Already loaded and cached\n }\n }\n\n // Use faster timeouts for testing environment\n // Safely check for Node.js environment to avoid runtime errors in browsers\n const isTest =\n typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"test\";\n const loadOptions: LoadOptions = {\n timeout: isTest ? 100 : 10000,\n maxRetries: isTest ? 1 : 3,\n retryDelay: isTest ? 10 : 1000,\n ...options,\n };\n\n // Use AudioAssetLoader with retry and fallback\n const result = await this.assetLoader.loadAsset(asset, loadOptions);\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Estimate size for cache management\n // 캐시 관리를 위한 크기 추정\n const estimatedSize = this.estimateAudioSize(asset);\n\n // Add to LRU cache with size tracking\n // 크기 추적과 함께 LRU 캐시에 추가\n this.audioCache.set(asset.id, asset, estimatedSize);\n\n // Track performance and memory\n const loadTime = performance.now() - startTime;\n this.monitor.recordLoad(asset.id, loadTime, estimatedSize / (1024 * 1024));\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n } else {\n // Record failure\n const error = result.error ?? new Error(\"Unknown load error\");\n this.monitor.recordLoadFailure(asset.id, error);\n\n // Still cache the placeholder audio if available\n if (result.audio) {\n this.soundCache.set(asset.id, result.audio);\n\n // Add to cache even if failed (placeholder)\n const estimatedSize = this.estimateAudioSize(asset);\n this.audioCache.set(asset.id, asset, estimatedSize);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.warn(`Failed to load audio asset ${asset.id}:`, err);\n this.monitor.recordLoadFailure(asset.id, err);\n }\n }\n\n /**\n * Estimate audio asset size in bytes\n * 바이트 단위의 오디오 자산 크기 추정\n *\n * @returns Estimated size in bytes\n */\n private estimateAudioSize(_asset: AudioAsset): number {\n // Use ESTIMATED_ASSET_SIZE_MB constant for consistent estimation\n // Conservative estimate: ~0.5MB per audio file (compressed MP3/WebM)\n return ESTIMATED_ASSET_SIZE_MB * 1024 * 1024;\n }\n\n async playSoundEffect(id: SoundEffectId): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n // Alias for playSoundEffect to match interface\n async playSFX(id: SoundEffectId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n async playMusic(id: MusicTrackId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n this.stopMusic();\n\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n let audio = this.soundCache.get(id);\n \n // On-demand loading: If music not in cache, load it first\n // 온디맨드 로딩: 음악이 캐시에 없으면 먼저 로드\n if (!audio) {\n // Check if this music is already being loaded to prevent race conditions\n // JavaScript's single-threaded nature ensures atomic check-and-add operations\n // 레이스 컨디션 방지를 위해 이미 로드 중인지 확인\n // JavaScript의 싱글 스레드 특성으로 원자적 check-and-add 작업 보장\n if (this.loadingMusic.has(id)) {\n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" is already being loaded, waiting...`);\n }\n // Wait for the ongoing load to complete\n // 진행 중인 로드가 완료될 때까지 대기\n await this.waitForMusicLoad(id);\n audio = this.soundCache.get(id);\n } else {\n // Mark as loading before starting the load (atomic operation in single-threaded JS)\n // 로드 시작 전에 로딩 중으로 표시 (싱글 스레드 JS에서 원자적 작업)\n this.loadingMusic.add(id);\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" not in cache, loading on-demand...`);\n }\n \n try {\n const { audioAssetRegistry } = await import(\"./AudioAssetRegistry\");\n const musicAsset = audioAssetRegistry.getMusic(id);\n \n if (musicAsset) {\n try {\n await this.loadAsset(musicAsset);\n audio = this.soundCache.get(id);\n \n if (import.meta.env.DEV && audio) {\n console.log(`[AudioManager] Successfully loaded music \"${id}\" on-demand`);\n }\n } catch (error) {\n console.warn(`Failed to load music asset ${id} on-demand:`, error);\n return;\n }\n } else {\n console.warn(`Music asset not found in registry: ${id}`);\n return;\n }\n } finally {\n // Always remove from loading set when done\n // 완료 시 항상 로딩 세트에서 제거\n this.loadingMusic.delete(id);\n }\n }\n }\n\n if (audio) {\n try {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._musicVolume) * this._masterVolume;\n audio.loop = true;\n this.currentMusic = audio;\n this._currentMusicTrack = id;\n await audio.play();\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Playing music: ${id} (volume: ${audio.volume.toFixed(2)})`);\n }\n } catch (error) {\n console.warn(`Failed to play music ${id}:`, error);\n }\n }\n }\n\n /**\n * Wait for a music asset to finish loading\n * 음악 자산 로드가 완료될 때까지 대기\n * @param id - Music track ID\n * @param maxWaitMs - Maximum time to wait in milliseconds\n */\n private async waitForMusicLoad(id: string, maxWaitMs: number = 5000): Promise<void> {\n const startTime = Date.now();\n const checkInterval = 100; // Check every 100ms\n \n while (this.loadingMusic.has(id)) {\n if (Date.now() - startTime > maxWaitMs) {\n console.warn(`[AudioManager] Timeout waiting for music \"${id}\" to load`);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval));\n }\n }\n\n stopMusic(): void {\n if (this.currentMusic) {\n this.currentMusic.pause();\n this.currentMusic.currentTime = 0;\n this.currentMusic = null;\n this._currentMusicTrack = null;\n }\n }\n\n stopAll(): void {\n this.stopMusic();\n this.soundCache.forEach((audio) => {\n if (!audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n });\n }\n\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void {\n const clampedVolume = Math.max(0, Math.min(1, volume));\n\n switch (type) {\n case \"master\":\n this._masterVolume = clampedVolume;\n break;\n case \"sfx\":\n this._sfxVolume = clampedVolume;\n break;\n case \"music\":\n this._musicVolume = clampedVolume;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n break;\n case \"voice\":\n // Handle voice volume if needed\n break;\n }\n }\n\n mute(): void {\n this._muted = true;\n if (this.currentMusic) {\n this.currentMusic.volume = 0;\n }\n }\n\n unmute(): void {\n this._muted = false;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n }\n\n async fadeOut(duration: number = 1000): Promise<void> {\n if (!this.currentMusic) return;\n\n const musicElement = this.currentMusic;\n return new Promise((resolve) => {\n const startVolume = musicElement.volume;\n const fadeStep = startVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume > 0) {\n this.currentMusic.volume = Math.max(\n 0,\n this.currentMusic.volume - fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n this.stopMusic();\n resolve();\n }\n }, 50);\n });\n }\n\n async fadeIn(trackId: MusicTrackId, duration: number = 1000): Promise<void> {\n await this.playMusic(trackId, 0);\n\n if (!this.currentMusic) return;\n\n return new Promise((resolve) => {\n const targetVolume = this._musicVolume * this._masterVolume;\n const fadeStep = targetVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume < targetVolume) {\n this.currentMusic.volume = Math.min(\n targetVolume,\n this.currentMusic.volume + fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n resolve();\n }\n }, 50);\n });\n }\n\n async crossfade(\n fromTrackId: MusicTrackId,\n toTrackId: MusicTrackId,\n duration: number = 1000\n ): Promise<void> {\n // Fix: Remove unused fromTrackId parameter or use it properly\n const fadeOutPromise = this.fadeOut(duration);\n await fadeOutPromise;\n await this.fadeIn(toTrackId, duration);\n console.log(`Crossfaded from ${fromTrackId} to ${toTrackId}`);\n }\n\n getLoadedAssets(): ReadonlyMap<string, HTMLAudioElement> {\n return new Map(this.soundCache);\n }\n\n // Additional methods to match interface\n async playVoice(id: string): Promise<void> {\n return this.playSoundEffect(id);\n }\n\n async playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void> {\n const soundId = `${archetype}_${techniqueId}`;\n return this.playSoundEffect(soundId);\n }\n\n async playTrigramStanceSound(stance: string): Promise<void> {\n const soundId = `stance_${stance}`;\n return this.playSoundEffect(soundId);\n }\n\n async playVitalPointHitSound(severity: string): Promise<void> {\n const soundId = `vital_point_${severity}`;\n return this.playSoundEffect(soundId);\n }\n\n async playDojiangAmbience(): Promise<void> {\n return this.playMusic(\"dojang_ambience\");\n }\n\n // Legacy getters for backward compatibility\n getMasterVolume(): number {\n return this._masterVolume;\n }\n\n getMusicVolume(): number {\n return this._musicVolume;\n }\n\n getSfxVolume(): number {\n return this._sfxVolume;\n }\n\n get initialized(): boolean {\n return this._isInitialized;\n }\n\n // New optimized methods\n\n /**\n * Batch load multiple assets with progress tracking\n * @param assets - Array of audio assets to load\n * @param options - Optional load configuration (timeout, retries, etc.)\n * @param onProgress - Optional callback for progress updates with loaded and total counts\n * @returns Promise that resolves when all assets are processed\n */\n async batchLoadAssets(\n assets: readonly AudioAsset[],\n options?: LoadOptions,\n onProgress?: (loaded: number, total: number) => void\n ): Promise<void> {\n const results = await this.assetLoader.batchLoad(\n assets,\n options,\n (progress) => {\n onProgress?.(progress.loaded, progress.total);\n }\n );\n\n // Process results\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const asset = assets[i];\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n }\n }\n }\n\n /**\n * Unload an asset to free memory\n * @param assetId - ID of the asset to unload\n * @returns true if asset was unloaded, false if asset was not found\n */\n unloadAsset(assetId: string): boolean {\n // Remove from cache\n const audio = this.soundCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n this.soundCache.delete(assetId);\n }\n\n // Remove from AudioCache (LRU cache)\n // LRU 캐시에서 제거\n const removedFromCache = this.audioCache.remove(assetId);\n\n // Remove from loader cache\n const unloaded = this.assetLoader.unloadAsset(assetId);\n\n // Remove pool if exists\n if (this.audioPool.hasPool(assetId)) {\n this.audioPool.removePool(assetId);\n }\n\n // Unregister from monitor\n this.monitor.unregisterAsset(assetId);\n\n return unloaded || audio !== undefined || removedFromCache;\n }\n\n /**\n * Get cache statistics including LRU cache metrics\n * LRU 캐시 메트릭을 포함한 캐시 통계 가져오기\n *\n * @returns Cache statistics\n */\n getCacheStats(): {\n readonly lruCache: {\n readonly totalSize: number;\n readonly assetCount: number;\n readonly criticalCount: number;\n readonly utilizationPercent: number;\n readonly evictionCount: number;\n readonly hitCount: number;\n readonly missCount: number;\n readonly hitRate: number;\n };\n readonly soundCache: number;\n readonly poolStats: Map<string, PoolStatistics>;\n } {\n return {\n lruCache: this.audioCache.getStats(),\n soundCache: this.soundCache.size,\n poolStats: this.audioPool.getAllStatistics(),\n };\n }\n\n /**\n * Get memory statistics including total loaded MB, asset count, and warnings\n * @returns Memory statistics object\n */\n getMemoryStats(): AudioMemoryStats {\n return this.monitor.getMemoryStats();\n }\n\n /**\n * Get performance statistics including load times and playback latency\n * @returns Performance statistics object\n */\n getPerformanceStats(): AudioPerformanceStats {\n return this.monitor.getPerformanceStats();\n }\n\n /**\n * Get FPS impact analysis showing baseline vs current FPS and detected drops\n * @returns FPS impact analysis object\n */\n getFPSImpact(): AudioFPSImpact {\n return this.monitor.getFPSImpact();\n }\n\n /**\n * Update FPS measurement for monitoring\n * @param fps - Current frames per second measurement\n */\n updateFPS(fps: number): void {\n this.monitor.updateFPS(fps);\n }\n\n /**\n * Get comprehensive monitoring report including memory, performance, FPS, and warnings\n * @returns Comprehensive monitoring report object\n */\n getMonitoringReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return this.monitor.getReport();\n }\n\n /**\n * Get all memory warnings\n * @returns Array of memory warnings\n */\n getMemoryWarnings(): readonly MemoryWarning[] {\n return this.monitor.getWarnings();\n }\n\n /**\n * Clear memory warnings\n */\n clearMemoryWarnings(): void {\n this.monitor.clearWarnings();\n }\n\n /**\n * Get pool statistics for a specific asset or all pools\n * @param assetId - Optional asset ID to get specific pool stats\n * @returns Pool statistics for the specified asset or all pools\n */\n getPoolStatistics(\n assetId?: string\n ): PoolStatistics | Map<string, PoolStatistics> | undefined {\n if (assetId) {\n return this.audioPool.getPoolStatistics(assetId);\n }\n return this.audioPool.getAllStatistics();\n }\n\n /**\n * Get loader statistics including cached assets and loading state\n * @returns Loader statistics object\n */\n getLoaderStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return this.assetLoader.getStatistics();\n }\n}\n\nexport default AudioManager;\n"],"mappings":"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"}
|
package/lib/audio/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/audio/types.ts"],"sourcesContent":["// filepath: /workspaces/blacktrigram/src/audio/types.ts\n/**\n * Type definitions for audio types\n * Auto-generated by type migration script\n */\n\nimport { KoreanText, PlayerArchetype, TrigramStance } from \"@/types\";\n\n// Export AudioCategory enum (only one declaration)\nexport enum AudioCategory {\n SFX = \"sfx\",\n MUSIC = \"music\",\n VOICE = \"voice\",\n UI = \"ui\",\n // Remove AMBIENT since it's causing errors\n}\n\n// Audio context for spatial audio\nexport interface AudioContext3D {\n readonly position: { x: number; y: number; z?: number };\n readonly velocity?: { x: number; y: number; z?: number };\n readonly orientation?: { x: number; y: number; z?: number };\n readonly maxDistance?: number;\n readonly rolloffFactor?: number;\n}\n\n// Combat audio mapping\n// Add missing type exports\nexport type SoundEffectId = string;\nexport type MusicTrackId = string;\nexport type VoiceLineId = string;\n\n// Audio format type (string literals only, not enum)\nexport type AudioFormat =\n | \"audio/mp3\"\n | \"audio/wav\"\n | \"audio/ogg\"\n | \"audio/webm\";\n\n// Audio effect definitions\nexport interface AudioEffect {\n readonly type: \"reverb\" | \"delay\" | \"distortion\" | \"filter\" | \"compressor\";\n readonly parameters: Record<string, number>;\n readonly enabled: boolean;\n}\n\n// Audio mixer channel\nexport interface AudioChannel {\n readonly id: string;\n readonly category: AudioCategory;\n readonly volume: number;\n readonly muted: boolean;\n readonly effects: readonly AudioEffect[];\n readonly connectedSources: readonly string[];\n}\n\n// Audio platform capabilities\nexport interface AudioCapabilities {\n readonly supportsWebAudio: boolean;\n readonly supportsHowler: boolean;\n readonly maxSources: number;\n readonly formats: readonly string[];\n readonly spatialAudio: boolean;\n readonly realTimeEffects: boolean;\n}\n\nexport interface IAudioManager {\n readonly isInitialized: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n\n initialize(config?: AudioConfig): Promise<void>;\n loadAsset(asset: AudioAsset): Promise<void>; // ✅ Added missing method\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void;\n playMusic(trackId: string): Promise<void>;\n playSoundEffect(soundId: string): Promise<void>;\n playSFX(soundId: string, volume?: number): Promise<void>; // ✅ Added missing playSFX method\n stopMusic(): void;\n mute(): void;\n unmute(): void;\n fadeIn(trackId: string, duration?: number): Promise<void>; // ✅ Added for combat audio\n fadeOut(duration?: number): Promise<void>; // ✅ Added for combat audio\n playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void>;\n playTrigramStanceSound(stance: string): Promise<void>;\n playVitalPointHitSound(severity: string): Promise<void>;\n playDojiangAmbience(): Promise<void>;\n}\n\nexport interface AudioAsset {\n readonly id: string;\n readonly name?: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n readonly volume?: number;\n readonly category?: string;\n}\n\nexport interface MusicTrack extends AudioAsset {\n readonly type: \"music\";\n readonly title?: KoreanText;\n readonly artist?: string;\n readonly album?: string;\n readonly bpm?: number;\n readonly loop?: boolean;\n readonly fadeInTime?: number;\n readonly fadeOutTime?: number;\n readonly variations?: readonly string[];\n readonly category: \"music\" | \"voice\";\n}\n\nexport interface SoundEffect extends AudioAsset {\n readonly type: \"sound\";\n readonly pitch?: number;\n readonly variations?: readonly string[];\n readonly category: \"sfx\" | \"ui\";\n}\n\nexport interface VoiceLine extends AudioAsset {\n readonly type: \"voice\";\n readonly text: KoreanText;\n readonly archetype?: PlayerArchetype;\n readonly emotion?:\n | \"neutral\"\n | \"aggressive\"\n | \"defensive\"\n | \"victorious\"\n | \"defeated\";\n category?: AudioCategory;\n volume?: number;\n}\n\nexport interface AudioConfig {\n readonly enableSpatialAudio: boolean;\n readonly maxSimultaneousSounds: number;\n readonly audioFormats: readonly string[];\n readonly fadeTransitionTime: number;\n readonly defaultVolume?: number;\n masterVolume?: number;\n musicVolume?: number;\n sfxVolume?: number;\n}\n\nexport interface AudioEvent {\n readonly type: \"play\" | \"stop\" | \"pause\" | \"resume\" | \"volume\" | \"fade\";\n readonly assetId: string;\n readonly volume?: number;\n readonly delay?: number;\n readonly fadeTime?: number;\n readonly loop?: boolean;\n readonly priority?: number;\n}\n\nexport interface CombatAudioMap {\n readonly attacks: Record<string, SoundEffectId>;\n readonly impacts: Record<string, SoundEffectId>;\n readonly stances: Record<TrigramStance, string>;\n readonly environments: Record<string, SoundEffectId>;\n readonly ui: Record<string, SoundEffectId>;\n}\n\nexport interface AudioState {\n readonly isPlaying: boolean;\n readonly isPaused: boolean;\n readonly currentTime: number;\n readonly duration: number;\n readonly volume: number;\n readonly loop: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n readonly currentMusicTrack: string | null;\n readonly isInitialized: boolean;\n readonly fallbackMode: boolean;\n}\n\nexport interface AudioPlaybackOptions {\n readonly volume?: number;\n readonly loop?: boolean;\n readonly fadeIn?: number;\n readonly fadeOut?: number;\n readonly delay?: number;\n readonly startTime?: number;\n readonly endTime?: number;\n readonly rate?: number;\n}\n\nexport interface ProceduralSoundConfig {\n readonly frequency: number;\n readonly duration: number;\n readonly type: \"sine\" | \"square\" | \"sawtooth\" | \"triangle\" | \"noise\";\n readonly attack?: number;\n readonly decay?: number;\n readonly sustain?: number;\n readonly release?: number;\n readonly volume?: number;\n}\n\nexport interface CombatAudioEvent {\n readonly type: \"attack\" | \"hit\" | \"block\" | \"dodge\" | \"stance_change\";\n readonly technique?: string;\n readonly stance?: string;\n readonly damage?: number;\n readonly critical?: boolean;\n}\n\n/**\n * Audio-specific body regions for impact sound mapping\n * Maps to Korean martial arts vital point locations\n */\nexport type AudioBodyRegion =\n | \"head\" // 두부 (Head/Skull): temple, jaw, neck\n | \"torso\" // 몸통 (Torso): ribs, sternum, solar plexus, organs\n | \"arms\" // 팔 (Arms): shoulder, elbow, forearm, wrist\n | \"legs\" // 다리 (Legs): hip, knee, shin, ankle\n | \"soft_tissue\"; // 연조직 (Soft tissue): muscle, flesh, non-bone areas\n\n/**\n * Impact intensity levels for bone/flesh contact\n * Determines audio selection and volume variation\n */\nexport type ImpactIntensity =\n | \"light\" // 경타 (Light): Glancing blows, minimal damage\n | \"medium\" // 중타 (Medium): Solid contact, moderate damage\n | \"heavy\" // 강타 (Heavy): Devastating strikes, severe damage\n | \"critical\" // 급소타 (Critical): Vital point precision strikes\n | \"fracture\"; // 골절 (Fracture): Bone-breaking force, <30% health\n\n/**\n * Bone impact audio event with body region and intensity\n * Used for anatomically accurate combat sound feedback\n */\nexport interface BoneImpactEvent {\n readonly region: AudioBodyRegion;\n readonly intensity: ImpactIntensity;\n readonly vitalPoint?: boolean; // True if hitting a vital point\n readonly remainingHealth?: number; // For fracture detection (<30%)\n}\n\nexport interface AudioLoadingState {\n readonly total: number;\n readonly loaded: number;\n readonly failed: number;\n readonly currentAsset?: string;\n readonly progress: number;\n readonly errors: readonly string[];\n}\nexport interface AudioSystemInterface {\n playSFX: (id: SoundEffectId, options?: AudioPlaybackOptions) => void;\n playMusic: (id: MusicTrackId, options?: AudioPlaybackOptions) => void;\n stopMusic: (id?: MusicTrackId, fadeOutDuration?: number) => void;\n setVolume: (type: \"master\" | \"sfx\" | \"music\", volume: number) => void;\n loadAudioAsset: (asset: AudioAsset) => Promise<void>;\n isMusicPlaying: (id?: MusicTrackId) => boolean;\n}\n\nexport interface AudioManagerInterface extends IAudioManager {}\n"],"mappings":";AASA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/audio/types.ts"],"sourcesContent":["// filepath: /workspaces/blacktrigram/src/audio/types.ts\n/**\n * Type definitions for audio types\n * Auto-generated by type migration script\n */\n\nimport { KoreanText, PlayerArchetype, TrigramStance } from \"@/types\";\n\n// Export AudioCategory enum (only one declaration)\nexport enum AudioCategory {\n SFX = \"sfx\",\n MUSIC = \"music\",\n VOICE = \"voice\",\n UI = \"ui\",\n // Remove AMBIENT since it's causing errors\n}\n\n// Audio context for spatial audio\nexport interface AudioContext3D {\n readonly position: { x: number; y: number; z?: number };\n readonly velocity?: { x: number; y: number; z?: number };\n readonly orientation?: { x: number; y: number; z?: number };\n readonly maxDistance?: number;\n readonly rolloffFactor?: number;\n}\n\n// Combat audio mapping\n// Add missing type exports\nexport type SoundEffectId = string;\nexport type MusicTrackId = string;\nexport type VoiceLineId = string;\n\n// Audio format type (string literals only, not enum)\nexport type AudioFormat =\n | \"audio/mp3\"\n | \"audio/wav\"\n | \"audio/ogg\"\n | \"audio/webm\";\n\n// Audio effect definitions\nexport interface AudioEffect {\n readonly type: \"reverb\" | \"delay\" | \"distortion\" | \"filter\" | \"compressor\";\n readonly parameters: Record<string, number>;\n readonly enabled: boolean;\n}\n\n// Audio mixer channel\nexport interface AudioChannel {\n readonly id: string;\n readonly category: AudioCategory;\n readonly volume: number;\n readonly muted: boolean;\n readonly effects: readonly AudioEffect[];\n readonly connectedSources: readonly string[];\n}\n\n// Audio platform capabilities\nexport interface AudioCapabilities {\n readonly supportsWebAudio: boolean;\n readonly supportsHowler: boolean;\n readonly maxSources: number;\n readonly formats: readonly string[];\n readonly spatialAudio: boolean;\n readonly realTimeEffects: boolean;\n}\n\nexport interface IAudioManager {\n readonly isInitialized: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n\n initialize(config?: AudioConfig): Promise<void>;\n loadAsset(asset: AudioAsset): Promise<void>; // ✅ Added missing method\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void;\n playMusic(trackId: string): Promise<void>;\n playSoundEffect(soundId: string): Promise<void>;\n playSFX(soundId: string, volume?: number): Promise<void>; // ✅ Added missing playSFX method\n stopMusic(): void;\n mute(): void;\n unmute(): void;\n fadeIn(trackId: string, duration?: number): Promise<void>; // ✅ Added for combat audio\n fadeOut(duration?: number): Promise<void>; // ✅ Added for combat audio\n playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void>;\n playTrigramStanceSound(stance: string): Promise<void>;\n playVitalPointHitSound(severity: string): Promise<void>;\n playDojiangAmbience(): Promise<void>;\n}\n\nexport interface AudioAsset {\n readonly id: string;\n readonly name?: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n readonly volume?: number;\n readonly category?: string;\n}\n\nexport interface MusicTrack extends AudioAsset {\n readonly type: \"music\";\n readonly title?: KoreanText;\n readonly artist?: string;\n readonly album?: string;\n readonly bpm?: number;\n readonly loop?: boolean;\n readonly fadeInTime?: number;\n readonly fadeOutTime?: number;\n readonly variations?: readonly string[];\n readonly category: \"music\" | \"voice\";\n}\n\nexport interface SoundEffect extends AudioAsset {\n readonly type: \"sound\";\n readonly pitch?: number;\n readonly variations?: readonly string[];\n readonly category: \"sfx\" | \"ui\";\n}\n\nexport interface VoiceLine extends AudioAsset {\n readonly type: \"voice\";\n readonly text: KoreanText;\n readonly archetype?: PlayerArchetype;\n readonly emotion?:\n | \"neutral\"\n | \"aggressive\"\n | \"defensive\"\n | \"victorious\"\n | \"defeated\";\n category?: AudioCategory;\n volume?: number;\n}\n\nexport interface AudioConfig {\n readonly enableSpatialAudio: boolean;\n readonly maxSimultaneousSounds: number;\n readonly audioFormats: readonly string[];\n readonly fadeTransitionTime: number;\n readonly defaultVolume?: number;\n masterVolume?: number;\n musicVolume?: number;\n sfxVolume?: number;\n}\n\nexport interface AudioEvent {\n readonly type: \"play\" | \"stop\" | \"pause\" | \"resume\" | \"volume\" | \"fade\";\n readonly assetId: string;\n readonly volume?: number;\n readonly delay?: number;\n readonly fadeTime?: number;\n readonly loop?: boolean;\n readonly priority?: number;\n}\n\nexport interface CombatAudioMap {\n readonly attacks: Record<string, SoundEffectId>;\n readonly impacts: Record<string, SoundEffectId>;\n readonly stances: Record<TrigramStance, string>;\n readonly environments: Record<string, SoundEffectId>;\n readonly ui: Record<string, SoundEffectId>;\n}\n\nexport interface AudioState {\n readonly isPlaying: boolean;\n readonly isPaused: boolean;\n readonly currentTime: number;\n readonly duration: number;\n readonly volume: number;\n readonly loop: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n readonly currentMusicTrack: string | null;\n readonly isInitialized: boolean;\n readonly fallbackMode: boolean;\n}\n\nexport interface AudioPlaybackOptions {\n readonly volume?: number;\n readonly loop?: boolean;\n readonly fadeIn?: number;\n readonly fadeOut?: number;\n readonly delay?: number;\n readonly startTime?: number;\n readonly endTime?: number;\n readonly rate?: number;\n}\n\nexport interface ProceduralSoundConfig {\n readonly frequency: number;\n readonly duration: number;\n readonly type: \"sine\" | \"square\" | \"sawtooth\" | \"triangle\" | \"noise\";\n readonly attack?: number;\n readonly decay?: number;\n readonly sustain?: number;\n readonly release?: number;\n readonly volume?: number;\n}\n\nexport interface CombatAudioEvent {\n readonly type: \"attack\" | \"hit\" | \"block\" | \"dodge\" | \"stance_change\";\n readonly technique?: string;\n readonly stance?: string;\n readonly damage?: number;\n readonly critical?: boolean;\n}\n\n/**\n * Audio-specific body regions for impact sound mapping\n * Maps to Korean martial arts vital point locations\n */\nexport type AudioBodyRegion =\n | \"head\" // 두부 (Head/Skull): temple, jaw, neck\n | \"torso\" // 몸통 (Torso): ribs, sternum, solar plexus, organs\n | \"arms\" // 팔 (Arms): shoulder, elbow, forearm, wrist\n | \"legs\" // 다리 (Legs): hip, knee, shin, ankle\n | \"soft_tissue\"; // 연조직 (Soft tissue): muscle, flesh, non-bone areas\n\n/**\n * Impact intensity levels for bone/flesh contact\n * Determines audio selection and volume variation\n */\nexport type ImpactIntensity =\n | \"light\" // 경타 (Light): Glancing blows, minimal damage\n | \"medium\" // 중타 (Medium): Solid contact, moderate damage\n | \"heavy\" // 강타 (Heavy): Devastating strikes, severe damage\n | \"critical\" // 급소타 (Critical): Vital point precision strikes\n | \"fracture\"; // 골절 (Fracture): Bone-breaking force, <30% health\n\n/**\n * Bone impact audio event with body region and intensity\n * Used for anatomically accurate combat sound feedback\n */\nexport interface BoneImpactEvent {\n readonly region: AudioBodyRegion;\n readonly intensity: ImpactIntensity;\n readonly vitalPoint?: boolean; // True if hitting a vital point\n readonly remainingHealth?: number; // For fracture detection (<30%)\n}\n\nexport interface AudioLoadingState {\n readonly total: number;\n readonly loaded: number;\n readonly failed: number;\n readonly currentAsset?: string;\n readonly progress: number;\n readonly errors: readonly string[];\n}\nexport interface AudioSystemInterface {\n playSFX: (id: SoundEffectId, options?: AudioPlaybackOptions) => void;\n playMusic: (id: MusicTrackId, options?: AudioPlaybackOptions) => void;\n stopMusic: (id?: MusicTrackId, fadeOutDuration?: number) => void;\n setVolume: (type: \"master\" | \"sfx\" | \"music\", volume: number) => void;\n loadAudioAsset: (asset: AudioAsset) => Promise<void>;\n isMusicPlaying: (id?: MusicTrackId) => boolean;\n}\n\nexport interface AudioManagerInterface extends IAudioManager {}\n"],"mappings":";AASA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,SAAM;AACN,eAAA,WAAQ;AACR,eAAA,WAAQ;AACR,eAAA,QAAK;;KAEN"}
|
|
@@ -21,7 +21,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
21
21
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
22
22
|
import { Canvas } from "@react-three/fiber";
|
|
23
23
|
//#region src/components/screens/intro/IntroScreen3D.tsx
|
|
24
|
-
var APP_VERSION = "0.7.
|
|
24
|
+
var APP_VERSION = "0.7.24";
|
|
25
25
|
var MENU_ITEMS = [
|
|
26
26
|
{
|
|
27
27
|
mode: GameMode.VERSUS,
|
|
@@ -183,7 +183,7 @@ var SplashScreen = ({ onStart, width, height }) => {
|
|
|
183
183
|
}),
|
|
184
184
|
/* @__PURE__ */ jsxs("div", {
|
|
185
185
|
role: "contentinfo",
|
|
186
|
-
"aria-label": `Application version 0.7.
|
|
186
|
+
"aria-label": `Application version 0.7.24`,
|
|
187
187
|
style: {
|
|
188
188
|
position: "absolute",
|
|
189
189
|
bottom: "20px",
|
|
@@ -192,7 +192,7 @@ var SplashScreen = ({ onStart, width, height }) => {
|
|
|
192
192
|
fontSize: "10px",
|
|
193
193
|
zIndex: 1
|
|
194
194
|
},
|
|
195
|
-
children: ["v", "0.7.
|
|
195
|
+
children: ["v", "0.7.24"]
|
|
196
196
|
})
|
|
197
197
|
]
|
|
198
198
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdaptiveDifficulty.js","names":[],"sources":["../../../src/systems/ai/AdaptiveDifficulty.ts"],"sourcesContent":["/**\n * Adaptive Difficulty System for Korean Martial Arts Combat\n * Tracks player skill metrics and adjusts AI difficulty dynamically\n */\n\nimport { AIPersonality } from \"./AIPersonality\";\n\n/**\n * Player skill metrics tracked for adaptive difficulty\n */\nexport interface PlayerSkillMetrics {\n averageAccuracy: number; // 0.0-1.0: Hit rate\n comboCount: number; // Total combos executed\n perfectBlocks: number; // Perfect timing blocks\n reactionTime: number; // Average reaction time in ms\n vitalPointHits: number; // Successful vital point strikes\n stanceTransitions: number; // Effective stance changes\n damageEfficiency: number; // 0.0-1.0: Damage dealt vs taken ratio\n matchesPlayed: number; // Total matches for scaling\n}\n\n/**\n * Difficulty tier levels (5 tiers for adaptive system)\n */\nexport enum DifficultyTier {\n BEGINNER = 1,\n NOVICE = 2,\n INTERMEDIATE = 3,\n ADVANCED = 4,\n EXPERT = 5,\n}\n\n/**\n * Difficulty parameters that control AI behavior\n * Applied dynamically based on player skill level\n * \n * @korean 난이도 매개변수 - 플레이어 실력에 따라 AI 행동 제어\n */\nexport interface DifficultyParameters {\n /** AI reaction time range in milliseconds */\n readonly reactionTimeMs: { readonly min: number; readonly max: number };\n /** Accuracy for vital point strikes (0.0-1.0) */\n readonly vitalPointAccuracy: number;\n /** Accuracy for basic attacks (0.0-1.0) */\n readonly basicAttackAccuracy: number;\n /** Block timing window in milliseconds (smaller = harder to block) */\n readonly blockTimingWindow: number;\n /** Decision quality affects technique selection optimality (0.0-1.0) */\n readonly decisionQuality: number;\n /** Aggression modifier multiplier (0.5-2.0) */\n readonly aggressionModifier: number;\n /** Chance to attempt combo sequences (0.0-1.0) */\n readonly comboChance: number;\n}\n\n/**\n * Difficulty parameter sets for each skill tier\n * Defines AI behavior characteristics at each difficulty level\n * \n * @korean 각 난이도 단계별 매개변수 설정\n */\nexport const DIFFICULTY_PARAMETERS: Record<DifficultyTier, DifficultyParameters> = {\n [DifficultyTier.BEGINNER]: {\n reactionTimeMs: { min: 800, max: 1200 },\n vitalPointAccuracy: 0.40,\n basicAttackAccuracy: 0.70,\n blockTimingWindow: 150,\n decisionQuality: 0.50,\n aggressionModifier: 0.7,\n comboChance: 0.20,\n },\n [DifficultyTier.NOVICE]: {\n reactionTimeMs: { min: 500, max: 800 },\n vitalPointAccuracy: 0.55,\n basicAttackAccuracy: 0.78,\n blockTimingWindow: 120,\n decisionQuality: 0.65,\n aggressionModifier: 0.9,\n comboChance: 0.35,\n },\n [DifficultyTier.INTERMEDIATE]: {\n reactionTimeMs: { min: 300, max: 500 },\n vitalPointAccuracy: 0.65,\n basicAttackAccuracy: 0.85,\n blockTimingWindow: 90,\n decisionQuality: 0.75,\n aggressionModifier: 1.1,\n comboChance: 0.50,\n },\n [DifficultyTier.ADVANCED]: {\n reactionTimeMs: { min: 150, max: 300 },\n vitalPointAccuracy: 0.75,\n basicAttackAccuracy: 0.90,\n blockTimingWindow: 70,\n decisionQuality: 0.85,\n aggressionModifier: 1.3,\n comboChance: 0.60,\n },\n [DifficultyTier.EXPERT]: {\n reactionTimeMs: { min: 50, max: 150 },\n vitalPointAccuracy: 0.85,\n basicAttackAccuracy: 0.95,\n blockTimingWindow: 50,\n decisionQuality: 0.95,\n aggressionModifier: 1.5,\n comboChance: 0.70,\n },\n};\n\n/**\n * Map skill score (0.0-1.0) to difficulty tier\n * Uses fixed thresholds for consistent tier assignment\n * \n * @korean 실력 점수를 난이도 단계로 변환\n * \n * @param score - Player skill score (0.0-1.0)\n * @returns Corresponding difficulty tier\n */\nexport function skillScoreToTier(score: number): DifficultyTier {\n if (score < 0.2) return DifficultyTier.BEGINNER;\n if (score < 0.4) return DifficultyTier.NOVICE;\n if (score < 0.6) return DifficultyTier.INTERMEDIATE;\n if (score < 0.8) return DifficultyTier.ADVANCED;\n return DifficultyTier.EXPERT;\n}\n\n/**\n * Linear interpolation helper\n * @param a - Start value\n * @param b - End value\n * @param t - Interpolation factor (0.0-1.0)\n * @returns Interpolated value\n */\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * Math.max(0, Math.min(1, t));\n}\n\n/**\n * Interpolate between two difficulty parameter sets\n * Used for smooth difficulty transitions over time\n * \n * @korean 난이도 매개변수 간 부드러운 전환\n * \n * @param from - Starting difficulty parameters\n * @param to - Target difficulty parameters\n * @param progress - Interpolation progress (0.0-1.0)\n * @returns Interpolated difficulty parameters\n */\nexport function interpolateDifficultyParameters(\n from: DifficultyParameters,\n to: DifficultyParameters,\n progress: number\n): DifficultyParameters {\n const t = Math.max(0, Math.min(1, progress));\n \n return {\n reactionTimeMs: {\n min: lerp(from.reactionTimeMs.min, to.reactionTimeMs.min, t),\n max: lerp(from.reactionTimeMs.max, to.reactionTimeMs.max, t),\n },\n vitalPointAccuracy: lerp(from.vitalPointAccuracy, to.vitalPointAccuracy, t),\n basicAttackAccuracy: lerp(from.basicAttackAccuracy, to.basicAttackAccuracy, t),\n blockTimingWindow: lerp(from.blockTimingWindow, to.blockTimingWindow, t),\n decisionQuality: lerp(from.decisionQuality, to.decisionQuality, t),\n aggressionModifier: lerp(from.aggressionModifier, to.aggressionModifier, t),\n comboChance: lerp(from.comboChance, to.comboChance, t),\n };\n}\n\n/**\n * Adaptive Difficulty System\n */\nexport class AdaptiveDifficulty {\n private playerSkillMetrics: PlayerSkillMetrics;\n private readonly skillDecay = 0.95; // Gradual skill decay between matches\n private readonly learningRate = 0.1; // How quickly to adapt\n\n constructor() {\n this.playerSkillMetrics = {\n averageAccuracy: 0.5,\n comboCount: 0,\n perfectBlocks: 0,\n reactionTime: 800,\n vitalPointHits: 0,\n stanceTransitions: 0,\n damageEfficiency: 0.5,\n matchesPlayed: 0,\n };\n }\n\n /**\n * Update player skill metrics based on match performance\n */\n updateSkillMetrics(matchData: {\n hitsLanded: number;\n totalAttacks: number;\n combosExecuted: number;\n perfectBlockCount: number;\n avgReactionTimeMs: number;\n vitalPointsHit: number;\n effectiveStanceChanges: number;\n damageDealt: number;\n damageTaken: number;\n }): void {\n const { metrics } = this;\n\n // Update accuracy with learning rate\n const matchAccuracy =\n matchData.totalAttacks > 0\n ? matchData.hitsLanded / matchData.totalAttacks\n : 0.5;\n metrics.averageAccuracy =\n metrics.averageAccuracy * (1 - this.learningRate) +\n matchAccuracy * this.learningRate;\n\n // Update combo count\n metrics.comboCount += matchData.combosExecuted;\n\n // Update perfect blocks\n metrics.perfectBlocks += matchData.perfectBlockCount;\n\n // Update reaction time\n if (matchData.avgReactionTimeMs > 0) {\n metrics.reactionTime =\n metrics.reactionTime * (1 - this.learningRate) +\n matchData.avgReactionTimeMs * this.learningRate;\n }\n\n // Update vital point hits\n metrics.vitalPointHits += matchData.vitalPointsHit;\n\n // Update stance transitions\n metrics.stanceTransitions += matchData.effectiveStanceChanges;\n\n // Update damage efficiency\n const matchEfficiency =\n matchData.damageTaken > 0\n ? Math.min(1, matchData.damageDealt / matchData.damageTaken)\n : matchData.damageDealt > 0\n ? 1.0 // Perfect defense with damage dealt\n : 0.5; // No damage on either side\n metrics.damageEfficiency =\n metrics.damageEfficiency * (1 - this.learningRate) +\n matchEfficiency * this.learningRate;\n\n // Increment matches played\n metrics.matchesPlayed += 1;\n\n // Apply skill decay to prevent over-adjustment\n this.applySkillDecay();\n }\n\n /**\n * Apply gradual skill decay to prevent over-adjustment\n */\n private applySkillDecay(): void {\n const { metrics } = this;\n metrics.averageAccuracy =\n metrics.averageAccuracy * this.skillDecay +\n 0.5 * (1 - this.skillDecay);\n metrics.damageEfficiency =\n metrics.damageEfficiency * this.skillDecay +\n 0.5 * (1 - this.skillDecay);\n }\n\n /**\n * Calculate overall player skill level (0.0 - 1.0)\n */\n calculatePlayerSkill(): number {\n const { metrics } = this;\n\n // Weight different skill components\n const accuracyScore = metrics.averageAccuracy * 0.3;\n const comboScore = Math.min(1, metrics.comboCount / 20) * 0.2;\n const blockScore = Math.min(1, metrics.perfectBlocks / 10) * 0.2;\n const reactionScore = Math.max(0, 1 - metrics.reactionTime / 1000) * 0.15;\n const vitalScore = Math.min(1, metrics.vitalPointHits / 15) * 0.15;\n\n return accuracyScore + comboScore + blockScore + reactionScore + vitalScore;\n }\n\n /**\n * Get current difficulty tier based on skill level\n */\n getDifficultyTier(): DifficultyTier {\n const skillLevel = this.calculatePlayerSkill();\n return skillScoreToTier(skillLevel);\n }\n\n /**\n * Get difficulty parameters for current skill tier\n * Returns the appropriate DifficultyParameters based on player skill\n * \n * @korean 현재 실력 단계에 맞는 난이도 매개변수 반환\n */\n getDifficultyParameters(): DifficultyParameters {\n const tier = this.getDifficultyTier();\n return DIFFICULTY_PARAMETERS[tier];\n }\n\n /**\n * Adjust AI personality based on player skill\n */\n adjustAIPersonality(personality: AIPersonality): AIPersonality {\n const skillLevel = this.calculatePlayerSkill();\n const tier = this.getDifficultyTier();\n\n // Scale factors based on difficulty tier\n const aggressionScale = 1 + tier * 0.1; // +10% per tier\n const feintScale = 1 + tier * 0.15; // +15% per tier\n const comboScale = 1 + tier * 0.12; // +12% per tier\n const stanceScale = 1 + tier * 0.08; // +8% per tier\n\n return {\n ...personality,\n aggressionLevel: Math.min(\n 0.95,\n personality.aggressionLevel * aggressionScale\n ),\n feintChance: Math.min(0.6, personality.feintChance * feintScale),\n comboTendency: Math.min(0.85, personality.comboTendency * comboScale),\n stanceSwitchFrequency: Math.min(\n 0.9,\n personality.stanceSwitchFrequency * stanceScale\n ),\n // Adjust retreat threshold - better players face more aggressive AI\n tacticalRetreatThreshold: Math.max(\n 0.1,\n personality.tacticalRetreatThreshold * (1 - skillLevel * 0.3)\n ),\n };\n }\n\n /**\n * Get skill metrics\n */\n getMetrics(): Readonly<PlayerSkillMetrics> {\n return { ...this.playerSkillMetrics };\n }\n\n /**\n * Reset skill metrics\n */\n reset(): void {\n this.playerSkillMetrics = {\n averageAccuracy: 0.5,\n comboCount: 0,\n perfectBlocks: 0,\n reactionTime: 800,\n vitalPointHits: 0,\n stanceTransitions: 0,\n damageEfficiency: 0.5,\n matchesPlayed: 0,\n };\n }\n\n /**\n * Get difficulty adjustment recommendation\n */\n getDifficultyRecommendation(): {\n tier: DifficultyTier;\n tierName: string;\n skillLevel: number;\n shouldIncrease: boolean;\n message: string;\n } {\n const skillLevel = this.calculatePlayerSkill();\n const tier = this.getDifficultyTier();\n const shouldIncrease = skillLevel > 0.7 && tier < DifficultyTier.EXPERT;\n\n const tierNames: Record<DifficultyTier, string> = {\n [DifficultyTier.BEGINNER]: \"Beginner (초보)\",\n [DifficultyTier.NOVICE]: \"Novice (입문)\",\n [DifficultyTier.INTERMEDIATE]: \"Intermediate (중급)\",\n [DifficultyTier.ADVANCED]: \"Advanced (고급)\",\n [DifficultyTier.EXPERT]: \"Expert (전문)\",\n };\n\n let message: string;\n if (shouldIncrease) {\n message = \"Player shows mastery - increasing difficulty\";\n } else if (skillLevel < 0.3) {\n message = \"Player struggling - maintaining current difficulty\";\n } else {\n message = \"Player performing well - difficulty appropriate\";\n }\n\n return {\n tier,\n tierName: tierNames[tier],\n skillLevel,\n shouldIncrease,\n message,\n };\n }\n\n /**\n * Export metrics for persistence\n */\n exportMetrics(): string {\n return JSON.stringify(this.playerSkillMetrics);\n }\n\n /**\n * Import metrics from persistence\n */\n importMetrics(data: string): boolean {\n try {\n const metrics = JSON.parse(data) as PlayerSkillMetrics;\n this.playerSkillMetrics = metrics;\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get reference to metrics (for internal use)\n */\n private get metrics(): PlayerSkillMetrics {\n return this.playerSkillMetrics;\n }\n}\n"],"mappings":";;;;AAwBA,IAAY,iBAAL,yBAAA,gBAAA;AACL,gBAAA,eAAA,cAAA,KAAA;AACA,gBAAA,eAAA,YAAA,KAAA;AACA,gBAAA,eAAA,kBAAA,KAAA;AACA,gBAAA,eAAA,cAAA,KAAA;AACA,gBAAA,eAAA,YAAA,KAAA;;KACD;;;;;;;AA+BD,IAAa,wBAAsE;EAChF,eAAe,WAAW;EACzB,gBAAgB;GAAE,KAAK;GAAK,KAAK;GAAM;EACvC,oBAAoB;EACpB,qBAAqB;EACrB,mBAAmB;EACnB,iBAAiB;EACjB,oBAAoB;EACpB,aAAa;EACd;EACA,eAAe,SAAS;EACvB,gBAAgB;GAAE,KAAK;GAAK,KAAK;GAAK;EACtC,oBAAoB;EACpB,qBAAqB;EACrB,mBAAmB;EACnB,iBAAiB;EACjB,oBAAoB;EACpB,aAAa;EACd;EACA,eAAe,eAAe;EAC7B,gBAAgB;GAAE,KAAK;GAAK,KAAK;GAAK;EACtC,oBAAoB;EACpB,qBAAqB;EACrB,mBAAmB;EACnB,iBAAiB;EACjB,oBAAoB;EACpB,aAAa;EACd;EACA,eAAe,WAAW;EACzB,gBAAgB;GAAE,KAAK;GAAK,KAAK;GAAK;EACtC,oBAAoB;EACpB,qBAAqB;EACrB,mBAAmB;EACnB,iBAAiB;EACjB,oBAAoB;EACpB,aAAa;EACd;EACA,eAAe,SAAS;EACvB,gBAAgB;GAAE,KAAK;GAAI,KAAK;GAAK;EACrC,oBAAoB;EACpB,qBAAqB;EACrB,mBAAmB;EACnB,iBAAiB;EACjB,oBAAoB;EACpB,aAAa;EACd;CACF;;;;;;;;;;AAWD,SAAgB,iBAAiB,OAA+B;AAC9D,KAAI,QAAQ,GAAK,QAAO,eAAe;AACvC,KAAI,QAAQ,GAAK,QAAO,eAAe;AACvC,KAAI,QAAQ,GAAK,QAAO,eAAe;AACvC,KAAI,QAAQ,GAAK,QAAO,eAAe;AACvC,QAAO,eAAe;;;;;;;;;AAUxB,SAAS,KAAK,GAAW,GAAW,GAAmB;AACrD,QAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;;;;;;;;;;;;;AAclD,SAAgB,gCACd,MACA,IACA,UACsB;CACtB,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,SAAS,CAAC;AAE5C,QAAO;EACL,gBAAgB;GACd,KAAK,KAAK,KAAK,eAAe,KAAK,GAAG,eAAe,KAAK,EAAE;GAC5D,KAAK,KAAK,KAAK,eAAe,KAAK,GAAG,eAAe,KAAK,EAAE;GAC7D;EACD,oBAAoB,KAAK,KAAK,oBAAoB,GAAG,oBAAoB,EAAE;EAC3E,qBAAqB,KAAK,KAAK,qBAAqB,GAAG,qBAAqB,EAAE;EAC9E,mBAAmB,KAAK,KAAK,mBAAmB,GAAG,mBAAmB,EAAE;EACxE,iBAAiB,KAAK,KAAK,iBAAiB,GAAG,iBAAiB,EAAE;EAClE,oBAAoB,KAAK,KAAK,oBAAoB,GAAG,oBAAoB,EAAE;EAC3E,aAAa,KAAK,KAAK,aAAa,GAAG,aAAa,EAAE;EACvD;;;;;AAMH,IAAa,qBAAb,MAAgC;CAC9B;CACA,aAA8B;CAC9B,eAAgC;CAEhC,cAAc;AACZ,OAAK,qBAAqB;GACxB,iBAAiB;GACjB,YAAY;GACZ,eAAe;GACf,cAAc;GACd,gBAAgB;GAChB,mBAAmB;GACnB,kBAAkB;GAClB,eAAe;GAChB;;;;;CAMH,mBAAmB,WAUV;EACP,MAAM,EAAE,YAAY;EAGpB,MAAM,gBACJ,UAAU,eAAe,IACrB,UAAU,aAAa,UAAU,eACjC;AACN,UAAQ,kBACN,QAAQ,mBAAmB,IAAI,KAAK,gBACpC,gBAAgB,KAAK;AAGvB,UAAQ,cAAc,UAAU;AAGhC,UAAQ,iBAAiB,UAAU;AAGnC,MAAI,UAAU,oBAAoB,EAChC,SAAQ,eACN,QAAQ,gBAAgB,IAAI,KAAK,gBACjC,UAAU,oBAAoB,KAAK;AAIvC,UAAQ,kBAAkB,UAAU;AAGpC,UAAQ,qBAAqB,UAAU;EAGvC,MAAM,kBACJ,UAAU,cAAc,IACpB,KAAK,IAAI,GAAG,UAAU,cAAc,UAAU,YAAY,GAC1D,UAAU,cAAc,IACxB,IACA;AACN,UAAQ,mBACN,QAAQ,oBAAoB,IAAI,KAAK,gBACrC,kBAAkB,KAAK;AAGzB,UAAQ,iBAAiB;AAGzB,OAAK,iBAAiB;;;;;CAMxB,kBAAgC;EAC9B,MAAM,EAAE,YAAY;AACpB,UAAQ,kBACN,QAAQ,kBAAkB,KAAK,aAC/B,MAAO,IAAI,KAAK;AAClB,UAAQ,mBACN,QAAQ,mBAAmB,KAAK,aAChC,MAAO,IAAI,KAAK;;;;;CAMpB,uBAA+B;EAC7B,MAAM,EAAE,YAAY;EAGpB,MAAM,gBAAgB,QAAQ,kBAAkB;EAChD,MAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,aAAa,GAAG,GAAG;EAC1D,MAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,gBAAgB,GAAG,GAAG;EAC7D,MAAM,gBAAgB,KAAK,IAAI,GAAG,IAAI,QAAQ,eAAe,IAAK,GAAG;EACrE,MAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,iBAAiB,GAAG,GAAG;AAE9D,SAAO,gBAAgB,aAAa,aAAa,gBAAgB;;;;;CAMnE,oBAAoC;AAElC,SAAO,iBADY,KAAK,sBAAsB,CACX;;;;;;;;CASrC,0BAAgD;AAE9C,SAAO,sBADM,KAAK,mBAAmB;;;;;CAOvC,oBAAoB,aAA2C;EAC7D,MAAM,aAAa,KAAK,sBAAsB;EAC9C,MAAM,OAAO,KAAK,mBAAmB;EAGrC,MAAM,kBAAkB,IAAI,OAAO;EACnC,MAAM,aAAa,IAAI,OAAO;EAC9B,MAAM,aAAa,IAAI,OAAO;EAC9B,MAAM,cAAc,IAAI,OAAO;AAE/B,SAAO;GACL,GAAG;GACH,iBAAiB,KAAK,IACpB,KACA,YAAY,kBAAkB,gBAC/B;GACD,aAAa,KAAK,IAAI,IAAK,YAAY,cAAc,WAAW;GAChE,eAAe,KAAK,IAAI,KAAM,YAAY,gBAAgB,WAAW;GACrE,uBAAuB,KAAK,IAC1B,IACA,YAAY,wBAAwB,YACrC;GAED,0BAA0B,KAAK,IAC7B,IACA,YAAY,4BAA4B,IAAI,aAAa,IAC1D;GACF;;;;;CAMH,aAA2C;AACzC,SAAO,EAAE,GAAG,KAAK,oBAAoB;;;;;CAMvC,QAAc;AACZ,OAAK,qBAAqB;GACxB,iBAAiB;GACjB,YAAY;GACZ,eAAe;GACf,cAAc;GACd,gBAAgB;GAChB,mBAAmB;GACnB,kBAAkB;GAClB,eAAe;GAChB;;;;;CAMH,8BAME;EACA,MAAM,aAAa,KAAK,sBAAsB;EAC9C,MAAM,OAAO,KAAK,mBAAmB;EACrC,MAAM,iBAAiB,aAAa,MAAO,OAAO,eAAe;EAEjE,MAAM,YAA4C;IAC/C,eAAe,WAAW;IAC1B,eAAe,SAAS;IACxB,eAAe,eAAe;IAC9B,eAAe,WAAW;IAC1B,eAAe,SAAS;GAC1B;EAED,IAAI;AACJ,MAAI,eACF,WAAU;WACD,aAAa,GACtB,WAAU;MAEV,WAAU;AAGZ,SAAO;GACL;GACA,UAAU,UAAU;GACpB;GACA;GACA;GACD;;;;;CAMH,gBAAwB;AACtB,SAAO,KAAK,UAAU,KAAK,mBAAmB;;;;;CAMhD,cAAc,MAAuB;AACnC,MAAI;AAEF,QAAK,qBADW,KAAK,MAAM,KAAK;AAEhC,UAAO;UACD;AACN,UAAO;;;;;;CAOX,IAAY,UAA8B;AACxC,SAAO,KAAK"}
|
|
1
|
+
{"version":3,"file":"AdaptiveDifficulty.js","names":[],"sources":["../../../src/systems/ai/AdaptiveDifficulty.ts"],"sourcesContent":["/**\n * Adaptive Difficulty System for Korean Martial Arts Combat\n * Tracks player skill metrics and adjusts AI difficulty dynamically\n */\n\nimport { AIPersonality } from \"./AIPersonality\";\n\n/**\n * Player skill metrics tracked for adaptive difficulty\n */\nexport interface PlayerSkillMetrics {\n averageAccuracy: number; // 0.0-1.0: Hit rate\n comboCount: number; // Total combos executed\n perfectBlocks: number; // Perfect timing blocks\n reactionTime: number; // Average reaction time in ms\n vitalPointHits: number; // Successful vital point strikes\n stanceTransitions: number; // Effective stance changes\n damageEfficiency: number; // 0.0-1.0: Damage dealt vs taken ratio\n matchesPlayed: number; // Total matches for scaling\n}\n\n/**\n * Difficulty tier levels (5 tiers for adaptive system)\n */\nexport enum DifficultyTier {\n BEGINNER = 1,\n NOVICE = 2,\n INTERMEDIATE = 3,\n ADVANCED = 4,\n EXPERT = 5,\n}\n\n/**\n * Difficulty parameters that control AI behavior\n * Applied dynamically based on player skill level\n * \n * @korean 난이도 매개변수 - 플레이어 실력에 따라 AI 행동 제어\n */\nexport interface DifficultyParameters {\n /** AI reaction time range in milliseconds */\n readonly reactionTimeMs: { readonly min: number; readonly max: number };\n /** Accuracy for vital point strikes (0.0-1.0) */\n readonly vitalPointAccuracy: number;\n /** Accuracy for basic attacks (0.0-1.0) */\n readonly basicAttackAccuracy: number;\n /** Block timing window in milliseconds (smaller = harder to block) */\n readonly blockTimingWindow: number;\n /** Decision quality affects technique selection optimality (0.0-1.0) */\n readonly decisionQuality: number;\n /** Aggression modifier multiplier (0.5-2.0) */\n readonly aggressionModifier: number;\n /** Chance to attempt combo sequences (0.0-1.0) */\n readonly comboChance: number;\n}\n\n/**\n * Difficulty parameter sets for each skill tier\n * Defines AI behavior characteristics at each difficulty level\n * \n * @korean 각 난이도 단계별 매개변수 설정\n */\nexport const DIFFICULTY_PARAMETERS: Record<DifficultyTier, DifficultyParameters> = {\n [DifficultyTier.BEGINNER]: {\n reactionTimeMs: { min: 800, max: 1200 },\n vitalPointAccuracy: 0.40,\n basicAttackAccuracy: 0.70,\n blockTimingWindow: 150,\n decisionQuality: 0.50,\n aggressionModifier: 0.7,\n comboChance: 0.20,\n },\n [DifficultyTier.NOVICE]: {\n reactionTimeMs: { min: 500, max: 800 },\n vitalPointAccuracy: 0.55,\n basicAttackAccuracy: 0.78,\n blockTimingWindow: 120,\n decisionQuality: 0.65,\n aggressionModifier: 0.9,\n comboChance: 0.35,\n },\n [DifficultyTier.INTERMEDIATE]: {\n reactionTimeMs: { min: 300, max: 500 },\n vitalPointAccuracy: 0.65,\n basicAttackAccuracy: 0.85,\n blockTimingWindow: 90,\n decisionQuality: 0.75,\n aggressionModifier: 1.1,\n comboChance: 0.50,\n },\n [DifficultyTier.ADVANCED]: {\n reactionTimeMs: { min: 150, max: 300 },\n vitalPointAccuracy: 0.75,\n basicAttackAccuracy: 0.90,\n blockTimingWindow: 70,\n decisionQuality: 0.85,\n aggressionModifier: 1.3,\n comboChance: 0.60,\n },\n [DifficultyTier.EXPERT]: {\n reactionTimeMs: { min: 50, max: 150 },\n vitalPointAccuracy: 0.85,\n basicAttackAccuracy: 0.95,\n blockTimingWindow: 50,\n decisionQuality: 0.95,\n aggressionModifier: 1.5,\n comboChance: 0.70,\n },\n};\n\n/**\n * Map skill score (0.0-1.0) to difficulty tier\n * Uses fixed thresholds for consistent tier assignment\n * \n * @korean 실력 점수를 난이도 단계로 변환\n * \n * @param score - Player skill score (0.0-1.0)\n * @returns Corresponding difficulty tier\n */\nexport function skillScoreToTier(score: number): DifficultyTier {\n if (score < 0.2) return DifficultyTier.BEGINNER;\n if (score < 0.4) return DifficultyTier.NOVICE;\n if (score < 0.6) return DifficultyTier.INTERMEDIATE;\n if (score < 0.8) return DifficultyTier.ADVANCED;\n return DifficultyTier.EXPERT;\n}\n\n/**\n * Linear interpolation helper\n * @param a - Start value\n * @param b - End value\n * @param t - Interpolation factor (0.0-1.0)\n * @returns Interpolated value\n */\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * Math.max(0, Math.min(1, t));\n}\n\n/**\n * Interpolate between two difficulty parameter sets\n * Used for smooth difficulty transitions over time\n * \n * @korean 난이도 매개변수 간 부드러운 전환\n * \n * @param from - Starting difficulty parameters\n * @param to - Target difficulty parameters\n * @param progress - Interpolation progress (0.0-1.0)\n * @returns Interpolated difficulty parameters\n */\nexport function interpolateDifficultyParameters(\n from: DifficultyParameters,\n to: DifficultyParameters,\n progress: number\n): DifficultyParameters {\n const t = Math.max(0, Math.min(1, progress));\n \n return {\n reactionTimeMs: {\n min: lerp(from.reactionTimeMs.min, to.reactionTimeMs.min, t),\n max: lerp(from.reactionTimeMs.max, to.reactionTimeMs.max, t),\n },\n vitalPointAccuracy: lerp(from.vitalPointAccuracy, to.vitalPointAccuracy, t),\n basicAttackAccuracy: lerp(from.basicAttackAccuracy, to.basicAttackAccuracy, t),\n blockTimingWindow: lerp(from.blockTimingWindow, to.blockTimingWindow, t),\n decisionQuality: lerp(from.decisionQuality, to.decisionQuality, t),\n aggressionModifier: lerp(from.aggressionModifier, to.aggressionModifier, t),\n comboChance: lerp(from.comboChance, to.comboChance, t),\n };\n}\n\n/**\n * Adaptive Difficulty System\n */\nexport class AdaptiveDifficulty {\n private playerSkillMetrics: PlayerSkillMetrics;\n private readonly skillDecay = 0.95; // Gradual skill decay between matches\n private readonly learningRate = 0.1; // How quickly to adapt\n\n constructor() {\n this.playerSkillMetrics = {\n averageAccuracy: 0.5,\n comboCount: 0,\n perfectBlocks: 0,\n reactionTime: 800,\n vitalPointHits: 0,\n stanceTransitions: 0,\n damageEfficiency: 0.5,\n matchesPlayed: 0,\n };\n }\n\n /**\n * Update player skill metrics based on match performance\n */\n updateSkillMetrics(matchData: {\n hitsLanded: number;\n totalAttacks: number;\n combosExecuted: number;\n perfectBlockCount: number;\n avgReactionTimeMs: number;\n vitalPointsHit: number;\n effectiveStanceChanges: number;\n damageDealt: number;\n damageTaken: number;\n }): void {\n const { metrics } = this;\n\n // Update accuracy with learning rate\n const matchAccuracy =\n matchData.totalAttacks > 0\n ? matchData.hitsLanded / matchData.totalAttacks\n : 0.5;\n metrics.averageAccuracy =\n metrics.averageAccuracy * (1 - this.learningRate) +\n matchAccuracy * this.learningRate;\n\n // Update combo count\n metrics.comboCount += matchData.combosExecuted;\n\n // Update perfect blocks\n metrics.perfectBlocks += matchData.perfectBlockCount;\n\n // Update reaction time\n if (matchData.avgReactionTimeMs > 0) {\n metrics.reactionTime =\n metrics.reactionTime * (1 - this.learningRate) +\n matchData.avgReactionTimeMs * this.learningRate;\n }\n\n // Update vital point hits\n metrics.vitalPointHits += matchData.vitalPointsHit;\n\n // Update stance transitions\n metrics.stanceTransitions += matchData.effectiveStanceChanges;\n\n // Update damage efficiency\n const matchEfficiency =\n matchData.damageTaken > 0\n ? Math.min(1, matchData.damageDealt / matchData.damageTaken)\n : matchData.damageDealt > 0\n ? 1.0 // Perfect defense with damage dealt\n : 0.5; // No damage on either side\n metrics.damageEfficiency =\n metrics.damageEfficiency * (1 - this.learningRate) +\n matchEfficiency * this.learningRate;\n\n // Increment matches played\n metrics.matchesPlayed += 1;\n\n // Apply skill decay to prevent over-adjustment\n this.applySkillDecay();\n }\n\n /**\n * Apply gradual skill decay to prevent over-adjustment\n */\n private applySkillDecay(): void {\n const { metrics } = this;\n metrics.averageAccuracy =\n metrics.averageAccuracy * this.skillDecay +\n 0.5 * (1 - this.skillDecay);\n metrics.damageEfficiency =\n metrics.damageEfficiency * this.skillDecay +\n 0.5 * (1 - this.skillDecay);\n }\n\n /**\n * Calculate overall player skill level (0.0 - 1.0)\n */\n calculatePlayerSkill(): number {\n const { metrics } = this;\n\n // Weight different skill components\n const accuracyScore = metrics.averageAccuracy * 0.3;\n const comboScore = Math.min(1, metrics.comboCount / 20) * 0.2;\n const blockScore = Math.min(1, metrics.perfectBlocks / 10) * 0.2;\n const reactionScore = Math.max(0, 1 - metrics.reactionTime / 1000) * 0.15;\n const vitalScore = Math.min(1, metrics.vitalPointHits / 15) * 0.15;\n\n return accuracyScore + comboScore + blockScore + reactionScore + vitalScore;\n }\n\n /**\n * Get current difficulty tier based on skill level\n */\n getDifficultyTier(): DifficultyTier {\n const skillLevel = this.calculatePlayerSkill();\n return skillScoreToTier(skillLevel);\n }\n\n /**\n * Get difficulty parameters for current skill tier\n * Returns the appropriate DifficultyParameters based on player skill\n * \n * @korean 현재 실력 단계에 맞는 난이도 매개변수 반환\n */\n getDifficultyParameters(): DifficultyParameters {\n const tier = this.getDifficultyTier();\n return DIFFICULTY_PARAMETERS[tier];\n }\n\n /**\n * Adjust AI personality based on player skill\n */\n adjustAIPersonality(personality: AIPersonality): AIPersonality {\n const skillLevel = this.calculatePlayerSkill();\n const tier = this.getDifficultyTier();\n\n // Scale factors based on difficulty tier\n const aggressionScale = 1 + tier * 0.1; // +10% per tier\n const feintScale = 1 + tier * 0.15; // +15% per tier\n const comboScale = 1 + tier * 0.12; // +12% per tier\n const stanceScale = 1 + tier * 0.08; // +8% per tier\n\n return {\n ...personality,\n aggressionLevel: Math.min(\n 0.95,\n personality.aggressionLevel * aggressionScale\n ),\n feintChance: Math.min(0.6, personality.feintChance * feintScale),\n comboTendency: Math.min(0.85, personality.comboTendency * comboScale),\n stanceSwitchFrequency: Math.min(\n 0.9,\n personality.stanceSwitchFrequency * stanceScale\n ),\n // Adjust retreat threshold - better players face more aggressive AI\n tacticalRetreatThreshold: Math.max(\n 0.1,\n personality.tacticalRetreatThreshold * (1 - skillLevel * 0.3)\n ),\n };\n }\n\n /**\n * Get skill metrics\n */\n getMetrics(): Readonly<PlayerSkillMetrics> {\n return { ...this.playerSkillMetrics };\n }\n\n /**\n * Reset skill metrics\n */\n reset(): void {\n this.playerSkillMetrics = {\n averageAccuracy: 0.5,\n comboCount: 0,\n perfectBlocks: 0,\n reactionTime: 800,\n vitalPointHits: 0,\n stanceTransitions: 0,\n damageEfficiency: 0.5,\n matchesPlayed: 0,\n };\n }\n\n /**\n * Get difficulty adjustment recommendation\n */\n getDifficultyRecommendation(): {\n tier: DifficultyTier;\n tierName: string;\n skillLevel: number;\n shouldIncrease: boolean;\n message: string;\n } {\n const skillLevel = this.calculatePlayerSkill();\n const tier = this.getDifficultyTier();\n const shouldIncrease = skillLevel > 0.7 && tier < DifficultyTier.EXPERT;\n\n const tierNames: Record<DifficultyTier, string> = {\n [DifficultyTier.BEGINNER]: \"Beginner (초보)\",\n [DifficultyTier.NOVICE]: \"Novice (입문)\",\n [DifficultyTier.INTERMEDIATE]: \"Intermediate (중급)\",\n [DifficultyTier.ADVANCED]: \"Advanced (고급)\",\n [DifficultyTier.EXPERT]: \"Expert (전문)\",\n };\n\n let message: string;\n if (shouldIncrease) {\n message = \"Player shows mastery - increasing difficulty\";\n } else if (skillLevel < 0.3) {\n message = \"Player struggling - maintaining current difficulty\";\n } else {\n message = \"Player performing well - difficulty appropriate\";\n }\n\n return {\n tier,\n tierName: tierNames[tier],\n skillLevel,\n shouldIncrease,\n message,\n };\n }\n\n /**\n * Export metrics for persistence\n */\n exportMetrics(): string {\n return JSON.stringify(this.playerSkillMetrics);\n }\n\n /**\n * Import metrics from persistence\n */\n importMetrics(data: string): boolean {\n try {\n const metrics = JSON.parse(data) as PlayerSkillMetrics;\n this.playerSkillMetrics = metrics;\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get reference to metrics (for internal use)\n */\n private get metrics(): PlayerSkillMetrics {\n return this.playerSkillMetrics;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../src/systems/ai/types.ts"],"sourcesContent":["/**\n * AI Combat System Type Definitions\n *\n * Core types for AI decision-making and combat behavior.\n * Separated to avoid circular dependencies between modules.\n *\n * @module systems/ai/types\n * @category AI Combat\n * @korean AI 전투 시스템 타입 정의\n */\n\nimport { Position, TrigramStance } from \"@/types\";\nimport { BalanceState } from \"@/types/player-visual\";\nimport type { CounterOpportunity } from \"@/types/physics\";\n\n/**\n * AI action types\n *\n * @korean AI 행동 유형\n */\nexport enum AIActionType {\n ATTACK = \"attack\",\n TECHNIQUE = \"technique\",\n DEFEND = \"defend\",\n COUNTER = \"counter\",\n RETREAT = \"retreat\",\n APPROACH = \"approach\",\n CIRCLE = \"circle\",\n STANCE_CHANGE = \"stance_change\",\n FEINT = \"feint\",\n WAIT = \"wait\",\n COMBO = \"combo\",\n}\n\n/**\n * AI decision result\n *\n * @korean AI 결정 결과\n */\nexport interface AIDecision {\n readonly action: AIActionType;\n readonly targetPosition?: Position;\n readonly targetStance?: TrigramStance;\n readonly targetVitalPoint?: string; // ID of vital point to target\n readonly priority: number; // 0-10: Decision confidence\n readonly reason: string; // For debugging/analysis\n}\n\n/**\n * Vulnerability assessment context for exploitation tactics\n *\n * Comprehensive analysis of opponent's defenseless states:\n * - **isHelpless**: Balance === HELPLESS (90% takedown priority)\n * - **isVulnerable**: Balance === VULNERABLE or HELPLESS (70% aggressive attack priority)\n * - **isShaken**: Balance === SHAKEN, VULNERABLE, or HELPLESS (50% pressure tactics priority)\n * - **hasLowStamina**: Stamina < 20% (60% exploitation priority)\n * - **hasNoKi**: Ki < 10% (50% technique spam priority)\n * - **overallVulnerability**: Composite vulnerability score (0.0-1.0)\n *\n * @korean 취약성 평가 컨텍스트\n */\nexport interface VulnerabilityContext {\n readonly isHelpless: boolean; // balance === HELPLESS\n readonly isVulnerable: boolean; // balance === VULNERABLE or HELPLESS\n readonly isShaken: boolean; // balance === SHAKEN, VULNERABLE, or HELPLESS\n readonly hasLowStamina: boolean; // stamina < 20%\n readonly hasNoKi: boolean; // ki < 10%\n readonly overallVulnerability: number; // 0.0-1.0 composite score\n}\n\n/**\n * Combat context for decision making\n *\n * @korean 전투 컨텍스트\n */\nexport interface CombatContext {\n readonly playerPosition: Position;\n readonly opponentPosition: Position;\n readonly playerHealth: number;\n readonly playerMaxHealth: number;\n readonly playerKi: number;\n readonly playerMaxKi: number;\n readonly playerStamina: number;\n readonly playerMaxStamina: number;\n readonly opponentHealth: number;\n readonly opponentMaxHealth?: number; // Opponent max health (if undefined, assumes symmetric with playerMaxHealth)\n readonly opponentStance: TrigramStance;\n readonly playerStance: TrigramStance;\n readonly distanceToOpponent: number;\n readonly timeInMatch: number;\n readonly isOpponentAttacking: boolean;\n readonly recentDamageTaken: number;\n readonly opponentBalance?: BalanceState; // Balance state: \"READY\" | \"SHAKEN\" | \"VULNERABLE\" | \"HELPLESS\"\n readonly opponentStamina?: number; // Opponent stamina for exploitation\n readonly opponentMaxStamina?: number; // Opponent max stamina\n readonly opponentKi?: number; // Opponent ki for exploitation\n readonly opponentMaxKi?: number; // Opponent max ki\n readonly stanceFatigue?: {\n readonly timeInStance: number; // Milliseconds in current stance\n };\n readonly arenaBounds: {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n readonly worldWidthMeters: number; // Arena width in meters for physics calculations\n readonly worldDepthMeters: number; // Arena depth in meters for physics calculations\n };\n /**\n * Detected counter-attack opportunity from opponent's limb exposure.\n * Includes exposed limb, timing window, vulnerability multiplier, and recommended counters.\n * **Korean**: 반격 기회 (Counter Opportunity)\n */\n readonly counterOpportunity?: CounterOpportunity;\n}\n"],"mappings":";;;;;;AAoBA,IAAY,eAAL,yBAAA,cAAA;AACL,cAAA,
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../src/systems/ai/types.ts"],"sourcesContent":["/**\n * AI Combat System Type Definitions\n *\n * Core types for AI decision-making and combat behavior.\n * Separated to avoid circular dependencies between modules.\n *\n * @module systems/ai/types\n * @category AI Combat\n * @korean AI 전투 시스템 타입 정의\n */\n\nimport { Position, TrigramStance } from \"@/types\";\nimport { BalanceState } from \"@/types/player-visual\";\nimport type { CounterOpportunity } from \"@/types/physics\";\n\n/**\n * AI action types\n *\n * @korean AI 행동 유형\n */\nexport enum AIActionType {\n ATTACK = \"attack\",\n TECHNIQUE = \"technique\",\n DEFEND = \"defend\",\n COUNTER = \"counter\",\n RETREAT = \"retreat\",\n APPROACH = \"approach\",\n CIRCLE = \"circle\",\n STANCE_CHANGE = \"stance_change\",\n FEINT = \"feint\",\n WAIT = \"wait\",\n COMBO = \"combo\",\n}\n\n/**\n * AI decision result\n *\n * @korean AI 결정 결과\n */\nexport interface AIDecision {\n readonly action: AIActionType;\n readonly targetPosition?: Position;\n readonly targetStance?: TrigramStance;\n readonly targetVitalPoint?: string; // ID of vital point to target\n readonly priority: number; // 0-10: Decision confidence\n readonly reason: string; // For debugging/analysis\n}\n\n/**\n * Vulnerability assessment context for exploitation tactics\n *\n * Comprehensive analysis of opponent's defenseless states:\n * - **isHelpless**: Balance === HELPLESS (90% takedown priority)\n * - **isVulnerable**: Balance === VULNERABLE or HELPLESS (70% aggressive attack priority)\n * - **isShaken**: Balance === SHAKEN, VULNERABLE, or HELPLESS (50% pressure tactics priority)\n * - **hasLowStamina**: Stamina < 20% (60% exploitation priority)\n * - **hasNoKi**: Ki < 10% (50% technique spam priority)\n * - **overallVulnerability**: Composite vulnerability score (0.0-1.0)\n *\n * @korean 취약성 평가 컨텍스트\n */\nexport interface VulnerabilityContext {\n readonly isHelpless: boolean; // balance === HELPLESS\n readonly isVulnerable: boolean; // balance === VULNERABLE or HELPLESS\n readonly isShaken: boolean; // balance === SHAKEN, VULNERABLE, or HELPLESS\n readonly hasLowStamina: boolean; // stamina < 20%\n readonly hasNoKi: boolean; // ki < 10%\n readonly overallVulnerability: number; // 0.0-1.0 composite score\n}\n\n/**\n * Combat context for decision making\n *\n * @korean 전투 컨텍스트\n */\nexport interface CombatContext {\n readonly playerPosition: Position;\n readonly opponentPosition: Position;\n readonly playerHealth: number;\n readonly playerMaxHealth: number;\n readonly playerKi: number;\n readonly playerMaxKi: number;\n readonly playerStamina: number;\n readonly playerMaxStamina: number;\n readonly opponentHealth: number;\n readonly opponentMaxHealth?: number; // Opponent max health (if undefined, assumes symmetric with playerMaxHealth)\n readonly opponentStance: TrigramStance;\n readonly playerStance: TrigramStance;\n readonly distanceToOpponent: number;\n readonly timeInMatch: number;\n readonly isOpponentAttacking: boolean;\n readonly recentDamageTaken: number;\n readonly opponentBalance?: BalanceState; // Balance state: \"READY\" | \"SHAKEN\" | \"VULNERABLE\" | \"HELPLESS\"\n readonly opponentStamina?: number; // Opponent stamina for exploitation\n readonly opponentMaxStamina?: number; // Opponent max stamina\n readonly opponentKi?: number; // Opponent ki for exploitation\n readonly opponentMaxKi?: number; // Opponent max ki\n readonly stanceFatigue?: {\n readonly timeInStance: number; // Milliseconds in current stance\n };\n readonly arenaBounds: {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n readonly worldWidthMeters: number; // Arena width in meters for physics calculations\n readonly worldDepthMeters: number; // Arena depth in meters for physics calculations\n };\n /**\n * Detected counter-attack opportunity from opponent's limb exposure.\n * Includes exposed limb, timing window, vulnerability multiplier, and recommended counters.\n * **Korean**: 반격 기회 (Counter Opportunity)\n */\n readonly counterOpportunity?: CounterOpportunity;\n}\n"],"mappings":";;;;;;AAoBA,IAAY,eAAL,yBAAA,cAAA;AACL,cAAA,YAAS;AACT,cAAA,eAAY;AACZ,cAAA,YAAS;AACT,cAAA,aAAU;AACV,cAAA,aAAU;AACV,cAAA,cAAW;AACX,cAAA,YAAS;AACT,cAAA,mBAAgB;AAChB,cAAA,WAAQ;AACR,cAAA,UAAO;AACP,cAAA,WAAQ;;KACT"}
|