blacktrigram 0.7.68 → 0.7.69

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.
Files changed (219) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/assets/index.css +987 -987
  3. package/lib/audio/AudioAssetLoader.js.map +1 -1
  4. package/lib/audio/AudioAssetRegistry.js +2 -2
  5. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  6. package/lib/audio/AudioCache.js.map +1 -1
  7. package/lib/audio/AudioManager.js +1 -1
  8. package/lib/audio/AudioManager.js.map +1 -1
  9. package/lib/audio/AudioMonitor.js.map +1 -1
  10. package/lib/audio/AudioPool.js.map +1 -1
  11. package/lib/audio/AudioProvider.js.map +1 -1
  12. package/lib/audio/AudioUtils.js.map +1 -1
  13. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +3 -3
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  18. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  19. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  20. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  25. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  26. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  27. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  28. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  29. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  30. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  31. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  32. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  34. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  35. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  36. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  38. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  39. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  40. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  41. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  42. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  43. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  44. package/lib/components/screens/training/TrainingScreen3D.js +1 -1
  45. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  46. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  47. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  48. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  49. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  50. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  51. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  52. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  53. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  54. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  55. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  56. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  57. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  58. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  59. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  60. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  61. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  62. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  63. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  64. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  65. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  66. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  67. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  68. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  69. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  70. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  71. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  72. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  73. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  74. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  75. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  76. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  77. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  78. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  79. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  80. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  81. package/lib/components/shared/ui/SplashScreen.js +2 -2
  82. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  83. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  84. package/lib/data/techniques.js.map +1 -1
  85. package/lib/hooks/useCombatTimer.js.map +1 -1
  86. package/lib/hooks/useKeyboardControls.js.map +1 -1
  87. package/lib/hooks/useSkeletalAnimation.js +2 -2
  88. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  89. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  90. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  91. package/lib/index.js +2 -2
  92. package/lib/systems/CombatSystem.js.map +1 -1
  93. package/lib/systems/EffectCalculator.js.map +1 -1
  94. package/lib/systems/ResponsiveScaling.js.map +1 -1
  95. package/lib/systems/TrigramSystem.js.map +1 -1
  96. package/lib/systems/ai/AIPersonality.js.map +1 -1
  97. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  98. package/lib/systems/ai/DecisionTree.js.map +1 -1
  99. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  100. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  101. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  102. package/lib/systems/animation/builders/SkeletonRig.js +1 -1
  103. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  104. package/lib/systems/animation/catalogs/AttackAnimations.js +1 -1
  105. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  106. package/lib/systems/animation/catalogs/BasicAnimations.js +1 -1
  107. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  108. package/lib/systems/animation/catalogs/ComboAnimations.js +1 -1
  109. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  110. package/lib/systems/animation/catalogs/DarkOpsAnimations.js +1 -1
  111. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  112. package/lib/systems/animation/catalogs/DefensiveAnimations.js +2 -11
  113. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  114. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js +1 -1
  115. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  116. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js +1 -1
  117. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  118. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  119. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -1
  120. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  121. package/lib/systems/animation/catalogs/GanStanceAnimations.js +1 -1
  122. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  123. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js +1 -1
  124. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  125. package/lib/systems/animation/catalogs/GeonStanceAnimations.js +1 -1
  126. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  127. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +1 -1
  128. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -1
  129. package/lib/systems/animation/catalogs/GrapplingAnimations.js +1 -1
  130. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  131. package/lib/systems/animation/catalogs/JinStanceAnimations.js +1 -1
  132. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  133. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js +1 -1
  134. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  135. package/lib/systems/animation/catalogs/KickAnimations.js +1 -1
  136. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  137. package/lib/systems/animation/catalogs/LiStanceAnimations.js +1 -1
  138. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  139. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  140. package/lib/systems/animation/catalogs/MovementAnimations.js +1 -1
  141. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  142. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  143. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  144. package/lib/systems/animation/catalogs/SonStanceAnimations.js +1 -1
  145. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  146. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js +1 -1
  147. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  148. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js +1 -1
  149. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  150. package/lib/systems/animation/catalogs/StanceAnimations.js +1 -1
  151. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  152. package/lib/systems/animation/catalogs/StanceAttackAnimations.js +215 -272
  153. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  154. package/lib/systems/animation/catalogs/StanceIdleAnimations.js +2 -11
  155. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  156. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js +2 -2
  157. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  158. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js +1 -1
  159. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  160. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  161. package/lib/systems/animation/catalogs/TaeStanceAnimations.js +1 -1
  162. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  163. package/lib/systems/animation/core/AnimationOptimizations.js +1 -1
  164. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  165. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  166. package/lib/systems/animation/core/AnimationRegistry.js +11 -209
  167. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  168. package/lib/systems/animation/core/AnimationStateMachine.js +2 -2
  169. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  170. package/lib/systems/animation/core/TechniqueAnimationMapper.js +1 -1
  171. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  172. package/lib/systems/animation/core/TechniqueAnimationMapping.js +1 -1
  173. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  174. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  175. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  176. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  177. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  178. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  179. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  180. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  181. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  182. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  183. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  184. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  185. package/lib/systems/combat/typeGuards.js.map +1 -1
  186. package/lib/systems/index.js +2 -2
  187. package/lib/systems/movement/integration.js.map +1 -1
  188. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  189. package/lib/systems/physics/CollisionDetection.js +1 -1
  190. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  191. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  192. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  193. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  194. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  195. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  196. package/lib/systems/trigram/StanceManager.js.map +1 -1
  197. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  198. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  199. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  200. package/lib/systems/trigram/techniques/index.js.map +1 -1
  201. package/lib/systems/trigram/types.js.map +1 -1
  202. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  203. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  204. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  205. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  206. package/lib/types/constants/designSystem.js.map +1 -1
  207. package/lib/utils/colorHelpers.js.map +1 -1
  208. package/lib/utils/colorUtils.js.map +1 -1
  209. package/lib/utils/controlMapping.js.map +1 -1
  210. package/lib/utils/deviceDetection.js.map +1 -1
  211. package/lib/utils/effectUtils.js.map +1 -1
  212. package/lib/utils/fabricTextures.js.map +1 -1
  213. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  214. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  215. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  216. package/lib/utils/safeAreaUtils.js.map +1 -1
  217. package/lib/utils/stanceHelpers.js.map +1 -1
  218. package/lib/utils/visualEffects.js.map +1 -1
  219. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"AudioAssetLoader.js","names":[],"sources":["../../src/audio/AudioAssetLoader.ts"],"sourcesContent":["/**\n * AudioAssetLoader - Handles audio asset loading with retry logic and format fallback\n * Implements production-ready asset loading strategies for Black Trigram\n */\n\nimport type { AudioAsset } from \"./types\";\n\nexport type LoadPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\n\nexport interface LoadOptions {\n readonly priority?: LoadPriority;\n readonly maxRetries?: number;\n readonly retryDelay?: number;\n readonly timeout?: number;\n}\n\nexport interface LoadResult {\n readonly success: boolean;\n readonly audio?: HTMLAudioElement;\n readonly error?: Error;\n readonly attemptCount: number;\n readonly loadTime: number;\n readonly formatUsed?: string;\n}\n\nexport interface BatchLoadProgress {\n readonly total: number;\n readonly loaded: number;\n readonly failed: number;\n readonly currentAsset?: string;\n readonly progress: number;\n}\n\nexport class AudioAssetLoader {\n private loadAttempts: Map<string, number> = new Map();\n private loadCache: Map<string, HTMLAudioElement> = new Map();\n private loadingPromises: Map<string, Promise<LoadResult>> = new Map();\n\n /**\n * Load a single audio asset with retry logic and format fallback\n */\n async loadAsset(\n asset: AudioAsset,\n options: LoadOptions = {}\n ): Promise<LoadResult> {\n const {\n priority = \"normal\",\n maxRetries = 3,\n retryDelay = 1000,\n timeout = 10000,\n } = options;\n\n // Check cache first\n const cached = this.loadCache.get(asset.id);\n if (cached) {\n return {\n success: true,\n audio: cached,\n attemptCount: 0,\n loadTime: 0,\n formatUsed: cached.src,\n };\n }\n\n // Check if already loading\n const existingPromise = this.loadingPromises.get(asset.id);\n if (existingPromise) {\n return existingPromise;\n }\n\n const startTime = performance.now();\n const loadPromise = this.loadWithRetry(\n asset,\n maxRetries,\n retryDelay,\n timeout,\n priority\n );\n\n this.loadingPromises.set(asset.id, loadPromise);\n\n try {\n const result = await loadPromise;\n this.loadingPromises.delete(asset.id);\n\n if (result.success && result.audio) {\n this.loadCache.set(asset.id, result.audio);\n }\n\n return {\n ...result,\n loadTime: performance.now() - startTime,\n };\n } catch (error) {\n this.loadingPromises.delete(asset.id);\n return {\n success: false,\n error: error instanceof Error ? error : new Error(String(error)),\n attemptCount: this.loadAttempts.get(asset.id) ?? 0,\n loadTime: performance.now() - startTime,\n };\n }\n }\n\n /**\n * Load asset with exponential backoff retry\n */\n private async loadWithRetry(\n asset: AudioAsset,\n maxRetries: number,\n baseRetryDelay: number,\n timeout: number,\n priority: LoadPriority\n ): Promise<LoadResult> {\n let lastError: Error | null = null;\n const attemptCount = this.loadAttempts.get(asset.id) ?? 0;\n this.loadAttempts.set(asset.id, attemptCount + 1);\n\n // Try all available formats with fallback\n const formats = this.getFormatsToTry(asset);\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n for (const format of formats) {\n try {\n const audio = await this.tryLoadFormat(format, timeout, priority);\n this.loadAttempts.delete(asset.id);\n return {\n success: true,\n audio,\n attemptCount: attempt + 1,\n loadTime: 0, // Will be set by caller\n formatUsed: format,\n };\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n console.warn(\n `Failed to load ${asset.id} format ${format} on attempt ${attempt + 1}:`,\n error\n );\n }\n }\n\n // Exponential backoff before retry\n if (attempt < maxRetries - 1) {\n const delay = baseRetryDelay * Math.pow(2, attempt);\n await this.sleep(delay);\n }\n }\n\n // All retries failed, return silent placeholder\n console.error(\n `All attempts failed for ${asset.id}, using silent placeholder`\n );\n return {\n success: false,\n audio: this.createSilentPlaceholder(),\n error: lastError ?? new Error(\"All load attempts failed\"),\n attemptCount: maxRetries,\n loadTime: 0,\n formatUsed: \"placeholder\",\n };\n }\n\n /**\n * Get formats to try in order of preference\n */\n private getFormatsToTry(asset: AudioAsset): string[] {\n const formats: string[] = [];\n\n // Try asset URL first\n if (asset.url) {\n formats.push(asset.url);\n }\n\n // Try variations if available\n if (\"variations\" in asset && Array.isArray(asset.variations)) {\n formats.push(...asset.variations);\n }\n\n // Format fallback: webm → mp3 → wav\n if (asset.url.endsWith(\".webm\")) {\n const mp3Url = asset.url.replace(\".webm\", \".mp3\");\n if (!formats.includes(mp3Url)) {\n formats.push(mp3Url);\n }\n }\n\n return formats;\n }\n\n /**\n * Try loading a specific format with timeout\n */\n private tryLoadFormat(\n url: string,\n timeout: number,\n _priority: LoadPriority\n ): Promise<HTMLAudioElement> {\n return new Promise((resolve, reject) => {\n const audio = new Audio();\n audio.preload = \"auto\";\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let resolved = false;\n\n const onLoad = () => {\n if (resolved) return;\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n resolve(audio);\n };\n\n const onError = () => {\n if (resolved) return;\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n audio.src = \"\";\n reject(new Error(`Failed to load: ${url}`));\n };\n\n timeoutId = setTimeout(() => {\n if (resolved) return;\n resolved = true;\n audio.src = \"\";\n reject(new Error(`Load timeout after ${timeout}ms`));\n }, timeout);\n\n audio.addEventListener(\"canplaythrough\", onLoad, { once: true });\n audio.addEventListener(\"error\", onError, { once: true });\n\n audio.src = url;\n audio.load();\n });\n }\n\n /**\n * Batch load multiple assets\n */\n async batchLoad(\n assets: readonly AudioAsset[],\n options: LoadOptions = {},\n onProgress?: (progress: BatchLoadProgress) => void\n ): Promise<LoadResult[]> {\n const results: LoadResult[] = [];\n let loaded = 0;\n let failed = 0;\n\n for (const asset of assets) {\n onProgress?.({\n total: assets.length,\n loaded,\n failed,\n currentAsset: asset.id,\n progress: (loaded + 1) / assets.length, // +1 to reflect current asset being loaded\n });\n\n const result = await this.loadAsset(asset, options);\n results.push(result);\n\n if (result.success) {\n loaded++;\n } else {\n failed++;\n }\n }\n\n onProgress?.({\n total: assets.length,\n loaded,\n failed,\n progress: 1.0,\n });\n\n return results;\n }\n\n /**\n * Preload assets by priority level\n */\n async preloadByPriority(\n assets: readonly AudioAsset[],\n priority: LoadPriority\n ): Promise<LoadResult[]> {\n // Filter assets by priority if they have metadata\n const priorityAssets = assets.filter(\n (asset) =>\n \"preloadPriority\" in asset && asset.preloadPriority === priority\n );\n\n if (priorityAssets.length === 0) {\n return [];\n }\n\n return this.batchLoad(priorityAssets, { priority });\n }\n\n /**\n * Unload an asset and free memory\n * @param assetId - ID of the asset to unload\n * @returns true if the asset was cached and unloaded, false if not found\n */\n unloadAsset(assetId: string): boolean {\n const audio = this.loadCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n audio.load(); // Reset to release memory\n this.loadCache.delete(assetId);\n this.loadAttempts.delete(assetId);\n return true;\n }\n return false;\n }\n\n /**\n * Get cached audio element\n * @param assetId - ID of the asset to retrieve\n * @returns The cached audio element or undefined if not found\n */\n getCached(assetId: string): HTMLAudioElement | undefined {\n return this.loadCache.get(assetId);\n }\n\n /**\n * Check if asset is cached\n * @param assetId - ID of the asset to check\n * @returns true if the asset is in cache, false otherwise\n */\n isCached(assetId: string): boolean {\n return this.loadCache.has(assetId);\n }\n\n /**\n * Get total number of cached assets\n * @returns The number of assets currently in cache\n */\n getCacheSize(): number {\n return this.loadCache.size;\n }\n\n /**\n * Clear all cached assets and free memory\n */\n clearCache(): void {\n this.loadCache.forEach((audio) => {\n audio.pause();\n audio.src = \"\";\n audio.load();\n });\n this.loadCache.clear();\n this.loadAttempts.clear();\n this.loadingPromises.clear();\n }\n\n /**\n * Create a silent audio placeholder for failed loads\n */\n private createSilentPlaceholder(): HTMLAudioElement {\n const audio = new Audio();\n // Valid 16-bit 44.1kHz mono silent WAV file (0.1s duration = 4410 samples)\n // This is a properly formatted WAV file with actual silent audio data\n // RIFF header + fmt chunk + data chunk with 4410 samples of 0x00 (16-bit silence)\n audio.src =\n \"data:audio/wav;base64,UklGRuQRAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\";\n return audio;\n }\n\n /**\n * Sleep utility for retry delays\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Get loading statistics including cache size, loading count, and total attempts\n * @returns Object with cached count, loading count, and total attempts\n */\n getStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return {\n cached: this.loadCache.size,\n loading: this.loadingPromises.size,\n totalAttempts: Array.from(this.loadAttempts.values()).reduce(\n (sum, val) => sum + val,\n 0\n ),\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AudioAssetLoader.js","names":[],"sources":["../../src/audio/AudioAssetLoader.ts"],"sourcesContent":["/**\n * AudioAssetLoader - Handles audio asset loading with retry logic and format fallback\n * Implements production-ready asset loading strategies for Black Trigram\n */\n\nimport type { AudioAsset } from \"./types\";\n\nexport type LoadPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\n\nexport interface LoadOptions {\n readonly priority?: LoadPriority;\n readonly maxRetries?: number;\n readonly retryDelay?: number;\n readonly timeout?: number;\n}\n\nexport interface LoadResult {\n readonly success: boolean;\n readonly audio?: HTMLAudioElement;\n readonly error?: Error;\n readonly attemptCount: number;\n readonly loadTime: number;\n readonly formatUsed?: string;\n}\n\nexport interface BatchLoadProgress {\n readonly total: number;\n readonly loaded: number;\n readonly failed: number;\n readonly currentAsset?: string;\n readonly progress: number;\n}\n\nexport class AudioAssetLoader {\n private loadAttempts: Map<string, number> = new Map();\n private loadCache: Map<string, HTMLAudioElement> = new Map();\n private loadingPromises: Map<string, Promise<LoadResult>> = new Map();\n\n /**\n * Load a single audio asset with retry logic and format fallback\n */\n async loadAsset(\n asset: AudioAsset,\n options: LoadOptions = {}\n ): Promise<LoadResult> {\n const {\n priority = \"normal\",\n maxRetries = 3,\n retryDelay = 1000,\n timeout = 10000,\n } = options;\n\n // Check cache first\n const cached = this.loadCache.get(asset.id);\n if (cached) {\n return {\n success: true,\n audio: cached,\n attemptCount: 0,\n loadTime: 0,\n formatUsed: cached.src,\n };\n }\n\n // Check if already loading\n const existingPromise = this.loadingPromises.get(asset.id);\n if (existingPromise) {\n return existingPromise;\n }\n\n const startTime = performance.now();\n const loadPromise = this.loadWithRetry(\n asset,\n maxRetries,\n retryDelay,\n timeout,\n priority\n );\n\n this.loadingPromises.set(asset.id, loadPromise);\n\n try {\n const result = await loadPromise;\n this.loadingPromises.delete(asset.id);\n\n if (result.success && result.audio) {\n this.loadCache.set(asset.id, result.audio);\n }\n\n return {\n ...result,\n loadTime: performance.now() - startTime,\n };\n } catch (error) {\n this.loadingPromises.delete(asset.id);\n return {\n success: false,\n error: error instanceof Error ? error : new Error(String(error)),\n attemptCount: this.loadAttempts.get(asset.id) ?? 0,\n loadTime: performance.now() - startTime,\n };\n }\n }\n\n /**\n * Load asset with exponential backoff retry\n */\n private async loadWithRetry(\n asset: AudioAsset,\n maxRetries: number,\n baseRetryDelay: number,\n timeout: number,\n priority: LoadPriority\n ): Promise<LoadResult> {\n let lastError: Error | null = null;\n const attemptCount = this.loadAttempts.get(asset.id) ?? 0;\n this.loadAttempts.set(asset.id, attemptCount + 1);\n\n // Try all available formats with fallback\n const formats = this.getFormatsToTry(asset);\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n for (const format of formats) {\n try {\n const audio = await this.tryLoadFormat(format, timeout, priority);\n this.loadAttempts.delete(asset.id);\n return {\n success: true,\n audio,\n attemptCount: attempt + 1,\n loadTime: 0, // Will be set by caller\n formatUsed: format,\n };\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n console.warn(\n `Failed to load ${asset.id} format ${format} on attempt ${attempt + 1}:`,\n error\n );\n }\n }\n\n // Exponential backoff before retry\n if (attempt < maxRetries - 1) {\n const delay = baseRetryDelay * Math.pow(2, attempt);\n await this.sleep(delay);\n }\n }\n\n // All retries failed, return silent placeholder\n console.error(\n `All attempts failed for ${asset.id}, using silent placeholder`\n );\n return {\n success: false,\n audio: this.createSilentPlaceholder(),\n error: lastError ?? new Error(\"All load attempts failed\"),\n attemptCount: maxRetries,\n loadTime: 0,\n formatUsed: \"placeholder\",\n };\n }\n\n /**\n * Get formats to try in order of preference\n */\n private getFormatsToTry(asset: AudioAsset): string[] {\n const formats: string[] = [];\n\n // Try asset URL first\n if (asset.url) {\n formats.push(asset.url);\n }\n\n // Try variations if available\n if (\"variations\" in asset && Array.isArray(asset.variations)) {\n formats.push(...asset.variations);\n }\n\n // Format fallback: webm → mp3 → wav\n if (asset.url.endsWith(\".webm\")) {\n const mp3Url = asset.url.replace(\".webm\", \".mp3\");\n if (!formats.includes(mp3Url)) {\n formats.push(mp3Url);\n }\n }\n\n return formats;\n }\n\n /**\n * Try loading a specific format with timeout\n */\n private tryLoadFormat(\n url: string,\n timeout: number,\n _priority: LoadPriority\n ): Promise<HTMLAudioElement> {\n return new Promise((resolve, reject) => {\n const audio = new Audio();\n audio.preload = \"auto\";\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let resolved = false;\n\n const onLoad = () => {\n if (resolved) return;\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n resolve(audio);\n };\n\n const onError = () => {\n if (resolved) return;\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n audio.src = \"\";\n reject(new Error(`Failed to load: ${url}`));\n };\n\n timeoutId = setTimeout(() => {\n if (resolved) return;\n resolved = true;\n audio.src = \"\";\n reject(new Error(`Load timeout after ${timeout}ms`));\n }, timeout);\n\n audio.addEventListener(\"canplaythrough\", onLoad, { once: true });\n audio.addEventListener(\"error\", onError, { once: true });\n\n audio.src = url;\n audio.load();\n });\n }\n\n /**\n * Batch load multiple assets\n */\n async batchLoad(\n assets: readonly AudioAsset[],\n options: LoadOptions = {},\n onProgress?: (progress: BatchLoadProgress) => void\n ): Promise<LoadResult[]> {\n const results: LoadResult[] = [];\n let loaded = 0;\n let failed = 0;\n\n for (const asset of assets) {\n onProgress?.({\n total: assets.length,\n loaded,\n failed,\n currentAsset: asset.id,\n progress: (loaded + 1) / assets.length, // +1 to reflect current asset being loaded\n });\n\n const result = await this.loadAsset(asset, options);\n results.push(result);\n\n if (result.success) {\n loaded++;\n } else {\n failed++;\n }\n }\n\n onProgress?.({\n total: assets.length,\n loaded,\n failed,\n progress: 1.0,\n });\n\n return results;\n }\n\n /**\n * Preload assets by priority level\n */\n async preloadByPriority(\n assets: readonly AudioAsset[],\n priority: LoadPriority\n ): Promise<LoadResult[]> {\n // Filter assets by priority if they have metadata\n const priorityAssets = assets.filter(\n (asset) =>\n \"preloadPriority\" in asset && asset.preloadPriority === priority\n );\n\n if (priorityAssets.length === 0) {\n return [];\n }\n\n return this.batchLoad(priorityAssets, { priority });\n }\n\n /**\n * Unload an asset and free memory\n * @param assetId - ID of the asset to unload\n * @returns true if the asset was cached and unloaded, false if not found\n */\n unloadAsset(assetId: string): boolean {\n const audio = this.loadCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n audio.load(); // Reset to release memory\n this.loadCache.delete(assetId);\n this.loadAttempts.delete(assetId);\n return true;\n }\n return false;\n }\n\n /**\n * Get cached audio element\n * @param assetId - ID of the asset to retrieve\n * @returns The cached audio element or undefined if not found\n */\n getCached(assetId: string): HTMLAudioElement | undefined {\n return this.loadCache.get(assetId);\n }\n\n /**\n * Check if asset is cached\n * @param assetId - ID of the asset to check\n * @returns true if the asset is in cache, false otherwise\n */\n isCached(assetId: string): boolean {\n return this.loadCache.has(assetId);\n }\n\n /**\n * Get total number of cached assets\n * @returns The number of assets currently in cache\n */\n getCacheSize(): number {\n return this.loadCache.size;\n }\n\n /**\n * Clear all cached assets and free memory\n */\n clearCache(): void {\n this.loadCache.forEach((audio) => {\n audio.pause();\n audio.src = \"\";\n audio.load();\n });\n this.loadCache.clear();\n this.loadAttempts.clear();\n this.loadingPromises.clear();\n }\n\n /**\n * Create a silent audio placeholder for failed loads\n */\n private createSilentPlaceholder(): HTMLAudioElement {\n const audio = new Audio();\n // Valid 16-bit 44.1kHz mono silent WAV file (0.1s duration = 4410 samples)\n // This is a properly formatted WAV file with actual silent audio data\n // RIFF header + fmt chunk + data chunk with 4410 samples of 0x00 (16-bit silence)\n audio.src =\n \"data:audio/wav;base64,UklGRuQRAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\";\n return audio;\n }\n\n /**\n * Sleep utility for retry delays\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Get loading statistics including cache size, loading count, and total attempts\n * @returns Object with cached count, loading count, and total attempts\n */\n getStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return {\n cached: this.loadCache.size,\n loading: this.loadingPromises.size,\n totalAttempts: Array.from(this.loadAttempts.values()).reduce(\n (sum, val) => sum + val,\n 0\n ),\n };\n }\n}\n"],"mappings":"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"}
