blacktrigram 0.7.39 → 0.7.40

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 (354) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/audio/AudioAssetLoader.js.map +1 -1
  3. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  4. package/lib/audio/AudioCache.js.map +1 -1
  5. package/lib/audio/AudioManager.js.map +1 -1
  6. package/lib/audio/AudioMonitor.js.map +1 -1
  7. package/lib/audio/AudioPool.js.map +1 -1
  8. package/lib/audio/AudioProvider.js.map +1 -1
  9. package/lib/audio/AudioUtils.js.map +1 -1
  10. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  11. package/lib/audio/VariantSelector.js.map +1 -1
  12. package/lib/audio/types.js.map +1 -1
  13. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  14. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  15. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  31. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  32. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  36. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  39. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  42. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  44. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  45. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  46. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  47. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  48. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  49. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  50. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  51. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  52. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  53. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  54. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  55. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  56. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  57. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  58. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  59. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  60. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  61. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  62. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  63. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  64. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  65. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  66. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  67. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  68. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  69. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  70. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  71. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  72. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  74. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  75. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  76. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  77. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  78. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  79. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  84. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  85. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  86. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  87. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  88. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  89. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  90. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  91. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  93. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  94. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  96. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  97. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  98. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  99. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  100. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  101. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  102. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  103. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  104. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  105. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  106. package/lib/components/shared/base/BaseButton.js.map +1 -1
  107. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  108. package/lib/components/shared/base/BasePanel.js.map +1 -1
  109. package/lib/components/shared/base/BaseText.js.map +1 -1
  110. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  111. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  112. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  113. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  114. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  115. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  116. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  117. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  118. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  119. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  120. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  121. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  122. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  123. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  124. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  125. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  126. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  127. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  128. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  129. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  130. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  131. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  132. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  133. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  134. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  135. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  136. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  137. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  138. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  139. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  140. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  141. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  142. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  143. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  144. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  145. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  146. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  147. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  148. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  149. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  150. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  151. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  152. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  153. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  154. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  155. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  156. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  157. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  158. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  159. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  160. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  161. package/lib/components/shared/ui/BackButton.js.map +1 -1
  162. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  163. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  164. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  165. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  166. package/lib/components/shared/ui/SplashScreen.js +2 -2
  167. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  168. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  169. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  170. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  171. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  172. package/lib/constants/bodyDimensions.js.map +1 -1
  173. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  174. package/lib/data/archetypeClothing.js.map +1 -1
  175. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  176. package/lib/data/techniqueMappings.js.map +1 -1
  177. package/lib/data/techniques.js.map +1 -1
  178. package/lib/hooks/useActionFeedback.js.map +1 -1
  179. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  180. package/lib/hooks/useCombatTimer.js.map +1 -1
  181. package/lib/hooks/useDebounce.js.map +1 -1
  182. package/lib/hooks/useHUDLayout.js.map +1 -1
  183. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  184. package/lib/hooks/useKeyboardControls.js.map +1 -1
  185. package/lib/hooks/useMatchCountdown.js.map +1 -1
  186. package/lib/hooks/useMuscleActivation.js.map +1 -1
  187. package/lib/hooks/usePauseMenu.js.map +1 -1
  188. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  189. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  190. package/lib/hooks/useRoundTransition.js.map +1 -1
  191. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  192. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  193. package/lib/hooks/useThrottle.js.map +1 -1
  194. package/lib/hooks/useTouchControls.js.map +1 -1
  195. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  196. package/lib/hooks/useWindowSize.js.map +1 -1
  197. package/lib/systems/CombatSystem.js.map +1 -1
  198. package/lib/systems/EffectCalculator.js.map +1 -1
  199. package/lib/systems/LayoutSystem.js.map +1 -1
  200. package/lib/systems/PlayerEffectManager.js.map +1 -1
  201. package/lib/systems/ResponsiveScaling.js.map +1 -1
  202. package/lib/systems/TrigramSystem.js.map +1 -1
  203. package/lib/systems/VitalPointSystem.js.map +1 -1
  204. package/lib/systems/ai/AIPersonality.js.map +1 -1
  205. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  206. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  207. package/lib/systems/ai/ComboSystem.js.map +1 -1
  208. package/lib/systems/ai/DecisionTree.js.map +1 -1
  209. package/lib/systems/ai/TrainingAI.js.map +1 -1
  210. package/lib/systems/ai/types.js.map +1 -1
  211. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  212. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  213. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  214. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  215. package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
  216. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  217. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  218. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  219. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  220. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  221. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  222. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  223. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  224. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  225. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  226. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  227. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  228. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  229. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  230. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  231. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  232. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  233. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  234. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  235. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  236. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  237. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  238. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  239. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  240. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  241. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  242. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  243. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  244. package/lib/systems/animation/core/types.js.map +1 -1
  245. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  246. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  247. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  248. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  249. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  250. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  251. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  252. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  253. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  254. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  255. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  256. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  257. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  258. package/lib/systems/bodypart/types.js.map +1 -1
  259. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  260. package/lib/systems/breathing/feedback.js.map +1 -1
  261. package/lib/systems/breathing/integration.js.map +1 -1
  262. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  263. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  264. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  265. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  266. package/lib/systems/combat/FallIntegration.js.map +1 -1
  267. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  268. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  269. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  270. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  271. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  272. package/lib/systems/combat/typeGuards.js.map +1 -1
  273. package/lib/systems/effects.js.map +1 -1
  274. package/lib/systems/game.js.map +1 -1
  275. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  276. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  277. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  278. package/lib/systems/movement/integration.js.map +1 -1
  279. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  280. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  281. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  282. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  283. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  284. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  285. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  286. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  287. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  288. package/lib/systems/trigram/StanceManager.js.map +1 -1
  289. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  290. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  291. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  292. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  293. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  294. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  295. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  296. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  297. package/lib/systems/trigram/techniques/index.js.map +1 -1
  298. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  299. package/lib/systems/trigram/types.js.map +1 -1
  300. package/lib/systems/types.js.map +1 -1
  301. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  302. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  303. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  304. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  305. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  306. package/lib/types/AccessibilityTypes.js.map +1 -1
  307. package/lib/types/PhysicsTypes.js.map +1 -1
  308. package/lib/types/common.js.map +1 -1
  309. package/lib/types/constants/colors.js.map +1 -1
  310. package/lib/types/constants/designSystem.js.map +1 -1
  311. package/lib/types/constants/layout.js.map +1 -1
  312. package/lib/types/constants/performance.js.map +1 -1
  313. package/lib/types/constants/typography.js.map +1 -1
  314. package/lib/types/facial.js.map +1 -1
  315. package/lib/types/hand-animation.js.map +1 -1
  316. package/lib/types/injury.js.map +1 -1
  317. package/lib/types/physics.js.map +1 -1
  318. package/lib/types/skeletal.js.map +1 -1
  319. package/lib/types/techniqueId.js.map +1 -1
  320. package/lib/utils/accessibility.js.map +1 -1
  321. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  322. package/lib/utils/assetConfig.js.map +1 -1
  323. package/lib/utils/characterScaling.js.map +1 -1
  324. package/lib/utils/colorHelpers.js.map +1 -1
  325. package/lib/utils/colorUtils.js.map +1 -1
  326. package/lib/utils/combatReadiness.js.map +1 -1
  327. package/lib/utils/controlMapping.js.map +1 -1
  328. package/lib/utils/deviceDetection.js.map +1 -1
  329. package/lib/utils/effectUtils.js.map +1 -1
  330. package/lib/utils/fabricTextures.js.map +1 -1
  331. package/lib/utils/hapticFeedback.js.map +1 -1
  332. package/lib/utils/haptics.js.map +1 -1
  333. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  334. package/lib/utils/inputSystem.js.map +1 -1
  335. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  336. package/lib/utils/math.js.map +1 -1
  337. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  338. package/lib/utils/mobileUIUtils.js.map +1 -1
  339. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  340. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  341. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  342. package/lib/utils/performanceOptimization.js.map +1 -1
  343. package/lib/utils/player3DHelpers.js.map +1 -1
  344. package/lib/utils/playerUtils.js.map +1 -1
  345. package/lib/utils/responsiveLayout.js.map +1 -1
  346. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  347. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  348. package/lib/utils/safeAreaUtils.js.map +1 -1
  349. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  350. package/lib/utils/skeletonScaling.js.map +1 -1
  351. package/lib/utils/stanceHelpers.js.map +1 -1
  352. package/lib/utils/threeObjectPool.js.map +1 -1
  353. package/lib/utils/visualEffects.js.map +1 -1
  354. package/package.json +5 -5
