blacktrigram 0.7.25 → 0.7.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js +5 -0
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js +19 -0
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js +5 -0
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/data/archetypePhysicalAttributes.js +420 -0
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js +4 -0
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js +3 -0
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js +47 -0
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js +30 -0
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js +4 -0
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js +7 -0
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js +25 -0
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +60 -0
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js +288 -0
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js +24 -0
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/constants/AnatomicalLimits.js +133 -1
- package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js +24 -0
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js +6 -0
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js +11 -0
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js +35 -0
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js +14 -0
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/types/LayoutTypes.js +32 -0
- package/lib/types/LayoutTypes.js.map +1 -1
- package/lib/types/constants/colors.js +9 -0
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js +84 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/index.js +5 -0
- package/lib/types/constants/index.js.map +1 -1
- package/lib/types/constants/layout.js +3 -0
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js +4 -0
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/physicsConstants.js +24 -0
- package/lib/types/physicsConstants.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js +9 -0
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/characterScaling.js +13 -0
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js +4 -0
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js +36 -0
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.d.ts +10 -0
- package/lib/utils/math.d.ts.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js +9 -0
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js +4 -0
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js +23 -0
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- 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":"AudioAssetRegistry.js","names":[],"sources":["../../src/audio/AudioAssetRegistry.ts"],"sourcesContent":["/**\n * Audio Asset Registry for Black Trigram Korean Martial Arts\n * Manages all audio assets including Korean martial arts specific sounds\n */\n\nimport type { AudioAsset } from \"./types\";\nimport {\n AudioCategory,\n CombatAudioMap,\n MusicTrack,\n MusicTrackId,\n SoundEffect,\n SoundEffectId,\n VoiceLine,\n VoiceLineId,\n} from \"./types\";\n\nexport type LoadPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\nexport interface IAudioAssetRegistry {\n readonly music: Record<string, MusicTrack>;\n readonly sfx: Record<string, SoundEffect>;\n readonly voice: Record<string, VoiceLine>;\n readonly combat: CombatAudioMap;\n}\n\nexport interface EnhancedAudioAsset {\n readonly id: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n volume?: number;\n readonly loop?: boolean;\n category?: AudioCategory;\n readonly metadata?: {\n readonly duration: number;\n readonly bitrate?: number;\n readonly channels?: number;\n readonly sampleRate?: number;\n };\n readonly preloadPriority?: LoadPriority;\n readonly streaming?: boolean;\n readonly compressionOptions?: {\n readonly format: string;\n readonly quality: number;\n };\n}\n\n// Enhanced audio registry with proper types\nexport interface EnhancedAudioAssetRegistry extends AudioAssetRegistry {\n readonly enhanced?: Record<string, EnhancedAudioAsset>;\n}\n\n// Asset group definition for batch loading\nexport interface AssetGroup {\n readonly id: string;\n readonly name: string;\n readonly priority: LoadPriority;\n readonly assets: readonly string[]; // Asset IDs\n readonly lazyLoad?: boolean;\n}\n\n// Manifest for efficient asset registration\nexport interface AudioAssetManifest {\n readonly version: string;\n readonly totalAssets: number;\n readonly totalSizeMB: number;\n readonly groups: readonly AssetGroup[];\n readonly assets: Record<string, EnhancedAudioAsset>;\n}\n\n// Fix: Use class implementation instead of interface merging\nexport class AudioAssetRegistry {\n private sfxMap = new Map<SoundEffectId, SoundEffect>();\n private musicMap = new Map<MusicTrackId, MusicTrack>();\n private voiceMap = new Map<VoiceLineId, VoiceLine>();\n private assetGroups: Map<string, AssetGroup> = new Map();\n\n // Fix: Implement required combat property with proper stances\n public combat: CombatAudioMap = {\n attacks: {},\n impacts: {},\n stances: {\n geon: \"stance_geon_sfx\",\n tae: \"stance_tae_sfx\",\n li: \"stance_li_sfx\",\n jin: \"stance_jin_sfx\",\n son: \"stance_son_sfx\",\n gam: \"stance_gam_sfx\",\n gan: \"stance_gan_sfx\",\n gon: \"stance_gon_sfx\",\n },\n environments: {},\n ui: {},\n };\n\n public sfx: Record<SoundEffectId, SoundEffect> = {};\n public music: Record<MusicTrackId, MusicTrack> = {};\n public voice: Record<VoiceLineId, VoiceLine> = {};\n\n constructor() {\n this.initializeDefaultAssets();\n this.initializeCombatAudioAssets();\n this.initializeAssetGroups();\n }\n\n private initializeDefaultAssets(): void {\n // Add intro_theme music with both mp3 and webm for intro screen\n this.registerMusic(\"intro_theme\", {\n id: \"intro_theme\",\n type: \"music\",\n name: \"Black Trigram Theme\",\n title: { korean: \"흑괘 테마\", english: \"Black Trigram Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/intro_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n loop: true,\n variations: [\n \"/assets/audio/music/intro_theme.mp3\",\n \"/assets/audio/music/intro_theme.webm\",\n ],\n bpm: 90,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n });\n\n // Menu UI Sound Effects\n this.registerSFX(\"menu_hover\", {\n id: \"menu_hover\",\n type: \"sound\",\n name: \"Menu Hover\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_hover.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5,\n variations: [\n \"/assets/audio/sfx/menu/menu_hover.webm\",\n \"/assets/audio/sfx/menu/menu_hover_1.webm\",\n \"/assets/audio/sfx/menu/menu_hover_2.webm\",\n \"/assets/audio/sfx/menu/menu_hover_3.webm\",\n \"/assets/audio/sfx/menu/menu_hover_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_select\", {\n id: \"menu_select\",\n type: \"sound\",\n name: \"Menu Select\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_select.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n variations: [\n \"/assets/audio/sfx/menu/menu_select.webm\",\n \"/assets/audio/sfx/menu/menu_select_1.webm\",\n \"/assets/audio/sfx/menu/menu_select_2.webm\",\n \"/assets/audio/sfx/menu/menu_select_3.webm\",\n \"/assets/audio/sfx/menu/menu_select_4.webm\",\n \"/assets/audio/sfx/menu/menu_select_5.webm\",\n \"/assets/audio/sfx/menu/menu_select_6.webm\",\n \"/assets/audio/sfx/menu/menu_select_7.webm\",\n \"/assets/audio/sfx/menu/menu_select_8.webm\",\n ],\n });\n\n this.registerSFX(\"menu_back\", {\n id: \"menu_back\",\n type: \"sound\",\n name: \"Menu Back\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_back.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n variations: [\n \"/assets/audio/sfx/menu/menu_back.webm\",\n \"/assets/audio/sfx/menu/menu_back_1.webm\",\n \"/assets/audio/sfx/menu/menu_back_2.webm\",\n \"/assets/audio/sfx/menu/menu_back_3.webm\",\n \"/assets/audio/sfx/menu/menu_back_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_navigate\", {\n id: \"menu_navigate\",\n type: \"sound\",\n name: \"Menu Navigate\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/menu_navigate.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5,\n variations: [\n \"/assets/audio/sfx/misc/menu_navigate.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_1.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_2.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_3.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_click\", {\n id: \"menu_click\",\n type: \"sound\",\n name: \"Menu Click\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/menu_click.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n variations: [\n \"/assets/audio/sfx/misc/menu_click.webm\",\n \"/assets/audio/sfx/misc/menu_click_1.webm\",\n \"/assets/audio/sfx/misc/menu_click_2.webm\",\n \"/assets/audio/sfx/misc/menu_click_3.webm\",\n \"/assets/audio/sfx/misc/menu_click_4.webm\",\n ],\n });\n }\n\n /**\n * Initialize comprehensive combat audio assets\n * Registers attack sounds, hit reactions, blocks, dodges, and stance changes\n */\n private initializeCombatAudioAssets(): void {\n // Combat Music Tracks\n this.registerMusic(\"combat_theme\", {\n id: \"combat_theme\",\n type: \"music\",\n name: \"Combat Theme\",\n title: { korean: \"전투 테마\", english: \"Combat Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/combat_theme.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/combat_theme.webm\",\n \"/assets/audio/music/combat_theme.mp3\",\n ],\n bpm: 140,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Philosophy Screen Music\n this.registerMusic(\"underground_theme\", {\n id: \"underground_theme\",\n type: \"music\",\n name: \"Underground Theme\",\n title: { korean: \"언더그라운드 테마\", english: \"Underground Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/underground_theme.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/underground_theme.webm\",\n \"/assets/audio/music/underground_theme.mp3\",\n ],\n bpm: 110,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Training Screen Music\n this.registerMusic(\"cyberpunk_fusion\", {\n id: \"cyberpunk_fusion\",\n type: \"music\",\n name: \"Cyberpunk Fusion\",\n title: { korean: \"사이버펑크 퓨전\", english: \"Cyberpunk Fusion\" },\n category: \"music\",\n url: \"/assets/audio/music/cyberpunk_fusion.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/cyberpunk_fusion.webm\",\n \"/assets/audio/music/cyberpunk_fusion.mp3\",\n ],\n bpm: 120,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Archetype-Specific Music Themes\n const archetypeThemes = [\n { id: \"musa_warrior_theme\", name: \"Musa Warrior Theme\", korean: \"무사 테마\", file: \"musa_warrior\" },\n { id: \"amsalja_shadow_theme\", name: \"Amsalja Shadow Theme\", korean: \"암살자 테마\", file: \"amsalja_shadow\" },\n { id: \"hacker_cyber_theme\", name: \"Hacker Cyber Theme\", korean: \"해커 테마\", file: \"hacker_cyber\" },\n { id: \"jeongbo_intel_theme\", name: \"Jeongbo Intel Theme\", korean: \"정보요원 테마\", file: \"jeongbo_intel\" },\n { id: \"jojik_street_theme\", name: \"Jojik Street Theme\", korean: \"조직폭력배 테마\", file: \"jojik_street\" },\n ];\n\n archetypeThemes.forEach(theme => {\n this.registerMusic(theme.id, {\n id: theme.id,\n type: \"music\",\n name: theme.name,\n title: { korean: theme.korean, english: theme.name },\n category: \"music\",\n url: `/assets/audio/music/archetype_themes/${theme.file}.mp3`,\n formats: [\"audio/mp3\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n bpm: 130,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n });\n\n // Attack Sounds - Light Punches (8 variations)\n for (let i = 1; i <= 8; i++) {\n this.registerSFX(`attack_punch_light_${i}`, {\n id: `attack_punch_light_${i}`,\n type: \"sound\",\n name: `Light Punch ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_punch_light_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Attack Sounds - Medium Punches (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_punch_medium_${i}`, {\n id: `attack_punch_medium_${i}`,\n type: \"sound\",\n name: `Medium Punch ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_punch_medium_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Attack Sounds - Generic Light/Medium/Heavy\n this.registerSFX(\"attack_light\", {\n id: \"attack_light\",\n type: \"sound\",\n name: \"Light Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_light.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/combat/attack_light.webm\",\n \"/assets/audio/sfx/combat/attack_light_3.webm\",\n \"/assets/audio/sfx/combat/attack_light_4.webm\",\n ],\n });\n\n this.registerSFX(\"attack_medium\", {\n id: \"attack_medium\",\n type: \"sound\",\n name: \"Medium Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_medium.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n variations: [\n \"/assets/audio/sfx/combat/attack_medium.webm\",\n \"/assets/audio/sfx/combat/attack_medium_1.webm\",\n \"/assets/audio/sfx/combat/attack_medium_3.webm\",\n ],\n });\n\n this.registerSFX(\"attack_heavy\", {\n id: \"attack_heavy\",\n type: \"sound\",\n name: \"Heavy Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_heavy.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/combat/attack_heavy.webm\",\n \"/assets/audio/sfx/combat/attack_heavy.mp3\",\n ],\n });\n\n // Attack Sounds - Critical (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_critical_${i}`, {\n id: `attack_critical_${i}`,\n type: \"sound\",\n name: `Critical Attack ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_critical_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Attack Sounds - Special Geon Technique (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_special_geon_${i}`, {\n id: `attack_special_geon_${i}`,\n type: \"sound\",\n name: `Geon Special ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_special_geon_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Light (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_light_${i}`, {\n id: `hit_light_${i}`,\n type: \"sound\",\n name: `Light Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_light_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Medium (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_medium_${i}`, {\n id: `hit_medium_${i}`,\n type: \"sound\",\n name: `Medium Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_medium_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Heavy (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_heavy_${i}`, {\n id: `hit_heavy_${i}`,\n type: \"sound\",\n name: `Heavy Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_heavy_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Critical (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_critical_${i}`, {\n id: `hit_critical_${i}`,\n type: \"sound\",\n name: `Critical Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_critical_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Flesh (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_flesh_${i}`, {\n id: `hit_flesh_${i}`,\n type: \"sound\",\n name: `Flesh Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/hit_flesh_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Body realistic sound (1 variation)\n this.registerSFX(\"body_realistic_sound\", {\n id: \"body_realistic_sound\",\n type: \"sound\",\n name: \"Body Impact\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/body_realistic_sound.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n\n // Combo System Sounds - 콤보 시스템 (Combo System)\n // Combo buildup sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`combo_buildup_${i}`, {\n id: `combo_buildup_${i}`,\n type: \"sound\",\n name: `Combo Buildup ${i} (콤보 축적)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/combo_buildup_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Combo finish sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`combo_finish_${i}`, {\n id: `combo_finish_${i}`,\n type: \"sound\",\n name: `Combo Finish ${i} (콤보 완성)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/combo_finish_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n });\n }\n\n // Match Countdown Sounds - 카운트다운 (Countdown)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`countdown_${i}`, {\n id: `countdown_${i}`,\n type: \"sound\",\n name: `Countdown ${i} (카운트다운)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/countdown_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Status Warning Sounds - 상태 경고 (Status Warnings)\n // Health low warnings (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`health_low_${i}`, {\n id: `health_low_${i}`,\n type: \"sound\",\n name: `Health Low ${i} (체력 부족)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/health_low_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n });\n }\n\n // Stamina depleted warnings (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`stamina_depleted_${i}`, {\n id: `stamina_depleted_${i}`,\n type: \"sound\",\n name: `Stamina Depleted ${i} (스태미나 고갈)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/stamina_depleted_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n });\n }\n\n // Victory sounds (4 variations) - 승리 (Victory)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`victory_${i}`, {\n id: `victory_${i}`,\n type: \"sound\",\n name: `Victory ${i} (승리)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/victory_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n });\n }\n\n // Defeat sounds (4 variations) - 패배 (Defeat)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`defeat_${i}`, {\n id: `defeat_${i}`,\n type: \"sound\",\n name: `Defeat ${i} (패배)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/defeat_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Movement Sounds - 이동 소리 (Movement Sounds)\n // Footstep sounds (3 standard variations with both mp3/webm)\n for (let i = 1; i <= 3; i++) {\n this.registerSFX(`footstep_${i}`, {\n id: `footstep_${i}`,\n type: \"sound\",\n name: `Footstep ${i} (발소리)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/footstep_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5, // Lower volume for footsteps\n });\n }\n\n // Footstep 4 only has webm format\n this.registerSFX(\"footstep_4\", {\n id: \"footstep_4\",\n type: \"sound\",\n name: \"Footstep 4 (발소리)\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/footstep_4.webm\",\n formats: [\"audio/webm\"],\n loaded: false,\n volume: 0.5,\n });\n\n // Block Sounds - Success (4 variations) - 막기 (Makgi)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`block_success_${i}`, {\n id: `block_success_${i}`,\n type: \"sound\",\n name: `Block Success ${i} (막기)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/blocks/block_success_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Block Sounds - Break (4 variations) - 방어붕괴 (Bangeo Bunggoe)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`block_break_${i}`, {\n id: `block_break_${i}`,\n type: \"sound\",\n name: `Block Break ${i} (방어붕괴)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/blocks/block_break_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Defensive Animation Sounds - Parry Deflection (받아넘기기)\n // Using dodge sounds as placeholder for parry whoosh/deflection\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`parry_deflect_${i}`, {\n id: `parry_deflect_${i}`,\n type: \"sound\",\n name: `Parry Deflection ${i} (받아넘기기)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/dodge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.65, // Slightly lower for deflection sound\n pitch: 1.2, // Higher pitch for quick deflection\n });\n }\n\n // Defensive Animation Sounds - Guard Recovery (방어복구)\n // Using stance change sounds for guard recovery\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`guard_recovery_${i}`, {\n id: `guard_recovery_${i}`,\n type: \"sound\",\n name: `Guard Recovery ${i} (방어복구)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/stance_change_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6, // Lower volume for recovery motion\n pitch: 0.9, // Slightly lower pitch for recovery\n });\n }\n\n // Dodge Sounds (8 variations)\n for (let i = 1; i <= 8; i++) {\n this.registerSFX(`dodge_${i}`, {\n id: `dodge_${i}`,\n type: \"sound\",\n name: `Dodge ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/dodge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Stance Change Sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`stance_change_${i}`, {\n id: `stance_change_${i}`,\n type: \"sound\",\n name: `Stance Change ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/stance_change_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Ki Energy Sounds - Charge (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`ki_charge_${i}`, {\n id: `ki_charge_${i}`,\n type: \"sound\",\n name: `Ki Charge ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/ki_energy/ki_charge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Ki Energy Sounds - Release (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`ki_release_${i}`, {\n id: `ki_release_${i}`,\n type: \"sound\",\n name: `Ki Release ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/ki_energy/ki_release_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Generic Ki sounds with variations\n this.registerSFX(\"ki_charge\", {\n id: \"ki_charge\",\n type: \"sound\",\n name: \"Ki Charge\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/ki_energy/ki_charge.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/ki_energy/ki_charge.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_1.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_2.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_3.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_4.webm\",\n ],\n });\n\n this.registerSFX(\"ki_release\", {\n id: \"ki_release\",\n type: \"sound\",\n name: \"Ki Release\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/ki_energy/ki_release.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/ki_energy/ki_release.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_1.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_2.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_3.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_4.webm\",\n ],\n });\n }\n\n public registerSFX(id: SoundEffectId, effect: SoundEffect): void {\n this.sfxMap.set(id, effect);\n this.sfx[id] = effect;\n }\n\n public registerMusic(id: MusicTrackId, track: MusicTrack): void {\n this.musicMap.set(id, track);\n this.music[id] = track;\n }\n\n public registerVoice(id: VoiceLineId, voice: VoiceLine): void {\n this.voiceMap.set(id, voice);\n this.voice[id] = voice;\n }\n\n public getSFX(id: SoundEffectId): SoundEffect | undefined {\n return this.sfxMap.get(id);\n }\n\n public getMusic(id: MusicTrackId): MusicTrack | undefined {\n return this.musicMap.get(id);\n }\n\n public getVoice(id: VoiceLineId): VoiceLine | undefined {\n return this.voiceMap.get(id);\n }\n\n public getAll(): IAudioAssetRegistry {\n return {\n sfx: this.sfx,\n music: this.music,\n voice: this.voice,\n combat: this.combat,\n };\n }\n\n // Korean martial arts sound effects registry\n private readonly soundEffects: Map<SoundEffectId, SoundEffect> = new Map([\n [\n \"attack_light\",\n {\n id: \"attack_light\",\n name: \"Light Attack\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/combat/attack_light.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n variations: [\"/assets/audio/sfx/combat/attack_light_1.mp3\", \"/assets/audio/sfx/combat/attack_light_2.mp3\"],\n },\n ],\n [\n \"stance_change\",\n {\n id: \"stance_change\",\n name: \"Stance Change\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/movement/stance_change.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.2,\n },\n ],\n [\n \"vital_hit_critical\",\n {\n id: \"vital_hit_critical\",\n name: \"Critical Vital Point Hit\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/combat/attack_critical.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.9,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 0.8,\n },\n ],\n [\n \"hit_light\",\n {\n id: \"hit_light\",\n name: \"Light Hit\", // Fix: Use simple string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/hits/hit_light.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n variations: [\n \"/assets/audio/sfx/hits/hit_light_1.mp3\",\n \"/assets/audio/sfx/hits/hit_light_2.mp3\",\n ],\n },\n ],\n ] as unknown as ReadonlyArray<[SoundEffectId, SoundEffect]>);\n\n // Korean martial arts music tracks\n private readonly musicTracks: Map<MusicTrackId, MusicTrack> = new Map([\n [\n \"intro_theme\",\n {\n id: \"intro_theme\",\n name: \"Black Trigram Theme\", // Fix: Use simple string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/intro_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n variations: [\n \"/assets/audio/music/intro_theme.mp3\",\n \"/assets/audio/music/intro_theme.webm\",\n ],\n bpm: 90,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n },\n ],\n [\n \"combat_theme\",\n {\n id: \"combat_theme\",\n name: \"Combat Music\", // Fix: Use string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/combat_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n title: { korean: \"전투 음악\", english: \"Combat Music\" },\n volume: 0.8,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n bpm: 140,\n fadeInTime: 1000,\n fadeOutTime: 2000,\n },\n ],\n [\n \"dojang_ambience\",\n {\n id: \"dojang_ambience\",\n name: \"Dojang Atmosphere\", // Fix: Use string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/underground_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n title: { korean: \"도장 분위기\", english: \"Dojang Atmosphere\" },\n volume: 0.4,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n bpm: 60,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n },\n ],\n ] as unknown as ReadonlyArray<[MusicTrackId, MusicTrack]>);\n\n // Fix: Remove unused destructured variables\n public loadSoundEffects(): void {\n // Process sound effects without unused variables\n this.soundEffects.forEach((effect) => {\n console.log(`Loading sound effect: ${effect.id}`);\n });\n }\n\n public loadMusicTracks(): void {\n // Process music tracks without unused variables\n this.musicTracks.forEach((track) => {\n console.log(`Loading music track: ${track.id}`);\n });\n }\n\n // Fix: Remove Map.find usage - Maps don't have find method\n public findSoundEffectByName(name: string): SoundEffect | undefined {\n for (const [, effect] of this.soundEffects) {\n if (effect.name === name) {\n return effect;\n }\n }\n return undefined;\n }\n\n public findMusicTrackByName(name: string): MusicTrack | undefined {\n for (const [, track] of this.musicTracks) {\n if (track.name === name) {\n return track;\n }\n }\n return undefined;\n }\n\n // Fix: Add getSoundEffect method\n public getSoundEffect(id: SoundEffectId): SoundEffect | undefined {\n return this.soundEffects.get(id);\n }\n\n // Fix: Add getMusicTrack method\n public getMusicTrack(id: MusicTrackId): MusicTrack | undefined {\n return this.musicTracks.get(id);\n }\n\n // Fix: Remove unused lightHitEffect and use string for name\n public getPlaceholderEffect(): SoundEffect {\n return {\n id: \"placeholder_hit\",\n name: \"Placeholder Hit\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/placeholder/hit.mp3\",\n formats: [\"audio/mp3\"],\n loaded: false,\n volume: 0.5,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n };\n }\n\n // Add missing loadAssets method\n public async loadAssets(): Promise<void> {\n console.log(\"Loading audio assets...\");\n // Implementation would preload audio files\n }\n\n /**\n * Initialize asset groups for batch loading\n */\n private initializeAssetGroups(): void {\n // Critical assets - load immediately on startup (menu UI sounds)\n this.registerAssetGroup({\n id: \"critical\",\n name: \"Critical UI Sounds\",\n priority: \"critical\",\n assets: [\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n ],\n lazyLoad: false,\n });\n\n // High priority - load during intro screen\n this.registerAssetGroup({\n id: \"intro_music\",\n name: \"Intro Screen Music\",\n priority: \"high\",\n assets: [\"intro_theme\"],\n lazyLoad: false,\n });\n\n // Combat music - high priority for combat screen\n this.registerAssetGroup({\n id: \"combat_music\",\n name: \"Combat Background Music\",\n priority: \"high\",\n assets: [\n \"combat_theme\",\n \"musa_warrior_theme\",\n \"amsalja_shadow_theme\",\n \"hacker_cyber_theme\",\n \"jeongbo_intel_theme\",\n \"jojik_street_theme\",\n ],\n lazyLoad: false,\n });\n\n // Screen music - normal priority for informational screens\n this.registerAssetGroup({\n id: \"screen_music\",\n name: \"Screen Background Music\",\n priority: \"normal\",\n assets: [\n \"underground_theme\",\n \"cyberpunk_fusion\",\n ],\n lazyLoad: true,\n });\n\n // Normal priority - combat SFX\n this.registerAssetGroup({\n id: \"combat_attacks\",\n name: \"Combat Attack Sounds\",\n priority: \"normal\",\n assets: [\n \"attack_light\",\n \"attack_medium\",\n \"attack_heavy\",\n \"attack_punch_light_1\",\n \"attack_punch_light_2\",\n \"attack_punch_medium_1\",\n \"attack_critical_1\",\n \"attack_critical_2\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_hits\",\n name: \"Combat Hit Reactions\",\n priority: \"normal\",\n assets: [\n \"hit_light_1\",\n \"hit_light_2\",\n \"hit_medium_1\",\n \"hit_medium_2\",\n \"hit_heavy_1\",\n \"hit_critical_1\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_defense\",\n name: \"Combat Defense Sounds\",\n priority: \"normal\",\n assets: [\n \"block_success_1\",\n \"block_success_2\",\n \"block_break_1\",\n \"dodge_1\",\n \"dodge_2\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_movement\",\n name: \"Combat Movement Sounds\",\n priority: \"normal\",\n assets: [\n \"stance_change_1\",\n \"stance_change_2\",\n \"stance_change_3\",\n \"dodge_3\",\n \"dodge_4\",\n ],\n lazyLoad: false,\n });\n\n // Training-specific sounds\n this.registerAssetGroup({\n id: \"training_sfx\",\n name: \"Training Sound Effects\",\n priority: \"normal\",\n assets: [\n \"ki_charge\",\n \"ki_release\",\n \"ki_charge_1\",\n \"ki_charge_2\",\n \"ki_release_1\",\n \"ki_release_2\",\n ],\n lazyLoad: true,\n });\n }\n\n /**\n * Register an asset group for batch loading\n * @param group - Asset group configuration with ID, name, priority, and asset IDs\n */\n public registerAssetGroup(group: AssetGroup): void {\n this.assetGroups.set(group.id, group);\n }\n\n /**\n * Get asset group by ID\n * @param groupId - ID of the asset group to retrieve\n * @returns The asset group or undefined if not found\n */\n public getAssetGroup(groupId: string): AssetGroup | undefined {\n return this.assetGroups.get(groupId);\n }\n\n /**\n * Get all registered asset groups\n * @returns Array of all asset groups\n */\n public getAllAssetGroups(): readonly AssetGroup[] {\n return Array.from(this.assetGroups.values());\n }\n\n /**\n * Get asset groups filtered by priority level\n * @param priority - Priority level to filter by (critical/high/normal/low)\n * @returns Array of asset groups matching the priority\n */\n public getAssetGroupsByPriority(priority: LoadPriority): readonly AssetGroup[] {\n return Array.from(this.assetGroups.values()).filter(\n (group) => group.priority === priority\n );\n }\n\n /**\n * Get all assets belonging to a specific group\n * @param groupId - ID of the asset group\n * @returns Array of audio assets in the group, or empty array if group not found\n */\n public getAssetsInGroup(groupId: string): readonly AudioAsset[] {\n const group = this.assetGroups.get(groupId);\n if (!group) return [];\n\n const assets: AudioAsset[] = [];\n for (const assetId of group.assets) {\n // Check all maps, take first match\n const asset = this.getSFX(assetId) ?? this.getMusic(assetId) ?? this.getVoice(assetId);\n if (asset) {\n assets.push(asset);\n }\n }\n\n return assets;\n }\n\n /**\n * Create a manifest for efficient asset registration\n * @returns Audio asset manifest with version, total assets, size estimates, groups, and all assets\n */\n public createManifest(): AudioAssetManifest {\n const allAssets: Record<string, EnhancedAudioAsset> = {};\n\n // Add SFX assets\n this.sfxMap.forEach((sfx, id) => {\n allAssets[id] = {\n ...sfx,\n preloadPriority: \"normal\",\n } as EnhancedAudioAsset;\n });\n\n // Add music assets\n this.musicMap.forEach((music, id) => {\n allAssets[id] = {\n ...music,\n preloadPriority: \"high\",\n } as EnhancedAudioAsset;\n });\n\n // Add voice assets\n this.voiceMap.forEach((voice, id) => {\n allAssets[id] = {\n ...voice,\n preloadPriority: \"normal\",\n } as EnhancedAudioAsset;\n });\n\n const totalAssets = Object.keys(allAssets).length;\n const estimatedSizeMB = totalAssets * ESTIMATED_ASSET_SIZE_MB;\n\n return {\n version: \"1.0.0\",\n totalAssets,\n totalSizeMB: estimatedSizeMB,\n groups: this.getAllAssetGroups(),\n assets: allAssets,\n };\n }\n}\n\n// Export singleton instance\nexport const audioAssetRegistry = new AudioAssetRegistry();\n\n// Default export\nexport default audioAssetRegistry;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioAssetRegistry.js","names":[],"sources":["../../src/audio/AudioAssetRegistry.ts"],"sourcesContent":["/**\n * Audio Asset Registry for Black Trigram Korean Martial Arts\n * Manages all audio assets including Korean martial arts specific sounds\n */\n\nimport type { AudioAsset } from \"./types\";\nimport {\n AudioCategory,\n CombatAudioMap,\n MusicTrack,\n MusicTrackId,\n SoundEffect,\n SoundEffectId,\n VoiceLine,\n VoiceLineId,\n} from \"./types\";\n\nexport type LoadPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\nexport interface IAudioAssetRegistry {\n readonly music: Record<string, MusicTrack>;\n readonly sfx: Record<string, SoundEffect>;\n readonly voice: Record<string, VoiceLine>;\n readonly combat: CombatAudioMap;\n}\n\nexport interface EnhancedAudioAsset {\n readonly id: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n volume?: number;\n readonly loop?: boolean;\n category?: AudioCategory;\n readonly metadata?: {\n readonly duration: number;\n readonly bitrate?: number;\n readonly channels?: number;\n readonly sampleRate?: number;\n };\n readonly preloadPriority?: LoadPriority;\n readonly streaming?: boolean;\n readonly compressionOptions?: {\n readonly format: string;\n readonly quality: number;\n };\n}\n\n// Enhanced audio registry with proper types\nexport interface EnhancedAudioAssetRegistry extends AudioAssetRegistry {\n readonly enhanced?: Record<string, EnhancedAudioAsset>;\n}\n\n// Asset group definition for batch loading\nexport interface AssetGroup {\n readonly id: string;\n readonly name: string;\n readonly priority: LoadPriority;\n readonly assets: readonly string[]; // Asset IDs\n readonly lazyLoad?: boolean;\n}\n\n// Manifest for efficient asset registration\nexport interface AudioAssetManifest {\n readonly version: string;\n readonly totalAssets: number;\n readonly totalSizeMB: number;\n readonly groups: readonly AssetGroup[];\n readonly assets: Record<string, EnhancedAudioAsset>;\n}\n\n// Fix: Use class implementation instead of interface merging\nexport class AudioAssetRegistry {\n private sfxMap = new Map<SoundEffectId, SoundEffect>();\n private musicMap = new Map<MusicTrackId, MusicTrack>();\n private voiceMap = new Map<VoiceLineId, VoiceLine>();\n private assetGroups: Map<string, AssetGroup> = new Map();\n\n // Fix: Implement required combat property with proper stances\n public combat: CombatAudioMap = {\n attacks: {},\n impacts: {},\n stances: {\n geon: \"stance_geon_sfx\",\n tae: \"stance_tae_sfx\",\n li: \"stance_li_sfx\",\n jin: \"stance_jin_sfx\",\n son: \"stance_son_sfx\",\n gam: \"stance_gam_sfx\",\n gan: \"stance_gan_sfx\",\n gon: \"stance_gon_sfx\",\n },\n environments: {},\n ui: {},\n };\n\n public sfx: Record<SoundEffectId, SoundEffect> = {};\n public music: Record<MusicTrackId, MusicTrack> = {};\n public voice: Record<VoiceLineId, VoiceLine> = {};\n\n constructor() {\n this.initializeDefaultAssets();\n this.initializeCombatAudioAssets();\n this.initializeAssetGroups();\n }\n\n private initializeDefaultAssets(): void {\n // Add intro_theme music with both mp3 and webm for intro screen\n this.registerMusic(\"intro_theme\", {\n id: \"intro_theme\",\n type: \"music\",\n name: \"Black Trigram Theme\",\n title: { korean: \"흑괘 테마\", english: \"Black Trigram Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/intro_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n loop: true,\n variations: [\n \"/assets/audio/music/intro_theme.mp3\",\n \"/assets/audio/music/intro_theme.webm\",\n ],\n bpm: 90,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n });\n\n // Menu UI Sound Effects\n this.registerSFX(\"menu_hover\", {\n id: \"menu_hover\",\n type: \"sound\",\n name: \"Menu Hover\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_hover.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5,\n variations: [\n \"/assets/audio/sfx/menu/menu_hover.webm\",\n \"/assets/audio/sfx/menu/menu_hover_1.webm\",\n \"/assets/audio/sfx/menu/menu_hover_2.webm\",\n \"/assets/audio/sfx/menu/menu_hover_3.webm\",\n \"/assets/audio/sfx/menu/menu_hover_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_select\", {\n id: \"menu_select\",\n type: \"sound\",\n name: \"Menu Select\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_select.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n variations: [\n \"/assets/audio/sfx/menu/menu_select.webm\",\n \"/assets/audio/sfx/menu/menu_select_1.webm\",\n \"/assets/audio/sfx/menu/menu_select_2.webm\",\n \"/assets/audio/sfx/menu/menu_select_3.webm\",\n \"/assets/audio/sfx/menu/menu_select_4.webm\",\n \"/assets/audio/sfx/menu/menu_select_5.webm\",\n \"/assets/audio/sfx/menu/menu_select_6.webm\",\n \"/assets/audio/sfx/menu/menu_select_7.webm\",\n \"/assets/audio/sfx/menu/menu_select_8.webm\",\n ],\n });\n\n this.registerSFX(\"menu_back\", {\n id: \"menu_back\",\n type: \"sound\",\n name: \"Menu Back\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/menu/menu_back.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n variations: [\n \"/assets/audio/sfx/menu/menu_back.webm\",\n \"/assets/audio/sfx/menu/menu_back_1.webm\",\n \"/assets/audio/sfx/menu/menu_back_2.webm\",\n \"/assets/audio/sfx/menu/menu_back_3.webm\",\n \"/assets/audio/sfx/menu/menu_back_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_navigate\", {\n id: \"menu_navigate\",\n type: \"sound\",\n name: \"Menu Navigate\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/menu_navigate.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5,\n variations: [\n \"/assets/audio/sfx/misc/menu_navigate.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_1.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_2.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_3.webm\",\n \"/assets/audio/sfx/misc/menu_navigate_4.webm\",\n ],\n });\n\n this.registerSFX(\"menu_click\", {\n id: \"menu_click\",\n type: \"sound\",\n name: \"Menu Click\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/menu_click.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n variations: [\n \"/assets/audio/sfx/misc/menu_click.webm\",\n \"/assets/audio/sfx/misc/menu_click_1.webm\",\n \"/assets/audio/sfx/misc/menu_click_2.webm\",\n \"/assets/audio/sfx/misc/menu_click_3.webm\",\n \"/assets/audio/sfx/misc/menu_click_4.webm\",\n ],\n });\n }\n\n /**\n * Initialize comprehensive combat audio assets\n * Registers attack sounds, hit reactions, blocks, dodges, and stance changes\n */\n private initializeCombatAudioAssets(): void {\n // Combat Music Tracks\n this.registerMusic(\"combat_theme\", {\n id: \"combat_theme\",\n type: \"music\",\n name: \"Combat Theme\",\n title: { korean: \"전투 테마\", english: \"Combat Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/combat_theme.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/combat_theme.webm\",\n \"/assets/audio/music/combat_theme.mp3\",\n ],\n bpm: 140,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Philosophy Screen Music\n this.registerMusic(\"underground_theme\", {\n id: \"underground_theme\",\n type: \"music\",\n name: \"Underground Theme\",\n title: { korean: \"언더그라운드 테마\", english: \"Underground Theme\" },\n category: \"music\",\n url: \"/assets/audio/music/underground_theme.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/underground_theme.webm\",\n \"/assets/audio/music/underground_theme.mp3\",\n ],\n bpm: 110,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Training Screen Music\n this.registerMusic(\"cyberpunk_fusion\", {\n id: \"cyberpunk_fusion\",\n type: \"music\",\n name: \"Cyberpunk Fusion\",\n title: { korean: \"사이버펑크 퓨전\", english: \"Cyberpunk Fusion\" },\n category: \"music\",\n url: \"/assets/audio/music/cyberpunk_fusion.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n variations: [\n \"/assets/audio/music/cyberpunk_fusion.webm\",\n \"/assets/audio/music/cyberpunk_fusion.mp3\",\n ],\n bpm: 120,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n\n // Archetype-Specific Music Themes\n const archetypeThemes = [\n { id: \"musa_warrior_theme\", name: \"Musa Warrior Theme\", korean: \"무사 테마\", file: \"musa_warrior\" },\n { id: \"amsalja_shadow_theme\", name: \"Amsalja Shadow Theme\", korean: \"암살자 테마\", file: \"amsalja_shadow\" },\n { id: \"hacker_cyber_theme\", name: \"Hacker Cyber Theme\", korean: \"해커 테마\", file: \"hacker_cyber\" },\n { id: \"jeongbo_intel_theme\", name: \"Jeongbo Intel Theme\", korean: \"정보요원 테마\", file: \"jeongbo_intel\" },\n { id: \"jojik_street_theme\", name: \"Jojik Street Theme\", korean: \"조직폭력배 테마\", file: \"jojik_street\" },\n ];\n\n archetypeThemes.forEach(theme => {\n this.registerMusic(theme.id, {\n id: theme.id,\n type: \"music\",\n name: theme.name,\n title: { korean: theme.korean, english: theme.name },\n category: \"music\",\n url: `/assets/audio/music/archetype_themes/${theme.file}.mp3`,\n formats: [\"audio/mp3\"],\n loaded: false,\n volume: 0.4, // 40% volume per acceptance criteria\n loop: true,\n bpm: 130,\n fadeInTime: 2000,\n fadeOutTime: 2000,\n });\n });\n\n // Attack Sounds - Light Punches (8 variations)\n for (let i = 1; i <= 8; i++) {\n this.registerSFX(`attack_punch_light_${i}`, {\n id: `attack_punch_light_${i}`,\n type: \"sound\",\n name: `Light Punch ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_punch_light_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Attack Sounds - Medium Punches (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_punch_medium_${i}`, {\n id: `attack_punch_medium_${i}`,\n type: \"sound\",\n name: `Medium Punch ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_punch_medium_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Attack Sounds - Generic Light/Medium/Heavy\n this.registerSFX(\"attack_light\", {\n id: \"attack_light\",\n type: \"sound\",\n name: \"Light Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_light.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/combat/attack_light.webm\",\n \"/assets/audio/sfx/combat/attack_light_3.webm\",\n \"/assets/audio/sfx/combat/attack_light_4.webm\",\n ],\n });\n\n this.registerSFX(\"attack_medium\", {\n id: \"attack_medium\",\n type: \"sound\",\n name: \"Medium Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_medium.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n variations: [\n \"/assets/audio/sfx/combat/attack_medium.webm\",\n \"/assets/audio/sfx/combat/attack_medium_1.webm\",\n \"/assets/audio/sfx/combat/attack_medium_3.webm\",\n ],\n });\n\n this.registerSFX(\"attack_heavy\", {\n id: \"attack_heavy\",\n type: \"sound\",\n name: \"Heavy Attack\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/combat/attack_heavy.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/combat/attack_heavy.webm\",\n \"/assets/audio/sfx/combat/attack_heavy.mp3\",\n ],\n });\n\n // Attack Sounds - Critical (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_critical_${i}`, {\n id: `attack_critical_${i}`,\n type: \"sound\",\n name: `Critical Attack ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_critical_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Attack Sounds - Special Geon Technique (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`attack_special_geon_${i}`, {\n id: `attack_special_geon_${i}`,\n type: \"sound\",\n name: `Geon Special ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/combat/attack_special_geon_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Light (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_light_${i}`, {\n id: `hit_light_${i}`,\n type: \"sound\",\n name: `Light Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_light_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Medium (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_medium_${i}`, {\n id: `hit_medium_${i}`,\n type: \"sound\",\n name: `Medium Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_medium_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Heavy (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_heavy_${i}`, {\n id: `hit_heavy_${i}`,\n type: \"sound\",\n name: `Heavy Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_heavy_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Critical (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_critical_${i}`, {\n id: `hit_critical_${i}`,\n type: \"sound\",\n name: `Critical Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/hits/hit_critical_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Hit Reaction Sounds - Flesh (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`hit_flesh_${i}`, {\n id: `hit_flesh_${i}`,\n type: \"sound\",\n name: `Flesh Hit ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/hit_flesh_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Body realistic sound (1 variation)\n this.registerSFX(\"body_realistic_sound\", {\n id: \"body_realistic_sound\",\n type: \"sound\",\n name: \"Body Impact\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/body_realistic_sound.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n\n // Combo System Sounds - 콤보 시스템 (Combo System)\n // Combo buildup sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`combo_buildup_${i}`, {\n id: `combo_buildup_${i}`,\n type: \"sound\",\n name: `Combo Buildup ${i} (콤보 축적)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/combo_buildup_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Combo finish sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`combo_finish_${i}`, {\n id: `combo_finish_${i}`,\n type: \"sound\",\n name: `Combo Finish ${i} (콤보 완성)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/combo_finish_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n });\n }\n\n // Match Countdown Sounds - 카운트다운 (Countdown)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`countdown_${i}`, {\n id: `countdown_${i}`,\n type: \"sound\",\n name: `Countdown ${i} (카운트다운)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/countdown_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Status Warning Sounds - 상태 경고 (Status Warnings)\n // Health low warnings (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`health_low_${i}`, {\n id: `health_low_${i}`,\n type: \"sound\",\n name: `Health Low ${i} (체력 부족)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/health_low_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n });\n }\n\n // Stamina depleted warnings (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`stamina_depleted_${i}`, {\n id: `stamina_depleted_${i}`,\n type: \"sound\",\n name: `Stamina Depleted ${i} (스태미나 고갈)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/stamina_depleted_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n });\n }\n\n // Victory sounds (4 variations) - 승리 (Victory)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`victory_${i}`, {\n id: `victory_${i}`,\n type: \"sound\",\n name: `Victory ${i} (승리)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/victory_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n });\n }\n\n // Defeat sounds (4 variations) - 패배 (Defeat)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`defeat_${i}`, {\n id: `defeat_${i}`,\n type: \"sound\",\n name: `Defeat ${i} (패배)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/defeat_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n });\n }\n\n // Movement Sounds - 이동 소리 (Movement Sounds)\n // Footstep sounds (3 standard variations with both mp3/webm)\n for (let i = 1; i <= 3; i++) {\n this.registerSFX(`footstep_${i}`, {\n id: `footstep_${i}`,\n type: \"sound\",\n name: `Footstep ${i} (발소리)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/misc/footstep_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.5, // Lower volume for footsteps\n });\n }\n\n // Footstep 4 only has webm format\n this.registerSFX(\"footstep_4\", {\n id: \"footstep_4\",\n type: \"sound\",\n name: \"Footstep 4 (발소리)\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/misc/footstep_4.webm\",\n formats: [\"audio/webm\"],\n loaded: false,\n volume: 0.5,\n });\n\n // Block Sounds - Success (4 variations) - 막기 (Makgi)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`block_success_${i}`, {\n id: `block_success_${i}`,\n type: \"sound\",\n name: `Block Success ${i} (막기)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/blocks/block_success_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Block Sounds - Break (4 variations) - 방어붕괴 (Bangeo Bunggoe)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`block_break_${i}`, {\n id: `block_break_${i}`,\n type: \"sound\",\n name: `Block Break ${i} (방어붕괴)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/blocks/block_break_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Defensive Animation Sounds - Parry Deflection (받아넘기기)\n // Using dodge sounds as placeholder for parry whoosh/deflection\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`parry_deflect_${i}`, {\n id: `parry_deflect_${i}`,\n type: \"sound\",\n name: `Parry Deflection ${i} (받아넘기기)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/dodge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.65, // Slightly lower for deflection sound\n pitch: 1.2, // Higher pitch for quick deflection\n });\n }\n\n // Defensive Animation Sounds - Guard Recovery (방어복구)\n // Using stance change sounds for guard recovery\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`guard_recovery_${i}`, {\n id: `guard_recovery_${i}`,\n type: \"sound\",\n name: `Guard Recovery ${i} (방어복구)`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/stance_change_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6, // Lower volume for recovery motion\n pitch: 0.9, // Slightly lower pitch for recovery\n });\n }\n\n // Dodge Sounds (8 variations)\n for (let i = 1; i <= 8; i++) {\n this.registerSFX(`dodge_${i}`, {\n id: `dodge_${i}`,\n type: \"sound\",\n name: `Dodge ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/dodge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Stance Change Sounds (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`stance_change_${i}`, {\n id: `stance_change_${i}`,\n type: \"sound\",\n name: `Stance Change ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/movement/stance_change_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Ki Energy Sounds - Charge (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`ki_charge_${i}`, {\n id: `ki_charge_${i}`,\n type: \"sound\",\n name: `Ki Charge ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/ki_energy/ki_charge_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Ki Energy Sounds - Release (4 variations)\n for (let i = 1; i <= 4; i++) {\n this.registerSFX(`ki_release_${i}`, {\n id: `ki_release_${i}`,\n type: \"sound\",\n name: `Ki Release ${i}`,\n category: \"sfx\",\n url: `/assets/audio/sfx/ki_energy/ki_release_${i}.webm`,\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n });\n }\n\n // Generic Ki sounds with variations\n this.registerSFX(\"ki_charge\", {\n id: \"ki_charge\",\n type: \"sound\",\n name: \"Ki Charge\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/ki_energy/ki_charge.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/ki_energy/ki_charge.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_1.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_2.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_3.webm\",\n \"/assets/audio/sfx/ki_energy/ki_charge_4.webm\",\n ],\n });\n\n this.registerSFX(\"ki_release\", {\n id: \"ki_release\",\n type: \"sound\",\n name: \"Ki Release\",\n category: \"sfx\",\n url: \"/assets/audio/sfx/ki_energy/ki_release.webm\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7, // 70% volume per acceptance criteria\n variations: [\n \"/assets/audio/sfx/ki_energy/ki_release.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_1.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_2.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_3.webm\",\n \"/assets/audio/sfx/ki_energy/ki_release_4.webm\",\n ],\n });\n }\n\n public registerSFX(id: SoundEffectId, effect: SoundEffect): void {\n this.sfxMap.set(id, effect);\n this.sfx[id] = effect;\n }\n\n public registerMusic(id: MusicTrackId, track: MusicTrack): void {\n this.musicMap.set(id, track);\n this.music[id] = track;\n }\n\n public registerVoice(id: VoiceLineId, voice: VoiceLine): void {\n this.voiceMap.set(id, voice);\n this.voice[id] = voice;\n }\n\n public getSFX(id: SoundEffectId): SoundEffect | undefined {\n return this.sfxMap.get(id);\n }\n\n public getMusic(id: MusicTrackId): MusicTrack | undefined {\n return this.musicMap.get(id);\n }\n\n public getVoice(id: VoiceLineId): VoiceLine | undefined {\n return this.voiceMap.get(id);\n }\n\n public getAll(): IAudioAssetRegistry {\n return {\n sfx: this.sfx,\n music: this.music,\n voice: this.voice,\n combat: this.combat,\n };\n }\n\n // Korean martial arts sound effects registry\n private readonly soundEffects: Map<SoundEffectId, SoundEffect> = new Map([\n [\n \"attack_light\",\n {\n id: \"attack_light\",\n name: \"Light Attack\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/combat/attack_light.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.8,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n variations: [\"/assets/audio/sfx/combat/attack_light_1.mp3\", \"/assets/audio/sfx/combat/attack_light_2.mp3\"],\n },\n ],\n [\n \"stance_change\",\n {\n id: \"stance_change\",\n name: \"Stance Change\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/movement/stance_change.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.6,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.2,\n },\n ],\n [\n \"vital_hit_critical\",\n {\n id: \"vital_hit_critical\",\n name: \"Critical Vital Point Hit\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/combat/attack_critical.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.9,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 0.8,\n },\n ],\n [\n \"hit_light\",\n {\n id: \"hit_light\",\n name: \"Light Hit\", // Fix: Use simple string instead of KoreanText object\n type: \"sound\",\n url: \"/assets/audio/sfx/hits/hit_light.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n variations: [\n \"/assets/audio/sfx/hits/hit_light_1.mp3\",\n \"/assets/audio/sfx/hits/hit_light_2.mp3\",\n ],\n },\n ],\n ] as unknown as ReadonlyArray<[SoundEffectId, SoundEffect]>);\n\n // Korean martial arts music tracks\n private readonly musicTracks: Map<MusicTrackId, MusicTrack> = new Map([\n [\n \"intro_theme\",\n {\n id: \"intro_theme\",\n name: \"Black Trigram Theme\", // Fix: Use simple string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/intro_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n volume: 0.7,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n variations: [\n \"/assets/audio/music/intro_theme.mp3\",\n \"/assets/audio/music/intro_theme.webm\",\n ],\n bpm: 90,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n },\n ],\n [\n \"combat_theme\",\n {\n id: \"combat_theme\",\n name: \"Combat Music\", // Fix: Use string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/combat_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n title: { korean: \"전투 음악\", english: \"Combat Music\" },\n volume: 0.8,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n bpm: 140,\n fadeInTime: 1000,\n fadeOutTime: 2000,\n },\n ],\n [\n \"dojang_ambience\",\n {\n id: \"dojang_ambience\",\n name: \"Dojang Atmosphere\", // Fix: Use string instead of KoreanText object\n type: \"music\",\n url: \"/assets/audio/music/underground_theme.mp3\",\n formats: [\"audio/mp3\", \"audio/webm\"],\n loaded: false,\n title: { korean: \"도장 분위기\", english: \"Dojang Atmosphere\" },\n volume: 0.4,\n loop: true,\n category: \"music\", // Fix: Use string literal instead of enum\n bpm: 60,\n fadeInTime: 3000,\n fadeOutTime: 3000,\n },\n ],\n ] as unknown as ReadonlyArray<[MusicTrackId, MusicTrack]>);\n\n // Fix: Remove unused destructured variables\n public loadSoundEffects(): void {\n // Process sound effects without unused variables\n this.soundEffects.forEach((effect) => {\n console.log(`Loading sound effect: ${effect.id}`);\n });\n }\n\n public loadMusicTracks(): void {\n // Process music tracks without unused variables\n this.musicTracks.forEach((track) => {\n console.log(`Loading music track: ${track.id}`);\n });\n }\n\n // Fix: Remove Map.find usage - Maps don't have find method\n public findSoundEffectByName(name: string): SoundEffect | undefined {\n for (const [, effect] of this.soundEffects) {\n if (effect.name === name) {\n return effect;\n }\n }\n return undefined;\n }\n\n public findMusicTrackByName(name: string): MusicTrack | undefined {\n for (const [, track] of this.musicTracks) {\n if (track.name === name) {\n return track;\n }\n }\n return undefined;\n }\n\n // Fix: Add getSoundEffect method\n public getSoundEffect(id: SoundEffectId): SoundEffect | undefined {\n return this.soundEffects.get(id);\n }\n\n // Fix: Add getMusicTrack method\n public getMusicTrack(id: MusicTrackId): MusicTrack | undefined {\n return this.musicTracks.get(id);\n }\n\n // Fix: Remove unused lightHitEffect and use string for name\n public getPlaceholderEffect(): SoundEffect {\n return {\n id: \"placeholder_hit\",\n name: \"Placeholder Hit\", // Fix: Use string instead of KoreanText object\n type: \"sound\",\n url: \"/placeholder/hit.mp3\",\n formats: [\"audio/mp3\"],\n loaded: false,\n volume: 0.5,\n category: \"sfx\", // Fix: Use string literal instead of enum\n pitch: 1.0,\n };\n }\n\n // Add missing loadAssets method\n public async loadAssets(): Promise<void> {\n console.log(\"Loading audio assets...\");\n // Implementation would preload audio files\n }\n\n /**\n * Initialize asset groups for batch loading\n */\n private initializeAssetGroups(): void {\n // Critical assets - load immediately on startup (menu UI sounds)\n this.registerAssetGroup({\n id: \"critical\",\n name: \"Critical UI Sounds\",\n priority: \"critical\",\n assets: [\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n ],\n lazyLoad: false,\n });\n\n // High priority - load during intro screen\n this.registerAssetGroup({\n id: \"intro_music\",\n name: \"Intro Screen Music\",\n priority: \"high\",\n assets: [\"intro_theme\"],\n lazyLoad: false,\n });\n\n // Combat music - high priority for combat screen\n this.registerAssetGroup({\n id: \"combat_music\",\n name: \"Combat Background Music\",\n priority: \"high\",\n assets: [\n \"combat_theme\",\n \"musa_warrior_theme\",\n \"amsalja_shadow_theme\",\n \"hacker_cyber_theme\",\n \"jeongbo_intel_theme\",\n \"jojik_street_theme\",\n ],\n lazyLoad: false,\n });\n\n // Screen music - normal priority for informational screens\n this.registerAssetGroup({\n id: \"screen_music\",\n name: \"Screen Background Music\",\n priority: \"normal\",\n assets: [\n \"underground_theme\",\n \"cyberpunk_fusion\",\n ],\n lazyLoad: true,\n });\n\n // Normal priority - combat SFX\n this.registerAssetGroup({\n id: \"combat_attacks\",\n name: \"Combat Attack Sounds\",\n priority: \"normal\",\n assets: [\n \"attack_light\",\n \"attack_medium\",\n \"attack_heavy\",\n \"attack_punch_light_1\",\n \"attack_punch_light_2\",\n \"attack_punch_medium_1\",\n \"attack_critical_1\",\n \"attack_critical_2\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_hits\",\n name: \"Combat Hit Reactions\",\n priority: \"normal\",\n assets: [\n \"hit_light_1\",\n \"hit_light_2\",\n \"hit_medium_1\",\n \"hit_medium_2\",\n \"hit_heavy_1\",\n \"hit_critical_1\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_defense\",\n name: \"Combat Defense Sounds\",\n priority: \"normal\",\n assets: [\n \"block_success_1\",\n \"block_success_2\",\n \"block_break_1\",\n \"dodge_1\",\n \"dodge_2\",\n ],\n lazyLoad: false,\n });\n\n this.registerAssetGroup({\n id: \"combat_movement\",\n name: \"Combat Movement Sounds\",\n priority: \"normal\",\n assets: [\n \"stance_change_1\",\n \"stance_change_2\",\n \"stance_change_3\",\n \"dodge_3\",\n \"dodge_4\",\n ],\n lazyLoad: false,\n });\n\n // Training-specific sounds\n this.registerAssetGroup({\n id: \"training_sfx\",\n name: \"Training Sound Effects\",\n priority: \"normal\",\n assets: [\n \"ki_charge\",\n \"ki_release\",\n \"ki_charge_1\",\n \"ki_charge_2\",\n \"ki_release_1\",\n \"ki_release_2\",\n ],\n lazyLoad: true,\n });\n }\n\n /**\n * Register an asset group for batch loading\n * @param group - Asset group configuration with ID, name, priority, and asset IDs\n */\n public registerAssetGroup(group: AssetGroup): void {\n this.assetGroups.set(group.id, group);\n }\n\n /**\n * Get asset group by ID\n * @param groupId - ID of the asset group to retrieve\n * @returns The asset group or undefined if not found\n */\n public getAssetGroup(groupId: string): AssetGroup | undefined {\n return this.assetGroups.get(groupId);\n }\n\n /**\n * Get all registered asset groups\n * @returns Array of all asset groups\n */\n public getAllAssetGroups(): readonly AssetGroup[] {\n return Array.from(this.assetGroups.values());\n }\n\n /**\n * Get asset groups filtered by priority level\n * @param priority - Priority level to filter by (critical/high/normal/low)\n * @returns Array of asset groups matching the priority\n */\n public getAssetGroupsByPriority(priority: LoadPriority): readonly AssetGroup[] {\n return Array.from(this.assetGroups.values()).filter(\n (group) => group.priority === priority\n );\n }\n\n /**\n * Get all assets belonging to a specific group\n * @param groupId - ID of the asset group\n * @returns Array of audio assets in the group, or empty array if group not found\n */\n public getAssetsInGroup(groupId: string): readonly AudioAsset[] {\n const group = this.assetGroups.get(groupId);\n if (!group) return [];\n\n const assets: AudioAsset[] = [];\n for (const assetId of group.assets) {\n // Check all maps, take first match\n const asset = this.getSFX(assetId) ?? this.getMusic(assetId) ?? this.getVoice(assetId);\n if (asset) {\n assets.push(asset);\n }\n }\n\n return assets;\n }\n\n /**\n * Create a manifest for efficient asset registration\n * @returns Audio asset manifest with version, total assets, size estimates, groups, and all assets\n */\n public createManifest(): AudioAssetManifest {\n const allAssets: Record<string, EnhancedAudioAsset> = {};\n\n // Add SFX assets\n this.sfxMap.forEach((sfx, id) => {\n allAssets[id] = {\n ...sfx,\n preloadPriority: \"normal\",\n } as EnhancedAudioAsset;\n });\n\n // Add music assets\n this.musicMap.forEach((music, id) => {\n allAssets[id] = {\n ...music,\n preloadPriority: \"high\",\n } as EnhancedAudioAsset;\n });\n\n // Add voice assets\n this.voiceMap.forEach((voice, id) => {\n allAssets[id] = {\n ...voice,\n preloadPriority: \"normal\",\n } as EnhancedAudioAsset;\n });\n\n const totalAssets = Object.keys(allAssets).length;\n const estimatedSizeMB = totalAssets * ESTIMATED_ASSET_SIZE_MB;\n\n return {\n version: \"1.0.0\",\n totalAssets,\n totalSizeMB: estimatedSizeMB,\n groups: this.getAllAssetGroups(),\n assets: allAssets,\n };\n }\n}\n\n// Export singleton instance\nexport const audioAssetRegistry = new AudioAssetRegistry();\n\n// Default export\nexport default audioAssetRegistry;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioManager.js","names":[],"sources":["../../src/audio/AudioManager.ts"],"sourcesContent":["import { AudioAssetLoader, LoadOptions } from \"./AudioAssetLoader\";\nimport { AudioCache, AudioCacheConfig } from \"./AudioCache\";\nimport {\n AudioFPSImpact,\n AudioMemoryStats,\n AudioMonitor,\n AudioPerformanceStats,\n MemoryWarning,\n} from \"./AudioMonitor\";\nimport { AudioElementPool, PoolStatistics } from \"./AudioPool\";\nimport {\n AudioAsset,\n AudioConfig,\n IAudioManager,\n MusicTrackId,\n SoundEffectId,\n} from \"./types\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\n/**\n * Critical assets that should never be evicted from cache\n * 캐시에서 절대 제거되지 않아야 하는 중요한 자산\n */\nconst CRITICAL_AUDIO_ASSETS = [\n // Menu sounds - instant playback required\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n // Common combat sounds - instant playback required\n \"hit_impact\",\n \"hit_light\",\n \"hit_medium\",\n \"hit_heavy\",\n \"guard_block\",\n \"attack_whoosh\",\n \"attack_light\",\n \"stance_change\",\n] as const;\n\nexport class AudioManager implements IAudioManager {\n private _masterVolume: number = 1.0;\n private _musicVolume: number = 0.7;\n private _sfxVolume: number = 0.8;\n private _muted: boolean = false;\n private _currentMusicTrack: string | null = null;\n private _fallbackMode: boolean = false;\n private currentMusic: HTMLAudioElement | null = null;\n private soundCache: Map<string, HTMLAudioElement> = new Map();\n private _isInitialized: boolean = false;\n\n // Track music assets currently being loaded to prevent race conditions\n // 레이스 컨디션 방지를 위해 현재 로드 중인 음악 자산 추적\n private loadingMusic: Set<string> = new Set();\n\n // New optimized components\n private assetLoader: AudioAssetLoader;\n private audioPool: AudioElementPool;\n private monitor: AudioMonitor;\n private audioCache: AudioCache;\n private frequentSounds: Set<string> = new Set([\n \"hit_light\",\n \"attack_light\",\n \"stance_change\",\n ]);\n\n constructor(config?: Partial<AudioConfig>) {\n if (config) {\n this._masterVolume = config.masterVolume ?? 1.0;\n this._musicVolume = config.musicVolume ?? 0.7;\n this._sfxVolume = config.sfxVolume ?? 0.8;\n }\n\n // Initialize new components\n this.assetLoader = new AudioAssetLoader();\n this.audioPool = new AudioElementPool();\n this.monitor = new AudioMonitor();\n\n // Initialize AudioCache with 30MB limit and critical assets\n // 30MB 제한 및 중요 자산으로 AudioCache 초기화\n const cacheConfig: AudioCacheConfig = {\n maxSizeBytes: 30 * 1024 * 1024, // 30MB default\n criticalAssets: [...CRITICAL_AUDIO_ASSETS],\n debug: typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"development\",\n };\n this.audioCache = new AudioCache(cacheConfig);\n }\n\n /**\n * Helper method to release pooled audio back to the pool after playback\n * @param id - Asset ID\n * @param audio - Audio element to release\n */\n private releasePooledAudio(id: string, audio: HTMLAudioElement): void {\n if (!audio.paused) {\n audio.addEventListener(\n \"ended\",\n () => {\n this.audioPool.release(id, audio);\n },\n { once: true }\n );\n } else {\n this.audioPool.release(id, audio);\n }\n }\n\n // Interface getters\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get fallbackMode(): boolean {\n return this._fallbackMode;\n }\n\n get currentMusicTrack(): string | null {\n return this._currentMusicTrack;\n }\n\n get masterVolume(): number {\n return this._masterVolume;\n }\n\n get sfxVolume(): number {\n return this._sfxVolume;\n }\n\n get musicVolume(): number {\n return this._musicVolume;\n }\n\n get muted(): boolean {\n return this._muted;\n }\n\n async initialize(config?: AudioConfig): Promise<void> {\n try {\n // Create AudioContext using globalThis for cross-platform compatibility\n const AudioContextClass =\n globalThis.AudioContext ||\n (globalThis as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n new AudioContextClass();\n this._isInitialized = true;\n this._fallbackMode = false;\n\n if (config) {\n this._masterVolume = config.masterVolume ?? this._masterVolume;\n this._musicVolume = config.musicVolume ?? this._musicVolume;\n this._sfxVolume = config.sfxVolume ?? this._sfxVolume;\n }\n\n // Set memory threshold if configured\n if (config?.maxSimultaneousSounds) {\n const estimatedMemoryMB =\n config.maxSimultaneousSounds * ESTIMATED_ASSET_SIZE_MB;\n this.monitor.setMemoryThreshold(estimatedMemoryMB);\n }\n } catch (error) {\n console.warn(\n \"AudioContext initialization failed, using fallback mode:\",\n error\n );\n this._isInitialized = true;\n this._fallbackMode = true;\n }\n }\n\n async loadAsset(asset: AudioAsset, options?: LoadOptions): Promise<void> {\n const startTime = performance.now();\n\n try {\n // Check cache first - instant playback for cached assets\n // 캐시 먼저 확인 - 캐시된 자산의 즉시 재생\n const cachedAsset = this.audioCache.get(asset.id);\n if (cachedAsset) {\n // Asset already loaded in cache, use it\n const cached = this.soundCache.get(asset.id);\n if (cached) {\n return; // Already loaded and cached\n }\n }\n\n // Use faster timeouts for testing environment\n // Safely check for Node.js environment to avoid runtime errors in browsers\n const isTest =\n typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"test\";\n const loadOptions: LoadOptions = {\n timeout: isTest ? 100 : 10000,\n maxRetries: isTest ? 1 : 3,\n retryDelay: isTest ? 10 : 1000,\n ...options,\n };\n\n // Use AudioAssetLoader with retry and fallback\n const result = await this.assetLoader.loadAsset(asset, loadOptions);\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Estimate size for cache management\n // 캐시 관리를 위한 크기 추정\n const estimatedSize = this.estimateAudioSize(asset);\n\n // Add to LRU cache with size tracking\n // 크기 추적과 함께 LRU 캐시에 추가\n this.audioCache.set(asset.id, asset, estimatedSize);\n\n // Track performance and memory\n const loadTime = performance.now() - startTime;\n this.monitor.recordLoad(asset.id, loadTime, estimatedSize / (1024 * 1024));\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n } else {\n // Record failure\n const error = result.error ?? new Error(\"Unknown load error\");\n this.monitor.recordLoadFailure(asset.id, error);\n\n // Still cache the placeholder audio if available\n if (result.audio) {\n this.soundCache.set(asset.id, result.audio);\n\n // Add to cache even if failed (placeholder)\n const estimatedSize = this.estimateAudioSize(asset);\n this.audioCache.set(asset.id, asset, estimatedSize);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.warn(`Failed to load audio asset ${asset.id}:`, err);\n this.monitor.recordLoadFailure(asset.id, err);\n }\n }\n\n /**\n * Estimate audio asset size in bytes\n * 바이트 단위의 오디오 자산 크기 추정\n *\n * @returns Estimated size in bytes\n */\n private estimateAudioSize(_asset: AudioAsset): number {\n // Use ESTIMATED_ASSET_SIZE_MB constant for consistent estimation\n // Conservative estimate: ~0.5MB per audio file (compressed MP3/WebM)\n return ESTIMATED_ASSET_SIZE_MB * 1024 * 1024;\n }\n\n async playSoundEffect(id: SoundEffectId): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n // Alias for playSoundEffect to match interface\n async playSFX(id: SoundEffectId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n async playMusic(id: MusicTrackId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n this.stopMusic();\n\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n let audio = this.soundCache.get(id);\n \n // On-demand loading: If music not in cache, load it first\n // 온디맨드 로딩: 음악이 캐시에 없으면 먼저 로드\n if (!audio) {\n // Check if this music is already being loaded to prevent race conditions\n // JavaScript's single-threaded nature ensures atomic check-and-add operations\n // 레이스 컨디션 방지를 위해 이미 로드 중인지 확인\n // JavaScript의 싱글 스레드 특성으로 원자적 check-and-add 작업 보장\n if (this.loadingMusic.has(id)) {\n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" is already being loaded, waiting...`);\n }\n // Wait for the ongoing load to complete\n // 진행 중인 로드가 완료될 때까지 대기\n await this.waitForMusicLoad(id);\n audio = this.soundCache.get(id);\n } else {\n // Mark as loading before starting the load (atomic operation in single-threaded JS)\n // 로드 시작 전에 로딩 중으로 표시 (싱글 스레드 JS에서 원자적 작업)\n this.loadingMusic.add(id);\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" not in cache, loading on-demand...`);\n }\n \n try {\n const { audioAssetRegistry } = await import(\"./AudioAssetRegistry\");\n const musicAsset = audioAssetRegistry.getMusic(id);\n \n if (musicAsset) {\n try {\n await this.loadAsset(musicAsset);\n audio = this.soundCache.get(id);\n \n if (import.meta.env.DEV && audio) {\n console.log(`[AudioManager] Successfully loaded music \"${id}\" on-demand`);\n }\n } catch (error) {\n console.warn(`Failed to load music asset ${id} on-demand:`, error);\n return;\n }\n } else {\n console.warn(`Music asset not found in registry: ${id}`);\n return;\n }\n } finally {\n // Always remove from loading set when done\n // 완료 시 항상 로딩 세트에서 제거\n this.loadingMusic.delete(id);\n }\n }\n }\n\n if (audio) {\n try {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._musicVolume) * this._masterVolume;\n audio.loop = true;\n this.currentMusic = audio;\n this._currentMusicTrack = id;\n await audio.play();\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Playing music: ${id} (volume: ${audio.volume.toFixed(2)})`);\n }\n } catch (error) {\n console.warn(`Failed to play music ${id}:`, error);\n }\n }\n }\n\n /**\n * Wait for a music asset to finish loading\n * 음악 자산 로드가 완료될 때까지 대기\n * @param id - Music track ID\n * @param maxWaitMs - Maximum time to wait in milliseconds\n */\n private async waitForMusicLoad(id: string, maxWaitMs: number = 5000): Promise<void> {\n const startTime = Date.now();\n const checkInterval = 100; // Check every 100ms\n \n while (this.loadingMusic.has(id)) {\n if (Date.now() - startTime > maxWaitMs) {\n console.warn(`[AudioManager] Timeout waiting for music \"${id}\" to load`);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval));\n }\n }\n\n stopMusic(): void {\n if (this.currentMusic) {\n this.currentMusic.pause();\n this.currentMusic.currentTime = 0;\n this.currentMusic = null;\n this._currentMusicTrack = null;\n }\n }\n\n stopAll(): void {\n this.stopMusic();\n this.soundCache.forEach((audio) => {\n if (!audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n });\n }\n\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void {\n const clampedVolume = Math.max(0, Math.min(1, volume));\n\n switch (type) {\n case \"master\":\n this._masterVolume = clampedVolume;\n break;\n case \"sfx\":\n this._sfxVolume = clampedVolume;\n break;\n case \"music\":\n this._musicVolume = clampedVolume;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n break;\n case \"voice\":\n // Handle voice volume if needed\n break;\n }\n }\n\n mute(): void {\n this._muted = true;\n if (this.currentMusic) {\n this.currentMusic.volume = 0;\n }\n }\n\n unmute(): void {\n this._muted = false;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n }\n\n async fadeOut(duration: number = 1000): Promise<void> {\n if (!this.currentMusic) return;\n\n const musicElement = this.currentMusic;\n return new Promise((resolve) => {\n const startVolume = musicElement.volume;\n const fadeStep = startVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume > 0) {\n this.currentMusic.volume = Math.max(\n 0,\n this.currentMusic.volume - fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n this.stopMusic();\n resolve();\n }\n }, 50);\n });\n }\n\n async fadeIn(trackId: MusicTrackId, duration: number = 1000): Promise<void> {\n await this.playMusic(trackId, 0);\n\n if (!this.currentMusic) return;\n\n return new Promise((resolve) => {\n const targetVolume = this._musicVolume * this._masterVolume;\n const fadeStep = targetVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume < targetVolume) {\n this.currentMusic.volume = Math.min(\n targetVolume,\n this.currentMusic.volume + fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n resolve();\n }\n }, 50);\n });\n }\n\n async crossfade(\n fromTrackId: MusicTrackId,\n toTrackId: MusicTrackId,\n duration: number = 1000\n ): Promise<void> {\n // Fix: Remove unused fromTrackId parameter or use it properly\n const fadeOutPromise = this.fadeOut(duration);\n await fadeOutPromise;\n await this.fadeIn(toTrackId, duration);\n console.log(`Crossfaded from ${fromTrackId} to ${toTrackId}`);\n }\n\n getLoadedAssets(): ReadonlyMap<string, HTMLAudioElement> {\n return new Map(this.soundCache);\n }\n\n // Additional methods to match interface\n async playVoice(id: string): Promise<void> {\n return this.playSoundEffect(id);\n }\n\n async playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void> {\n const soundId = `${archetype}_${techniqueId}`;\n return this.playSoundEffect(soundId);\n }\n\n async playTrigramStanceSound(stance: string): Promise<void> {\n const soundId = `stance_${stance}`;\n return this.playSoundEffect(soundId);\n }\n\n async playVitalPointHitSound(severity: string): Promise<void> {\n const soundId = `vital_point_${severity}`;\n return this.playSoundEffect(soundId);\n }\n\n async playDojiangAmbience(): Promise<void> {\n return this.playMusic(\"dojang_ambience\");\n }\n\n // Legacy getters for backward compatibility\n getMasterVolume(): number {\n return this._masterVolume;\n }\n\n getMusicVolume(): number {\n return this._musicVolume;\n }\n\n getSfxVolume(): number {\n return this._sfxVolume;\n }\n\n get initialized(): boolean {\n return this._isInitialized;\n }\n\n // New optimized methods\n\n /**\n * Batch load multiple assets with progress tracking\n * @param assets - Array of audio assets to load\n * @param options - Optional load configuration (timeout, retries, etc.)\n * @param onProgress - Optional callback for progress updates with loaded and total counts\n * @returns Promise that resolves when all assets are processed\n */\n async batchLoadAssets(\n assets: readonly AudioAsset[],\n options?: LoadOptions,\n onProgress?: (loaded: number, total: number) => void\n ): Promise<void> {\n const results = await this.assetLoader.batchLoad(\n assets,\n options,\n (progress) => {\n onProgress?.(progress.loaded, progress.total);\n }\n );\n\n // Process results\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const asset = assets[i];\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n }\n }\n }\n\n /**\n * Unload an asset to free memory\n * @param assetId - ID of the asset to unload\n * @returns true if asset was unloaded, false if asset was not found\n */\n unloadAsset(assetId: string): boolean {\n // Remove from cache\n const audio = this.soundCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n this.soundCache.delete(assetId);\n }\n\n // Remove from AudioCache (LRU cache)\n // LRU 캐시에서 제거\n const removedFromCache = this.audioCache.remove(assetId);\n\n // Remove from loader cache\n const unloaded = this.assetLoader.unloadAsset(assetId);\n\n // Remove pool if exists\n if (this.audioPool.hasPool(assetId)) {\n this.audioPool.removePool(assetId);\n }\n\n // Unregister from monitor\n this.monitor.unregisterAsset(assetId);\n\n return unloaded || audio !== undefined || removedFromCache;\n }\n\n /**\n * Get cache statistics including LRU cache metrics\n * LRU 캐시 메트릭을 포함한 캐시 통계 가져오기\n *\n * @returns Cache statistics\n */\n getCacheStats(): {\n readonly lruCache: {\n readonly totalSize: number;\n readonly assetCount: number;\n readonly criticalCount: number;\n readonly utilizationPercent: number;\n readonly evictionCount: number;\n readonly hitCount: number;\n readonly missCount: number;\n readonly hitRate: number;\n };\n readonly soundCache: number;\n readonly poolStats: Map<string, PoolStatistics>;\n } {\n return {\n lruCache: this.audioCache.getStats(),\n soundCache: this.soundCache.size,\n poolStats: this.audioPool.getAllStatistics(),\n };\n }\n\n /**\n * Get memory statistics including total loaded MB, asset count, and warnings\n * @returns Memory statistics object\n */\n getMemoryStats(): AudioMemoryStats {\n return this.monitor.getMemoryStats();\n }\n\n /**\n * Get performance statistics including load times and playback latency\n * @returns Performance statistics object\n */\n getPerformanceStats(): AudioPerformanceStats {\n return this.monitor.getPerformanceStats();\n }\n\n /**\n * Get FPS impact analysis showing baseline vs current FPS and detected drops\n * @returns FPS impact analysis object\n */\n getFPSImpact(): AudioFPSImpact {\n return this.monitor.getFPSImpact();\n }\n\n /**\n * Update FPS measurement for monitoring\n * @param fps - Current frames per second measurement\n */\n updateFPS(fps: number): void {\n this.monitor.updateFPS(fps);\n }\n\n /**\n * Get comprehensive monitoring report including memory, performance, FPS, and warnings\n * @returns Comprehensive monitoring report object\n */\n getMonitoringReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return this.monitor.getReport();\n }\n\n /**\n * Get all memory warnings\n * @returns Array of memory warnings\n */\n getMemoryWarnings(): readonly MemoryWarning[] {\n return this.monitor.getWarnings();\n }\n\n /**\n * Clear memory warnings\n */\n clearMemoryWarnings(): void {\n this.monitor.clearWarnings();\n }\n\n /**\n * Get pool statistics for a specific asset or all pools\n * @param assetId - Optional asset ID to get specific pool stats\n * @returns Pool statistics for the specified asset or all pools\n */\n getPoolStatistics(\n assetId?: string\n ): PoolStatistics | Map<string, PoolStatistics> | undefined {\n if (assetId) {\n return this.audioPool.getPoolStatistics(assetId);\n }\n return this.audioPool.getAllStatistics();\n }\n\n /**\n * Get loader statistics including cached assets and loading state\n * @returns Loader statistics object\n */\n getLoaderStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return this.assetLoader.getStatistics();\n }\n}\n\nexport default AudioManager;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioManager.js","names":[],"sources":["../../src/audio/AudioManager.ts"],"sourcesContent":["import { AudioAssetLoader, LoadOptions } from \"./AudioAssetLoader\";\nimport { AudioCache, AudioCacheConfig } from \"./AudioCache\";\nimport {\n AudioFPSImpact,\n AudioMemoryStats,\n AudioMonitor,\n AudioPerformanceStats,\n MemoryWarning,\n} from \"./AudioMonitor\";\nimport { AudioElementPool, PoolStatistics } from \"./AudioPool\";\nimport {\n AudioAsset,\n AudioConfig,\n IAudioManager,\n MusicTrackId,\n SoundEffectId,\n} from \"./types\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\n/**\n * Critical assets that should never be evicted from cache\n * 캐시에서 절대 제거되지 않아야 하는 중요한 자산\n */\nconst CRITICAL_AUDIO_ASSETS = [\n // Menu sounds - instant playback required\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n // Common combat sounds - instant playback required\n \"hit_impact\",\n \"hit_light\",\n \"hit_medium\",\n \"hit_heavy\",\n \"guard_block\",\n \"attack_whoosh\",\n \"attack_light\",\n \"stance_change\",\n] as const;\n\nexport class AudioManager implements IAudioManager {\n private _masterVolume: number = 1.0;\n private _musicVolume: number = 0.7;\n private _sfxVolume: number = 0.8;\n private _muted: boolean = false;\n private _currentMusicTrack: string | null = null;\n private _fallbackMode: boolean = false;\n private currentMusic: HTMLAudioElement | null = null;\n private soundCache: Map<string, HTMLAudioElement> = new Map();\n private _isInitialized: boolean = false;\n\n // Track music assets currently being loaded to prevent race conditions\n // 레이스 컨디션 방지를 위해 현재 로드 중인 음악 자산 추적\n private loadingMusic: Set<string> = new Set();\n\n // New optimized components\n private assetLoader: AudioAssetLoader;\n private audioPool: AudioElementPool;\n private monitor: AudioMonitor;\n private audioCache: AudioCache;\n private frequentSounds: Set<string> = new Set([\n \"hit_light\",\n \"attack_light\",\n \"stance_change\",\n ]);\n\n constructor(config?: Partial<AudioConfig>) {\n if (config) {\n this._masterVolume = config.masterVolume ?? 1.0;\n this._musicVolume = config.musicVolume ?? 0.7;\n this._sfxVolume = config.sfxVolume ?? 0.8;\n }\n\n // Initialize new components\n this.assetLoader = new AudioAssetLoader();\n this.audioPool = new AudioElementPool();\n this.monitor = new AudioMonitor();\n\n // Initialize AudioCache with 30MB limit and critical assets\n // 30MB 제한 및 중요 자산으로 AudioCache 초기화\n const cacheConfig: AudioCacheConfig = {\n maxSizeBytes: 30 * 1024 * 1024, // 30MB default\n criticalAssets: [...CRITICAL_AUDIO_ASSETS],\n debug: typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"development\",\n };\n this.audioCache = new AudioCache(cacheConfig);\n }\n\n /**\n * Helper method to release pooled audio back to the pool after playback\n * @param id - Asset ID\n * @param audio - Audio element to release\n */\n private releasePooledAudio(id: string, audio: HTMLAudioElement): void {\n if (!audio.paused) {\n audio.addEventListener(\n \"ended\",\n () => {\n this.audioPool.release(id, audio);\n },\n { once: true }\n );\n } else {\n this.audioPool.release(id, audio);\n }\n }\n\n // Interface getters\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get fallbackMode(): boolean {\n return this._fallbackMode;\n }\n\n get currentMusicTrack(): string | null {\n return this._currentMusicTrack;\n }\n\n get masterVolume(): number {\n return this._masterVolume;\n }\n\n get sfxVolume(): number {\n return this._sfxVolume;\n }\n\n get musicVolume(): number {\n return this._musicVolume;\n }\n\n get muted(): boolean {\n return this._muted;\n }\n\n async initialize(config?: AudioConfig): Promise<void> {\n try {\n // Create AudioContext using globalThis for cross-platform compatibility\n const AudioContextClass =\n globalThis.AudioContext ||\n (globalThis as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n new AudioContextClass();\n this._isInitialized = true;\n this._fallbackMode = false;\n\n if (config) {\n this._masterVolume = config.masterVolume ?? this._masterVolume;\n this._musicVolume = config.musicVolume ?? this._musicVolume;\n this._sfxVolume = config.sfxVolume ?? this._sfxVolume;\n }\n\n // Set memory threshold if configured\n if (config?.maxSimultaneousSounds) {\n const estimatedMemoryMB =\n config.maxSimultaneousSounds * ESTIMATED_ASSET_SIZE_MB;\n this.monitor.setMemoryThreshold(estimatedMemoryMB);\n }\n } catch (error) {\n console.warn(\n \"AudioContext initialization failed, using fallback mode:\",\n error\n );\n this._isInitialized = true;\n this._fallbackMode = true;\n }\n }\n\n async loadAsset(asset: AudioAsset, options?: LoadOptions): Promise<void> {\n const startTime = performance.now();\n\n try {\n // Check cache first - instant playback for cached assets\n // 캐시 먼저 확인 - 캐시된 자산의 즉시 재생\n const cachedAsset = this.audioCache.get(asset.id);\n if (cachedAsset) {\n // Asset already loaded in cache, use it\n const cached = this.soundCache.get(asset.id);\n if (cached) {\n return; // Already loaded and cached\n }\n }\n\n // Use faster timeouts for testing environment\n // Safely check for Node.js environment to avoid runtime errors in browsers\n const isTest =\n typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"test\";\n const loadOptions: LoadOptions = {\n timeout: isTest ? 100 : 10000,\n maxRetries: isTest ? 1 : 3,\n retryDelay: isTest ? 10 : 1000,\n ...options,\n };\n\n // Use AudioAssetLoader with retry and fallback\n const result = await this.assetLoader.loadAsset(asset, loadOptions);\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Estimate size for cache management\n // 캐시 관리를 위한 크기 추정\n const estimatedSize = this.estimateAudioSize(asset);\n\n // Add to LRU cache with size tracking\n // 크기 추적과 함께 LRU 캐시에 추가\n this.audioCache.set(asset.id, asset, estimatedSize);\n\n // Track performance and memory\n const loadTime = performance.now() - startTime;\n this.monitor.recordLoad(asset.id, loadTime, estimatedSize / (1024 * 1024));\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n } else {\n // Record failure\n const error = result.error ?? new Error(\"Unknown load error\");\n this.monitor.recordLoadFailure(asset.id, error);\n\n // Still cache the placeholder audio if available\n if (result.audio) {\n this.soundCache.set(asset.id, result.audio);\n\n // Add to cache even if failed (placeholder)\n const estimatedSize = this.estimateAudioSize(asset);\n this.audioCache.set(asset.id, asset, estimatedSize);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.warn(`Failed to load audio asset ${asset.id}:`, err);\n this.monitor.recordLoadFailure(asset.id, err);\n }\n }\n\n /**\n * Estimate audio asset size in bytes\n * 바이트 단위의 오디오 자산 크기 추정\n *\n * @returns Estimated size in bytes\n */\n private estimateAudioSize(_asset: AudioAsset): number {\n // Use ESTIMATED_ASSET_SIZE_MB constant for consistent estimation\n // Conservative estimate: ~0.5MB per audio file (compressed MP3/WebM)\n return ESTIMATED_ASSET_SIZE_MB * 1024 * 1024;\n }\n\n async playSoundEffect(id: SoundEffectId): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n // Alias for playSoundEffect to match interface\n async playSFX(id: SoundEffectId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n async playMusic(id: MusicTrackId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n this.stopMusic();\n\n // Update cache access time for LRU tracking\n // LRU 추적을 위한 캐시 액세스 시간 업데이트\n this.audioCache.get(id);\n\n let audio = this.soundCache.get(id);\n \n // On-demand loading: If music not in cache, load it first\n // 온디맨드 로딩: 음악이 캐시에 없으면 먼저 로드\n if (!audio) {\n // Check if this music is already being loaded to prevent race conditions\n // JavaScript's single-threaded nature ensures atomic check-and-add operations\n // 레이스 컨디션 방지를 위해 이미 로드 중인지 확인\n // JavaScript의 싱글 스레드 특성으로 원자적 check-and-add 작업 보장\n if (this.loadingMusic.has(id)) {\n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" is already being loaded, waiting...`);\n }\n // Wait for the ongoing load to complete\n // 진행 중인 로드가 완료될 때까지 대기\n await this.waitForMusicLoad(id);\n audio = this.soundCache.get(id);\n } else {\n // Mark as loading before starting the load (atomic operation in single-threaded JS)\n // 로드 시작 전에 로딩 중으로 표시 (싱글 스레드 JS에서 원자적 작업)\n this.loadingMusic.add(id);\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" not in cache, loading on-demand...`);\n }\n \n try {\n const { audioAssetRegistry } = await import(\"./AudioAssetRegistry\");\n const musicAsset = audioAssetRegistry.getMusic(id);\n \n if (musicAsset) {\n try {\n await this.loadAsset(musicAsset);\n audio = this.soundCache.get(id);\n \n if (import.meta.env.DEV && audio) {\n console.log(`[AudioManager] Successfully loaded music \"${id}\" on-demand`);\n }\n } catch (error) {\n console.warn(`Failed to load music asset ${id} on-demand:`, error);\n return;\n }\n } else {\n console.warn(`Music asset not found in registry: ${id}`);\n return;\n }\n } finally {\n // Always remove from loading set when done\n // 완료 시 항상 로딩 세트에서 제거\n this.loadingMusic.delete(id);\n }\n }\n }\n\n if (audio) {\n try {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._musicVolume) * this._masterVolume;\n audio.loop = true;\n this.currentMusic = audio;\n this._currentMusicTrack = id;\n await audio.play();\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Playing music: ${id} (volume: ${audio.volume.toFixed(2)})`);\n }\n } catch (error) {\n console.warn(`Failed to play music ${id}:`, error);\n }\n }\n }\n\n /**\n * Wait for a music asset to finish loading\n * 음악 자산 로드가 완료될 때까지 대기\n * @param id - Music track ID\n * @param maxWaitMs - Maximum time to wait in milliseconds\n */\n private async waitForMusicLoad(id: string, maxWaitMs: number = 5000): Promise<void> {\n const startTime = Date.now();\n const checkInterval = 100; // Check every 100ms\n \n while (this.loadingMusic.has(id)) {\n if (Date.now() - startTime > maxWaitMs) {\n console.warn(`[AudioManager] Timeout waiting for music \"${id}\" to load`);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval));\n }\n }\n\n stopMusic(): void {\n if (this.currentMusic) {\n this.currentMusic.pause();\n this.currentMusic.currentTime = 0;\n this.currentMusic = null;\n this._currentMusicTrack = null;\n }\n }\n\n stopAll(): void {\n this.stopMusic();\n this.soundCache.forEach((audio) => {\n if (!audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n });\n }\n\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void {\n const clampedVolume = Math.max(0, Math.min(1, volume));\n\n switch (type) {\n case \"master\":\n this._masterVolume = clampedVolume;\n break;\n case \"sfx\":\n this._sfxVolume = clampedVolume;\n break;\n case \"music\":\n this._musicVolume = clampedVolume;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n break;\n case \"voice\":\n // Handle voice volume if needed\n break;\n }\n }\n\n mute(): void {\n this._muted = true;\n if (this.currentMusic) {\n this.currentMusic.volume = 0;\n }\n }\n\n unmute(): void {\n this._muted = false;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n }\n\n async fadeOut(duration: number = 1000): Promise<void> {\n if (!this.currentMusic) return;\n\n const musicElement = this.currentMusic;\n return new Promise((resolve) => {\n const startVolume = musicElement.volume;\n const fadeStep = startVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume > 0) {\n this.currentMusic.volume = Math.max(\n 0,\n this.currentMusic.volume - fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n this.stopMusic();\n resolve();\n }\n }, 50);\n });\n }\n\n async fadeIn(trackId: MusicTrackId, duration: number = 1000): Promise<void> {\n await this.playMusic(trackId, 0);\n\n if (!this.currentMusic) return;\n\n return new Promise((resolve) => {\n const targetVolume = this._musicVolume * this._masterVolume;\n const fadeStep = targetVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume < targetVolume) {\n this.currentMusic.volume = Math.min(\n targetVolume,\n this.currentMusic.volume + fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n resolve();\n }\n }, 50);\n });\n }\n\n async crossfade(\n fromTrackId: MusicTrackId,\n toTrackId: MusicTrackId,\n duration: number = 1000\n ): Promise<void> {\n // Fix: Remove unused fromTrackId parameter or use it properly\n const fadeOutPromise = this.fadeOut(duration);\n await fadeOutPromise;\n await this.fadeIn(toTrackId, duration);\n console.log(`Crossfaded from ${fromTrackId} to ${toTrackId}`);\n }\n\n getLoadedAssets(): ReadonlyMap<string, HTMLAudioElement> {\n return new Map(this.soundCache);\n }\n\n // Additional methods to match interface\n async playVoice(id: string): Promise<void> {\n return this.playSoundEffect(id);\n }\n\n async playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void> {\n const soundId = `${archetype}_${techniqueId}`;\n return this.playSoundEffect(soundId);\n }\n\n async playTrigramStanceSound(stance: string): Promise<void> {\n const soundId = `stance_${stance}`;\n return this.playSoundEffect(soundId);\n }\n\n async playVitalPointHitSound(severity: string): Promise<void> {\n const soundId = `vital_point_${severity}`;\n return this.playSoundEffect(soundId);\n }\n\n async playDojiangAmbience(): Promise<void> {\n return this.playMusic(\"dojang_ambience\");\n }\n\n // Legacy getters for backward compatibility\n getMasterVolume(): number {\n return this._masterVolume;\n }\n\n getMusicVolume(): number {\n return this._musicVolume;\n }\n\n getSfxVolume(): number {\n return this._sfxVolume;\n }\n\n get initialized(): boolean {\n return this._isInitialized;\n }\n\n // New optimized methods\n\n /**\n * Batch load multiple assets with progress tracking\n * @param assets - Array of audio assets to load\n * @param options - Optional load configuration (timeout, retries, etc.)\n * @param onProgress - Optional callback for progress updates with loaded and total counts\n * @returns Promise that resolves when all assets are processed\n */\n async batchLoadAssets(\n assets: readonly AudioAsset[],\n options?: LoadOptions,\n onProgress?: (loaded: number, total: number) => void\n ): Promise<void> {\n const results = await this.assetLoader.batchLoad(\n assets,\n options,\n (progress) => {\n onProgress?.(progress.loaded, progress.total);\n }\n );\n\n // Process results\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const asset = assets[i];\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n }\n }\n }\n\n /**\n * Unload an asset to free memory\n * @param assetId - ID of the asset to unload\n * @returns true if asset was unloaded, false if asset was not found\n */\n unloadAsset(assetId: string): boolean {\n // Remove from cache\n const audio = this.soundCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n this.soundCache.delete(assetId);\n }\n\n // Remove from AudioCache (LRU cache)\n // LRU 캐시에서 제거\n const removedFromCache = this.audioCache.remove(assetId);\n\n // Remove from loader cache\n const unloaded = this.assetLoader.unloadAsset(assetId);\n\n // Remove pool if exists\n if (this.audioPool.hasPool(assetId)) {\n this.audioPool.removePool(assetId);\n }\n\n // Unregister from monitor\n this.monitor.unregisterAsset(assetId);\n\n return unloaded || audio !== undefined || removedFromCache;\n }\n\n /**\n * Get cache statistics including LRU cache metrics\n * LRU 캐시 메트릭을 포함한 캐시 통계 가져오기\n *\n * @returns Cache statistics\n */\n getCacheStats(): {\n readonly lruCache: {\n readonly totalSize: number;\n readonly assetCount: number;\n readonly criticalCount: number;\n readonly utilizationPercent: number;\n readonly evictionCount: number;\n readonly hitCount: number;\n readonly missCount: number;\n readonly hitRate: number;\n };\n readonly soundCache: number;\n readonly poolStats: Map<string, PoolStatistics>;\n } {\n return {\n lruCache: this.audioCache.getStats(),\n soundCache: this.soundCache.size,\n poolStats: this.audioPool.getAllStatistics(),\n };\n }\n\n /**\n * Get memory statistics including total loaded MB, asset count, and warnings\n * @returns Memory statistics object\n */\n getMemoryStats(): AudioMemoryStats {\n return this.monitor.getMemoryStats();\n }\n\n /**\n * Get performance statistics including load times and playback latency\n * @returns Performance statistics object\n */\n getPerformanceStats(): AudioPerformanceStats {\n return this.monitor.getPerformanceStats();\n }\n\n /**\n * Get FPS impact analysis showing baseline vs current FPS and detected drops\n * @returns FPS impact analysis object\n */\n getFPSImpact(): AudioFPSImpact {\n return this.monitor.getFPSImpact();\n }\n\n /**\n * Update FPS measurement for monitoring\n * @param fps - Current frames per second measurement\n */\n updateFPS(fps: number): void {\n this.monitor.updateFPS(fps);\n }\n\n /**\n * Get comprehensive monitoring report including memory, performance, FPS, and warnings\n * @returns Comprehensive monitoring report object\n */\n getMonitoringReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return this.monitor.getReport();\n }\n\n /**\n * Get all memory warnings\n * @returns Array of memory warnings\n */\n getMemoryWarnings(): readonly MemoryWarning[] {\n return this.monitor.getWarnings();\n }\n\n /**\n * Clear memory warnings\n */\n clearMemoryWarnings(): void {\n this.monitor.clearWarnings();\n }\n\n /**\n * Get pool statistics for a specific asset or all pools\n * @param assetId - Optional asset ID to get specific pool stats\n * @returns Pool statistics for the specified asset or all pools\n */\n getPoolStatistics(\n assetId?: string\n ): PoolStatistics | Map<string, PoolStatistics> | undefined {\n if (assetId) {\n return this.audioPool.getPoolStatistics(assetId);\n }\n return this.audioPool.getAllStatistics();\n }\n\n /**\n * Get loader statistics including cached assets and loading state\n * @returns Loader statistics object\n */\n getLoaderStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return this.assetLoader.getStatistics();\n }\n}\n\nexport default AudioManager;\n"],"mappings":"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"}
|