@@ -629,7 +629,7 @@ var AudioAssetRegistry = class {
629
629
  combat: this.combat
630
630
  };
631
631
  }
632
- soundEffects = new Map([
632
+ soundEffects = /* @__PURE__ */ new Map([
633
633
  ["attack_light", {
634
634
  id: "attack_light",
635
635
  name: "Light Attack",
@@ -677,7 +677,7 @@ var AudioAssetRegistry = class {
677
677
  variations: ["/assets/audio/sfx/hits/hit_light_1.mp3", "/assets/audio/sfx/hits/hit_light_2.mp3"]
678
678
  }]
679
679
  ]);
680
- musicTracks = new Map([
680
+ musicTracks = /* @__PURE__ */ new Map([
681
681
  ["intro_theme", {
682
682
  id: "intro_theme",
683
683
  name: "Black Trigram Theme",
@@ -1 +1 @@
1
- {"version":3,"file":"AudioAssetRegistry.js","names":[],"sources":["../../src/audio/AudioAssetRegistry.ts"],"sourcesContent":["/**\n * Audio Asset Registry for Black Trigram Korean Martial Arts\n * Manages all audio assets including Korean martial arts specific sounds\n */\n\nimport type { AudioAsset } from \"./types\";\nimport {\n AudioCategory,\n CombatAudioMap,\n MusicTrack,\n MusicTrackId,\n SoundEffect,\n SoundEffectId,\n VoiceLine,\n VoiceLineId,\n} from \"./types\";\n\nexport type LoadPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\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\nexport interface IAudioAssetRegistry {\n readonly music: Record<string, MusicTrack>;\n readonly sfx: Record<string, SoundEffect>;\n readonly voice: Record<string, VoiceLine>;\n readonly combat: CombatAudioMap;\n}\n\nexport interface EnhancedAudioAsset {\n readonly id: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n volume?: number;\n readonly loop?: boolean;\n category?: AudioCategory;\n readonly metadata?: {\n readonly duration: number;\n readonly bitrate?: number;\n readonly channels?: number;\n readonly sampleRate?: number;\n };\n readonly preloadPriority?: LoadPriority;\n readonly streaming?: boolean;\n readonly compressionOptions?: {\n readonly format: string;\n readonly quality: number;\n };\n}\n\n// Enhanced audio registry with proper types\nexport interface EnhancedAudioAssetRegistry extends AudioAssetRegistry {\n readonly enhanced?: Record<string, EnhancedAudioAsset>;\n}\n\n// Asset group definition for batch loading\nexport interface AssetGroup {\n readonly id: string;\n readonly name: string;\n readonly priority: LoadPriority;\n readonly assets: readonly string[]; // Asset IDs\n readonly lazyLoad?: boolean;\n}\n\n// Manifest for efficient asset registration\nexport interface AudioAssetManifest {\n readonly version: string;\n readonly totalAssets: number;\n readonly totalSizeMB: number;\n readonly groups: readonly AssetGroup[];\n readonly assets: Record<string, EnhancedAudioAsset>;\n}\n\n// Fix: Use class implementation instead of interface merging\nexport class AudioAssetRegistry {\n private sfxMap = new Map<SoundEffectId, SoundEffect>();\n private musicMap = new Map<MusicTrackId, MusicTrack>();\n private voiceMap = new Map<VoiceLineId, VoiceLine>();\n private assetGroups: Map<string, AssetGroup> = new Map();\n\n // Fix: Implement required combat property with proper stances\n public combat: CombatAudioMap = {\n attacks: {},\n impacts: {},\n stances: {\n geon: \"stance_geon_sfx\",\n tae: \"stance_tae_sfx\",\n li: \"stance_li_sfx\",\n jin: \"stance_jin_sfx\",\n son: \"stance_son_sfx\",\n gam: \"stance_gam_sfx\",\n gan: \"stance_gan_sfx\",\n gon: \"stance_gon_sfx\",\n },\n environments: {},\n ui: {},\n };\n\n public sfx: Record<SoundEffectId, SoundEffect> = {};\n public music: Record<MusicTrackId, MusicTrack> = {};\n public voice: Record<VoiceLineId, VoiceLine> = {};\n\n constructor() {\n this.initializeDefaultAssets();\n this.initializeCombatAudioAssets();\n this.initializeAssetGroups();\n }\n\n private initializeDefaultAssets(): void {\n // Add intro_theme music with both mp3 and webm for intro screen\n this.registerMusic(\"intro_theme\", {\n id: \"intro_theme\",\n type: \"music\",\n name: \"Black Trigram Theme\",\n title: { korean: \"흑괘 테마\", english: \"Black Trigram Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/intro_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n loop: true,\n variations: [\n \"/assets/audio/music/intro_theme.mp3\",\n \"/assets/audio/music/intro_theme.webm\",\n ],\n bpm: 90,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n });\n\n // Menu UI Sound Effects\n this.registerSFX(\"menu_hover\", {\n id: \"menu_hover\",\n type: \"sound\",\n name: \"Menu Hover\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_hover.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5,\n variations: [\n \"/assets/audio/sfx/menu/menu_hover.webm\",\n \"/assets/audio/sfx/menu/menu_hover_1.webm\",\n \"/assets/audio/sfx/menu/menu_hover_2.webm\",\n \"/assets/audio/sfx/menu/menu_hover_3.webm\",\n \"/assets/audio/sfx/menu/menu_hover_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_select\", {\n id: \"menu_select\",\n type: \"sound\",\n name: \"Menu Select\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_select.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n variations: [\n \"/assets/audio/sfx/menu/menu_select.webm\",\n \"/assets/audio/sfx/menu/menu_select_1.webm\",\n \"/assets/audio/sfx/menu/menu_select_2.webm\",\n \"/assets/audio/sfx/menu/menu_select_3.webm\",\n \"/assets/audio/sfx/menu/menu_select_4.webm\",\n \"/assets/audio/sfx/menu/menu_select_5.webm\",\n \"/assets/audio/sfx/menu/menu_select_6.webm\",\n \"/assets/audio/sfx/menu/menu_select_7.webm\",\n \"/assets/audio/sfx/menu/menu_select_8.webm\",\n ],\n });\n\n this.registerSFX(\"menu_back\", {\n id: \"menu_back\",\n type: \"sound\",\n name: \"Menu Back\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_back.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n variations: [\n \"/assets/audio/sfx/menu/menu_back.webm\",\n \"/assets/audio/sfx/menu/menu_back_1.webm\",\n \"/assets/audio/sfx/menu/menu_back_2.webm\",\n \"/assets/audio/sfx/menu/menu_back_3.webm\",\n \"/assets/audio/sfx/menu/menu_back_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_navigate\", {\n id: \"menu_navigate\",\n type: \"sound\",\n name: \"Menu Navigate\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/menu_navigate.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5,\n variations: [\n \"/assets/audio/sfx/misc/menu_navigate.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_1.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_2.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_3.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_click\", {\n id: \"menu_click\",\n type: \"sound\",\n name: \"Menu Click\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/menu_click.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n variations: [\n \"/assets/audio/sfx/misc/menu_click.webm\",\n \"/assets/audio/sfx/misc/menu_click_1.webm\",\n \"/assets/audio/sfx/misc/menu_click_2.webm\",\n \"/assets/audio/sfx/misc/menu_click_3.webm\",\n \"/assets/audio/sfx/misc/menu_click_4.webm\",\n ],\n });\n }\n\n /**\n * Initialize comprehensive combat audio assets\n * Registers attack sounds, hit reactions, blocks, dodges, and stance changes\n */\n private initializeCombatAudioAssets(): void {\n // Combat Music Tracks\n this.registerMusic(\"combat_theme\", {\n id: \"combat_theme\",\n type: \"music\",\n name: \"Combat Theme\",\n title: { korean: \"전투 테마\", english: \"Combat Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/combat_theme.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/combat_theme.webm\",\n \"/assets/audio/music/combat_theme.mp3\",\n ],\n bpm: 140,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Philosophy Screen Music\n this.registerMusic(\"underground_theme\", {\n id: \"underground_theme\",\n type: \"music\",\n name: \"Underground Theme\",\n title: { korean: \"언더그라운드 테마\", english: \"Underground Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/underground_theme.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/underground_theme.webm\",\n \"/assets/audio/music/underground_theme.mp3\",\n ],\n bpm: 110,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Training Screen Music\n this.registerMusic(\"cyberpunk_fusion\", {\n id: \"cyberpunk_fusion\",\n type: \"music\",\n name: \"Cyberpunk Fusion\",\n title: { korean: \"사이버펑크 퓨전\", english: \"Cyberpunk Fusion\" },\n category: \"music\",\n url: \"/assets/audio/music/cyberpunk_fusion.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/cyberpunk_fusion.webm\",\n \"/assets/audio/music/cyberpunk_fusion.mp3\",\n ],\n bpm: 120,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Archetype-Specific Music Themes\n const archetypeThemes = [\n { id: \"musa_warrior_theme\", name: \"Musa Warrior Theme\", korean: \"무사 테마\", file: \"musa_warrior\" },\n { id: \"amsalja_shadow_theme\", name: \"Amsalja Shadow Theme\", korean: \"암살자 테마\", file: \"amsalja_shadow\" },\n { id: \"hacker_cyber_theme\", name: \"Hacker Cyber Theme\", korean: \"해커 테마\", file: \"hacker_cyber\" },\n { id: \"jeongbo_intel_theme\", name: \"Jeongbo Intel Theme\", korean: \"정보요원 테마\", file: \"jeongbo_intel\" },\n { id: \"jojik_street_theme\", name: \"Jojik Street Theme\", korean: \"조직폭력배 테마\", file: \"jojik_street\" },\n ];\n\n archetypeThemes.forEach(theme => {\n this.registerMusic(theme.id, {\n id: theme.id,\n type: \"music\",\n name: theme.name,\n title: { korean: theme.korean, english: theme.name },\n category: \"music\",\n url: `/assets/audio/music/archetype_themes/${theme.file}.mp3`,\n formats: [\"audio/mp3\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n bpm: 130,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n });\n\n // Attack Sounds - Light Punches (8 variations)\n for (let i = 1; i <= 8; i++) {\n this.registerSFX(`attack_punch_light_${i}`, {\n id: `attack_punch_light_${i}`,\n type: \"sound\",\n name: `Light Punch ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_punch_light_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Attack Sounds - Medium Punches (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_punch_medium_${i}`, {\n id: `attack_punch_medium_${i}`,\n type: \"sound\",\n name: `Medium Punch ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_punch_medium_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Attack Sounds - Generic Light/Medium/Heavy\n this.registerSFX(\"attack_light\", {\n id: \"attack_light\",\n type: \"sound\",\n name: \"Light Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_light.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/combat/attack_light.webm\",\n \"/assets/audio/sfx/combat/attack_light_3.webm\",\n \"/assets/audio/sfx/combat/attack_light_4.webm\",\n ],\n });\n\n this.registerSFX(\"attack_medium\", {\n id: \"attack_medium\",\n type: \"sound\",\n name: \"Medium Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_medium.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n variations: [\n \"/assets/audio/sfx/combat/attack_medium.webm\",\n \"/assets/audio/sfx/combat/attack_medium_1.webm\",\n \"/assets/audio/sfx/combat/attack_medium_3.webm\",\n ],\n });\n\n this.registerSFX(\"attack_heavy\", {\n id: \"attack_heavy\",\n type: \"sound\",\n name: \"Heavy Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_heavy.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/combat/attack_heavy.webm\",\n \"/assets/audio/sfx/combat/attack_heavy.mp3\",\n ],\n });\n\n // Attack Sounds - Critical (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_critical_${i}`, {\n id: `attack_critical_${i}`,\n type: \"sound\",\n name: `Critical Attack ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_critical_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Attack Sounds - Special Geon Technique (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_special_geon_${i}`, {\n id: `attack_special_geon_${i}`,\n type: \"sound\",\n name: `Geon Special ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_special_geon_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Light (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_light_${i}`, {\n id: `hit_light_${i}`,\n type: \"sound\",\n name: `Light Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_light_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Medium (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_medium_${i}`, {\n id: `hit_medium_${i}`,\n type: \"sound\",\n name: `Medium Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_medium_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Heavy (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_heavy_${i}`, {\n id: `hit_heavy_${i}`,\n type: \"sound\",\n name: `Heavy Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_heavy_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Critical (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_critical_${i}`, {\n id: `hit_critical_${i}`,\n type: \"sound\",\n name: `Critical Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_critical_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Flesh (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_flesh_${i}`, {\n id: `hit_flesh_${i}`,\n type: \"sound\",\n name: `Flesh Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/hit_flesh_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Body realistic sound (1 variation)\n this.registerSFX(\"body_realistic_sound\", {\n id: \"body_realistic_sound\",\n type: \"sound\",\n name: \"Body Impact\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/body_realistic_sound.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n\n // Combo System Sounds - 콤보 시스템 (Combo System)\n // Combo buildup sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`combo_buildup_${i}`, {\n id: `combo_buildup_${i}`,\n type: \"sound\",\n name: `Combo Buildup ${i} (콤보 축적)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/combo_buildup_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Combo finish sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`combo_finish_${i}`, {\n id: `combo_finish_${i}`,\n type: \"sound\",\n name: `Combo Finish ${i} (콤보 완성)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/combo_finish_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n });\n }\n\n // Match Countdown Sounds - 카운트다운 (Countdown)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`countdown_${i}`, {\n id: `countdown_${i}`,\n type: \"sound\",\n name: `Countdown ${i} (카운트다운)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/countdown_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Status Warning Sounds - 상태 경고 (Status Warnings)\n // Health low warnings (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`health_low_${i}`, {\n id: `health_low_${i}`,\n type: \"sound\",\n name: `Health Low ${i} (체력 부족)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/health_low_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n });\n }\n\n // Stamina depleted warnings (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`stamina_depleted_${i}`, {\n id: `stamina_depleted_${i}`,\n type: \"sound\",\n name: `Stamina Depleted ${i} (스태미나 고갈)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/stamina_depleted_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n });\n }\n\n // Victory sounds (4 variations) - 승리 (Victory)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`victory_${i}`, {\n id: `victory_${i}`,\n type: \"sound\",\n name: `Victory ${i} (승리)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/victory_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n });\n }\n\n // Defeat sounds (4 variations) - 패배 (Defeat)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`defeat_${i}`, {\n id: `defeat_${i}`,\n type: \"sound\",\n name: `Defeat ${i} (패배)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/defeat_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Movement Sounds - 이동 소리 (Movement Sounds)\n // Footstep sounds (3 standard variations with both mp3/webm)\n for (let i = 1; i <= 3; i++) {\n this.registerSFX(`footstep_${i}`, {\n id: `footstep_${i}`,\n type: \"sound\",\n name: `Footstep ${i} (발소리)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/footstep_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5, // Lower volume for footsteps\n });\n }\n\n // Footstep 4 only has webm format\n this.registerSFX(\"footstep_4\", {\n id: \"footstep_4\",\n type: \"sound\",\n name: \"Footstep 4 (발소리)\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/footstep_4.webm\",\n formats: [\"audio/webm\"],\n loaded: false,\n volume: 0.5,\n });\n\n // Block Sounds - Success (4 variations) - 막기 (Makgi)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`block_success_${i}`, {\n id: `block_success_${i}`,\n type: \"sound\",\n name: `Block Success ${i} (막기)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/blocks/block_success_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Block Sounds - Break (4 variations) - 방어붕괴 (Bangeo Bunggoe)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`block_break_${i}`, {\n id: `block_break_${i}`,\n type: \"sound\",\n name: `Block Break ${i} (방어붕괴)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/blocks/block_break_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Defensive Animation Sounds - Parry Deflection (받아넘기기)\n // Using dodge sounds as placeholder for parry whoosh/deflection\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`parry_deflect_${i}`, {\n id: `parry_deflect_${i}`,\n type: \"sound\",\n name: `Parry Deflection ${i} (받아넘기기)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/dodge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.65, // Slightly lower for deflection sound\n pitch: 1.2, // Higher pitch for quick deflection\n });\n }\n\n // Defensive Animation Sounds - Guard Recovery (방어복구)\n // Using stance change sounds for guard recovery\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`guard_recovery_${i}`, {\n id: `guard_recovery_${i}`,\n type: \"sound\",\n name: `Guard Recovery ${i} (방어복구)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/stance_change_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6, // Lower volume for recovery motion\n pitch: 0.9, // Slightly lower pitch for recovery\n });\n }\n\n // Dodge Sounds (8 variations)\n for (let i = 1; i <= 8; i++) {\n this.registerSFX(`dodge_${i}`, {\n id: `dodge_${i}`,\n type: \"sound\",\n name: `Dodge ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/dodge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Stance Change Sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`stance_change_${i}`, {\n id: `stance_change_${i}`,\n type: \"sound\",\n name: `Stance Change ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/stance_change_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Ki Energy Sounds - Charge (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`ki_charge_${i}`, {\n id: `ki_charge_${i}`,\n type: \"sound\",\n name: `Ki Charge ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/ki_energy/ki_charge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Ki Energy Sounds - Release (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`ki_release_${i}`, {\n id: `ki_release_${i}`,\n type: \"sound\",\n name: `Ki Release ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/ki_energy/ki_release_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Generic Ki sounds with variations\n this.registerSFX(\"ki_charge\", {\n id: \"ki_charge\",\n type: \"sound\",\n name: \"Ki Charge\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/ki_energy/ki_charge.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/ki_energy/ki_charge.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_1.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_2.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_3.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_4.webm\",\n ],\n });\n\n this.registerSFX(\"ki_release\", {\n id: \"ki_release\",\n type: \"sound\",\n name: \"Ki Release\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/ki_energy/ki_release.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/ki_energy/ki_release.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_1.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_2.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_3.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_4.webm\",\n ],\n });\n }\n\n public registerSFX(id: SoundEffectId, effect: SoundEffect): void {\n this.sfxMap.set(id, effect);\n this.sfx[id] = effect;\n }\n\n public registerMusic(id: MusicTrackId, track: MusicTrack): void {\n this.musicMap.set(id, track);\n this.music[id] = track;\n }\n\n public registerVoice(id: VoiceLineId, voice: VoiceLine): void {\n this.voiceMap.set(id, voice);\n this.voice[id] = voice;\n }\n\n public getSFX(id: SoundEffectId): SoundEffect | undefined {\n return this.sfxMap.get(id);\n }\n\n public getMusic(id: MusicTrackId): MusicTrack | undefined {\n return this.musicMap.get(id);\n }\n\n public getVoice(id: VoiceLineId): VoiceLine | undefined {\n return this.voiceMap.get(id);\n }\n\n public getAll(): IAudioAssetRegistry {\n return {\n sfx: this.sfx,\n music: this.music,\n voice: this.voice,\n combat: this.combat,\n };\n }\n\n // Korean martial arts sound effects registry\n private readonly soundEffects: Map<SoundEffectId, SoundEffect> = new Map([\n [\n \"attack_light\",\n {\n id: \"attack_light\",\n name: \"Light Attack\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/combat/attack_light.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n variations: [\"/assets/audio/sfx/combat/attack_light_1.mp3\", \"/assets/audio/sfx/combat/attack_light_2.mp3\"],\n },\n ],\n [\n \"stance_change\",\n {\n id: \"stance_change\",\n name: \"Stance Change\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/movement/stance_change.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.2,\n },\n ],\n [\n \"vital_hit_critical\",\n {\n id: \"vital_hit_critical\",\n name: \"Critical Vital Point Hit\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/combat/attack_critical.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.9,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 0.8,\n },\n ],\n [\n \"hit_light\",\n {\n id: \"hit_light\",\n name: \"Light Hit\", // Fix: Use simple string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/hits/hit_light.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n variations: [\n \"/assets/audio/sfx/hits/hit_light_1.mp3\",\n \"/assets/audio/sfx/hits/hit_light_2.mp3\",\n ],\n },\n ],\n ] as unknown as ReadonlyArray<[SoundEffectId, SoundEffect]>);\n\n // Korean martial arts music tracks\n private readonly musicTracks: Map<MusicTrackId, MusicTrack> = new Map([\n [\n \"intro_theme\",\n {\n id: \"intro_theme\",\n name: \"Black Trigram Theme\", // Fix: Use simple string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/intro_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n variations: [\n \"/assets/audio/music/intro_theme.mp3\",\n \"/assets/audio/music/intro_theme.webm\",\n ],\n bpm: 90,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n },\n ],\n [\n \"combat_theme\",\n {\n id: \"combat_theme\",\n name: \"Combat Music\", // Fix: Use string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/combat_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n title: { korean: \"전투 음악\", english: \"Combat Music\" },\n volume: 0.8,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n bpm: 140,\n fadeInTime: 1000,\n fadeOutTime: 2000,\n },\n ],\n [\n \"dojang_ambience\",\n {\n id: \"dojang_ambience\",\n name: \"Dojang Atmosphere\", // Fix: Use string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/underground_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n title: { korean: \"도장 분위기\", english: \"Dojang Atmosphere\" },\n volume: 0.4,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n bpm: 60,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n },\n ],\n ] as unknown as ReadonlyArray<[MusicTrackId, MusicTrack]>);\n\n // Fix: Remove unused destructured variables\n public loadSoundEffects(): void {\n // Process sound effects without unused variables\n this.soundEffects.forEach((effect) => {\n console.log(`Loading sound effect: ${effect.id}`);\n });\n }\n\n public loadMusicTracks(): void {\n // Process music tracks without unused variables\n this.musicTracks.forEach((track) => {\n console.log(`Loading music track: ${track.id}`);\n });\n }\n\n // Fix: Remove Map.find usage - Maps don't have find method\n public findSoundEffectByName(name: string): SoundEffect | undefined {\n for (const [, effect] of this.soundEffects) {\n if (effect.name === name) {\n return effect;\n }\n }\n return undefined;\n }\n\n public findMusicTrackByName(name: string): MusicTrack | undefined {\n for (const [, track] of this.musicTracks) {\n if (track.name === name) {\n return track;\n }\n }\n return undefined;\n }\n\n // Fix: Add getSoundEffect method\n public getSoundEffect(id: SoundEffectId): SoundEffect | undefined {\n return this.soundEffects.get(id);\n }\n\n // Fix: Add getMusicTrack method\n public getMusicTrack(id: MusicTrackId): MusicTrack | undefined {\n return this.musicTracks.get(id);\n }\n\n // Fix: Remove unused lightHitEffect and use string for name\n public getPlaceholderEffect(): SoundEffect {\n return {\n id: \"placeholder_hit\",\n name: \"Placeholder Hit\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/placeholder/hit.mp3\",\n formats: [\"audio/mp3\"],\n loaded: false,\n volume: 0.5,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n };\n }\n\n // Add missing loadAssets method\n public async loadAssets(): Promise<void> {\n console.log(\"Loading audio assets...\");\n // Implementation would preload audio files\n }\n\n /**\n * Initialize asset groups for batch loading\n */\n private initializeAssetGroups(): void {\n // Critical assets - load immediately on startup (menu UI sounds)\n this.registerAssetGroup({\n id: \"critical\",\n name: \"Critical UI Sounds\",\n priority: \"critical\",\n assets: [\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n ],\n lazyLoad: false,\n });\n\n // High priority - load during intro screen\n this.registerAssetGroup({\n id: \"intro_music\",\n name: \"Intro Screen Music\",\n priority: \"high\",\n assets: [\"intro_theme\"],\n lazyLoad: false,\n });\n\n // Combat music - high priority for combat screen\n this.registerAssetGroup({\n id: \"combat_music\",\n name: \"Combat Background Music\",\n priority: \"high\",\n assets: [\n \"combat_theme\",\n \"musa_warrior_theme\",\n \"amsalja_shadow_theme\",\n \"hacker_cyber_theme\",\n \"jeongbo_intel_theme\",\n \"jojik_street_theme\",\n ],\n lazyLoad: false,\n });\n\n // Screen music - normal priority for informational screens\n this.registerAssetGroup({\n id: \"screen_music\",\n name: \"Screen Background Music\",\n priority: \"normal\",\n assets: [\n \"underground_theme\",\n \"cyberpunk_fusion\",\n ],\n lazyLoad: true,\n });\n\n // Normal priority - combat SFX\n this.registerAssetGroup({\n id: \"combat_attacks\",\n name: \"Combat Attack Sounds\",\n priority: \"normal\",\n assets: [\n \"attack_light\",\n \"attack_medium\",\n \"attack_heavy\",\n \"attack_punch_light_1\",\n \"attack_punch_light_2\",\n \"attack_punch_medium_1\",\n \"attack_critical_1\",\n \"attack_critical_2\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_hits\",\n name: \"Combat Hit Reactions\",\n priority: \"normal\",\n assets: [\n \"hit_light_1\",\n \"hit_light_2\",\n \"hit_medium_1\",\n \"hit_medium_2\",\n \"hit_heavy_1\",\n \"hit_critical_1\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_defense\",\n name: \"Combat Defense Sounds\",\n priority: \"normal\",\n assets: [\n \"block_success_1\",\n \"block_success_2\",\n \"block_break_1\",\n \"dodge_1\",\n \"dodge_2\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_movement\",\n name: \"Combat Movement Sounds\",\n priority: \"normal\",\n assets: [\n \"stance_change_1\",\n \"stance_change_2\",\n \"stance_change_3\",\n \"dodge_3\",\n \"dodge_4\",\n ],\n lazyLoad: false,\n });\n\n // Training-specific sounds\n this.registerAssetGroup({\n id: \"training_sfx\",\n name: \"Training Sound Effects\",\n priority: \"normal\",\n assets: [\n \"ki_charge\",\n \"ki_release\",\n \"ki_charge_1\",\n \"ki_charge_2\",\n \"ki_release_1\",\n \"ki_release_2\",\n ],\n lazyLoad: true,\n });\n }\n\n /**\n * Register an asset group for batch loading\n * @param group - Asset group configuration with ID, name, priority, and asset IDs\n */\n public registerAssetGroup(group: AssetGroup): void {\n this.assetGroups.set(group.id, group);\n }\n\n /**\n * Get asset group by ID\n * @param groupId - ID of the asset group to retrieve\n * @returns The asset group or undefined if not found\n */\n public getAssetGroup(groupId: string): AssetGroup | undefined {\n return this.assetGroups.get(groupId);\n }\n\n /**\n * Get all registered asset groups\n * @returns Array of all asset groups\n */\n public getAllAssetGroups(): readonly AssetGroup[] {\n return Array.from(this.assetGroups.values());\n }\n\n /**\n * Get asset groups filtered by priority level\n * @param priority - Priority level to filter by (critical/high/normal/low)\n * @returns Array of asset groups matching the priority\n */\n public getAssetGroupsByPriority(priority: LoadPriority): readonly AssetGroup[] {\n return Array.from(this.assetGroups.values()).filter(\n (group) => group.priority === priority\n );\n }\n\n /**\n * Get all assets belonging to a specific group\n * @param groupId - ID of the asset group\n * @returns Array of audio assets in the group, or empty array if group not found\n */\n public getAssetsInGroup(groupId: string): readonly AudioAsset[] {\n const group = this.assetGroups.get(groupId);\n if (!group) return [];\n\n const assets: AudioAsset[] = [];\n for (const assetId of group.assets) {\n // Check all maps, take first match\n const asset = this.getSFX(assetId) ?? this.getMusic(assetId) ?? this.getVoice(assetId);\n if (asset) {\n assets.push(asset);\n }\n }\n\n return assets;\n }\n\n /**\n * Create a manifest for efficient asset registration\n * @returns Audio asset manifest with version, total assets, size estimates, groups, and all assets\n */\n public createManifest(): AudioAssetManifest {\n const allAssets: Record<string, EnhancedAudioAsset> = {};\n\n // Add SFX assets\n this.sfxMap.forEach((sfx, id) => {\n allAssets[id] = {\n ...sfx,\n preloadPriority: \"normal\",\n } as EnhancedAudioAsset;\n });\n\n // Add music assets\n this.musicMap.forEach((music, id) => {\n allAssets[id] = {\n ...music,\n preloadPriority: \"high\",\n } as EnhancedAudioAsset;\n });\n\n // Add voice assets\n this.voiceMap.forEach((voice, id) => {\n allAssets[id] = {\n ...voice,\n preloadPriority: \"normal\",\n } as EnhancedAudioAsset;\n });\n\n const totalAssets = Object.keys(allAssets).length;\n const estimatedSizeMB = totalAssets * ESTIMATED_ASSET_SIZE_MB;\n\n return {\n version: \"1.0.0\",\n totalAssets,\n totalSizeMB: estimatedSizeMB,\n groups: this.getAllAssetGroups(),\n assets: allAssets,\n };\n }\n}\n\n// Export singleton instance\nexport const audioAssetRegistry = new AudioAssetRegistry();\n\n// Default export\nexport default audioAssetRegistry;\n"],"mappings":"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"}
1
+ {"version":3,"file":"AudioAssetRegistry.js","names":[],"sources":["../../src/audio/AudioAssetRegistry.ts"],"sourcesContent":["/**\n * Audio Asset Registry for Black Trigram Korean Martial Arts\n * Manages all audio assets including Korean martial arts specific sounds\n */\n\nimport type { AudioAsset } from \"./types\";\nimport {\n AudioCategory,\n CombatAudioMap,\n MusicTrack,\n MusicTrackId,\n SoundEffect,\n SoundEffectId,\n VoiceLine,\n VoiceLineId,\n} from \"./types\";\n\nexport type LoadPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\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\nexport interface IAudioAssetRegistry {\n readonly music: Record<string, MusicTrack>;\n readonly sfx: Record<string, SoundEffect>;\n readonly voice: Record<string, VoiceLine>;\n readonly combat: CombatAudioMap;\n}\n\nexport interface EnhancedAudioAsset {\n readonly id: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n volume?: number;\n readonly loop?: boolean;\n category?: AudioCategory;\n readonly metadata?: {\n readonly duration: number;\n readonly bitrate?: number;\n readonly channels?: number;\n readonly sampleRate?: number;\n };\n readonly preloadPriority?: LoadPriority;\n readonly streaming?: boolean;\n readonly compressionOptions?: {\n readonly format: string;\n readonly quality: number;\n };\n}\n\n// Enhanced audio registry with proper types\nexport interface EnhancedAudioAssetRegistry extends AudioAssetRegistry {\n readonly enhanced?: Record<string, EnhancedAudioAsset>;\n}\n\n// Asset group definition for batch loading\nexport interface AssetGroup {\n readonly id: string;\n readonly name: string;\n readonly priority: LoadPriority;\n readonly assets: readonly string[]; // Asset IDs\n readonly lazyLoad?: boolean;\n}\n\n// Manifest for efficient asset registration\nexport interface AudioAssetManifest {\n readonly version: string;\n readonly totalAssets: number;\n readonly totalSizeMB: number;\n readonly groups: readonly AssetGroup[];\n readonly assets: Record<string, EnhancedAudioAsset>;\n}\n\n// Fix: Use class implementation instead of interface merging\nexport class AudioAssetRegistry {\n private sfxMap = new Map<SoundEffectId, SoundEffect>();\n private musicMap = new Map<MusicTrackId, MusicTrack>();\n private voiceMap = new Map<VoiceLineId, VoiceLine>();\n private assetGroups: Map<string, AssetGroup> = new Map();\n\n // Fix: Implement required combat property with proper stances\n public combat: CombatAudioMap = {\n attacks: {},\n impacts: {},\n stances: {\n geon: \"stance_geon_sfx\",\n tae: \"stance_tae_sfx\",\n li: \"stance_li_sfx\",\n jin: \"stance_jin_sfx\",\n son: \"stance_son_sfx\",\n gam: \"stance_gam_sfx\",\n gan: \"stance_gan_sfx\",\n gon: \"stance_gon_sfx\",\n },\n environments: {},\n ui: {},\n };\n\n public sfx: Record<SoundEffectId, SoundEffect> = {};\n public music: Record<MusicTrackId, MusicTrack> = {};\n public voice: Record<VoiceLineId, VoiceLine> = {};\n\n constructor() {\n this.initializeDefaultAssets();\n this.initializeCombatAudioAssets();\n this.initializeAssetGroups();\n }\n\n private initializeDefaultAssets(): void {\n // Add intro_theme music with both mp3 and webm for intro screen\n this.registerMusic(\"intro_theme\", {\n id: \"intro_theme\",\n type: \"music\",\n name: \"Black Trigram Theme\",\n title: { korean: \"흑괘 테마\", english: \"Black Trigram Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/intro_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n loop: true,\n variations: [\n \"/assets/audio/music/intro_theme.mp3\",\n \"/assets/audio/music/intro_theme.webm\",\n ],\n bpm: 90,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n });\n\n // Menu UI Sound Effects\n this.registerSFX(\"menu_hover\", {\n id: \"menu_hover\",\n type: \"sound\",\n name: \"Menu Hover\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_hover.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5,\n variations: [\n \"/assets/audio/sfx/menu/menu_hover.webm\",\n \"/assets/audio/sfx/menu/menu_hover_1.webm\",\n \"/assets/audio/sfx/menu/menu_hover_2.webm\",\n \"/assets/audio/sfx/menu/menu_hover_3.webm\",\n \"/assets/audio/sfx/menu/menu_hover_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_select\", {\n id: \"menu_select\",\n type: \"sound\",\n name: \"Menu Select\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_select.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n variations: [\n \"/assets/audio/sfx/menu/menu_select.webm\",\n \"/assets/audio/sfx/menu/menu_select_1.webm\",\n \"/assets/audio/sfx/menu/menu_select_2.webm\",\n \"/assets/audio/sfx/menu/menu_select_3.webm\",\n \"/assets/audio/sfx/menu/menu_select_4.webm\",\n \"/assets/audio/sfx/menu/menu_select_5.webm\",\n \"/assets/audio/sfx/menu/menu_select_6.webm\",\n \"/assets/audio/sfx/menu/menu_select_7.webm\",\n \"/assets/audio/sfx/menu/menu_select_8.webm\",\n ],\n });\n\n this.registerSFX(\"menu_back\", {\n id: \"menu_back\",\n type: \"sound\",\n name: \"Menu Back\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_back.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n variations: [\n \"/assets/audio/sfx/menu/menu_back.webm\",\n \"/assets/audio/sfx/menu/menu_back_1.webm\",\n \"/assets/audio/sfx/menu/menu_back_2.webm\",\n \"/assets/audio/sfx/menu/menu_back_3.webm\",\n \"/assets/audio/sfx/menu/menu_back_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_navigate\", {\n id: \"menu_navigate\",\n type: \"sound\",\n name: \"Menu Navigate\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/menu_navigate.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5,\n variations: [\n \"/assets/audio/sfx/misc/menu_navigate.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_1.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_2.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_3.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_click\", {\n id: \"menu_click\",\n type: \"sound\",\n name: \"Menu Click\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/menu_click.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n variations: [\n \"/assets/audio/sfx/misc/menu_click.webm\",\n \"/assets/audio/sfx/misc/menu_click_1.webm\",\n \"/assets/audio/sfx/misc/menu_click_2.webm\",\n \"/assets/audio/sfx/misc/menu_click_3.webm\",\n \"/assets/audio/sfx/misc/menu_click_4.webm\",\n ],\n });\n }\n\n /**\n * Initialize comprehensive combat audio assets\n * Registers attack sounds, hit reactions, blocks, dodges, and stance changes\n */\n private initializeCombatAudioAssets(): void {\n // Combat Music Tracks\n this.registerMusic(\"combat_theme\", {\n id: \"combat_theme\",\n type: \"music\",\n name: \"Combat Theme\",\n title: { korean: \"전투 테마\", english: \"Combat Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/combat_theme.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/combat_theme.webm\",\n \"/assets/audio/music/combat_theme.mp3\",\n ],\n bpm: 140,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Philosophy Screen Music\n this.registerMusic(\"underground_theme\", {\n id: \"underground_theme\",\n type: \"music\",\n name: \"Underground Theme\",\n title: { korean: \"언더그라운드 테마\", english: \"Underground Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/underground_theme.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/underground_theme.webm\",\n \"/assets/audio/music/underground_theme.mp3\",\n ],\n bpm: 110,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Training Screen Music\n this.registerMusic(\"cyberpunk_fusion\", {\n id: \"cyberpunk_fusion\",\n type: \"music\",\n name: \"Cyberpunk Fusion\",\n title: { korean: \"사이버펑크 퓨전\", english: \"Cyberpunk Fusion\" },\n category: \"music\",\n url: \"/assets/audio/music/cyberpunk_fusion.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/cyberpunk_fusion.webm\",\n \"/assets/audio/music/cyberpunk_fusion.mp3\",\n ],\n bpm: 120,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Archetype-Specific Music Themes\n const archetypeThemes = [\n { id: \"musa_warrior_theme\", name: \"Musa Warrior Theme\", korean: \"무사 테마\", file: \"musa_warrior\" },\n { id: \"amsalja_shadow_theme\", name: \"Amsalja Shadow Theme\", korean: \"암살자 테마\", file: \"amsalja_shadow\" },\n { id: \"hacker_cyber_theme\", name: \"Hacker Cyber Theme\", korean: \"해커 테마\", file: \"hacker_cyber\" },\n { id: \"jeongbo_intel_theme\", name: \"Jeongbo Intel Theme\", korean: \"정보요원 테마\", file: \"jeongbo_intel\" },\n { id: \"jojik_street_theme\", name: \"Jojik Street Theme\", korean: \"조직폭력배 테마\", file: \"jojik_street\" },\n ];\n\n archetypeThemes.forEach(theme => {\n this.registerMusic(theme.id, {\n id: theme.id,\n type: \"music\",\n name: theme.name,\n title: { korean: theme.korean, english: theme.name },\n category: \"music\",\n url: `/assets/audio/music/archetype_themes/${theme.file}.mp3`,\n formats: [\"audio/mp3\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n bpm: 130,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n });\n\n // Attack Sounds - Light Punches (8 variations)\n for (let i = 1; i <= 8; i++) {\n this.registerSFX(`attack_punch_light_${i}`, {\n id: `attack_punch_light_${i}`,\n type: \"sound\",\n name: `Light Punch ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_punch_light_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Attack Sounds - Medium Punches (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_punch_medium_${i}`, {\n id: `attack_punch_medium_${i}`,\n type: \"sound\",\n name: `Medium Punch ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_punch_medium_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Attack Sounds - Generic Light/Medium/Heavy\n this.registerSFX(\"attack_light\", {\n id: \"attack_light\",\n type: \"sound\",\n name: \"Light Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_light.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/combat/attack_light.webm\",\n \"/assets/audio/sfx/combat/attack_light_3.webm\",\n \"/assets/audio/sfx/combat/attack_light_4.webm\",\n ],\n });\n\n this.registerSFX(\"attack_medium\", {\n id: \"attack_medium\",\n type: \"sound\",\n name: \"Medium Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_medium.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n variations: [\n \"/assets/audio/sfx/combat/attack_medium.webm\",\n \"/assets/audio/sfx/combat/attack_medium_1.webm\",\n \"/assets/audio/sfx/combat/attack_medium_3.webm\",\n ],\n });\n\n this.registerSFX(\"attack_heavy\", {\n id: \"attack_heavy\",\n type: \"sound\",\n name: \"Heavy Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_heavy.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/combat/attack_heavy.webm\",\n \"/assets/audio/sfx/combat/attack_heavy.mp3\",\n ],\n });\n\n // Attack Sounds - Critical (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_critical_${i}`, {\n id: `attack_critical_${i}`,\n type: \"sound\",\n name: `Critical Attack ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_critical_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Attack Sounds - Special Geon Technique (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_special_geon_${i}`, {\n id: `attack_special_geon_${i}`,\n type: \"sound\",\n name: `Geon Special ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_special_geon_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Light (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_light_${i}`, {\n id: `hit_light_${i}`,\n type: \"sound\",\n name: `Light Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_light_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Medium (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_medium_${i}`, {\n id: `hit_medium_${i}`,\n type: \"sound\",\n name: `Medium Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_medium_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Heavy (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_heavy_${i}`, {\n id: `hit_heavy_${i}`,\n type: \"sound\",\n name: `Heavy Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_heavy_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Critical (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_critical_${i}`, {\n id: `hit_critical_${i}`,\n type: \"sound\",\n name: `Critical Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_critical_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Flesh (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_flesh_${i}`, {\n id: `hit_flesh_${i}`,\n type: \"sound\",\n name: `Flesh Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/hit_flesh_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Body realistic sound (1 variation)\n this.registerSFX(\"body_realistic_sound\", {\n id: \"body_realistic_sound\",\n type: \"sound\",\n name: \"Body Impact\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/body_realistic_sound.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n\n // Combo System Sounds - 콤보 시스템 (Combo System)\n // Combo buildup sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`combo_buildup_${i}`, {\n id: `combo_buildup_${i}`,\n type: \"sound\",\n name: `Combo Buildup ${i} (콤보 축적)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/combo_buildup_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Combo finish sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`combo_finish_${i}`, {\n id: `combo_finish_${i}`,\n type: \"sound\",\n name: `Combo Finish ${i} (콤보 완성)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/combo_finish_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n });\n }\n\n // Match Countdown Sounds - 카운트다운 (Countdown)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`countdown_${i}`, {\n id: `countdown_${i}`,\n type: \"sound\",\n name: `Countdown ${i} (카운트다운)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/countdown_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Status Warning Sounds - 상태 경고 (Status Warnings)\n // Health low warnings (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`health_low_${i}`, {\n id: `health_low_${i}`,\n type: \"sound\",\n name: `Health Low ${i} (체력 부족)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/health_low_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n });\n }\n\n // Stamina depleted warnings (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`stamina_depleted_${i}`, {\n id: `stamina_depleted_${i}`,\n type: \"sound\",\n name: `Stamina Depleted ${i} (스태미나 고갈)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/stamina_depleted_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n });\n }\n\n // Victory sounds (4 variations) - 승리 (Victory)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`victory_${i}`, {\n id: `victory_${i}`,\n type: \"sound\",\n name: `Victory ${i} (승리)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/victory_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n });\n }\n\n // Defeat sounds (4 variations) - 패배 (Defeat)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`defeat_${i}`, {\n id: `defeat_${i}`,\n type: \"sound\",\n name: `Defeat ${i} (패배)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/defeat_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Movement Sounds - 이동 소리 (Movement Sounds)\n // Footstep sounds (3 standard variations with both mp3/webm)\n for (let i = 1; i <= 3; i++) {\n this.registerSFX(`footstep_${i}`, {\n id: `footstep_${i}`,\n type: \"sound\",\n name: `Footstep ${i} (발소리)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/footstep_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5, // Lower volume for footsteps\n });\n }\n\n // Footstep 4 only has webm format\n this.registerSFX(\"footstep_4\", {\n id: \"footstep_4\",\n type: \"sound\",\n name: \"Footstep 4 (발소리)\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/footstep_4.webm\",\n formats: [\"audio/webm\"],\n loaded: false,\n volume: 0.5,\n });\n\n // Block Sounds - Success (4 variations) - 막기 (Makgi)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`block_success_${i}`, {\n id: `block_success_${i}`,\n type: \"sound\",\n name: `Block Success ${i} (막기)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/blocks/block_success_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Block Sounds - Break (4 variations) - 방어붕괴 (Bangeo Bunggoe)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`block_break_${i}`, {\n id: `block_break_${i}`,\n type: \"sound\",\n name: `Block Break ${i} (방어붕괴)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/blocks/block_break_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Defensive Animation Sounds - Parry Deflection (받아넘기기)\n // Using dodge sounds as placeholder for parry whoosh/deflection\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`parry_deflect_${i}`, {\n id: `parry_deflect_${i}`,\n type: \"sound\",\n name: `Parry Deflection ${i} (받아넘기기)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/dodge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.65, // Slightly lower for deflection sound\n pitch: 1.2, // Higher pitch for quick deflection\n });\n }\n\n // Defensive Animation Sounds - Guard Recovery (방어복구)\n // Using stance change sounds for guard recovery\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`guard_recovery_${i}`, {\n id: `guard_recovery_${i}`,\n type: \"sound\",\n name: `Guard Recovery ${i} (방어복구)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/stance_change_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6, // Lower volume for recovery motion\n pitch: 0.9, // Slightly lower pitch for recovery\n });\n }\n\n // Dodge Sounds (8 variations)\n for (let i = 1; i <= 8; i++) {\n this.registerSFX(`dodge_${i}`, {\n id: `dodge_${i}`,\n type: \"sound\",\n name: `Dodge ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/dodge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Stance Change Sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`stance_change_${i}`, {\n id: `stance_change_${i}`,\n type: \"sound\",\n name: `Stance Change ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/stance_change_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Ki Energy Sounds - Charge (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`ki_charge_${i}`, {\n id: `ki_charge_${i}`,\n type: \"sound\",\n name: `Ki Charge ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/ki_energy/ki_charge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Ki Energy Sounds - Release (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`ki_release_${i}`, {\n id: `ki_release_${i}`,\n type: \"sound\",\n name: `Ki Release ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/ki_energy/ki_release_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Generic Ki sounds with variations\n this.registerSFX(\"ki_charge\", {\n id: \"ki_charge\",\n type: \"sound\",\n name: \"Ki Charge\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/ki_energy/ki_charge.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/ki_energy/ki_charge.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_1.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_2.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_3.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_4.webm\",\n ],\n });\n\n this.registerSFX(\"ki_release\", {\n id: \"ki_release\",\n type: \"sound\",\n name: \"Ki Release\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/ki_energy/ki_release.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/ki_energy/ki_release.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_1.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_2.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_3.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_4.webm\",\n ],\n });\n }\n\n public registerSFX(id: SoundEffectId, effect: SoundEffect): void {\n this.sfxMap.set(id, effect);\n this.sfx[id] = effect;\n }\n\n public registerMusic(id: MusicTrackId, track: MusicTrack): void {\n this.musicMap.set(id, track);\n this.music[id] = track;\n }\n\n public registerVoice(id: VoiceLineId, voice: VoiceLine): void {\n this.voiceMap.set(id, voice);\n this.voice[id] = voice;\n }\n\n public getSFX(id: SoundEffectId): SoundEffect | undefined {\n return this.sfxMap.get(id);\n }\n\n public getMusic(id: MusicTrackId): MusicTrack | undefined {\n return this.musicMap.get(id);\n }\n\n public getVoice(id: VoiceLineId): VoiceLine | undefined {\n return this.voiceMap.get(id);\n }\n\n public getAll(): IAudioAssetRegistry {\n return {\n sfx: this.sfx,\n music: this.music,\n voice: this.voice,\n combat: this.combat,\n };\n }\n\n // Korean martial arts sound effects registry\n private readonly soundEffects: Map<SoundEffectId, SoundEffect> = new Map([\n [\n \"attack_light\",\n {\n id: \"attack_light\",\n name: \"Light Attack\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/combat/attack_light.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n variations: [\"/assets/audio/sfx/combat/attack_light_1.mp3\", \"/assets/audio/sfx/combat/attack_light_2.mp3\"],\n },\n ],\n [\n \"stance_change\",\n {\n id: \"stance_change\",\n name: \"Stance Change\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/movement/stance_change.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.2,\n },\n ],\n [\n \"vital_hit_critical\",\n {\n id: \"vital_hit_critical\",\n name: \"Critical Vital Point Hit\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/combat/attack_critical.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.9,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 0.8,\n },\n ],\n [\n \"hit_light\",\n {\n id: \"hit_light\",\n name: \"Light Hit\", // Fix: Use simple string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/hits/hit_light.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n variations: [\n \"/assets/audio/sfx/hits/hit_light_1.mp3\",\n \"/assets/audio/sfx/hits/hit_light_2.mp3\",\n ],\n },\n ],\n ] as unknown as ReadonlyArray<[SoundEffectId, SoundEffect]>);\n\n // Korean martial arts music tracks\n private readonly musicTracks: Map<MusicTrackId, MusicTrack> = new Map([\n [\n \"intro_theme\",\n {\n id: \"intro_theme\",\n name: \"Black Trigram Theme\", // Fix: Use simple string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/intro_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n variations: [\n \"/assets/audio/music/intro_theme.mp3\",\n \"/assets/audio/music/intro_theme.webm\",\n ],\n bpm: 90,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n },\n ],\n [\n \"combat_theme\",\n {\n id: \"combat_theme\",\n name: \"Combat Music\", // Fix: Use string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/combat_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n title: { korean: \"전투 음악\", english: \"Combat Music\" },\n volume: 0.8,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n bpm: 140,\n fadeInTime: 1000,\n fadeOutTime: 2000,\n },\n ],\n [\n \"dojang_ambience\",\n {\n id: \"dojang_ambience\",\n name: \"Dojang Atmosphere\", // Fix: Use string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/underground_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n title: { korean: \"도장 분위기\", english: \"Dojang Atmosphere\" },\n volume: 0.4,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n bpm: 60,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n },\n ],\n ] as unknown as ReadonlyArray<[MusicTrackId, MusicTrack]>);\n\n // Fix: Remove unused destructured variables\n public loadSoundEffects(): void {\n // Process sound effects without unused variables\n this.soundEffects.forEach((effect) => {\n console.log(`Loading sound effect: ${effect.id}`);\n });\n }\n\n public loadMusicTracks(): void {\n // Process music tracks without unused variables\n this.musicTracks.forEach((track) => {\n console.log(`Loading music track: ${track.id}`);\n });\n }\n\n // Fix: Remove Map.find usage - Maps don't have find method\n public findSoundEffectByName(name: string): SoundEffect | undefined {\n for (const [, effect] of this.soundEffects) {\n if (effect.name === name) {\n return effect;\n }\n }\n return undefined;\n }\n\n public findMusicTrackByName(name: string): MusicTrack | undefined {\n for (const [, track] of this.musicTracks) {\n if (track.name === name) {\n return track;\n }\n }\n return undefined;\n }\n\n // Fix: Add getSoundEffect method\n public getSoundEffect(id: SoundEffectId): SoundEffect | undefined {\n return this.soundEffects.get(id);\n }\n\n // Fix: Add getMusicTrack method\n public getMusicTrack(id: MusicTrackId): MusicTrack | undefined {\n return this.musicTracks.get(id);\n }\n\n // Fix: Remove unused lightHitEffect and use string for name\n public getPlaceholderEffect(): SoundEffect {\n return {\n id: \"placeholder_hit\",\n name: \"Placeholder Hit\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/placeholder/hit.mp3\",\n formats: [\"audio/mp3\"],\n loaded: false,\n volume: 0.5,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n };\n }\n\n // Add missing loadAssets method\n public async loadAssets(): Promise<void> {\n console.log(\"Loading audio assets...\");\n // Implementation would preload audio files\n }\n\n /**\n * Initialize asset groups for batch loading\n */\n private initializeAssetGroups(): void {\n // Critical assets - load immediately on startup (menu UI sounds)\n this.registerAssetGroup({\n id: \"critical\",\n name: \"Critical UI Sounds\",\n priority: \"critical\",\n assets: [\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n ],\n lazyLoad: false,\n });\n\n // High priority - load during intro screen\n this.registerAssetGroup({\n id: \"intro_music\",\n name: \"Intro Screen Music\",\n priority: \"high\",\n assets: [\"intro_theme\"],\n lazyLoad: false,\n });\n\n // Combat music - high priority for combat screen\n this.registerAssetGroup({\n id: \"combat_music\",\n name: \"Combat Background Music\",\n priority: \"high\",\n assets: [\n \"combat_theme\",\n \"musa_warrior_theme\",\n \"amsalja_shadow_theme\",\n \"hacker_cyber_theme\",\n \"jeongbo_intel_theme\",\n \"jojik_street_theme\",\n ],\n lazyLoad: false,\n });\n\n // Screen music - normal priority for informational screens\n this.registerAssetGroup({\n id: \"screen_music\",\n name: \"Screen Background Music\",\n priority: \"normal\",\n assets: [\n \"underground_theme\",\n \"cyberpunk_fusion\",\n ],\n lazyLoad: true,\n });\n\n // Normal priority - combat SFX\n this.registerAssetGroup({\n id: \"combat_attacks\",\n name: \"Combat Attack Sounds\",\n priority: \"normal\",\n assets: [\n \"attack_light\",\n \"attack_medium\",\n \"attack_heavy\",\n \"attack_punch_light_1\",\n \"attack_punch_light_2\",\n \"attack_punch_medium_1\",\n \"attack_critical_1\",\n \"attack_critical_2\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_hits\",\n name: \"Combat Hit Reactions\",\n priority: \"normal\",\n assets: [\n \"hit_light_1\",\n \"hit_light_2\",\n \"hit_medium_1\",\n \"hit_medium_2\",\n \"hit_heavy_1\",\n \"hit_critical_1\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_defense\",\n name: \"Combat Defense Sounds\",\n priority: \"normal\",\n assets: [\n \"block_success_1\",\n \"block_success_2\",\n \"block_break_1\",\n \"dodge_1\",\n \"dodge_2\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_movement\",\n name: \"Combat Movement Sounds\",\n priority: \"normal\",\n assets: [\n \"stance_change_1\",\n \"stance_change_2\",\n \"stance_change_3\",\n \"dodge_3\",\n \"dodge_4\",\n ],\n lazyLoad: false,\n });\n\n // Training-specific sounds\n this.registerAssetGroup({\n id: \"training_sfx\",\n name: \"Training Sound Effects\",\n priority: \"normal\",\n assets: [\n \"ki_charge\",\n \"ki_release\",\n \"ki_charge_1\",\n \"ki_charge_2\",\n \"ki_release_1\",\n \"ki_release_2\",\n ],\n lazyLoad: true,\n });\n }\n\n /**\n * Register an asset group for batch loading\n * @param group - Asset group configuration with ID, name, priority, and asset IDs\n */\n public registerAssetGroup(group: AssetGroup): void {\n this.assetGroups.set(group.id, group);\n }\n\n /**\n * Get asset group by ID\n * @param groupId - ID of the asset group to retrieve\n * @returns The asset group or undefined if not found\n */\n public getAssetGroup(groupId: string): AssetGroup | undefined {\n return this.assetGroups.get(groupId);\n }\n\n /**\n * Get all registered asset groups\n * @returns Array of all asset groups\n */\n public getAllAssetGroups(): readonly AssetGroup[] {\n return Array.from(this.assetGroups.values());\n }\n\n /**\n * Get asset groups filtered by priority level\n * @param priority - Priority level to filter by (critical/high/normal/low)\n * @returns Array of asset groups matching the priority\n */\n public getAssetGroupsByPriority(priority: LoadPriority): readonly AssetGroup[] {\n return Array.from(this.assetGroups.values()).filter(\n (group) => group.priority === priority\n );\n }\n\n /**\n * Get all assets belonging to a specific group\n * @param groupId - ID of the asset group\n * @returns Array of audio assets in the group, or empty array if group not found\n */\n public getAssetsInGroup(groupId: string): readonly AudioAsset[] {\n const group = this.assetGroups.get(groupId);\n if (!group) return [];\n\n const assets: AudioAsset[] = [];\n for (const assetId of group.assets) {\n // Check all maps, take first match\n const asset = this.getSFX(assetId) ?? this.getMusic(assetId) ?? this.getVoice(assetId);\n if (asset) {\n assets.push(asset);\n }\n }\n\n return assets;\n }\n\n /**\n * Create a manifest for efficient asset registration\n * @returns Audio asset manifest with version, total assets, size estimates, groups, and all assets\n */\n public createManifest(): AudioAssetManifest {\n const allAssets: Record<string, EnhancedAudioAsset> = {};\n\n // Add SFX assets\n this.sfxMap.forEach((sfx, id) => {\n allAssets[id] = {\n ...sfx,\n preloadPriority: \"normal\",\n } as EnhancedAudioAsset;\n });\n\n // Add music assets\n this.musicMap.forEach((music, id) => {\n allAssets[id] = {\n ...music,\n preloadPriority: \"high\",\n } as EnhancedAudioAsset;\n });\n\n // Add voice assets\n this.voiceMap.forEach((voice, id) => {\n allAssets[id] = {\n ...voice,\n preloadPriority: \"normal\",\n } as EnhancedAudioAsset;\n });\n\n const totalAssets = Object.keys(allAssets).length;\n const estimatedSizeMB = totalAssets * ESTIMATED_ASSET_SIZE_MB;\n\n return {\n version: \"1.0.0\",\n totalAssets,\n totalSizeMB: estimatedSizeMB,\n groups: this.getAllAssetGroups(),\n assets: allAssets,\n };\n }\n}\n\n// Export singleton instance\nexport const audioAssetRegistry = new AudioAssetRegistry();\n\n// Default export\nexport default audioAssetRegistry;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AudioCache.js","names":[],"sources":["../../src/audio/AudioCache.ts"],"sourcesContent":["/**\n * AudioCache - LRU cache for audio assets\n * 오디오 캐시 - 오디오 자산을 위한 LRU 캐시\n *\n * Manages audio asset memory with automatic eviction of least-recently-used assets\n * 최근 사용 빈도가 낮은 자산을 자동으로 제거하여 오디오 자산 메모리를 관리합니다\n */\n\nimport type { AudioAsset } from \"./types\";\n\n/**\n * Configuration for AudioCache\n * AudioCache 구성\n */\nexport interface AudioCacheConfig {\n /** Maximum cache size in bytes (default: 30MB) */\n readonly maxSizeBytes: number;\n /** Asset IDs that should never be evicted */\n readonly criticalAssets: readonly string[];\n /** Enable debug logging */\n readonly debug?: boolean;\n}\n\n/**\n * Cache entry with metadata\n * 메타데이터가 있는 캐시 항목\n */\ninterface CacheEntry {\n readonly asset: AudioAsset;\n readonly size: number;\n lastAccessed: number;\n isCritical: boolean; // Mutable to allow dynamic updates via updateCriticalAssets()\n}\n\n/**\n * Cache statistics\n * 캐시 통계\n */\nexport interface CacheStats {\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\n/**\n * AudioCache - LRU cache implementation for audio assets\n * 오디오 자산을 위한 LRU 캐시 구현\n */\nexport class AudioCache {\n private cache: Map<string, CacheEntry> = new Map();\n private currentSize: number = 0;\n private maxSize: number;\n private criticalAssets: Set<string>;\n private debug: boolean;\n private evictionCount: number = 0;\n private hitCount: number = 0;\n private missCount: number = 0;\n\n constructor(config: AudioCacheConfig) {\n this.maxSize = config.maxSizeBytes;\n this.criticalAssets = new Set(config.criticalAssets);\n this.debug = config.debug ?? false;\n\n if (this.debug) {\n console.log(\n `[AudioCache] Initialized with max size: ${(this.maxSize / 1024 / 1024).toFixed(1)}MB, critical assets: ${this.criticalAssets.size}`\n );\n }\n }\n\n /**\n * Add asset to cache with LRU tracking\n * LRU 추적으로 캐시에 자산 추가\n *\n * @param id - Asset ID\n * @param asset - Audio asset\n * @param sizeBytes - Estimated size in bytes\n */\n set(id: string, asset: AudioAsset, sizeBytes: number): void {\n const isCritical = this.criticalAssets.has(id);\n\n // If asset already exists, remove its size first\n const existing = this.cache.get(id);\n if (existing) {\n this.currentSize -= existing.size;\n }\n\n // Check if we need to evict (only for new assets or size increases)\n const needsSpace = this.currentSize + sizeBytes > this.maxSize;\n if (needsSpace) {\n while (\n this.currentSize + sizeBytes > this.maxSize &&\n this.canEvict()\n ) {\n this.evictLRU();\n }\n }\n\n // Add to cache\n this.cache.set(id, {\n asset,\n size: sizeBytes,\n lastAccessed: Date.now(),\n isCritical,\n });\n this.currentSize += sizeBytes;\n\n if (this.debug) {\n console.log(\n `[AudioCache] Added: ${id} (${(sizeBytes / 1024).toFixed(1)}KB)${isCritical ? \" [CRITICAL]\" : \"\"} - Total: ${(this.currentSize / 1024 / 1024).toFixed(1)}MB / ${(this.maxSize / 1024 / 1024).toFixed(1)}MB (${((this.currentSize / this.maxSize) * 100).toFixed(1)}%)`\n );\n }\n }\n\n /**\n * Get asset from cache and update access time\n * 캐시에서 자산을 가져오고 액세스 시간 업데이트\n *\n * @param id - Asset ID\n * @returns Audio asset or undefined if not found\n */\n get(id: string): AudioAsset | undefined {\n const cached = this.cache.get(id);\n if (cached) {\n cached.lastAccessed = Date.now(); // Update LRU\n this.hitCount++;\n\n if (this.debug) {\n console.log(`[AudioCache] Hit: ${id}`);\n }\n\n return cached.asset;\n }\n\n this.missCount++;\n\n if (this.debug) {\n console.log(`[AudioCache] Miss: ${id}`);\n }\n\n return undefined;\n }\n\n /**\n * Check if asset exists in cache\n * 캐시에 자산이 있는지 확인\n *\n * @param id - Asset ID\n * @returns True if asset is cached\n */\n has(id: string): boolean {\n return this.cache.has(id);\n }\n\n /**\n * Remove asset from cache\n * 캐시에서 자산 제거\n *\n * @param id - Asset ID\n * @returns True if asset was removed\n */\n remove(id: string): boolean {\n const entry = this.cache.get(id);\n if (entry) {\n this.cache.delete(id);\n this.currentSize -= entry.size;\n\n if (this.debug) {\n console.log(\n `[AudioCache] Removed: ${id} (${(entry.size / 1024).toFixed(1)}KB)`\n );\n }\n\n // Unload audio element to free memory\n if (entry.asset && \"src\" in entry.asset) {\n const audioElement = entry.asset as AudioAsset & {\n src?: string;\n pause?: () => void;\n };\n if (audioElement.pause) {\n audioElement.pause();\n }\n if (audioElement.src !== undefined) {\n audioElement.src = \"\";\n }\n }\n\n return true;\n }\n return false;\n }\n\n /**\n * Check if can evict (has non-critical assets)\n * 제거 가능 여부 확인 (중요하지 않은 자산이 있는지)\n *\n * @returns True if there are non-critical assets to evict\n */\n private canEvict(): boolean {\n for (const entry of this.cache.values()) {\n if (!entry.isCritical) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Evict least-recently-used non-critical asset\n * 최근 사용 빈도가 가장 낮은 중요하지 않은 자산 제거\n */\n private evictLRU(): void {\n let oldestId: string | null = null;\n let oldestTime = Infinity;\n\n // Find oldest non-critical asset\n for (const [id, entry] of this.cache) {\n if (!entry.isCritical && entry.lastAccessed < oldestTime) {\n oldestId = id;\n oldestTime = entry.lastAccessed;\n }\n }\n\n if (oldestId) {\n const evicted = this.cache.get(oldestId);\n if (!evicted) return; // Should not happen, but handle safely\n \n this.cache.delete(oldestId);\n this.currentSize -= evicted.size;\n this.evictionCount++;\n\n if (this.debug) {\n const age = Date.now() - evicted.lastAccessed;\n console.log(\n `[AudioCache] Evicted: ${oldestId} (${(evicted.size / 1024).toFixed(1)}KB, age: ${(age / 1000).toFixed(1)}s) - Total: ${(this.currentSize / 1024 / 1024).toFixed(1)}MB`\n );\n }\n\n // Unload audio element to free memory\n if (evicted.asset && \"src\" in evicted.asset) {\n const audioElement = evicted.asset as AudioAsset & {\n src?: string;\n pause?: () => void;\n };\n if (audioElement.pause) {\n audioElement.pause();\n }\n if (audioElement.src !== undefined) {\n audioElement.src = \"\";\n }\n }\n }\n }\n\n /**\n * Get cache statistics\n * 캐시 통계 가져오기\n *\n * @returns Cache statistics\n */\n getStats(): CacheStats {\n const criticalCount = Array.from(this.cache.values()).filter(\n (e) => e.isCritical\n ).length;\n const hitRate =\n this.hitCount + this.missCount > 0\n ? this.hitCount / (this.hitCount + this.missCount)\n : 0;\n\n return {\n totalSize: this.currentSize,\n assetCount: this.cache.size,\n criticalCount,\n utilizationPercent: (this.currentSize / this.maxSize) * 100,\n evictionCount: this.evictionCount,\n hitCount: this.hitCount,\n missCount: this.missCount,\n hitRate,\n };\n }\n\n /**\n * Clear entire cache\n * 전체 캐시 지우기\n */\n clear(): void {\n // Unload all audio elements\n for (const entry of this.cache.values()) {\n if (entry.asset && \"src\" in entry.asset) {\n const audioElement = entry.asset as AudioAsset & {\n src?: string;\n pause?: () => void;\n };\n if (audioElement.pause) {\n audioElement.pause();\n }\n if (audioElement.src !== undefined) {\n audioElement.src = \"\";\n }\n }\n }\n\n this.cache.clear();\n this.currentSize = 0;\n \n // Reset statistics counters to maintain accurate statistics after clear\n this.evictionCount = 0;\n this.hitCount = 0;\n this.missCount = 0;\n\n if (this.debug) {\n console.log(\"[AudioCache] Cleared entire cache and reset statistics\");\n }\n }\n\n /**\n * Get all cached asset IDs\n * 모든 캐시된 자산 ID 가져오기\n *\n * @returns Array of asset IDs\n */\n getCachedAssetIds(): readonly string[] {\n return Array.from(this.cache.keys());\n }\n\n /**\n * Get detailed cache information for debugging\n * 디버깅을 위한 상세 캐시 정보 가져오기\n */\n getDebugInfo(): {\n readonly entries: ReadonlyArray<{\n id: string;\n size: number;\n isCritical: boolean;\n lastAccessed: number;\n age: number;\n }>;\n readonly stats: CacheStats;\n } {\n const now = Date.now();\n const entries = Array.from(this.cache.entries()).map(([id, entry]) => ({\n id,\n size: entry.size,\n isCritical: entry.isCritical,\n lastAccessed: entry.lastAccessed,\n age: now - entry.lastAccessed,\n }));\n\n // Sort by last accessed (oldest first)\n entries.sort((a, b) => a.lastAccessed - b.lastAccessed);\n\n return {\n entries,\n stats: this.getStats(),\n };\n }\n\n /**\n * Update critical assets list\n * 중요 자산 목록 업데이트\n *\n * @param criticalAssets - New list of critical asset IDs\n */\n updateCriticalAssets(criticalAssets: readonly string[]): void {\n this.criticalAssets = new Set(criticalAssets);\n\n // Update isCritical flag for existing entries\n for (const [id, entry] of this.cache) {\n entry.isCritical = this.criticalAssets.has(id);\n }\n\n if (this.debug) {\n console.log(\n `[AudioCache] Updated critical assets: ${this.criticalAssets.size}`\n );\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AudioCache.js","names":[],"sources":["../../src/audio/AudioCache.ts"],"sourcesContent":["/**\n * AudioCache - LRU cache for audio assets\n * 오디오 캐시 - 오디오 자산을 위한 LRU 캐시\n *\n * Manages audio asset memory with automatic eviction of least-recently-used assets\n * 최근 사용 빈도가 낮은 자산을 자동으로 제거하여 오디오 자산 메모리를 관리합니다\n */\n\nimport type { AudioAsset } from \"./types\";\n\n/**\n * Configuration for AudioCache\n * AudioCache 구성\n */\nexport interface AudioCacheConfig {\n /** Maximum cache size in bytes (default: 30MB) */\n readonly maxSizeBytes: number;\n /** Asset IDs that should never be evicted */\n readonly criticalAssets: readonly string[];\n /** Enable debug logging */\n readonly debug?: boolean;\n}\n\n/**\n * Cache entry with metadata\n * 메타데이터가 있는 캐시 항목\n */\ninterface CacheEntry {\n readonly asset: AudioAsset;\n readonly size: number;\n lastAccessed: number;\n isCritical: boolean; // Mutable to allow dynamic updates via updateCriticalAssets()\n}\n\n/**\n * Cache statistics\n * 캐시 통계\n */\nexport interface CacheStats {\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\n/**\n * AudioCache - LRU cache implementation for audio assets\n * 오디오 자산을 위한 LRU 캐시 구현\n */\nexport class AudioCache {\n private cache: Map<string, CacheEntry> = new Map();\n private currentSize: number = 0;\n private maxSize: number;\n private criticalAssets: Set<string>;\n private debug: boolean;\n private evictionCount: number = 0;\n private hitCount: number = 0;\n private missCount: number = 0;\n\n constructor(config: AudioCacheConfig) {\n this.maxSize = config.maxSizeBytes;\n this.criticalAssets = new Set(config.criticalAssets);\n this.debug = config.debug ?? false;\n\n if (this.debug) {\n console.log(\n `[AudioCache] Initialized with max size: ${(this.maxSize / 1024 / 1024).toFixed(1)}MB, critical assets: ${this.criticalAssets.size}`\n );\n }\n }\n\n /**\n * Add asset to cache with LRU tracking\n * LRU 추적으로 캐시에 자산 추가\n *\n * @param id - Asset ID\n * @param asset - Audio asset\n * @param sizeBytes - Estimated size in bytes\n */\n set(id: string, asset: AudioAsset, sizeBytes: number): void {\n const isCritical = this.criticalAssets.has(id);\n\n // If asset already exists, remove its size first\n const existing = this.cache.get(id);\n if (existing) {\n this.currentSize -= existing.size;\n }\n\n // Check if we need to evict (only for new assets or size increases)\n const needsSpace = this.currentSize + sizeBytes > this.maxSize;\n if (needsSpace) {\n while (\n this.currentSize + sizeBytes > this.maxSize &&\n this.canEvict()\n ) {\n this.evictLRU();\n }\n }\n\n // Add to cache\n this.cache.set(id, {\n asset,\n size: sizeBytes,\n lastAccessed: Date.now(),\n isCritical,\n });\n this.currentSize += sizeBytes;\n\n if (this.debug) {\n console.log(\n `[AudioCache] Added: ${id} (${(sizeBytes / 1024).toFixed(1)}KB)${isCritical ? \" [CRITICAL]\" : \"\"} - Total: ${(this.currentSize / 1024 / 1024).toFixed(1)}MB / ${(this.maxSize / 1024 / 1024).toFixed(1)}MB (${((this.currentSize / this.maxSize) * 100).toFixed(1)}%)`\n );\n }\n }\n\n /**\n * Get asset from cache and update access time\n * 캐시에서 자산을 가져오고 액세스 시간 업데이트\n *\n * @param id - Asset ID\n * @returns Audio asset or undefined if not found\n */\n get(id: string): AudioAsset | undefined {\n const cached = this.cache.get(id);\n if (cached) {\n cached.lastAccessed = Date.now(); // Update LRU\n this.hitCount++;\n\n if (this.debug) {\n console.log(`[AudioCache] Hit: ${id}`);\n }\n\n return cached.asset;\n }\n\n this.missCount++;\n\n if (this.debug) {\n console.log(`[AudioCache] Miss: ${id}`);\n }\n\n return undefined;\n }\n\n /**\n * Check if asset exists in cache\n * 캐시에 자산이 있는지 확인\n *\n * @param id - Asset ID\n * @returns True if asset is cached\n */\n has(id: string): boolean {\n return this.cache.has(id);\n }\n\n /**\n * Remove asset from cache\n * 캐시에서 자산 제거\n *\n * @param id - Asset ID\n * @returns True if asset was removed\n */\n remove(id: string): boolean {\n const entry = this.cache.get(id);\n if (entry) {\n this.cache.delete(id);\n this.currentSize -= entry.size;\n\n if (this.debug) {\n console.log(\n `[AudioCache] Removed: ${id} (${(entry.size / 1024).toFixed(1)}KB)`\n );\n }\n\n // Unload audio element to free memory\n if (entry.asset && \"src\" in entry.asset) {\n const audioElement = entry.asset as AudioAsset & {\n src?: string;\n pause?: () => void;\n };\n if (audioElement.pause) {\n audioElement.pause();\n }\n if (audioElement.src !== undefined) {\n audioElement.src = \"\";\n }\n }\n\n return true;\n }\n return false;\n }\n\n /**\n * Check if can evict (has non-critical assets)\n * 제거 가능 여부 확인 (중요하지 않은 자산이 있는지)\n *\n * @returns True if there are non-critical assets to evict\n */\n private canEvict(): boolean {\n for (const entry of this.cache.values()) {\n if (!entry.isCritical) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Evict least-recently-used non-critical asset\n * 최근 사용 빈도가 가장 낮은 중요하지 않은 자산 제거\n */\n private evictLRU(): void {\n let oldestId: string | null = null;\n let oldestTime = Infinity;\n\n // Find oldest non-critical asset\n for (const [id, entry] of this.cache) {\n if (!entry.isCritical && entry.lastAccessed < oldestTime) {\n oldestId = id;\n oldestTime = entry.lastAccessed;\n }\n }\n\n if (oldestId) {\n const evicted = this.cache.get(oldestId);\n if (!evicted) return; // Should not happen, but handle safely\n \n this.cache.delete(oldestId);\n this.currentSize -= evicted.size;\n this.evictionCount++;\n\n if (this.debug) {\n const age = Date.now() - evicted.lastAccessed;\n console.log(\n `[AudioCache] Evicted: ${oldestId} (${(evicted.size / 1024).toFixed(1)}KB, age: ${(age / 1000).toFixed(1)}s) - Total: ${(this.currentSize / 1024 / 1024).toFixed(1)}MB`\n );\n }\n\n // Unload audio element to free memory\n if (evicted.asset && \"src\" in evicted.asset) {\n const audioElement = evicted.asset as AudioAsset & {\n src?: string;\n pause?: () => void;\n };\n if (audioElement.pause) {\n audioElement.pause();\n }\n if (audioElement.src !== undefined) {\n audioElement.src = \"\";\n }\n }\n }\n }\n\n /**\n * Get cache statistics\n * 캐시 통계 가져오기\n *\n * @returns Cache statistics\n */\n getStats(): CacheStats {\n const criticalCount = Array.from(this.cache.values()).filter(\n (e) => e.isCritical\n ).length;\n const hitRate =\n this.hitCount + this.missCount > 0\n ? this.hitCount / (this.hitCount + this.missCount)\n : 0;\n\n return {\n totalSize: this.currentSize,\n assetCount: this.cache.size,\n criticalCount,\n utilizationPercent: (this.currentSize / this.maxSize) * 100,\n evictionCount: this.evictionCount,\n hitCount: this.hitCount,\n missCount: this.missCount,\n hitRate,\n };\n }\n\n /**\n * Clear entire cache\n * 전체 캐시 지우기\n */\n clear(): void {\n // Unload all audio elements\n for (const entry of this.cache.values()) {\n if (entry.asset && \"src\" in entry.asset) {\n const audioElement = entry.asset as AudioAsset & {\n src?: string;\n pause?: () => void;\n };\n if (audioElement.pause) {\n audioElement.pause();\n }\n if (audioElement.src !== undefined) {\n audioElement.src = \"\";\n }\n }\n }\n\n this.cache.clear();\n this.currentSize = 0;\n \n // Reset statistics counters to maintain accurate statistics after clear\n this.evictionCount = 0;\n this.hitCount = 0;\n this.missCount = 0;\n\n if (this.debug) {\n console.log(\"[AudioCache] Cleared entire cache and reset statistics\");\n }\n }\n\n /**\n * Get all cached asset IDs\n * 모든 캐시된 자산 ID 가져오기\n *\n * @returns Array of asset IDs\n */\n getCachedAssetIds(): readonly string[] {\n return Array.from(this.cache.keys());\n }\n\n /**\n * Get detailed cache information for debugging\n * 디버깅을 위한 상세 캐시 정보 가져오기\n */\n getDebugInfo(): {\n readonly entries: ReadonlyArray<{\n id: string;\n size: number;\n isCritical: boolean;\n lastAccessed: number;\n age: number;\n }>;\n readonly stats: CacheStats;\n } {\n const now = Date.now();\n const entries = Array.from(this.cache.entries()).map(([id, entry]) => ({\n id,\n size: entry.size,\n isCritical: entry.isCritical,\n lastAccessed: entry.lastAccessed,\n age: now - entry.lastAccessed,\n }));\n\n // Sort by last accessed (oldest first)\n entries.sort((a, b) => a.lastAccessed - b.lastAccessed);\n\n return {\n entries,\n stats: this.getStats(),\n };\n }\n\n /**\n * Update critical assets list\n * 중요 자산 목록 업데이트\n *\n * @param criticalAssets - New list of critical asset IDs\n */\n updateCriticalAssets(criticalAssets: readonly string[]): void {\n this.criticalAssets = new Set(criticalAssets);\n\n // Update isCritical flag for existing entries\n for (const [id, entry] of this.cache) {\n entry.isCritical = this.criticalAssets.has(id);\n }\n\n if (this.debug) {\n console.log(\n `[AudioCache] Updated critical assets: ${this.criticalAssets.size}`\n );\n }\n }\n}\n"],"mappings":"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"}
@@ -57,7 +57,7 @@ var AudioManager = class {
57
57
  audioPool;
58
58
  monitor;
59
59
  audioCache;
60
- frequentSounds = new Set([
60
+ frequentSounds = /* @__PURE__ */ new Set([
61
61
  "hit_light",
62
62
  "attack_light",
63
63
  "stance_change"