package/lib/App2.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"App2.js","names":[],"sources":["../src/App.tsx"],"sourcesContent":["import {\n lazy,\n Suspense,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport \"./App.css\";\n// Main application component - manages game state and screen navigation\nimport { useAudio } from \"./audio/AudioProvider\";\nimport { CombatScreen3D as CombatScreen } from \"./components/screens/combat/CombatScreen3D\";\nimport { ControlsScreen3D as ControlsScreen } from \"./components/screens/controls/ControlsScreen3D\";\nimport { EndScreen3D } from \"./components/screens/endscreen\";\nimport { IntroScreen3D as IntroScreen } from \"./components/screens/intro/IntroScreen3D\";\nimport { PhilosophyScreen3D as PhilosophyScreen } from \"./components/screens/philosophy/PhilosophyScreen3D\";\nimport { PerformanceDebugOverlayHtml } from \"./components/shared/debug/PerformanceDebugOverlayHtml\";\nimport { ErrorModal } from \"./components/shared/ui/ErrorModal\";\nimport { LoadingState } from \"./components/shared/ui/LoadingState\";\nimport { SplashScreen } from \"./components/shared/ui/SplashScreen\";\nimport { PlayerState } from \"./systems\";\nimport { MatchStatistics } from \"./systems/combat\";\nimport { GameMode, PlayerArchetype } from \"./types/common\";\nimport { clearPlatformCache, detectPlatform } from \"./utils/deviceDetection\";\nimport { createPlayerFromArchetype } from \"./utils/playerUtils\";\n\n// Lazy load heavy screens\nconst TrainingScreen = lazy(() =>\n import(\"./components/screens/training/TrainingScreen3D\").then((m) => ({\n default: m.TrainingScreen3D,\n })),\n);\n\n// 150ms delay to allow WebGL context cleanup between full-screen 3D scene transitions\nconst SCREEN_TRANSITION_DELAY_MS = 150;\n// 100ms delay for lighter menu/UI transitions where WebGL teardown/re-init cost is lower\nconst MENU_TRANSITION_DELAY_MS = 100;\n\nfunction App() {\n const [gameMode, setGameMode] = useState<GameMode | null>(null);\n const [selectedArchetype, setSelectedArchetype] = useState<PlayerArchetype>(\n PlayerArchetype.MUSA,\n );\n const [isGameActive, setIsGameActive] = useState(false);\n const [gameWinner, setGameWinner] = useState<PlayerState | null>(null);\n const [matchStats, setMatchStats] = useState<MatchStatistics | null>(null);\n const [appReady, setAppReady] = useState(false);\n const [showSplash, setShowSplash] = useState(true);\n const [showAudioError, setShowAudioError] = useState(false);\n // Performance debug overlay toggle (P key in dev mode)\n const [showPerformanceDebug, setShowPerformanceDebug] = useState(false);\n // Transition state to allow WebGL cleanup between screens\n const [isTransitioning, setIsTransitioning] = useState(false);\n const pendingModeRef = useRef<{\n mode: GameMode;\n archetype?: PlayerArchetype;\n } | null>(null);\n\n // Combat players state - managed here so updates persist\n const [combatPlayers, setCombatPlayers] = useState<PlayerState[]>([]);\n\n const audio = useAudio();\n\n // Add responsive screen size detection with proper device detection\n // Uses user-agent detection first for high-res mobile devices\n const [screenSize, setScreenSize] = useState(() => {\n const platform = detectPlatform();\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n isMobile: platform.isMobile,\n isTablet: platform.isTablet,\n isDesktop: platform.isDesktop,\n };\n });\n\n useEffect(() => {\n // Define handlers outside async function for proper cleanup\n const handleGlobalError = (e: ErrorEvent) => {\n console.error(\"Global error:\", e.error);\n };\n\n const handleUnhandledRejection = (e: PromiseRejectionEvent) => {\n console.error(\"Unhandled promise rejection:\", e.reason);\n if (\n e.reason?.message?.includes(\"Failed to load\") ||\n e.reason?.message?.includes(\"no supported source\")\n ) {\n e.preventDefault();\n }\n };\n\n const initializeApp = async () => {\n try {\n window.focus();\n\n window.addEventListener(\"error\", handleGlobalError);\n window.addEventListener(\"unhandledrejection\", handleUnhandledRejection);\n\n // PHASE 2: Performance optimization initialization\n console.log(\"🔧 Initializing animation performance optimizations...\");\n\n // 1. Prewarm object pools for animation optimization\n // This eliminates GC pressure from ~1,344 allocations per frame\n const { ThreeObjectPools } = await import(\"./utils/threeObjectPool\");\n ThreeObjectPools.prewarmAll();\n const poolStatus = ThreeObjectPools.getStatus();\n console.log(\" ✓ Object pools prewarmed:\", poolStatus);\n\n // 2. Precompute all animations for 90%+ cache hit rate\n const { precomputeAnimation } =\n await import(\"./systems/animation/core/AnimationOptimizations\");\n const { ALL_ANIMATIONS } =\n await import(\"./systems/animation/core/AnimationRegistry\");\n\n let precomputedCount = 0;\n ALL_ANIMATIONS.forEach((animation) => {\n // Precompute at 60fps for smooth playback\n // Use animation.name as the unique identifier\n precomputeAnimation(animation.name, animation, 60);\n precomputedCount++;\n });\n console.log(` ✓ Precomputed ${precomputedCount} animations at 60fps`);\n\n console.log(\n \"✅ Animation optimizations ready (expect <5ms frame time, 90%+ cache hit)\",\n );\n\n setAppReady(true);\n console.log(\"🎯 Black Trigram app initialized\");\n } catch (error) {\n console.error(\"Failed to initialize app:\", error);\n setAppReady(true);\n }\n };\n\n initializeApp();\n\n // Cleanup global event handlers to prevent memory leaks\n return () => {\n window.removeEventListener(\"error\", handleGlobalError);\n window.removeEventListener(\n \"unhandledrejection\",\n handleUnhandledRejection,\n );\n };\n }, []);\n\n // Shared audio initialization logic for splash and retry\n const initializeAudioWithRetry = useCallback(async () => {\n if (!appReady) {\n console.warn(\"App not ready yet, please wait...\");\n return false;\n }\n try {\n await audio.initializeAudio();\n console.log(\"🎵 Audio initialized\");\n return true;\n } catch (error) {\n console.error(\"Failed to initialize audio:\", error);\n return false;\n }\n }, [audio, appReady]);\n\n // Handle splash screen start - initialize audio on user gesture\n const handleSplashStart = useCallback(async () => {\n setShowAudioError(false);\n const success = await initializeAudioWithRetry();\n if (success) {\n setShowSplash(false);\n } else {\n setShowAudioError(true);\n }\n }, [initializeAudioWithRetry]);\n\n const handleAudioErrorRetry = useCallback(async () => {\n setShowAudioError(false);\n const success = await initializeAudioWithRetry();\n if (success) {\n setShowSplash(false);\n } else {\n setShowAudioError(true);\n }\n }, [initializeAudioWithRetry]);\n\n const handleAudioErrorContinue = useCallback(() => {\n // Continue without sound\n setShowAudioError(false);\n setShowSplash(false);\n console.log(\"Continuing without audio (silent mode)\");\n }, []);\n\n // ✅ SIMPLIFIED: Handle game mode selection directly\n const handleGameStart = useCallback(\n (mode: GameMode, archetype?: PlayerArchetype) => {\n console.log(\"🎮 Starting game mode:\", mode, \"with archetype:\", archetype);\n\n // Store pending mode and start transition to allow WebGL cleanup\n pendingModeRef.current = { mode, archetype };\n setIsTransitioning(true);\n\n // Clear current mode first (unmounts Canvas)\n setGameMode(null);\n setIsGameActive(false);\n\n // After brief delay, mount new screen\n // Increased delay to allow proper WebGL context cleanup\n setTimeout(() => {\n const pending = pendingModeRef.current;\n if (!pending) return;\n\n // ✅ NEW: Handle controls and philosophy as separate modes\n if (\n pending.mode === GameMode.CONTROLS ||\n pending.mode === GameMode.PHILOSOPHY\n ) {\n setGameMode(pending.mode);\n setIsGameActive(false); // These are not game modes, just screens\n } else {\n setGameMode(pending.mode);\n setIsGameActive(true);\n }\n\n setGameWinner(null);\n setMatchStats(null);\n if (pending.archetype) {\n setSelectedArchetype(pending.archetype);\n }\n\n setIsTransitioning(false);\n pendingModeRef.current = null;\n }, SCREEN_TRANSITION_DELAY_MS); // Delay for WebGL cleanup\n },\n [],\n );\n\n const handleGameEnd = useCallback(\n (winner: number) => {\n setIsGameActive(false);\n setGameWinner(createPlayerFromArchetype(selectedArchetype, winner));\n // Reset combat players for next match\n setCombatPlayers([]);\n\n setMatchStats({\n totalDamageDealt: 150,\n totalDamageTaken: 100,\n criticalHits: 3,\n vitalPointHits: 2,\n techniquesUsed: 8,\n perfectStrikes: 1,\n consecutiveWins: 1,\n matchDuration: 120,\n totalMatches: 1,\n maxRounds: 3,\n winner: winner,\n totalRounds: 2,\n currentRound: 2,\n timeRemaining: 0,\n combatEvents: [],\n finalScore: {\n player1: winner === 0 ? 2 : 0,\n player2: winner === 1 ? 2 : 0,\n },\n roundsWon: {\n player1: winner === 0 ? 2 : 0,\n player2: winner === 1 ? 2 : 0,\n },\n player1: {\n wins: winner === 0 ? 1 : 0,\n losses: winner === 0 ? 0 : 1,\n hitsTaken: 5,\n hitsLanded: 8,\n totalDamageDealt: winner === 0 ? 150 : 100,\n totalDamageReceived: winner === 0 ? 100 : 150,\n techniques: [\"천둥벽력\", \"유수연타\"],\n perfectStrikes: winner === 0 ? 1 : 0,\n vitalPointHits: winner === 0 ? 2 : 1,\n consecutiveWins: winner === 0 ? 1 : 0,\n matchDuration: 120,\n },\n player2: {\n wins: winner === 1 ? 1 : 0,\n losses: winner === 1 ? 0 : 1,\n hitsTaken: 8,\n hitsLanded: 5,\n totalDamageDealt: winner === 1 ? 150 : 100,\n totalDamageReceived: winner === 1 ? 100 : 150,\n techniques: [\"화염지창\", \"벽력일섬\"],\n perfectStrikes: winner === 1 ? 1 : 0,\n vitalPointHits: winner === 1 ? 2 : 1,\n consecutiveWins: winner === 1 ? 1 : 0,\n matchDuration: 120,\n },\n });\n },\n [selectedArchetype],\n );\n\n const handleReturnToMenu = useCallback(() => {\n // Use same transition logic for return to menu\n setIsTransitioning(true);\n setGameMode(null);\n setIsGameActive(false);\n setGameWinner(null);\n setMatchStats(null);\n // Reset combat players so they reinitialize next combat\n setCombatPlayers([]);\n setTimeout(() => setIsTransitioning(false), MENU_TRANSITION_DELAY_MS);\n }, []);\n\n const handleRematch = useCallback(() => {\n // Restart combat with same settings\n if (!gameMode) {\n console.error(\n \"Cannot rematch: gameMode is not set. This should not happen - EndScreen only renders when gameMode is set.\",\n { gameMode, isGameActive, gameWinner, matchStats }\n );\n return;\n }\n \n setIsTransitioning(true);\n setGameWinner(null);\n setMatchStats(null);\n // Reset combat players so they reinitialize for rematch\n setCombatPlayers([]);\n \n setTimeout(() => {\n setIsGameActive(true);\n setIsTransitioning(false);\n }, SCREEN_TRANSITION_DELAY_MS);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- isGameActive, gameWinner, matchStats only used in error logging, not function logic\n }, [gameMode]);\n\n const handleViewTraining = useCallback(() => {\n // Navigate to training mode\n setIsTransitioning(true);\n setGameWinner(null);\n setMatchStats(null);\n setCombatPlayers([]);\n \n setTimeout(() => {\n setGameMode(GameMode.TRAINING);\n setIsGameActive(true);\n setIsTransitioning(false);\n }, SCREEN_TRANSITION_DELAY_MS);\n }, []);\n\n const renderCurrentScreen = () => {\n // Show loading during screen transitions\n if (isTransitioning) {\n return (\n <LoadingState\n progress={undefined}\n message=\"전환 중... | Transitioning...\"\n stage=\"assets\"\n />\n );\n }\n\n if (gameWinner && matchStats) {\n // ✅ NEW: Use EndScreen3D component\n return (\n <EndScreen3D\n winner={gameWinner}\n matchStats={matchStats}\n onReturnToMenu={handleReturnToMenu}\n onRematch={handleRematch}\n onViewReplay={handleViewTraining}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n }\n\n // ✅ NEW: Handle standalone screens first\n if (gameMode === GameMode.CONTROLS) {\n return (\n <ControlsScreen\n onReturnToMenu={handleReturnToMenu}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n }\n\n if (gameMode === GameMode.PHILOSOPHY) {\n return (\n <PhilosophyScreen\n onReturnToMenu={handleReturnToMenu}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n }\n\n // ✅ SIMPLIFIED: Only active game modes use isGameActive\n if (isGameActive && gameMode) {\n switch (gameMode) {\n case GameMode.TRAINING:\n return (\n <Suspense\n fallback={\n <LoadingState\n progress={undefined}\n message=\"훈련장 로딩 중... | Loading Training...\"\n stage=\"assets\"\n />\n }\n >\n <TrainingScreen\n onPlayerUpdate={(updates) => {\n console.log(\"Training player updated:\", updates);\n }}\n onReturnToMenu={handleReturnToMenu}\n width={screenSize.width}\n height={screenSize.height}\n initialArchetype={selectedArchetype}\n />\n </Suspense>\n );\n case GameMode.VERSUS:\n case GameMode.PRACTICE:\n // Initialize players if not already set\n if (combatPlayers.length === 0) {\n const player1 = createPlayerFromArchetype(selectedArchetype, 0);\n const player2 = createPlayerFromArchetype(\n PlayerArchetype.AMSALJA,\n 1,\n );\n // Use setTimeout to defer state update and avoid render-during-render\n setTimeout(() => setCombatPlayers([player1, player2]), 0);\n // Return loading state while players initialize\n return (\n <LoadingState\n progress={undefined}\n message=\"전투 준비 중... | Preparing Combat...\"\n stage=\"assets\"\n />\n );\n }\n\n return (\n <CombatScreen\n players={combatPlayers}\n currentRound={1}\n timeRemaining={180}\n isPaused={false}\n onPlayerUpdate={(playerIndex, updates) => {\n // Actually update the player state so damage persists!\n setCombatPlayers((prevPlayers) => {\n const newPlayers = [...prevPlayers];\n if (newPlayers[playerIndex]) {\n newPlayers[playerIndex] = {\n ...newPlayers[playerIndex],\n ...updates,\n };\n }\n return newPlayers;\n });\n }}\n onReturnToMenu={handleReturnToMenu}\n onGameEnd={handleGameEnd}\n gameMode={gameMode}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n default:\n return (\n <IntroScreen\n onMenuSelect={handleGameStart}\n onArchetypeSelect={setSelectedArchetype}\n selectedArchetype={selectedArchetype}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n }\n }\n\n // ✅ SIMPLIFIED: Default to intro screen\n return (\n <IntroScreen\n onMenuSelect={handleGameStart}\n onArchetypeSelect={setSelectedArchetype}\n selectedArchetype={selectedArchetype}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n };\n\n const containerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n containerRef.current?.focus();\n }, [appReady]);\n\n useEffect(() => {\n const handleResize = () => {\n // Clear cached platform info to get fresh detection on resize\n clearPlatformCache();\n const platform = detectPlatform();\n setScreenSize({\n width: platform.screenWidth,\n height: platform.screenHeight,\n isMobile: platform.isMobile,\n isTablet: platform.isTablet,\n isDesktop: platform.isDesktop,\n });\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n // F9 key toggle for performance debug overlay (dev mode only)\n // Note: P key is reserved for Philosophy screen\n useEffect(() => {\n if (!import.meta.env.DEV) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"F9\") {\n e.preventDefault();\n setShowPerformanceDebug((prev) => !prev);\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, []);\n\n if (!appReady) {\n return (\n <div className=\"app loading\" data-testid=\"app-container\">\n <LoadingState\n progress={undefined}\n message=\"앱 초기화 중 | Initializing app...\"\n stage=\"initialization\"\n />\n </div>\n );\n }\n\n // Show splash screen first to get user gesture for audio\n if (showSplash) {\n return (\n <div className=\"app\" data-testid=\"app-container\">\n <SplashScreen\n onStart={handleSplashStart}\n width={screenSize.width}\n height={screenSize.height}\n />\n {showAudioError && (\n <ErrorModal\n message=\"오디오 초기화에 실패했습니다. 재시도하거나 소리 없이 계속할 수 있습니다. | Audio initialization failed. You can retry or continue without sound.\"\n onRetry={handleAudioErrorRetry}\n onContinue={handleAudioErrorContinue}\n />\n )}\n </div>\n );\n }\n\n return (\n <div\n className=\"app\"\n tabIndex={0}\n ref={containerRef}\n data-testid=\"app-container\"\n >\n {/* All screens now use Three.js or pure React/HTML */}\n {renderCurrentScreen()}\n\n {/* Performance debug overlay (dev mode only, toggle with P key) */}\n {showPerformanceDebug && <PerformanceDebugOverlayHtml />}\n </div>\n );\n}\n\nexport default App;\n"],"mappings":"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"}
1
+ {"version":3,"file":"App2.js","names":[],"sources":["../src/App.tsx"],"sourcesContent":["import {\n lazy,\n Suspense,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport \"./App.css\";\n// Main application component - manages game state and screen navigation\nimport { useAudio } from \"./audio/AudioProvider\";\nimport { CombatScreen3D as CombatScreen } from \"./components/screens/combat/CombatScreen3D\";\nimport { ControlsScreen3D as ControlsScreen } from \"./components/screens/controls/ControlsScreen3D\";\nimport { EndScreen3D } from \"./components/screens/endscreen\";\nimport { IntroScreen3D as IntroScreen } from \"./components/screens/intro/IntroScreen3D\";\nimport { PhilosophyScreen3D as PhilosophyScreen } from \"./components/screens/philosophy/PhilosophyScreen3D\";\nimport { PerformanceDebugOverlayHtml } from \"./components/shared/debug/PerformanceDebugOverlayHtml\";\nimport { ErrorModal } from \"./components/shared/ui/ErrorModal\";\nimport { LoadingState } from \"./components/shared/ui/LoadingState\";\nimport { SplashScreen } from \"./components/shared/ui/SplashScreen\";\nimport { PlayerState } from \"./systems\";\nimport { MatchStatistics } from \"./systems/combat\";\nimport { GameMode, PlayerArchetype } from \"./types/common\";\nimport { clearPlatformCache, detectPlatform } from \"./utils/deviceDetection\";\nimport { createPlayerFromArchetype } from \"./utils/playerUtils\";\n\n// Lazy load heavy screens\nconst TrainingScreen = lazy(() =>\n import(\"./components/screens/training/TrainingScreen3D\").then((m) => ({\n default: m.TrainingScreen3D,\n })),\n);\n\n// 150ms delay to allow WebGL context cleanup between full-screen 3D scene transitions\nconst SCREEN_TRANSITION_DELAY_MS = 150;\n// 100ms delay for lighter menu/UI transitions where WebGL teardown/re-init cost is lower\nconst MENU_TRANSITION_DELAY_MS = 100;\n\nfunction App() {\n const [gameMode, setGameMode] = useState<GameMode | null>(null);\n const [selectedArchetype, setSelectedArchetype] = useState<PlayerArchetype>(\n PlayerArchetype.MUSA,\n );\n const [isGameActive, setIsGameActive] = useState(false);\n const [gameWinner, setGameWinner] = useState<PlayerState | null>(null);\n const [matchStats, setMatchStats] = useState<MatchStatistics | null>(null);\n const [appReady, setAppReady] = useState(false);\n const [showSplash, setShowSplash] = useState(true);\n const [showAudioError, setShowAudioError] = useState(false);\n // Performance debug overlay toggle (P key in dev mode)\n const [showPerformanceDebug, setShowPerformanceDebug] = useState(false);\n // Transition state to allow WebGL cleanup between screens\n const [isTransitioning, setIsTransitioning] = useState(false);\n const pendingModeRef = useRef<{\n mode: GameMode;\n archetype?: PlayerArchetype;\n } | null>(null);\n\n // Combat players state - managed here so updates persist\n const [combatPlayers, setCombatPlayers] = useState<PlayerState[]>([]);\n\n const audio = useAudio();\n\n // Add responsive screen size detection with proper device detection\n // Uses user-agent detection first for high-res mobile devices\n const [screenSize, setScreenSize] = useState(() => {\n const platform = detectPlatform();\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n isMobile: platform.isMobile,\n isTablet: platform.isTablet,\n isDesktop: platform.isDesktop,\n };\n });\n\n useEffect(() => {\n // Define handlers outside async function for proper cleanup\n const handleGlobalError = (e: ErrorEvent) => {\n console.error(\"Global error:\", e.error);\n };\n\n const handleUnhandledRejection = (e: PromiseRejectionEvent) => {\n console.error(\"Unhandled promise rejection:\", e.reason);\n if (\n e.reason?.message?.includes(\"Failed to load\") ||\n e.reason?.message?.includes(\"no supported source\")\n ) {\n e.preventDefault();\n }\n };\n\n const initializeApp = async () => {\n try {\n window.focus();\n\n window.addEventListener(\"error\", handleGlobalError);\n window.addEventListener(\"unhandledrejection\", handleUnhandledRejection);\n\n // PHASE 2: Performance optimization initialization\n console.log(\"🔧 Initializing animation performance optimizations...\");\n\n // 1. Prewarm object pools for animation optimization\n // This eliminates GC pressure from ~1,344 allocations per frame\n const { ThreeObjectPools } = await import(\"./utils/threeObjectPool\");\n ThreeObjectPools.prewarmAll();\n const poolStatus = ThreeObjectPools.getStatus();\n console.log(\" ✓ Object pools prewarmed:\", poolStatus);\n\n // 2. Precompute all animations for 90%+ cache hit rate\n const { precomputeAnimation } =\n await import(\"./systems/animation/core/AnimationOptimizations\");\n const { ALL_ANIMATIONS } =\n await import(\"./systems/animation/core/AnimationRegistry\");\n\n let precomputedCount = 0;\n ALL_ANIMATIONS.forEach((animation) => {\n // Precompute at 60fps for smooth playback\n // Use animation.name as the unique identifier\n precomputeAnimation(animation.name, animation, 60);\n precomputedCount++;\n });\n console.log(` ✓ Precomputed ${precomputedCount} animations at 60fps`);\n\n console.log(\n \"✅ Animation optimizations ready (expect <5ms frame time, 90%+ cache hit)\",\n );\n\n setAppReady(true);\n console.log(\"🎯 Black Trigram app initialized\");\n } catch (error) {\n console.error(\"Failed to initialize app:\", error);\n setAppReady(true);\n }\n };\n\n initializeApp();\n\n // Cleanup global event handlers to prevent memory leaks\n return () => {\n window.removeEventListener(\"error\", handleGlobalError);\n window.removeEventListener(\n \"unhandledrejection\",\n handleUnhandledRejection,\n );\n };\n }, []);\n\n // Shared audio initialization logic for splash and retry\n const initializeAudioWithRetry = useCallback(async () => {\n if (!appReady) {\n console.warn(\"App not ready yet, please wait...\");\n return false;\n }\n try {\n await audio.initializeAudio();\n console.log(\"🎵 Audio initialized\");\n return true;\n } catch (error) {\n console.error(\"Failed to initialize audio:\", error);\n return false;\n }\n }, [audio, appReady]);\n\n // Handle splash screen start - initialize audio on user gesture\n const handleSplashStart = useCallback(async () => {\n setShowAudioError(false);\n const success = await initializeAudioWithRetry();\n if (success) {\n setShowSplash(false);\n } else {\n setShowAudioError(true);\n }\n }, [initializeAudioWithRetry]);\n\n const handleAudioErrorRetry = useCallback(async () => {\n setShowAudioError(false);\n const success = await initializeAudioWithRetry();\n if (success) {\n setShowSplash(false);\n } else {\n setShowAudioError(true);\n }\n }, [initializeAudioWithRetry]);\n\n const handleAudioErrorContinue = useCallback(() => {\n // Continue without sound\n setShowAudioError(false);\n setShowSplash(false);\n console.log(\"Continuing without audio (silent mode)\");\n }, []);\n\n // ✅ SIMPLIFIED: Handle game mode selection directly\n const handleGameStart = useCallback(\n (mode: GameMode, archetype?: PlayerArchetype) => {\n console.log(\"🎮 Starting game mode:\", mode, \"with archetype:\", archetype);\n\n // Store pending mode and start transition to allow WebGL cleanup\n pendingModeRef.current = { mode, archetype };\n setIsTransitioning(true);\n\n // Clear current mode first (unmounts Canvas)\n setGameMode(null);\n setIsGameActive(false);\n\n // After brief delay, mount new screen\n // Increased delay to allow proper WebGL context cleanup\n setTimeout(() => {\n const pending = pendingModeRef.current;\n if (!pending) return;\n\n // ✅ NEW: Handle controls and philosophy as separate modes\n if (\n pending.mode === GameMode.CONTROLS ||\n pending.mode === GameMode.PHILOSOPHY\n ) {\n setGameMode(pending.mode);\n setIsGameActive(false); // These are not game modes, just screens\n } else {\n setGameMode(pending.mode);\n setIsGameActive(true);\n }\n\n setGameWinner(null);\n setMatchStats(null);\n if (pending.archetype) {\n setSelectedArchetype(pending.archetype);\n }\n\n setIsTransitioning(false);\n pendingModeRef.current = null;\n }, SCREEN_TRANSITION_DELAY_MS); // Delay for WebGL cleanup\n },\n [],\n );\n\n const handleGameEnd = useCallback(\n (winner: number) => {\n setIsGameActive(false);\n setGameWinner(createPlayerFromArchetype(selectedArchetype, winner));\n // Reset combat players for next match\n setCombatPlayers([]);\n\n setMatchStats({\n totalDamageDealt: 150,\n totalDamageTaken: 100,\n criticalHits: 3,\n vitalPointHits: 2,\n techniquesUsed: 8,\n perfectStrikes: 1,\n consecutiveWins: 1,\n matchDuration: 120,\n totalMatches: 1,\n maxRounds: 3,\n winner: winner,\n totalRounds: 2,\n currentRound: 2,\n timeRemaining: 0,\n combatEvents: [],\n finalScore: {\n player1: winner === 0 ? 2 : 0,\n player2: winner === 1 ? 2 : 0,\n },\n roundsWon: {\n player1: winner === 0 ? 2 : 0,\n player2: winner === 1 ? 2 : 0,\n },\n player1: {\n wins: winner === 0 ? 1 : 0,\n losses: winner === 0 ? 0 : 1,\n hitsTaken: 5,\n hitsLanded: 8,\n totalDamageDealt: winner === 0 ? 150 : 100,\n totalDamageReceived: winner === 0 ? 100 : 150,\n techniques: [\"천둥벽력\", \"유수연타\"],\n perfectStrikes: winner === 0 ? 1 : 0,\n vitalPointHits: winner === 0 ? 2 : 1,\n consecutiveWins: winner === 0 ? 1 : 0,\n matchDuration: 120,\n },\n player2: {\n wins: winner === 1 ? 1 : 0,\n losses: winner === 1 ? 0 : 1,\n hitsTaken: 8,\n hitsLanded: 5,\n totalDamageDealt: winner === 1 ? 150 : 100,\n totalDamageReceived: winner === 1 ? 100 : 150,\n techniques: [\"화염지창\", \"벽력일섬\"],\n perfectStrikes: winner === 1 ? 1 : 0,\n vitalPointHits: winner === 1 ? 2 : 1,\n consecutiveWins: winner === 1 ? 1 : 0,\n matchDuration: 120,\n },\n });\n },\n [selectedArchetype],\n );\n\n const handleReturnToMenu = useCallback(() => {\n // Use same transition logic for return to menu\n setIsTransitioning(true);\n setGameMode(null);\n setIsGameActive(false);\n setGameWinner(null);\n setMatchStats(null);\n // Reset combat players so they reinitialize next combat\n setCombatPlayers([]);\n setTimeout(() => setIsTransitioning(false), MENU_TRANSITION_DELAY_MS);\n }, []);\n\n const handleRematch = useCallback(() => {\n // Restart combat with same settings\n if (!gameMode) {\n console.error(\n \"Cannot rematch: gameMode is not set. This should not happen - EndScreen only renders when gameMode is set.\",\n { gameMode, isGameActive, gameWinner, matchStats }\n );\n return;\n }\n \n setIsTransitioning(true);\n setGameWinner(null);\n setMatchStats(null);\n // Reset combat players so they reinitialize for rematch\n setCombatPlayers([]);\n \n setTimeout(() => {\n setIsGameActive(true);\n setIsTransitioning(false);\n }, SCREEN_TRANSITION_DELAY_MS);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- isGameActive, gameWinner, matchStats only used in error logging, not function logic\n }, [gameMode]);\n\n const handleViewTraining = useCallback(() => {\n // Navigate to training mode\n setIsTransitioning(true);\n setGameWinner(null);\n setMatchStats(null);\n setCombatPlayers([]);\n \n setTimeout(() => {\n setGameMode(GameMode.TRAINING);\n setIsGameActive(true);\n setIsTransitioning(false);\n }, SCREEN_TRANSITION_DELAY_MS);\n }, []);\n\n const renderCurrentScreen = () => {\n // Show loading during screen transitions\n if (isTransitioning) {\n return (\n <LoadingState\n progress={undefined}\n message=\"전환 중... | Transitioning...\"\n stage=\"assets\"\n />\n );\n }\n\n if (gameWinner && matchStats) {\n // ✅ NEW: Use EndScreen3D component\n return (\n <EndScreen3D\n winner={gameWinner}\n matchStats={matchStats}\n onReturnToMenu={handleReturnToMenu}\n onRematch={handleRematch}\n onViewReplay={handleViewTraining}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n }\n\n // ✅ NEW: Handle standalone screens first\n if (gameMode === GameMode.CONTROLS) {\n return (\n <ControlsScreen\n onReturnToMenu={handleReturnToMenu}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n }\n\n if (gameMode === GameMode.PHILOSOPHY) {\n return (\n <PhilosophyScreen\n onReturnToMenu={handleReturnToMenu}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n }\n\n // ✅ SIMPLIFIED: Only active game modes use isGameActive\n if (isGameActive && gameMode) {\n switch (gameMode) {\n case GameMode.TRAINING:\n return (\n <Suspense\n fallback={\n <LoadingState\n progress={undefined}\n message=\"훈련장 로딩 중... | Loading Training...\"\n stage=\"assets\"\n />\n }\n >\n <TrainingScreen\n onPlayerUpdate={(updates) => {\n console.log(\"Training player updated:\", updates);\n }}\n onReturnToMenu={handleReturnToMenu}\n width={screenSize.width}\n height={screenSize.height}\n initialArchetype={selectedArchetype}\n />\n </Suspense>\n );\n case GameMode.VERSUS:\n case GameMode.PRACTICE:\n // Initialize players if not already set\n if (combatPlayers.length === 0) {\n const player1 = createPlayerFromArchetype(selectedArchetype, 0);\n const player2 = createPlayerFromArchetype(\n PlayerArchetype.AMSALJA,\n 1,\n );\n // Use setTimeout to defer state update and avoid render-during-render\n setTimeout(() => setCombatPlayers([player1, player2]), 0);\n // Return loading state while players initialize\n return (\n <LoadingState\n progress={undefined}\n message=\"전투 준비 중... | Preparing Combat...\"\n stage=\"assets\"\n />\n );\n }\n\n return (\n <CombatScreen\n players={combatPlayers}\n currentRound={1}\n timeRemaining={180}\n isPaused={false}\n onPlayerUpdate={(playerIndex, updates) => {\n // Actually update the player state so damage persists!\n setCombatPlayers((prevPlayers) => {\n const newPlayers = [...prevPlayers];\n if (newPlayers[playerIndex]) {\n newPlayers[playerIndex] = {\n ...newPlayers[playerIndex],\n ...updates,\n };\n }\n return newPlayers;\n });\n }}\n onReturnToMenu={handleReturnToMenu}\n onGameEnd={handleGameEnd}\n gameMode={gameMode}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n default:\n return (\n <IntroScreen\n onMenuSelect={handleGameStart}\n onArchetypeSelect={setSelectedArchetype}\n selectedArchetype={selectedArchetype}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n }\n }\n\n // ✅ SIMPLIFIED: Default to intro screen\n return (\n <IntroScreen\n onMenuSelect={handleGameStart}\n onArchetypeSelect={setSelectedArchetype}\n selectedArchetype={selectedArchetype}\n width={screenSize.width}\n height={screenSize.height}\n />\n );\n };\n\n const containerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n containerRef.current?.focus();\n }, [appReady]);\n\n useEffect(() => {\n const handleResize = () => {\n // Clear cached platform info to get fresh detection on resize\n clearPlatformCache();\n const platform = detectPlatform();\n setScreenSize({\n width: platform.screenWidth,\n height: platform.screenHeight,\n isMobile: platform.isMobile,\n isTablet: platform.isTablet,\n isDesktop: platform.isDesktop,\n });\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n // F9 key toggle for performance debug overlay (dev mode only)\n // Note: P key is reserved for Philosophy screen\n useEffect(() => {\n if (!import.meta.env.DEV) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"F9\") {\n e.preventDefault();\n setShowPerformanceDebug((prev) => !prev);\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, []);\n\n if (!appReady) {\n return (\n <div className=\"app loading\" data-testid=\"app-container\">\n <LoadingState\n progress={undefined}\n message=\"앱 초기화 중 | Initializing app...\"\n stage=\"initialization\"\n />\n </div>\n );\n }\n\n // Show splash screen first to get user gesture for audio\n if (showSplash) {\n return (\n <div className=\"app\" data-testid=\"app-container\">\n <SplashScreen\n onStart={handleSplashStart}\n width={screenSize.width}\n height={screenSize.height}\n />\n {showAudioError && (\n <ErrorModal\n message=\"오디오 초기화에 실패했습니다. 재시도하거나 소리 없이 계속할 수 있습니다. | Audio initialization failed. You can retry or continue without sound.\"\n onRetry={handleAudioErrorRetry}\n onContinue={handleAudioErrorContinue}\n />\n )}\n </div>\n );\n }\n\n return (\n <div\n className=\"app\"\n tabIndex={0}\n ref={containerRef}\n data-testid=\"app-container\"\n >\n {/* All screens now use Three.js or pure React/HTML */}\n {renderCurrentScreen()}\n\n {/* Performance debug overlay (dev mode only, toggle with P key) */}\n {showPerformanceDebug && <PerformanceDebugOverlayHtml />}\n </div>\n );\n}\n\nexport default App;\n"],"mappings":"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"}
@@ -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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"}
@@ -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":";AAoBA,IAAM,0BAA0B;AAwDhC,IAAa,qBAAb,MAAgC;CAC9B,yBAAiB,IAAI,KAAiC;CACtD,2BAAmB,IAAI,KAA+B;CACtD,2BAAmB,IAAI,KAA6B;CACpD,8BAA+C,IAAI,KAAK;CAGxD,SAAgC;EAC9B,SAAS,EAAE;EACX,SAAS,EAAE;EACX,SAAS;GACP,MAAM;GACN,KAAK;GACL,IAAI;GACJ,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACN;EACD,cAAc,EAAE;EAChB,IAAI,EAAE;EACP;CAED,MAAiD,EAAE;CACnD,QAAiD,EAAE;CACnD,QAA+C,EAAE;CAEjD,cAAc;AACZ,OAAK,yBAAyB;AAC9B,OAAK,6BAA6B;AAClC,OAAK,uBAAuB;;CAG9B,0BAAwC;AAEtC,OAAK,cAAc,eAAe;GAChC,IAAI;GACJ,MAAM;GACN,MAAM;GACN,OAAO;IAAE,QAAQ;IAAS,SAAS;IAAuB;GAC1D,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,MAAM;GACN,YAAY,CACV,uCACA,uCACD;GACD,KAAK;GACL,YAAY;GACZ,aAAa;GACd,CAAC;AAGF,OAAK,YAAY,cAAc;GAC7B,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY;IACV;IACA;IACA;IACA;IACA;IACD;GACF,CAAC;AAEF,OAAK,YAAY,eAAe;GAC9B,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY;IACV;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GACF,CAAC;AAEF,OAAK,YAAY,aAAa;GAC5B,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY;IACV;IACA;IACA;IACA;IACA;IACD;GACF,CAAC;AAEF,OAAK,YAAY,iBAAiB;GAChC,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY;IACV;IACA;IACA;IACA;IACA;IACD;GACF,CAAC;AAEF,OAAK,YAAY,cAAc;GAC7B,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY;IACV;IACA;IACA;IACA;IACA;IACD;GACF,CAAC;;;;;;CAOJ,8BAA4C;AAE1C,OAAK,cAAc,gBAAgB;GACjC,IAAI;GACJ,MAAM;GACN,MAAM;GACN,OAAO;IAAE,QAAQ;IAAS,SAAS;IAAgB;GACnD,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,MAAM;GACN,YAAY,CACV,yCACA,uCACD;GACD,KAAK;GACL,YAAY;GACZ,aAAa;GACd,CAAC;AAGF,OAAK,cAAc,qBAAqB;GACtC,IAAI;GACJ,MAAM;GACN,MAAM;GACN,OAAO;IAAE,QAAQ;IAAa,SAAS;IAAqB;GAC5D,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,MAAM;GACN,YAAY,CACV,8CACA,4CACD;GACD,KAAK;GACL,YAAY;GACZ,aAAa;GACd,CAAC;AAGF,OAAK,cAAc,oBAAoB;GACrC,IAAI;GACJ,MAAM;GACN,MAAM;GACN,OAAO;IAAE,QAAQ;IAAY,SAAS;IAAoB;GAC1D,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,MAAM;GACN,YAAY,CACV,6CACA,2CACD;GACD,KAAK;GACL,YAAY;GACZ,aAAa;GACd,CAAC;AAWF;GAPE;IAAE,IAAI;IAAsB,MAAM;IAAsB,QAAQ;IAAS,MAAM;IAAgB;GAC/F;IAAE,IAAI;IAAwB,MAAM;IAAwB,QAAQ;IAAU,MAAM;IAAkB;GACtG;IAAE,IAAI;IAAsB,MAAM;IAAsB,QAAQ;IAAS,MAAM;IAAgB;GAC/F;IAAE,IAAI;IAAuB,MAAM;IAAuB,QAAQ;IAAW,MAAM;IAAiB;GACpG;IAAE,IAAI;IAAsB,MAAM;IAAsB,QAAQ;IAAY,MAAM;IAAgB;GAGpG,CAAgB,SAAQ,UAAS;AAC/B,QAAK,cAAc,MAAM,IAAI;IAC3B,IAAI,MAAM;IACV,MAAM;IACN,MAAM,MAAM;IACZ,OAAO;KAAE,QAAQ,MAAM;KAAQ,SAAS,MAAM;KAAM;IACpD,UAAU;IACV,KAAK,wCAAwC,MAAM,KAAK;IACxD,SAAS,CAAC,YAAY;IACtB,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,KAAK;IACL,YAAY;IACZ,aAAa;IACd,CAAC;IACF;AAGF,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,sBAAsB,KAAK;GAC1C,IAAI,sBAAsB;GAC1B,MAAM;GACN,MAAM,eAAe;GACrB,UAAU;GACV,KAAK,+CAA+C,EAAE;GACtD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,uBAAuB,KAAK;GAC3C,IAAI,uBAAuB;GAC3B,MAAM;GACN,MAAM,gBAAgB;GACtB,UAAU;GACV,KAAK,gDAAgD,EAAE;GACvD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,YAAY,gBAAgB;GAC/B,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY;IACV;IACA;IACA;IACD;GACF,CAAC;AAEF,OAAK,YAAY,iBAAiB;GAChC,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY;IACV;IACA;IACA;IACD;GACF,CAAC;AAEF,OAAK,YAAY,gBAAgB;GAC/B,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY,CACV,8CACA,4CACD;GACF,CAAC;AAGF,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,mBAAmB,KAAK;GACvC,IAAI,mBAAmB;GACvB,MAAM;GACN,MAAM,mBAAmB;GACzB,UAAU;GACV,KAAK,4CAA4C,EAAE;GACnD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,uBAAuB,KAAK;GAC3C,IAAI,uBAAuB;GAC3B,MAAM;GACN,MAAM,gBAAgB;GACtB,UAAU;GACV,KAAK,gDAAgD,EAAE;GACvD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,aAAa,KAAK;GACjC,IAAI,aAAa;GACjB,MAAM;GACN,MAAM,aAAa;GACnB,UAAU;GACV,KAAK,oCAAoC,EAAE;GAC3C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,cAAc,KAAK;GAClC,IAAI,cAAc;GAClB,MAAM;GACN,MAAM,cAAc;GACpB,UAAU;GACV,KAAK,qCAAqC,EAAE;GAC5C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,aAAa,KAAK;GACjC,IAAI,aAAa;GACjB,MAAM;GACN,MAAM,aAAa;GACnB,UAAU;GACV,KAAK,oCAAoC,EAAE;GAC3C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,gBAAgB,KAAK;GACpC,IAAI,gBAAgB;GACpB,MAAM;GACN,MAAM,gBAAgB;GACtB,UAAU;GACV,KAAK,uCAAuC,EAAE;GAC9C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,aAAa,KAAK;GACjC,IAAI,aAAa;GACjB,MAAM;GACN,MAAM,aAAa;GACnB,UAAU;GACV,KAAK,oCAAoC,EAAE;GAC3C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,YAAY,wBAAwB;GACvC,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIF,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,iBAAiB,KAAK;GACrC,IAAI,iBAAiB;GACrB,MAAM;GACN,MAAM,iBAAiB,EAAE;GACzB,UAAU;GACV,KAAK,wCAAwC,EAAE;GAC/C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,gBAAgB,KAAK;GACpC,IAAI,gBAAgB;GACpB,MAAM;GACN,MAAM,gBAAgB,EAAE;GACxB,UAAU;GACV,KAAK,uCAAuC,EAAE;GAC9C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,aAAa,KAAK;GACjC,IAAI,aAAa;GACjB,MAAM;GACN,MAAM,aAAa,EAAE;GACrB,UAAU;GACV,KAAK,oCAAoC,EAAE;GAC3C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAKJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,cAAc,KAAK;GAClC,IAAI,cAAc;GAClB,MAAM;GACN,MAAM,cAAc,EAAE;GACtB,UAAU;GACV,KAAK,qCAAqC,EAAE;GAC5C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,oBAAoB,KAAK;GACxC,IAAI,oBAAoB;GACxB,MAAM;GACN,MAAM,oBAAoB,EAAE;GAC5B,UAAU;GACV,KAAK,2CAA2C,EAAE;GAClD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,WAAW,KAAK;GAC/B,IAAI,WAAW;GACf,MAAM;GACN,MAAM,WAAW,EAAE;GACnB,UAAU;GACV,KAAK,kCAAkC,EAAE;GACzC,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,UAAU,KAAK;GAC9B,IAAI,UAAU;GACd,MAAM;GACN,MAAM,UAAU,EAAE;GAClB,UAAU;GACV,KAAK,iCAAiC,EAAE;GACxC,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAKJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,YAAY,KAAK;GAChC,IAAI,YAAY;GAChB,MAAM;GACN,MAAM,YAAY,EAAE;GACpB,UAAU;GACV,KAAK,mCAAmC,EAAE;GAC1C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,YAAY,cAAc;GAC7B,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa;GACvB,QAAQ;GACR,QAAQ;GACT,CAAC;AAGF,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,iBAAiB,KAAK;GACrC,IAAI,iBAAiB;GACrB,MAAM;GACN,MAAM,iBAAiB,EAAE;GACzB,UAAU;GACV,KAAK,0CAA0C,EAAE;GACjD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,eAAe,KAAK;GACnC,IAAI,eAAe;GACnB,MAAM;GACN,MAAM,eAAe,EAAE;GACvB,UAAU;GACV,KAAK,wCAAwC,EAAE;GAC/C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAKJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,iBAAiB,KAAK;GACrC,IAAI,iBAAiB;GACrB,MAAM;GACN,MAAM,oBAAoB,EAAE;GAC5B,UAAU;GACV,KAAK,oCAAoC,EAAE;GAC3C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,OAAO;GACR,CAAC;AAKJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,kBAAkB,KAAK;GACtC,IAAI,kBAAkB;GACtB,MAAM;GACN,MAAM,kBAAkB,EAAE;GAC1B,UAAU;GACV,KAAK,4CAA4C,EAAE;GACnD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,OAAO;GACR,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,SAAS,KAAK;GAC7B,IAAI,SAAS;GACb,MAAM;GACN,MAAM,SAAS;GACf,UAAU;GACV,KAAK,oCAAoC,EAAE;GAC3C,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,iBAAiB,KAAK;GACrC,IAAI,iBAAiB;GACrB,MAAM;GACN,MAAM,iBAAiB;GACvB,UAAU;GACV,KAAK,4CAA4C,EAAE;GACnD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,aAAa,KAAK;GACjC,IAAI,aAAa;GACjB,MAAM;GACN,MAAM,aAAa;GACnB,UAAU;GACV,KAAK,yCAAyC,EAAE;GAChD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,IAAI,IAAI,GAAG,KAAK,GAAG,IACtB,MAAK,YAAY,cAAc,KAAK;GAClC,IAAI,cAAc;GAClB,MAAM;GACN,MAAM,cAAc;GACpB,UAAU;GACV,KAAK,0CAA0C,EAAE;GACjD,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACT,CAAC;AAIJ,OAAK,YAAY,aAAa;GAC5B,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY;IACV;IACA;IACA;IACA;IACA;IACD;GACF,CAAC;AAEF,OAAK,YAAY,cAAc;GAC7B,IAAI;GACJ,MAAM;GACN,MAAM;GACN,UAAU;GACV,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,YAAY;IACV;IACA;IACA;IACA;IACA;IACD;GACF,CAAC;;CAGJ,YAAmB,IAAmB,QAA2B;AAC/D,OAAK,OAAO,IAAI,IAAI,OAAO;AAC3B,OAAK,IAAI,MAAM;;CAGjB,cAAqB,IAAkB,OAAyB;AAC9D,OAAK,SAAS,IAAI,IAAI,MAAM;AAC5B,OAAK,MAAM,MAAM;;CAGnB,cAAqB,IAAiB,OAAwB;AAC5D,OAAK,SAAS,IAAI,IAAI,MAAM;AAC5B,OAAK,MAAM,MAAM;;CAGnB,OAAc,IAA4C;AACxD,SAAO,KAAK,OAAO,IAAI,GAAG;;CAG5B,SAAgB,IAA0C;AACxD,SAAO,KAAK,SAAS,IAAI,GAAG;;CAG9B,SAAgB,IAAwC;AACtD,SAAO,KAAK,SAAS,IAAI,GAAG;;CAG9B,SAAqC;AACnC,SAAO;GACL,KAAK,KAAK;GACV,OAAO,KAAK;GACZ,OAAO,KAAK;GACZ,QAAQ,KAAK;GACd;;CAIH,eAAiE,IAAI,IAAI;EACvE,CACE,gBACA;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,UAAU;GACV,OAAO;GACP,YAAY,CAAC,+CAA+C,8CAA8C;GAC3G,CACF;EACD,CACE,iBACA;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,UAAU;GACV,OAAO;GACR,CACF;EACD,CACE,sBACA;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,UAAU;GACV,OAAO;GACR,CACF;EACD,CACE,aACA;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,UAAU;GACV,OAAO;GACP,YAAY,CACV,0CACA,yCACD;GACF,CACF;EACF,CAA2D;CAG5D,cAA8D,IAAI,IAAI;EACpE,CACE,eACA;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,QAAQ;GACR,MAAM;GACN,UAAU;GACV,YAAY,CACV,uCACA,uCACD;GACD,KAAK;GACL,YAAY;GACZ,aAAa;GACd,CACF;EACD,CACE,gBACA;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,OAAO;IAAE,QAAQ;IAAS,SAAS;IAAgB;GACnD,QAAQ;GACR,MAAM;GACN,UAAU;GACV,KAAK;GACL,YAAY;GACZ,aAAa;GACd,CACF;EACD,CACE,mBACA;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,KAAK;GACL,SAAS,CAAC,aAAa,aAAa;GACpC,QAAQ;GACR,OAAO;IAAE,QAAQ;IAAU,SAAS;IAAqB;GACzD,QAAQ;GACR,MAAM;GACN,UAAU;GACV,KAAK;GACL,YAAY;GACZ,aAAa;GACd,CACF;EACF,CAAyD;CAG1D,mBAAgC;AAE9B,OAAK,aAAa,SAAS,WAAW;AACpC,WAAQ,IAAI,yBAAyB,OAAO,KAAK;IACjD;;CAGJ,kBAA+B;AAE7B,OAAK,YAAY,SAAS,UAAU;AAClC,WAAQ,IAAI,wBAAwB,MAAM,KAAK;IAC/C;;CAIJ,sBAA6B,MAAuC;AAClE,OAAK,MAAM,GAAG,WAAW,KAAK,aAC5B,KAAI,OAAO,SAAS,KAClB,QAAO;;CAMb,qBAA4B,MAAsC;AAChE,OAAK,MAAM,GAAG,UAAU,KAAK,YAC3B,KAAI,MAAM,SAAS,KACjB,QAAO;;CAOb,eAAsB,IAA4C;AAChE,SAAO,KAAK,aAAa,IAAI,GAAG;;CAIlC,cAAqB,IAA0C;AAC7D,SAAO,KAAK,YAAY,IAAI,GAAG;;CAIjC,uBAA2C;AACzC,SAAO;GACL,IAAI;GACJ,MAAM;GACN,MAAM;GACN,KAAK;GACL,SAAS,CAAC,YAAY;GACtB,QAAQ;GACR,QAAQ;GACR,UAAU;GACV,OAAO;GACR;;CAIH,MAAa,aAA4B;AACvC,UAAQ,IAAI,0BAA0B;;;;;CAOxC,wBAAsC;AAEpC,OAAK,mBAAmB;GACtB,IAAI;GACJ,MAAM;GACN,UAAU;GACV,QAAQ;IACN;IACA;IACA;IACA;IACA;IACD;GACD,UAAU;GACX,CAAC;AAGF,OAAK,mBAAmB;GACtB,IAAI;GACJ,MAAM;GACN,UAAU;GACV,QAAQ,CAAC,cAAc;GACvB,UAAU;GACX,CAAC;AAGF,OAAK,mBAAmB;GACtB,IAAI;GACJ,MAAM;GACN,UAAU;GACV,QAAQ;IACN;IACA;IACA;IACA;IACA;IACA;IACD;GACD,UAAU;GACX,CAAC;AAGF,OAAK,mBAAmB;GACtB,IAAI;GACJ,MAAM;GACN,UAAU;GACV,QAAQ,CACN,qBACA,mBACD;GACD,UAAU;GACX,CAAC;AAGF,OAAK,mBAAmB;GACtB,IAAI;GACJ,MAAM;GACN,UAAU;GACV,QAAQ;IACN;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GACD,UAAU;GACX,CAAC;AAEF,OAAK,mBAAmB;GACtB,IAAI;GACJ,MAAM;GACN,UAAU;GACV,QAAQ;IACN;IACA;IACA;IACA;IACA;IACA;IACD;GACD,UAAU;GACX,CAAC;AAEF,OAAK,mBAAmB;GACtB,IAAI;GACJ,MAAM;GACN,UAAU;GACV,QAAQ;IACN;IACA;IACA;IACA;IACA;IACD;GACD,UAAU;GACX,CAAC;AAEF,OAAK,mBAAmB;GACtB,IAAI;GACJ,MAAM;GACN,UAAU;GACV,QAAQ;IACN;IACA;IACA;IACA;IACA;IACD;GACD,UAAU;GACX,CAAC;AAGF,OAAK,mBAAmB;GACtB,IAAI;GACJ,MAAM;GACN,UAAU;GACV,QAAQ;IACN;IACA;IACA;IACA;IACA;IACA;IACD;GACD,UAAU;GACX,CAAC;;;;;;CAOJ,mBAA0B,OAAyB;AACjD,OAAK,YAAY,IAAI,MAAM,IAAI,MAAM;;;;;;;CAQvC,cAAqB,SAAyC;AAC5D,SAAO,KAAK,YAAY,IAAI,QAAQ;;;;;;CAOtC,oBAAkD;AAChD,SAAO,MAAM,KAAK,KAAK,YAAY,QAAQ,CAAC;;;;;;;CAQ9C,yBAAgC,UAA+C;AAC7E,SAAO,MAAM,KAAK,KAAK,YAAY,QAAQ,CAAC,CAAC,QAC1C,UAAU,MAAM,aAAa,SAC/B;;;;;;;CAQH,iBAAwB,SAAwC;EAC9D,MAAM,QAAQ,KAAK,YAAY,IAAI,QAAQ;AAC3C,MAAI,CAAC,MAAO,QAAO,EAAE;EAErB,MAAM,SAAuB,EAAE;AAC/B,OAAK,MAAM,WAAW,MAAM,QAAQ;GAElC,MAAM,QAAQ,KAAK,OAAO,QAAQ,IAAI,KAAK,SAAS,QAAQ,IAAI,KAAK,SAAS,QAAQ;AACtF,OAAI,MACF,QAAO,KAAK,MAAM;;AAItB,SAAO;;;;;;CAOT,iBAA4C;EAC1C,MAAM,YAAgD,EAAE;AAGxD,OAAK,OAAO,SAAS,KAAK,OAAO;AAC/B,aAAU,MAAM;IACd,GAAG;IACH,iBAAiB;IAClB;IACD;AAGF,OAAK,SAAS,SAAS,OAAO,OAAO;AACnC,aAAU,MAAM;IACd,GAAG;IACH,iBAAiB;IAClB;IACD;AAGF,OAAK,SAAS,SAAS,OAAO,OAAO;AACnC,aAAU,MAAM;IACd,GAAG;IACH,iBAAiB;IAClB;IACD;EAEF,MAAM,cAAc,OAAO,KAAK,UAAU,CAAC;AAG3C,SAAO;GACL,SAAS;GACT;GACA,aALsB,cAAc;GAMpC,QAAQ,KAAK,mBAAmB;GAChC,QAAQ;GACT;;;AAKL,IAAa,qBAAqB,IAAI,oBAAoB"}
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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"}