blacktrigram 0.7.39 → 0.7.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/audio/AudioAssetLoader.js.map +1 -1
  3. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  4. package/lib/audio/AudioCache.js.map +1 -1
  5. package/lib/audio/AudioManager.js.map +1 -1
  6. package/lib/audio/AudioMonitor.js.map +1 -1
  7. package/lib/audio/AudioPool.js.map +1 -1
  8. package/lib/audio/AudioProvider.js.map +1 -1
  9. package/lib/audio/AudioUtils.js.map +1 -1
  10. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  11. package/lib/audio/VariantSelector.js.map +1 -1
  12. package/lib/audio/types.js.map +1 -1
  13. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  14. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  15. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  31. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  32. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  36. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  39. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  42. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  44. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  45. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  46. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  47. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  48. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  49. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  50. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  51. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  52. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  53. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  54. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  55. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  56. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  57. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  58. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  59. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  60. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  61. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  62. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  63. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  64. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  65. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  66. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  67. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  68. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  69. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  70. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  71. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  72. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  74. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  75. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  76. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  77. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  78. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  79. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  84. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  85. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  86. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  87. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  88. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  89. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  90. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  91. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  93. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  94. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  96. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  97. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  98. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  99. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  100. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  101. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  102. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  103. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  104. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  105. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  106. package/lib/components/shared/base/BaseButton.js.map +1 -1
  107. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  108. package/lib/components/shared/base/BasePanel.js.map +1 -1
  109. package/lib/components/shared/base/BaseText.js.map +1 -1
  110. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  111. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  112. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  113. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  114. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  115. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  116. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  117. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  118. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  119. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  120. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  121. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  122. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  123. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  124. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  125. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  126. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  127. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  128. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  129. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  130. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  131. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  132. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  133. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  134. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  135. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  136. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  137. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  138. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  139. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  140. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  141. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  142. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  143. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  144. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  145. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  146. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  147. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  148. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  149. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  150. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  151. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  152. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  153. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  154. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  155. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  156. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  157. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  158. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  159. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  160. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  161. package/lib/components/shared/ui/BackButton.js.map +1 -1
  162. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  163. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  164. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  165. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  166. package/lib/components/shared/ui/SplashScreen.js +2 -2
  167. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  168. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  169. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  170. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  171. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  172. package/lib/constants/bodyDimensions.js.map +1 -1
  173. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  174. package/lib/data/archetypeClothing.js.map +1 -1
  175. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  176. package/lib/data/techniqueMappings.js.map +1 -1
  177. package/lib/data/techniques.js.map +1 -1
  178. package/lib/hooks/useActionFeedback.js.map +1 -1
  179. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  180. package/lib/hooks/useCombatTimer.js.map +1 -1
  181. package/lib/hooks/useDebounce.js.map +1 -1
  182. package/lib/hooks/useHUDLayout.js.map +1 -1
  183. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  184. package/lib/hooks/useKeyboardControls.js.map +1 -1
  185. package/lib/hooks/useMatchCountdown.js.map +1 -1
  186. package/lib/hooks/useMuscleActivation.js.map +1 -1
  187. package/lib/hooks/usePauseMenu.js.map +1 -1
  188. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  189. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  190. package/lib/hooks/useRoundTransition.js.map +1 -1
  191. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  192. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  193. package/lib/hooks/useThrottle.js.map +1 -1
  194. package/lib/hooks/useTouchControls.js.map +1 -1
  195. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  196. package/lib/hooks/useWindowSize.js.map +1 -1
  197. package/lib/systems/CombatSystem.js.map +1 -1
  198. package/lib/systems/EffectCalculator.js.map +1 -1
  199. package/lib/systems/LayoutSystem.js.map +1 -1
  200. package/lib/systems/PlayerEffectManager.js.map +1 -1
  201. package/lib/systems/ResponsiveScaling.js.map +1 -1
  202. package/lib/systems/TrigramSystem.js.map +1 -1
  203. package/lib/systems/VitalPointSystem.js.map +1 -1
  204. package/lib/systems/ai/AIPersonality.js.map +1 -1
  205. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  206. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  207. package/lib/systems/ai/ComboSystem.js.map +1 -1
  208. package/lib/systems/ai/DecisionTree.js.map +1 -1
  209. package/lib/systems/ai/TrainingAI.js.map +1 -1
  210. package/lib/systems/ai/types.js.map +1 -1
  211. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  212. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  213. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  214. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  215. package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
  216. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  217. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  218. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  219. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  220. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  221. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  222. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  223. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  224. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  225. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  226. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  227. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  228. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  229. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  230. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  231. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  232. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  233. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  234. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  235. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  236. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  237. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  238. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  239. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  240. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  241. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  242. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  243. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  244. package/lib/systems/animation/core/types.js.map +1 -1
  245. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  246. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  247. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  248. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  249. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  250. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  251. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  252. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  253. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  254. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  255. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  256. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  257. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  258. package/lib/systems/bodypart/types.js.map +1 -1
  259. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  260. package/lib/systems/breathing/feedback.js.map +1 -1
  261. package/lib/systems/breathing/integration.js.map +1 -1
  262. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  263. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  264. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  265. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  266. package/lib/systems/combat/FallIntegration.js.map +1 -1
  267. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  268. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  269. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  270. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  271. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  272. package/lib/systems/combat/typeGuards.js.map +1 -1
  273. package/lib/systems/effects.js.map +1 -1
  274. package/lib/systems/game.js.map +1 -1
  275. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  276. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  277. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  278. package/lib/systems/movement/integration.js.map +1 -1
  279. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  280. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  281. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  282. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  283. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  284. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  285. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  286. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  287. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  288. package/lib/systems/trigram/StanceManager.js.map +1 -1
  289. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  290. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  291. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  292. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  293. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  294. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  295. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  296. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  297. package/lib/systems/trigram/techniques/index.js.map +1 -1
  298. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  299. package/lib/systems/trigram/types.js.map +1 -1
  300. package/lib/systems/types.js.map +1 -1
  301. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  302. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  303. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  304. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  305. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  306. package/lib/types/AccessibilityTypes.js.map +1 -1
  307. package/lib/types/PhysicsTypes.js.map +1 -1
  308. package/lib/types/common.js.map +1 -1
  309. package/lib/types/constants/colors.js.map +1 -1
  310. package/lib/types/constants/designSystem.js.map +1 -1
  311. package/lib/types/constants/layout.js.map +1 -1
  312. package/lib/types/constants/performance.js.map +1 -1
  313. package/lib/types/constants/typography.js.map +1 -1
  314. package/lib/types/facial.js.map +1 -1
  315. package/lib/types/hand-animation.js.map +1 -1
  316. package/lib/types/injury.js.map +1 -1
  317. package/lib/types/physics.js.map +1 -1
  318. package/lib/types/skeletal.js.map +1 -1
  319. package/lib/types/techniqueId.js.map +1 -1
  320. package/lib/utils/accessibility.js.map +1 -1
  321. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  322. package/lib/utils/assetConfig.js.map +1 -1
  323. package/lib/utils/characterScaling.js.map +1 -1
  324. package/lib/utils/colorHelpers.js.map +1 -1
  325. package/lib/utils/colorUtils.js.map +1 -1
  326. package/lib/utils/combatReadiness.js.map +1 -1
  327. package/lib/utils/controlMapping.js.map +1 -1
  328. package/lib/utils/deviceDetection.js.map +1 -1
  329. package/lib/utils/effectUtils.js.map +1 -1
  330. package/lib/utils/fabricTextures.js.map +1 -1
  331. package/lib/utils/hapticFeedback.js.map +1 -1
  332. package/lib/utils/haptics.js.map +1 -1
  333. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  334. package/lib/utils/inputSystem.js.map +1 -1
  335. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  336. package/lib/utils/math.js.map +1 -1
  337. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  338. package/lib/utils/mobileUIUtils.js.map +1 -1
  339. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  340. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  341. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  342. package/lib/utils/performanceOptimization.js.map +1 -1
  343. package/lib/utils/player3DHelpers.js.map +1 -1
  344. package/lib/utils/playerUtils.js.map +1 -1
  345. package/lib/utils/responsiveLayout.js.map +1 -1
  346. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  347. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  348. package/lib/utils/safeAreaUtils.js.map +1 -1
  349. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  350. package/lib/utils/skeletonScaling.js.map +1 -1
  351. package/lib/utils/stanceHelpers.js.map +1 -1
  352. package/lib/utils/threeObjectPool.js.map +1 -1
  353. package/lib/utils/visualEffects.js.map +1 -1
  354. package/package.json +8 -8
@@ -1 +1 @@
1
- {"version":3,"file":"useDebounce.js","names":[],"sources":["../../src/hooks/useDebounce.ts"],"sourcesContent":["/**\n * useDebounce Hook\n * \n * Debounces a function to execute only after a delay since the last call.\n * Useful for search inputs, resize handlers, and other delayed actions.\n * \n * Uses a ref pattern to ensure the latest callback is always called\n * without recreating the debounced function on every render.\n * \n * @module hooks/useDebounce\n * @category Performance\n * @korean 디바운스 훅\n */\n\nimport { useCallback, useRef, useLayoutEffect, useEffect } from 'react';\n\n/**\n * Hook to debounce a callback function\n * \n * @param callback - Function to debounce\n * @param delay - Delay in milliseconds before execution\n * @returns Debounced function\n * \n * @example\n * ```tsx\n * const handleSearch = useDebounce((query: string) => {\n * // Perform search\n * }, 300);\n * ```\n */\nexport function useDebounce<T extends (...args: unknown[]) => void>(\n callback: T,\n delay: number\n): (...args: Parameters<T>) => void {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n \n // Keep callback ref up to date\n useLayoutEffect(() => {\n callbackRef.current = callback;\n });\n \n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return useCallback(\n (...args: Parameters<T>) => {\n // Clear existing timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n // Set new timeout\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n timeoutRef.current = null;\n }, delay);\n },\n [delay]\n );\n}\n\nexport default useDebounce;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YACd,UACA,OACkC;CAClC,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAO,SAAS;AAGpC,uBAAsB;AACpB,cAAY,UAAU;GACtB;AAGF,iBAAgB;AACd,eAAa;AACX,OAAI,WAAW,QACb,cAAa,WAAW,QAAQ;;IAGnC,EAAE,CAAC;AAEN,QAAO,aACJ,GAAG,SAAwB;AAE1B,MAAI,WAAW,QACb,cAAa,WAAW,QAAQ;AAIlC,aAAW,UAAU,iBAAiB;AACpC,eAAY,QAAQ,GAAG,KAAK;AAC5B,cAAW,UAAU;KACpB,MAAM;IAEX,CAAC,MAAM,CACR"}
1
+ {"version":3,"file":"useDebounce.js","names":[],"sources":["../../src/hooks/useDebounce.ts"],"sourcesContent":["/**\n * useDebounce Hook\n * \n * Debounces a function to execute only after a delay since the last call.\n * Useful for search inputs, resize handlers, and other delayed actions.\n * \n * Uses a ref pattern to ensure the latest callback is always called\n * without recreating the debounced function on every render.\n * \n * @module hooks/useDebounce\n * @category Performance\n * @korean 디바운스 훅\n */\n\nimport { useCallback, useRef, useLayoutEffect, useEffect } from 'react';\n\n/**\n * Hook to debounce a callback function\n * \n * @param callback - Function to debounce\n * @param delay - Delay in milliseconds before execution\n * @returns Debounced function\n * \n * @example\n * ```tsx\n * const handleSearch = useDebounce((query: string) => {\n * // Perform search\n * }, 300);\n * ```\n */\nexport function useDebounce<T extends (...args: unknown[]) => void>(\n callback: T,\n delay: number\n): (...args: Parameters<T>) => void {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n \n // Keep callback ref up to date\n useLayoutEffect(() => {\n callbackRef.current = callback;\n });\n \n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return useCallback(\n (...args: Parameters<T>) => {\n // Clear existing timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n // Set new timeout\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n timeoutRef.current = null;\n }, delay);\n },\n [delay]\n );\n}\n\nexport default useDebounce;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YACd,UACA,OACkC;CAClC,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAO,SAAS;CAGpC,sBAAsB;EACpB,YAAY,UAAU;GACtB;CAGF,gBAAgB;EACd,aAAa;GACX,IAAI,WAAW,SACb,aAAa,WAAW,QAAQ;;IAGnC,EAAE,CAAC;CAEN,OAAO,aACJ,GAAG,SAAwB;EAE1B,IAAI,WAAW,SACb,aAAa,WAAW,QAAQ;EAIlC,WAAW,UAAU,iBAAiB;GACpC,YAAY,QAAQ,GAAG,KAAK;GAC5B,WAAW,UAAU;KACpB,MAAM;IAEX,CAAC,MAAM,CACR"}
@@ -1 +1 @@
1
- {"version":3,"file":"useHUDLayout.js","names":[],"sources":["../../src/hooks/useHUDLayout.ts"],"sourcesContent":["/**\n * useHUDLayout - Centralized HUD layout calculations\n * \n * Extracts common HUD layout patterns used across Training and Combat screens.\n * Provides resolution-based responsive sizing, positioning, and spacing calculations.\n * \n * @module hooks\n * @korean HUD레이아웃훅 - 중앙화된 HUD 레이아웃 계산\n */\n\nimport { useMemo } from 'react';\nimport type { HUDPosition } from '../components/shared/ui/BaseHUDContainer';\nimport { getResponsiveSize, getHUDHeight, getResponsivePadding } from '../utils/responsiveLayout';\n\n/**\n * HUD position type - left, right, top, or bottom\n * Re-exported from BaseHUDContainer to maintain single source of truth\n */\nexport type { HUDPosition };\n\n/**\n * Result of HUD layout calculations\n */\nexport interface HUDLayoutResult {\n /** HUD width as percentage of screen (0.0-1.0) */\n readonly hudWidthPercent: number;\n /** HUD height as percentage of screen (0.0-1.0) */\n readonly hudHeightPercent: number;\n /** HUD width in pixels */\n readonly hudWidth: number;\n /** HUD height in pixels */\n readonly hudHeight: number;\n /** Top offset in pixels (for left/right HUDs) */\n readonly topOffset: number;\n /** Bottom offset in pixels (for left/right HUDs) */\n readonly bottomOffset: number;\n /** Available height between top and bottom bars */\n readonly availableHeight: number;\n /** Internal padding in pixels */\n readonly padding: number;\n /** Gap between sections in pixels */\n readonly gap: number;\n}\n\n/**\n * Custom hook for calculating HUD layout dimensions\n * \n * Provides consistent layout calculations across Training and Combat screens.\n * Uses resolution-based responsive sizing for smooth scaling across all screen sizes.\n * \n * @param width - Screen width in pixels\n * @param height - Screen height in pixels\n * @param positionScale - Position scale multiplier for large displays (1.0-1.5)\n * @param position - HUD position (left, right, top, bottom)\n * @param context - Context ('training' or 'combat') for context-specific dimensions\n * @param paddingOverride - Optional padding override (final pixel value, already scaled). When provided, bypasses default padding calculation.\n * @param gapOverride - Optional gap override (final pixel value, already scaled). When provided, bypasses default gap calculation.\n * @returns Calculated layout dimensions and offsets\n * \n * @example\n * ```tsx\n * const layout = useHUDLayout(\n * 1920, 1080, 1.0, 'left', 'training'\n * );\n * // Resolution-based sizing interpolates smoothly between breakpoints\n * // layout.hudWidth = 269 (14% of 1920 for desktop)\n * // layout.topOffset = 64.8 (getHUDHeight(1080, 0.06) = 64.8)\n * // layout.bottomOffset = 118.8 (getHUDHeight(1080, 0.11) = 118.8)\n * // layout.availableHeight = 896.4 (1080 - 64.8 - 118.8)\n * ```\n */\nexport function useHUDLayout(\n width: number,\n height: number,\n positionScale: number,\n position: HUDPosition,\n context: 'training' | 'combat' = 'training',\n paddingOverride?: number,\n gapOverride?: number\n): HUDLayoutResult {\n return useMemo(() => {\n\n // Resolution-based width calculation: interpolates smoothly between breakpoints\n // Left/Right HUDs: 14-18% of screen width (mobile: 18%, tablet: 16%, desktop: 14%)\n // Top/Bottom HUDs: 100% width\n const hudWidthPercent = (position === 'left' || position === 'right')\n ? getResponsiveSize(width, {\n mobile: 18, // 18% for small screens\n tablet: 16, // 16% for medium screens\n desktop: 14, // 14% for large screens\n }) / 100 // Convert to decimal for percentage calculation\n : 1.0; // Top and bottom HUDs use full width\n\n // Resolution-based height calculation for top/bottom bars\n // Training: ~6% for top, ~11% for bottom\n // Combat: ~8% for top, ~12% for bottom\n const topHeightPercent = context === 'training' ? 0.06 : 0.08;\n const bottomHeightPercent = context === 'training' ? 0.11 : 0.12;\n \n const scaledTopHeight = getHUDHeight(height, topHeightPercent) * positionScale;\n const scaledBottomHeight = getHUDHeight(height, bottomHeightPercent) * positionScale;\n\n // Calculate HUD dimensions in pixels\n const hudWidth = Math.round(width * hudWidthPercent);\n const hudHeight = position === 'top' || position === 'bottom'\n ? (position === 'top' ? scaledTopHeight : scaledBottomHeight)\n : Math.max(0, height - scaledTopHeight - scaledBottomHeight);\n\n // Calculate offsets for left/right HUDs\n const topOffset = scaledTopHeight;\n const bottomOffset = scaledBottomHeight;\n const availableHeight = Math.max(0, height - topOffset - bottomOffset);\n\n // Resolution-based padding using responsive utility\n const defaultPadding = getResponsivePadding(width) * positionScale;\n \n // Resolution-based gap: context-specific values\n // Training uses slightly larger gaps than combat for better readability\n const defaultGap = context === 'training'\n ? getResponsiveSize(width, {\n mobile: 12,\n tablet: 15,\n desktop: 18,\n }) * positionScale\n : getResponsiveSize(width, {\n mobile: 10,\n tablet: 12,\n desktop: 14,\n }) * positionScale;\n \n // Use overrides if provided, otherwise use defaults\n // Overrides allow per-position customization (e.g., TrainingRightHUD uses tighter spacing)\n const padding = paddingOverride ?? defaultPadding;\n const gap = gapOverride ?? defaultGap;\n\n // Guard against division by zero and ensure valid percentage\n const safeHeight = Math.max(height, 1);\n const hudHeightPercent = Math.max(0, Math.min(1, hudHeight / safeHeight));\n\n return {\n hudWidthPercent,\n hudHeightPercent,\n hudWidth,\n hudHeight,\n topOffset,\n bottomOffset,\n availableHeight,\n padding,\n gap,\n };\n }, [width, height, positionScale, position, context, paddingOverride, gapOverride]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,SAAgB,aACd,OACA,QACA,eACA,UACA,UAAiC,YACjC,iBACA,aACiB;AACjB,QAAO,cAAc;EAKnB,MAAM,kBAAmB,aAAa,UAAU,aAAa,UACzD,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;GACV,CAAC,GAAG,MACL;EAKJ,MAAM,mBAAmB,YAAY,aAAa,MAAO;EACzD,MAAM,sBAAsB,YAAY,aAAa,MAAO;EAE5D,MAAM,kBAAkB,aAAa,QAAQ,iBAAiB,GAAG;EACjE,MAAM,qBAAqB,aAAa,QAAQ,oBAAoB,GAAG;EAGvE,MAAM,WAAW,KAAK,MAAM,QAAQ,gBAAgB;EACpD,MAAM,YAAY,aAAa,SAAS,aAAa,WAChD,aAAa,QAAQ,kBAAkB,qBACxC,KAAK,IAAI,GAAG,SAAS,kBAAkB,mBAAmB;EAG9D,MAAM,YAAY;EAClB,MAAM,eAAe;EACrB,MAAM,kBAAkB,KAAK,IAAI,GAAG,SAAS,YAAY,aAAa;EAGtE,MAAM,iBAAiB,qBAAqB,MAAM,GAAG;EAIrD,MAAM,aAAa,YAAY,aAC3B,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;GACV,CAAC,GAAG,gBACL,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;GACV,CAAC,GAAG;EAIT,MAAM,UAAU,mBAAmB;EACnC,MAAM,MAAM,eAAe;AAM3B,SAAO;GACL;GACA,kBAJuB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,YAD9B,KAAK,IAAI,QAAQ,EACyB,CAAW,CAItE;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAO;EAAQ;EAAe;EAAU;EAAS;EAAiB;EAAY,CAAC"}
1
+ {"version":3,"file":"useHUDLayout.js","names":[],"sources":["../../src/hooks/useHUDLayout.ts"],"sourcesContent":["/**\n * useHUDLayout - Centralized HUD layout calculations\n * \n * Extracts common HUD layout patterns used across Training and Combat screens.\n * Provides resolution-based responsive sizing, positioning, and spacing calculations.\n * \n * @module hooks\n * @korean HUD레이아웃훅 - 중앙화된 HUD 레이아웃 계산\n */\n\nimport { useMemo } from 'react';\nimport type { HUDPosition } from '../components/shared/ui/BaseHUDContainer';\nimport { getResponsiveSize, getHUDHeight, getResponsivePadding } from '../utils/responsiveLayout';\n\n/**\n * HUD position type - left, right, top, or bottom\n * Re-exported from BaseHUDContainer to maintain single source of truth\n */\nexport type { HUDPosition };\n\n/**\n * Result of HUD layout calculations\n */\nexport interface HUDLayoutResult {\n /** HUD width as percentage of screen (0.0-1.0) */\n readonly hudWidthPercent: number;\n /** HUD height as percentage of screen (0.0-1.0) */\n readonly hudHeightPercent: number;\n /** HUD width in pixels */\n readonly hudWidth: number;\n /** HUD height in pixels */\n readonly hudHeight: number;\n /** Top offset in pixels (for left/right HUDs) */\n readonly topOffset: number;\n /** Bottom offset in pixels (for left/right HUDs) */\n readonly bottomOffset: number;\n /** Available height between top and bottom bars */\n readonly availableHeight: number;\n /** Internal padding in pixels */\n readonly padding: number;\n /** Gap between sections in pixels */\n readonly gap: number;\n}\n\n/**\n * Custom hook for calculating HUD layout dimensions\n * \n * Provides consistent layout calculations across Training and Combat screens.\n * Uses resolution-based responsive sizing for smooth scaling across all screen sizes.\n * \n * @param width - Screen width in pixels\n * @param height - Screen height in pixels\n * @param positionScale - Position scale multiplier for large displays (1.0-1.5)\n * @param position - HUD position (left, right, top, bottom)\n * @param context - Context ('training' or 'combat') for context-specific dimensions\n * @param paddingOverride - Optional padding override (final pixel value, already scaled). When provided, bypasses default padding calculation.\n * @param gapOverride - Optional gap override (final pixel value, already scaled). When provided, bypasses default gap calculation.\n * @returns Calculated layout dimensions and offsets\n * \n * @example\n * ```tsx\n * const layout = useHUDLayout(\n * 1920, 1080, 1.0, 'left', 'training'\n * );\n * // Resolution-based sizing interpolates smoothly between breakpoints\n * // layout.hudWidth = 269 (14% of 1920 for desktop)\n * // layout.topOffset = 64.8 (getHUDHeight(1080, 0.06) = 64.8)\n * // layout.bottomOffset = 118.8 (getHUDHeight(1080, 0.11) = 118.8)\n * // layout.availableHeight = 896.4 (1080 - 64.8 - 118.8)\n * ```\n */\nexport function useHUDLayout(\n width: number,\n height: number,\n positionScale: number,\n position: HUDPosition,\n context: 'training' | 'combat' = 'training',\n paddingOverride?: number,\n gapOverride?: number\n): HUDLayoutResult {\n return useMemo(() => {\n\n // Resolution-based width calculation: interpolates smoothly between breakpoints\n // Left/Right HUDs: 14-18% of screen width (mobile: 18%, tablet: 16%, desktop: 14%)\n // Top/Bottom HUDs: 100% width\n const hudWidthPercent = (position === 'left' || position === 'right')\n ? getResponsiveSize(width, {\n mobile: 18, // 18% for small screens\n tablet: 16, // 16% for medium screens\n desktop: 14, // 14% for large screens\n }) / 100 // Convert to decimal for percentage calculation\n : 1.0; // Top and bottom HUDs use full width\n\n // Resolution-based height calculation for top/bottom bars\n // Training: ~6% for top, ~11% for bottom\n // Combat: ~8% for top, ~12% for bottom\n const topHeightPercent = context === 'training' ? 0.06 : 0.08;\n const bottomHeightPercent = context === 'training' ? 0.11 : 0.12;\n \n const scaledTopHeight = getHUDHeight(height, topHeightPercent) * positionScale;\n const scaledBottomHeight = getHUDHeight(height, bottomHeightPercent) * positionScale;\n\n // Calculate HUD dimensions in pixels\n const hudWidth = Math.round(width * hudWidthPercent);\n const hudHeight = position === 'top' || position === 'bottom'\n ? (position === 'top' ? scaledTopHeight : scaledBottomHeight)\n : Math.max(0, height - scaledTopHeight - scaledBottomHeight);\n\n // Calculate offsets for left/right HUDs\n const topOffset = scaledTopHeight;\n const bottomOffset = scaledBottomHeight;\n const availableHeight = Math.max(0, height - topOffset - bottomOffset);\n\n // Resolution-based padding using responsive utility\n const defaultPadding = getResponsivePadding(width) * positionScale;\n \n // Resolution-based gap: context-specific values\n // Training uses slightly larger gaps than combat for better readability\n const defaultGap = context === 'training'\n ? getResponsiveSize(width, {\n mobile: 12,\n tablet: 15,\n desktop: 18,\n }) * positionScale\n : getResponsiveSize(width, {\n mobile: 10,\n tablet: 12,\n desktop: 14,\n }) * positionScale;\n \n // Use overrides if provided, otherwise use defaults\n // Overrides allow per-position customization (e.g., TrainingRightHUD uses tighter spacing)\n const padding = paddingOverride ?? defaultPadding;\n const gap = gapOverride ?? defaultGap;\n\n // Guard against division by zero and ensure valid percentage\n const safeHeight = Math.max(height, 1);\n const hudHeightPercent = Math.max(0, Math.min(1, hudHeight / safeHeight));\n\n return {\n hudWidthPercent,\n hudHeightPercent,\n hudWidth,\n hudHeight,\n topOffset,\n bottomOffset,\n availableHeight,\n padding,\n gap,\n };\n }, [width, height, positionScale, position, context, paddingOverride, gapOverride]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,SAAgB,aACd,OACA,QACA,eACA,UACA,UAAiC,YACjC,iBACA,aACiB;CACjB,OAAO,cAAc;EAKnB,MAAM,kBAAmB,aAAa,UAAU,aAAa,UACzD,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;GACV,CAAC,GAAG,MACL;EAKJ,MAAM,mBAAmB,YAAY,aAAa,MAAO;EACzD,MAAM,sBAAsB,YAAY,aAAa,MAAO;EAE5D,MAAM,kBAAkB,aAAa,QAAQ,iBAAiB,GAAG;EACjE,MAAM,qBAAqB,aAAa,QAAQ,oBAAoB,GAAG;EAGvE,MAAM,WAAW,KAAK,MAAM,QAAQ,gBAAgB;EACpD,MAAM,YAAY,aAAa,SAAS,aAAa,WAChD,aAAa,QAAQ,kBAAkB,qBACxC,KAAK,IAAI,GAAG,SAAS,kBAAkB,mBAAmB;EAG9D,MAAM,YAAY;EAClB,MAAM,eAAe;EACrB,MAAM,kBAAkB,KAAK,IAAI,GAAG,SAAS,YAAY,aAAa;EAGtE,MAAM,iBAAiB,qBAAqB,MAAM,GAAG;EAIrD,MAAM,aAAa,YAAY,aAC3B,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;GACV,CAAC,GAAG,gBACL,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;GACV,CAAC,GAAG;EAIT,MAAM,UAAU,mBAAmB;EACnC,MAAM,MAAM,eAAe;EAM3B,OAAO;GACL;GACA,kBAJuB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,YAD9B,KAAK,IAAI,QAAQ,EACyB,CAAW,CAItE;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAO;EAAQ;EAAe;EAAU;EAAS;EAAiB;EAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useHandPoseTransitions.js","names":[],"sources":["../../src/hooks/useHandPoseTransitions.ts"],"sourcesContent":["/**\n * useHandPoseTransitions - Shared hook for hand animation management\n *\n * Manages hand pose transitions and animation state for both hands.\n * Reduces code duplication in skeletal animation components.\n *\n * @module hooks/useHandPoseTransitions\n * @category Hooks\n * @korean 손자세전환훅\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n createInitialHandAnimationState,\n getTechniqueHandPose,\n updateHandAnimationState,\n} from \"../systems/animation\";\nimport type { HandAnimationState } from \"../types/hand-animation\";\nimport { HandPoseType } from \"../types/hand-animation\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\n\n/**\n * Default transition duration for hand pose changes (in seconds)\n * @korean 손자세전환기본지속시간\n */\nconst DEFAULT_HAND_TRANSITION_DURATION = 0.2;\n\n/**\n * Frequency of React state syncs during hand animations.\n * Value of 20 means sync every 5% progress (~every 3 frames at 60fps).\n * Reduces React re-renders from 60/sec to ~20/sec during transitions.\n * @korean 손상태동기화빈도\n */\nconst HAND_STATE_SYNC_FREQUENCY = 20;\n\n/**\n * Options for useHandPoseTransitions hook\n * @korean 손자세전환훅옵션\n */\nexport interface UseHandPoseTransitionsOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n}\n\n/**\n * Return type for useHandPoseTransitions hook\n * @korean 손자세전환훅반환타입\n */\nexport interface UseHandPoseTransitionsReturn {\n /** Left hand animation state */\n readonly leftHandState: HandAnimationState;\n /** Right hand animation state */\n readonly rightHandState: HandAnimationState;\n /** Update hand animations (call in useFrame) */\n readonly updateHandAnimations: (delta: number) => void;\n}\n\n/**\n * Updates hand animation state for a single hand at 60fps.\n * Uses refs to avoid triggering React re-renders on every frame.\n * Only syncs to React state periodically or when transition completes.\n *\n * @param handStateRef - Ref storing current hand animation state\n * @param setHandState - React setState function to update hand state\n * @param delta - Time elapsed since last frame (in seconds)\n * @korean 손애니메이션프레임업데이트\n */\nconst updateHandAnimationFrame = (\n handStateRef: React.MutableRefObject<HandAnimationState>,\n setHandState: React.Dispatch<React.SetStateAction<HandAnimationState>>,\n delta: number\n): void => {\n if (handStateRef.current && handStateRef.current.targetPose !== null) {\n const previousState = handStateRef.current;\n const updatedState = updateHandAnimationState(\n previousState,\n previousState.targetPose,\n delta,\n DEFAULT_HAND_TRANSITION_DURATION\n );\n\n handStateRef.current = updatedState;\n\n // Sync to React state periodically (approximately every 3 frames at 60fps)\n if (\n updatedState.targetPose === null ||\n Math.floor(updatedState.transitionProgress * HAND_STATE_SYNC_FREQUENCY) !==\n Math.floor(\n previousState.transitionProgress * HAND_STATE_SYNC_FREQUENCY\n )\n ) {\n setHandState(updatedState);\n }\n }\n};\n\n/**\n * useHandPoseTransitions hook\n *\n * Manages hand pose transitions for both hands based on current animation.\n * Automatically selects appropriate hand poses for different techniques\n * and handles smooth transitions at 60fps.\n *\n * @param options - Hand animation options\n * @returns Hand states and update function\n *\n * @example\n * ```tsx\n * const { leftHandState, rightHandState, updateHandAnimations } =\n * useHandPoseTransitions({\n * currentAnimation: \"attack\",\n * attackAnimation: \"jab\",\n * isBlocking: false,\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateHandAnimations(delta);\n * });\n * ```\n *\n * @korean 손자세전환훅\n */\nexport function useHandPoseTransitions(\n options: UseHandPoseTransitionsOptions\n): UseHandPoseTransitionsReturn {\n const { currentAnimation, attackAnimation, isBlocking = false } = options;\n\n // Hand animation states\n const [leftHandState, setLeftHandState] = useState<HandAnimationState>(\n createInitialHandAnimationState(HandPoseType.OPEN)\n );\n const [rightHandState, setRightHandState] = useState<HandAnimationState>(\n createInitialHandAnimationState(HandPoseType.OPEN)\n );\n\n // Refs for 60fps animation updates without triggering React re-renders\n const leftHandStateRef = useRef<HandAnimationState>(leftHandState);\n const rightHandStateRef = useRef<HandAnimationState>(rightHandState);\n\n // Sync refs with state\n useEffect(() => {\n leftHandStateRef.current = leftHandState;\n }, [leftHandState]);\n\n useEffect(() => {\n rightHandStateRef.current = rightHandState;\n }, [rightHandState]);\n\n // Update hand poses based on current animation\n useEffect(() => {\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - use technique-specific hand poses\n const handPose = getTechniqueHandPose(attackAnimation);\n setLeftHandState((prev) =>\n updateHandAnimationState(\n prev,\n handPose.leftHandPose,\n 0,\n handPose.transitionDuration\n )\n );\n setRightHandState((prev) =>\n updateHandAnimationState(\n prev,\n handPose.rightHandPose,\n 0,\n handPose.transitionDuration\n )\n );\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Open hands for blocking\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n } else if (currentAnimation === \"walk\") {\n // Relaxed hands while walking\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.2)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.2)\n );\n } else if (currentAnimation === \"stance_change\") {\n // Guard hands during stance change\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.15)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.15)\n );\n } else if (\n currentAnimation?.startsWith(\"step_\") ||\n currentAnimation?.startsWith(\"footwork_\")\n ) {\n // Maintain guard hands during steps and footwork\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n } else if (currentAnimation?.startsWith(\"stance_guard_\")) {\n // Guard hands in open position\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.2)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.2)\n );\n } else {\n // Idle - return to relaxed hand pose\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.3)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.3)\n );\n }\n }, [currentAnimation, attackAnimation, isBlocking]);\n\n // Update hand animations at 60fps\n const updateHandAnimations = (delta: number): void => {\n updateHandAnimationFrame(leftHandStateRef, setLeftHandState, delta);\n updateHandAnimationFrame(rightHandStateRef, setRightHandState, delta);\n };\n\n return {\n leftHandState,\n rightHandState,\n updateHandAnimations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAyBA,IAAM,mCAAmC;;;;;;;AAQzC,IAAM,4BAA4B;;;;;;;;;;;AAsClC,IAAM,4BACJ,cACA,cACA,UACS;AACT,KAAI,aAAa,WAAW,aAAa,QAAQ,eAAe,MAAM;EACpE,MAAM,gBAAgB,aAAa;EACnC,MAAM,eAAe,yBACnB,eACA,cAAc,YACd,OACA,iCACD;AAED,eAAa,UAAU;AAGvB,MACE,aAAa,eAAe,QAC5B,KAAK,MAAM,aAAa,qBAAqB,0BAA0B,KACrE,KAAK,MACH,cAAc,qBAAqB,0BACpC,CAEH,cAAa,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgChC,SAAgB,uBACd,SAC8B;CAC9B,MAAM,EAAE,kBAAkB,iBAAiB,aAAa,UAAU;CAGlE,MAAM,CAAC,eAAe,oBAAoB,SACxC,gCAAgC,aAAa,KAAK,CACnD;CACD,MAAM,CAAC,gBAAgB,qBAAqB,SAC1C,gCAAgC,aAAa,KAAK,CACnD;CAGD,MAAM,mBAAmB,OAA2B,cAAc;CAClE,MAAM,oBAAoB,OAA2B,eAAe;AAGpE,iBAAgB;AACd,mBAAiB,UAAU;IAC1B,CAAC,cAAc,CAAC;AAEnB,iBAAgB;AACd,oBAAkB,UAAU;IAC3B,CAAC,eAAe,CAAC;AAGpB,iBAAgB;AACd,MAAI,qBAAqB,YAAY,iBAAiB;GAEpD,MAAM,WAAW,qBAAqB,gBAAgB;AACtD,qBAAkB,SAChB,yBACE,MACA,SAAS,cACT,GACA,SAAS,mBACV,CACF;AACD,sBAAmB,SACjB,yBACE,MACA,SAAS,eACT,GACA,SAAS,mBACV,CACF;aACQ,qBAAqB,YAAY,YAAY;AAEtD,qBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;AACD,sBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;aACQ,qBAAqB,QAAQ;AAEtC,qBAAkB,SAChB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;AACD,sBAAmB,SACjB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;aACQ,qBAAqB,iBAAiB;AAE/C,qBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,IAAK,CAC3D;AACD,sBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,IAAK,CAC3D;aAED,kBAAkB,WAAW,QAAQ,IACrC,kBAAkB,WAAW,YAAY,EACzC;AAEA,qBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;AACD,sBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;aACQ,kBAAkB,WAAW,gBAAgB,EAAE;AAExD,qBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;AACD,sBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;SACI;AAEL,qBAAkB,SAChB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;AACD,sBAAmB,SACjB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;;IAEF;EAAC;EAAkB;EAAiB;EAAW,CAAC;CAGnD,MAAM,wBAAwB,UAAwB;AACpD,2BAAyB,kBAAkB,kBAAkB,MAAM;AACnE,2BAAyB,mBAAmB,mBAAmB,MAAM;;AAGvE,QAAO;EACL;EACA;EACA;EACD"}
1
+ {"version":3,"file":"useHandPoseTransitions.js","names":[],"sources":["../../src/hooks/useHandPoseTransitions.ts"],"sourcesContent":["/**\n * useHandPoseTransitions - Shared hook for hand animation management\n *\n * Manages hand pose transitions and animation state for both hands.\n * Reduces code duplication in skeletal animation components.\n *\n * @module hooks/useHandPoseTransitions\n * @category Hooks\n * @korean 손자세전환훅\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n createInitialHandAnimationState,\n getTechniqueHandPose,\n updateHandAnimationState,\n} from \"../systems/animation\";\nimport type { HandAnimationState } from \"../types/hand-animation\";\nimport { HandPoseType } from \"../types/hand-animation\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\n\n/**\n * Default transition duration for hand pose changes (in seconds)\n * @korean 손자세전환기본지속시간\n */\nconst DEFAULT_HAND_TRANSITION_DURATION = 0.2;\n\n/**\n * Frequency of React state syncs during hand animations.\n * Value of 20 means sync every 5% progress (~every 3 frames at 60fps).\n * Reduces React re-renders from 60/sec to ~20/sec during transitions.\n * @korean 손상태동기화빈도\n */\nconst HAND_STATE_SYNC_FREQUENCY = 20;\n\n/**\n * Options for useHandPoseTransitions hook\n * @korean 손자세전환훅옵션\n */\nexport interface UseHandPoseTransitionsOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n}\n\n/**\n * Return type for useHandPoseTransitions hook\n * @korean 손자세전환훅반환타입\n */\nexport interface UseHandPoseTransitionsReturn {\n /** Left hand animation state */\n readonly leftHandState: HandAnimationState;\n /** Right hand animation state */\n readonly rightHandState: HandAnimationState;\n /** Update hand animations (call in useFrame) */\n readonly updateHandAnimations: (delta: number) => void;\n}\n\n/**\n * Updates hand animation state for a single hand at 60fps.\n * Uses refs to avoid triggering React re-renders on every frame.\n * Only syncs to React state periodically or when transition completes.\n *\n * @param handStateRef - Ref storing current hand animation state\n * @param setHandState - React setState function to update hand state\n * @param delta - Time elapsed since last frame (in seconds)\n * @korean 손애니메이션프레임업데이트\n */\nconst updateHandAnimationFrame = (\n handStateRef: React.MutableRefObject<HandAnimationState>,\n setHandState: React.Dispatch<React.SetStateAction<HandAnimationState>>,\n delta: number\n): void => {\n if (handStateRef.current && handStateRef.current.targetPose !== null) {\n const previousState = handStateRef.current;\n const updatedState = updateHandAnimationState(\n previousState,\n previousState.targetPose,\n delta,\n DEFAULT_HAND_TRANSITION_DURATION\n );\n\n handStateRef.current = updatedState;\n\n // Sync to React state periodically (approximately every 3 frames at 60fps)\n if (\n updatedState.targetPose === null ||\n Math.floor(updatedState.transitionProgress * HAND_STATE_SYNC_FREQUENCY) !==\n Math.floor(\n previousState.transitionProgress * HAND_STATE_SYNC_FREQUENCY\n )\n ) {\n setHandState(updatedState);\n }\n }\n};\n\n/**\n * useHandPoseTransitions hook\n *\n * Manages hand pose transitions for both hands based on current animation.\n * Automatically selects appropriate hand poses for different techniques\n * and handles smooth transitions at 60fps.\n *\n * @param options - Hand animation options\n * @returns Hand states and update function\n *\n * @example\n * ```tsx\n * const { leftHandState, rightHandState, updateHandAnimations } =\n * useHandPoseTransitions({\n * currentAnimation: \"attack\",\n * attackAnimation: \"jab\",\n * isBlocking: false,\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateHandAnimations(delta);\n * });\n * ```\n *\n * @korean 손자세전환훅\n */\nexport function useHandPoseTransitions(\n options: UseHandPoseTransitionsOptions\n): UseHandPoseTransitionsReturn {\n const { currentAnimation, attackAnimation, isBlocking = false } = options;\n\n // Hand animation states\n const [leftHandState, setLeftHandState] = useState<HandAnimationState>(\n createInitialHandAnimationState(HandPoseType.OPEN)\n );\n const [rightHandState, setRightHandState] = useState<HandAnimationState>(\n createInitialHandAnimationState(HandPoseType.OPEN)\n );\n\n // Refs for 60fps animation updates without triggering React re-renders\n const leftHandStateRef = useRef<HandAnimationState>(leftHandState);\n const rightHandStateRef = useRef<HandAnimationState>(rightHandState);\n\n // Sync refs with state\n useEffect(() => {\n leftHandStateRef.current = leftHandState;\n }, [leftHandState]);\n\n useEffect(() => {\n rightHandStateRef.current = rightHandState;\n }, [rightHandState]);\n\n // Update hand poses based on current animation\n useEffect(() => {\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - use technique-specific hand poses\n const handPose = getTechniqueHandPose(attackAnimation);\n setLeftHandState((prev) =>\n updateHandAnimationState(\n prev,\n handPose.leftHandPose,\n 0,\n handPose.transitionDuration\n )\n );\n setRightHandState((prev) =>\n updateHandAnimationState(\n prev,\n handPose.rightHandPose,\n 0,\n handPose.transitionDuration\n )\n );\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Open hands for blocking\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n } else if (currentAnimation === \"walk\") {\n // Relaxed hands while walking\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.2)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.2)\n );\n } else if (currentAnimation === \"stance_change\") {\n // Guard hands during stance change\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.15)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.15)\n );\n } else if (\n currentAnimation?.startsWith(\"step_\") ||\n currentAnimation?.startsWith(\"footwork_\")\n ) {\n // Maintain guard hands during steps and footwork\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n } else if (currentAnimation?.startsWith(\"stance_guard_\")) {\n // Guard hands in open position\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.2)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.2)\n );\n } else {\n // Idle - return to relaxed hand pose\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.3)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.3)\n );\n }\n }, [currentAnimation, attackAnimation, isBlocking]);\n\n // Update hand animations at 60fps\n const updateHandAnimations = (delta: number): void => {\n updateHandAnimationFrame(leftHandStateRef, setLeftHandState, delta);\n updateHandAnimationFrame(rightHandStateRef, setRightHandState, delta);\n };\n\n return {\n leftHandState,\n rightHandState,\n updateHandAnimations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAyBA,IAAM,mCAAmC;;;;;;;AAQzC,IAAM,4BAA4B;;;;;;;;;;;AAsClC,IAAM,4BACJ,cACA,cACA,UACS;CACT,IAAI,aAAa,WAAW,aAAa,QAAQ,eAAe,MAAM;EACpE,MAAM,gBAAgB,aAAa;EACnC,MAAM,eAAe,yBACnB,eACA,cAAc,YACd,OACA,iCACD;EAED,aAAa,UAAU;EAGvB,IACE,aAAa,eAAe,QAC5B,KAAK,MAAM,aAAa,qBAAqB,0BAA0B,KACrE,KAAK,MACH,cAAc,qBAAqB,0BACpC,EAEH,aAAa,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgChC,SAAgB,uBACd,SAC8B;CAC9B,MAAM,EAAE,kBAAkB,iBAAiB,aAAa,UAAU;CAGlE,MAAM,CAAC,eAAe,oBAAoB,SACxC,gCAAgC,aAAa,KAAK,CACnD;CACD,MAAM,CAAC,gBAAgB,qBAAqB,SAC1C,gCAAgC,aAAa,KAAK,CACnD;CAGD,MAAM,mBAAmB,OAA2B,cAAc;CAClE,MAAM,oBAAoB,OAA2B,eAAe;CAGpE,gBAAgB;EACd,iBAAiB,UAAU;IAC1B,CAAC,cAAc,CAAC;CAEnB,gBAAgB;EACd,kBAAkB,UAAU;IAC3B,CAAC,eAAe,CAAC;CAGpB,gBAAgB;EACd,IAAI,qBAAqB,YAAY,iBAAiB;GAEpD,MAAM,WAAW,qBAAqB,gBAAgB;GACtD,kBAAkB,SAChB,yBACE,MACA,SAAS,cACT,GACA,SAAS,mBACV,CACF;GACD,mBAAmB,SACjB,yBACE,MACA,SAAS,eACT,GACA,SAAS,mBACV,CACF;SACI,IAAI,qBAAqB,YAAY,YAAY;GAEtD,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;SACI,IAAI,qBAAqB,QAAQ;GAEtC,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;SACI,IAAI,qBAAqB,iBAAiB;GAE/C,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,IAAK,CAC3D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,IAAK,CAC3D;SACI,IACL,kBAAkB,WAAW,QAAQ,IACrC,kBAAkB,WAAW,YAAY,EACzC;GAEA,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;SACI,IAAI,kBAAkB,WAAW,gBAAgB,EAAE;GAExD,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;SACI;GAEL,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;;IAEF;EAAC;EAAkB;EAAiB;EAAW,CAAC;CAGnD,MAAM,wBAAwB,UAAwB;EACpD,yBAAyB,kBAAkB,kBAAkB,MAAM;EACnE,yBAAyB,mBAAmB,mBAAmB,MAAM;;CAGvE,OAAO;EACL;EACA;EACA;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardControls.js","names":[],"sources":["../../src/hooks/useKeyboardControls.ts"],"sourcesContent":["/**\n * Custom hook for keyboard controls with visual feedback\n * Manages keyboard input for trigram stance switching and combat actions\n *\n * @module hooks/useKeyboardControls\n * @category Input System\n * @korean 키보드 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FOOTWORK_KOREAN_TERMS } from \"../systems/animation/core/types\";\nimport { ControlMapper } from \"../utils/controlMapping\";\n\n// Korean terminology constants (module-level to avoid recreation on every keystroke)\nconst DIAGONAL_KOREAN_TERMS: Record<string, string> = {\n step_forward_left: \"전좌측보법 (Forward-Left)\",\n step_forward_right: \"전우측보법 (Forward-Right)\",\n step_back_left: \"후좌측보법 (Back-Left)\",\n step_back_right: \"후우측보법 (Back-Right)\",\n};\n\nconst STEP_KOREAN_TERMS: Record<string, string> = {\n step_forward: \"전진보법 (Forward Step)\",\n step_back: \"후퇴보법 (Retreat Step)\",\n step_left: \"좌측면보법 (Left Step)\",\n step_right: \"우측면보법 (Right Step)\",\n};\n\n// Footwork pattern Korean terminology - maps animation states to display names\n// Reuses FOOTWORK_KOREAN_TERMS from types.ts but adds specific animation state labels\nconst FOOTWORK_DISPLAY_TERMS: Record<string, string> = {\n footwork_circular_left: `${FOOTWORK_KOREAN_TERMS.circular.korean} 좌 (Circular Left)`,\n footwork_circular_right: `${FOOTWORK_KOREAN_TERMS.circular.korean} 우 (Circular Right)`,\n footwork_pivot_left: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 좌 (Pivot Left)`,\n footwork_pivot_right: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 우 (Pivot Right)`,\n footwork_slide_forward: `${FOOTWORK_KOREAN_TERMS.slide.korean} 전 (Slide Forward)`,\n footwork_slide_back: `${FOOTWORK_KOREAN_TERMS.slide.korean} 후 (Slide Back)`,\n footwork_slide_left: `${FOOTWORK_KOREAN_TERMS.slide.korean} 좌 (Slide Left)`,\n footwork_slide_right: `${FOOTWORK_KOREAN_TERMS.slide.korean} 우 (Slide Right)`,\n footwork_shuffle: `${FOOTWORK_KOREAN_TERMS.shuffle.korean} (Shuffle)`,\n};\n\n// Step direction mapping (all move_* actions covered explicitly)\nconst STEP_DIRECTION_MAP: Record<string, string> = {\n move_up: \"step_forward\",\n move_down: \"step_back\",\n move_left: \"step_left\",\n move_right: \"step_right\",\n};\n\n/**\n * Queued input for input buffer display\n */\nexport interface QueuedInput {\n readonly action: string;\n readonly key: string;\n readonly timestamp: number;\n}\n\n/**\n * Props for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsProps {\n /** Callback when stance changes (receives stance index 0-7) */\n readonly onStanceChange: (stance: number) => void;\n /** Callback for combat actions (attack, block, etc.) */\n readonly onAction: (action: string) => void;\n /** Whether keyboard input is enabled */\n readonly enabled?: boolean;\n /** Current stance index (0-7) for validation */\n readonly currentStance?: number;\n /** Callback when hints toggle is requested */\n readonly onToggleHints?: () => void;\n /** Play sound effect function */\n readonly playSFX?: (soundId: string) => void;\n /** Current animation state for grounded detection */\n readonly currentAnimationState?: string;\n}\n\n/**\n * Return type for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsReturn {\n /** Queued inputs for display */\n readonly queuedInputs: readonly QueuedInput[];\n /** Whether hints are visible */\n readonly showHints: boolean;\n /** Toggle hints visibility */\n readonly toggleHints: () => void;\n}\n\n/**\n * Maximum number of inputs to keep in queue\n */\nconst MAX_QUEUE_SIZE = 3;\n\n/**\n * Time to keep inputs in queue (milliseconds)\n */\nconst QUEUE_RETENTION_TIME = 2000;\n\n/**\n * Custom hook for handling keyboard controls with visual feedback\n *\n * Features:\n * - Trigram stance switching (1-8 keys or custom bindings)\n * - Combat action handling (attack, block, movement)\n * - Input queue visualization\n * - Keyboard hints toggle (F1)\n * - Invalid input prevention\n * - Audio feedback integration\n *\n * @example\n * ```typescript\n * const { queuedInputs, showHints, toggleHints } = useKeyboardControls({\n * onStanceChange: (stance) => handleStanceChange(stance),\n * onAction: (action) => handleAction(action),\n * enabled: !isPaused,\n * currentStance: player.stance,\n * playSFX: audio.playSFX,\n * });\n * ```\n *\n * @public\n * @korean 키보드컨트롤사용\n */\nexport function useKeyboardControls({\n onStanceChange,\n onAction,\n enabled = true,\n currentStance = 0,\n onToggleHints,\n playSFX,\n currentAnimationState,\n}: UseKeyboardControlsProps): UseKeyboardControlsReturn {\n const [queuedInputs, setQueuedInputs] = useState<readonly QueuedInput[]>([]);\n const [showHints, setShowHints] = useState(false);\n const controlMapperRef = useRef<ControlMapper>(new ControlMapper());\n const lastStanceChangeRef = useRef<number>(0);\n\n // Track currently pressed keys for diagonal step detection\n const pressedKeysRef = useRef<Set<string>>(new Set());\n\n /**\n * Toggle hints visibility\n */\n const toggleHints = useCallback(() => {\n setShowHints((prev) => {\n const newState = !prev;\n if (onToggleHints) {\n onToggleHints();\n }\n // Play UI sound\n if (playSFX) {\n playSFX(\"menu_select\");\n }\n return newState;\n });\n }, [onToggleHints, playSFX]);\n\n /**\n * Detect diagonal step direction from pressed keys\n * Returns step action for diagonal or null if not diagonal\n *\n * @korean 대각선발걸음감지\n */\n const getDiagonalStepAction = useCallback((): string | null => {\n const keys = pressedKeysRef.current;\n const mapper = controlMapperRef.current;\n\n // Check for forward diagonals\n if (keys.has(mapper.getBindings().movement.up.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_forward_left\"; // 전좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_forward_right\"; // 전우측보법\n }\n }\n\n // Check for backward diagonals\n if (keys.has(mapper.getBindings().movement.down.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_back_left\"; // 후좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_back_right\"; // 후우측보법\n }\n }\n\n return null;\n }, []);\n\n /**\n * Add input to queue\n */\n const addToQueue = useCallback((action: string, key: string) => {\n const newInput: QueuedInput = {\n action,\n key,\n timestamp: Date.now(),\n };\n\n setQueuedInputs((prev) => {\n const updated = [newInput, ...prev].slice(0, MAX_QUEUE_SIZE);\n return updated;\n });\n }, []);\n\n /**\n * Clean up old queued inputs\n * Single interval for component lifetime to avoid recreating on every input\n */\n useEffect(() => {\n const interval = setInterval(() => {\n const now = Date.now();\n setQueuedInputs((prev) => {\n if (prev.length === 0) return prev;\n return prev.filter(\n (input) => now - input.timestamp < QUEUE_RETENTION_TIME\n );\n });\n }, 500);\n\n return () => clearInterval(interval);\n }, []); // Empty deps - single interval for component lifetime\n\n /**\n * Handle keyboard input\n */\n useEffect(() => {\n if (!enabled) return;\n\n // Copy ref value for cleanup\n const pressedKeys = pressedKeysRef.current;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const mapper = controlMapperRef.current;\n\n // Track pressed keys for diagonal detection\n pressedKeysRef.current.add(e.key.toLowerCase());\n\n // F1: Toggle hints (prevent default browser help)\n if (e.key === \"F1\") {\n e.preventDefault();\n toggleHints();\n return;\n }\n\n // Escape: Close hints if open\n if (e.key === \"Escape\" && showHints) {\n e.preventDefault();\n setShowHints(false);\n return;\n }\n\n // Recovery input detection (기상 입력 감지)\n // When player is in ground state, any button triggers recovery options\n const isGrounded = currentAnimationState?.startsWith(\"ground_\");\n\n if (isGrounded) {\n // Space: Quick/default recovery\n if (e.key === \" \") {\n e.preventDefault();\n onAction(\"recovery_quick\");\n addToQueue(\"기상 (Quick Recovery)\", \"Space\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // R or Enter: Roll recovery (회전기상) - fast but costs stamina\n if (e.key.toLowerCase() === \"r\" || e.key === \"Enter\") {\n e.preventDefault();\n onAction(\"recovery_roll\");\n addToQueue(\"회전기상 (Roll Recovery)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // Shift: Defensive getup (방어기상) - slow but protected\n if (e.shiftKey) {\n e.preventDefault();\n onAction(\"recovery_defensive\");\n addToQueue(\"방어기상 (Defensive Getup)\", \"Shift\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // If grounded, don't process other inputs (can't attack/move while down)\n return;\n }\n\n // Check for stance change (1-8 or custom bindings)\n const stanceIndex = mapper.getStanceForKey(e.key);\n if (stanceIndex !== null) {\n e.preventDefault();\n\n // Prevent switching to the same stance\n if (stanceIndex === currentStance) {\n // Invalid input feedback (shake animation trigger)\n addToQueue(`Stance ${stanceIndex + 1} (already active)`, e.key);\n\n // Play error sound\n if (playSFX) {\n playSFX(\"menu_error\");\n }\n return;\n }\n\n // Throttle stance changes (minimum 8 frames at 60fps = 133ms)\n const now = Date.now();\n if (now - lastStanceChangeRef.current < 133) {\n return;\n }\n lastStanceChangeRef.current = now;\n\n // Execute stance change\n onStanceChange(stanceIndex);\n addToQueue(`Stance ${stanceIndex + 1}`, e.key);\n\n // Play stance change sound\n if (playSFX) {\n playSFX(\"stance_change\");\n }\n return;\n }\n\n // Handle other actions\n const action = mapper.getActionForKey(e.key);\n if (action) {\n e.preventDefault();\n\n switch (action) {\n case \"attack\":\n onAction(\"attack\");\n addToQueue(\"Attack\", e.key);\n if (playSFX) playSFX(\"attack_light\");\n break;\n\n case \"block\":\n onAction(\"block\");\n addToQueue(\"Block\", e.key);\n if (playSFX) playSFX(\"block\");\n break;\n\n case \"move_up\":\n case \"move_down\":\n case \"move_left\":\n case \"move_right\":\n // Check for Alt+A/D modifier for slide left/right (MUST be checked first)\n if (e.altKey) {\n // Alt modifier for slide left/right footwork patterns\n let slideFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Alt+A: Slide left (미끄럼보 좌)\n slideFootwork = \"footwork_slide_left\";\n } else if (action === \"move_right\") {\n // Alt+D: Slide right (미끄럼보 우)\n slideFootwork = \"footwork_slide_right\";\n }\n\n if (slideFootwork) {\n onAction(slideFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[slideFootwork] ?? \"Slide Footwork\",\n `Alt+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey && e.ctrlKey) {\n // Shift+Ctrl modifier for advanced footwork patterns\n let advancedFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Shift+Ctrl+A: Pivot left (축족회전 좌)\n advancedFootwork = \"footwork_pivot_left\";\n } else if (action === \"move_right\") {\n // Shift+Ctrl+D: Pivot right (축족회전 우)\n advancedFootwork = \"footwork_pivot_right\";\n } else if (action === \"move_up\" || action === \"move_down\") {\n // Shift+Ctrl+W or Shift+Ctrl+S: Shuffle step (섞음보)\n advancedFootwork = \"footwork_shuffle\";\n }\n\n if (advancedFootwork) {\n onAction(advancedFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[advancedFootwork] ??\n \"Advanced Footwork\",\n `Shift+Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.ctrlKey) {\n // Footwork patterns with Ctrl+WASD\n let footworkAction: string | null = null;\n\n if (action === \"move_left\") {\n // Ctrl+A: Circular step left (원형보)\n footworkAction = \"footwork_circular_left\";\n } else if (action === \"move_right\") {\n // Ctrl+D: Circular step right (원형보)\n footworkAction = \"footwork_circular_right\";\n } else if (action === \"move_up\") {\n // Ctrl+W: Slide forward (미끄럼보)\n footworkAction = \"footwork_slide_forward\";\n } else if (action === \"move_down\") {\n // Ctrl+S: Slide back (미끄럼보)\n footworkAction = \"footwork_slide_back\";\n }\n\n if (footworkAction) {\n onAction(footworkAction);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[footworkAction] ?? \"Footwork\",\n `Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey) {\n // Check if Shift is held for tactical step instead of walk\n // Check for diagonal step first\n const diagonalStep = getDiagonalStepAction();\n\n if (diagonalStep) {\n // Diagonal tactical step\n onAction(diagonalStep);\n\n addToQueue(\n DIAGONAL_KOREAN_TERMS[diagonalStep] ?? \"Diagonal Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n } else {\n // Cardinal direction tactical step\n // Map move_up/move_down to step_forward/step_back to match AnimationState\n const stepDirection = STEP_DIRECTION_MAP[action];\n onAction(stepDirection);\n\n addToQueue(\n STEP_KOREAN_TERMS[stepDirection] ?? \"Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n }\n } else {\n // Regular movement\n onAction(action);\n addToQueue(action, e.key);\n }\n break;\n\n case \"stance_side_switch\":\n // H key: Switch front foot (stance side switch)\n onAction(\"stance_side_switch\");\n addToQueue(\"발 바꿈 (Switch Stance Side)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n break;\n\n case \"vital_points_overlay\":\n // Vital points overlay toggle\n onAction(\"vital_points_overlay\");\n addToQueue(\"Vital Points\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n\n case \"pause\":\n // Pause menu toggle\n onAction(\"pause\");\n addToQueue(\"Pause\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n }\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n // Remove key from pressed keys set\n pressedKeysRef.current.delete(e.key.toLowerCase());\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n window.addEventListener(\"keyup\", handleKeyUp);\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n window.removeEventListener(\"keyup\", handleKeyUp);\n // Clear pressed keys on cleanup using copied ref value\n pressedKeys.clear();\n };\n }, [\n enabled,\n currentStance,\n onStanceChange,\n onAction,\n addToQueue,\n toggleHints,\n getDiagonalStepAction,\n showHints,\n playSFX,\n currentAnimationState,\n ]);\n\n return {\n queuedInputs,\n showHints,\n toggleHints,\n };\n}\n"],"mappings":";;;;;;;;;;;;AAcA,IAAM,wBAAgD;CACpD,mBAAmB;CACnB,oBAAoB;CACpB,gBAAgB;CAChB,iBAAiB;CAClB;AAED,IAAM,oBAA4C;CAChD,cAAc;CACd,WAAW;CACX,WAAW;CACX,YAAY;CACb;AAID,IAAM,yBAAiD;CACrD,wBAAwB,GAAG,sBAAsB,SAAS,OAAO;CACjE,yBAAyB,GAAG,sBAAsB,SAAS,OAAO;CAClE,qBAAqB,GAAG,sBAAsB,MAAM,OAAO;CAC3D,sBAAsB,GAAG,sBAAsB,MAAM,OAAO;CAC5D,wBAAwB,GAAG,sBAAsB,MAAM,OAAO;CAC9D,qBAAqB,GAAG,sBAAsB,MAAM,OAAO;CAC3D,qBAAqB,GAAG,sBAAsB,MAAM,OAAO;CAC3D,sBAAsB,GAAG,sBAAsB,MAAM,OAAO;CAC5D,kBAAkB,GAAG,sBAAsB,QAAQ,OAAO;CAC3D;AAGD,IAAM,qBAA6C;CACjD,SAAS;CACT,WAAW;CACX,WAAW;CACX,YAAY;CACb;;;;AA8CD,IAAM,iBAAiB;;;;AAKvB,IAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2B7B,SAAgB,oBAAoB,EAClC,gBACA,UACA,UAAU,MACV,gBAAgB,GAChB,eACA,SACA,yBACsD;CACtD,MAAM,CAAC,cAAc,mBAAmB,SAAiC,EAAE,CAAC;CAC5E,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,mBAAmB,OAAsB,IAAI,eAAe,CAAC;CACnE,MAAM,sBAAsB,OAAe,EAAE;CAG7C,MAAM,iBAAiB,uBAAoB,IAAI,KAAK,CAAC;;;;CAKrD,MAAM,cAAc,kBAAkB;AACpC,gBAAc,SAAS;GACrB,MAAM,WAAW,CAAC;AAClB,OAAI,cACF,gBAAe;AAGjB,OAAI,QACF,SAAQ,cAAc;AAExB,UAAO;IACP;IACD,CAAC,eAAe,QAAQ,CAAC;;;;;;;CAQ5B,MAAM,wBAAwB,kBAAiC;EAC7D,MAAM,OAAO,eAAe;EAC5B,MAAM,SAAS,iBAAiB;AAGhC,MAAI,KAAK,IAAI,OAAO,aAAa,CAAC,SAAS,GAAG,aAAa,CAAC,EAAE;AAC5D,OAAI,KAAK,IAAI,OAAO,aAAa,CAAC,SAAS,KAAK,aAAa,CAAC,CAC5D,QAAO;AAET,OAAI,KAAK,IAAI,OAAO,aAAa,CAAC,SAAS,MAAM,aAAa,CAAC,CAC7D,QAAO;;AAKX,MAAI,KAAK,IAAI,OAAO,aAAa,CAAC,SAAS,KAAK,aAAa,CAAC,EAAE;AAC9D,OAAI,KAAK,IAAI,OAAO,aAAa,CAAC,SAAS,KAAK,aAAa,CAAC,CAC5D,QAAO;AAET,OAAI,KAAK,IAAI,OAAO,aAAa,CAAC,SAAS,MAAM,aAAa,CAAC,CAC7D,QAAO;;AAIX,SAAO;IACN,EAAE,CAAC;;;;CAKN,MAAM,aAAa,aAAa,QAAgB,QAAgB;EAC9D,MAAM,WAAwB;GAC5B;GACA;GACA,WAAW,KAAK,KAAK;GACtB;AAED,mBAAiB,SAAS;AAExB,UADgB,CAAC,UAAU,GAAG,KAAK,CAAC,MAAM,GAAG,eACtC;IACP;IACD,EAAE,CAAC;;;;;AAMN,iBAAgB;EACd,MAAM,WAAW,kBAAkB;GACjC,MAAM,MAAM,KAAK,KAAK;AACtB,oBAAiB,SAAS;AACxB,QAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,WAAO,KAAK,QACT,UAAU,MAAM,MAAM,YAAY,qBACpC;KACD;KACD,IAAI;AAEP,eAAa,cAAc,SAAS;IACnC,EAAE,CAAC;;;;AAKN,iBAAgB;AACd,MAAI,CAAC,QAAS;EAGd,MAAM,cAAc,eAAe;EAEnC,MAAM,iBAAiB,MAAqB;GAC1C,MAAM,SAAS,iBAAiB;AAGhC,kBAAe,QAAQ,IAAI,EAAE,IAAI,aAAa,CAAC;AAG/C,OAAI,EAAE,QAAQ,MAAM;AAClB,MAAE,gBAAgB;AAClB,iBAAa;AACb;;AAIF,OAAI,EAAE,QAAQ,YAAY,WAAW;AACnC,MAAE,gBAAgB;AAClB,iBAAa,MAAM;AACnB;;AAOF,OAFmB,uBAAuB,WAAW,UAAU,EAE/C;AAEd,QAAI,EAAE,QAAQ,KAAK;AACjB,OAAE,gBAAgB;AAClB,cAAS,iBAAiB;AAC1B,gBAAW,uBAAuB,QAAQ;AAC1C,SAAI,QAAS,SAAQ,gBAAgB;AACrC;;AAIF,QAAI,EAAE,IAAI,aAAa,KAAK,OAAO,EAAE,QAAQ,SAAS;AACpD,OAAE,gBAAgB;AAClB,cAAS,gBAAgB;AACzB,gBAAW,wBAAwB,EAAE,IAAI;AACzC,SAAI,QAAS,SAAQ,gBAAgB;AACrC;;AAIF,QAAI,EAAE,UAAU;AACd,OAAE,gBAAgB;AAClB,cAAS,qBAAqB;AAC9B,gBAAW,0BAA0B,QAAQ;AAC7C,SAAI,QAAS,SAAQ,gBAAgB;AACrC;;AAIF;;GAIF,MAAM,cAAc,OAAO,gBAAgB,EAAE,IAAI;AACjD,OAAI,gBAAgB,MAAM;AACxB,MAAE,gBAAgB;AAGlB,QAAI,gBAAgB,eAAe;AAEjC,gBAAW,UAAU,cAAc,EAAE,oBAAoB,EAAE,IAAI;AAG/D,SAAI,QACF,SAAQ,aAAa;AAEvB;;IAIF,MAAM,MAAM,KAAK,KAAK;AACtB,QAAI,MAAM,oBAAoB,UAAU,IACtC;AAEF,wBAAoB,UAAU;AAG9B,mBAAe,YAAY;AAC3B,eAAW,UAAU,cAAc,KAAK,EAAE,IAAI;AAG9C,QAAI,QACF,SAAQ,gBAAgB;AAE1B;;GAIF,MAAM,SAAS,OAAO,gBAAgB,EAAE,IAAI;AAC5C,OAAI,QAAQ;AACV,MAAE,gBAAgB;AAElB,YAAQ,QAAR;KACE,KAAK;AACH,eAAS,SAAS;AAClB,iBAAW,UAAU,EAAE,IAAI;AAC3B,UAAI,QAAS,SAAQ,eAAe;AACpC;KAEF,KAAK;AACH,eAAS,QAAQ;AACjB,iBAAW,SAAS,EAAE,IAAI;AAC1B,UAAI,QAAS,SAAQ,QAAQ;AAC7B;KAEF,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK;AAEH,UAAI,EAAE,QAAQ;OAEZ,IAAI,gBAA+B;AAEnC,WAAI,WAAW,YAEb,iBAAgB;gBACP,WAAW,aAEpB,iBAAgB;AAGlB,WAAI,eAAe;AACjB,iBAAS,cAAc;AACvB,mBACE,uBAAuB,kBAAkB,kBACzC,OAAO,EAAE,MACV;AACD,YAAI,QAAS,SAAQ,WAAW;;iBAEzB,EAAE,YAAY,EAAE,SAAS;OAElC,IAAI,mBAAkC;AAEtC,WAAI,WAAW,YAEb,oBAAmB;gBACV,WAAW,aAEpB,oBAAmB;gBACV,WAAW,aAAa,WAAW,YAE5C,oBAAmB;AAGrB,WAAI,kBAAkB;AACpB,iBAAS,iBAAiB;AAC1B,mBACE,uBAAuB,qBACrB,qBACF,cAAc,EAAE,MACjB;AACD,YAAI,QAAS,SAAQ,WAAW;;iBAEzB,EAAE,SAAS;OAEpB,IAAI,iBAAgC;AAEpC,WAAI,WAAW,YAEb,kBAAiB;gBACR,WAAW,aAEpB,kBAAiB;gBACR,WAAW,UAEpB,kBAAiB;gBACR,WAAW,YAEpB,kBAAiB;AAGnB,WAAI,gBAAgB;AAClB,iBAAS,eAAe;AACxB,mBACE,uBAAuB,mBAAmB,YAC1C,QAAQ,EAAE,MACX;AACD,YAAI,QAAS,SAAQ,WAAW;;iBAEzB,EAAE,UAAU;OAGrB,MAAM,eAAe,uBAAuB;AAE5C,WAAI,cAAc;AAEhB,iBAAS,aAAa;AAEtB,mBACE,sBAAsB,iBAAiB,iBACvC,SAAS,EAAE,MACZ;AAED,YAAI,QAAS,SAAQ,WAAW;cAC3B;QAGL,MAAM,gBAAgB,mBAAmB;AACzC,iBAAS,cAAc;AAEvB,mBACE,kBAAkB,kBAAkB,QACpC,SAAS,EAAE,MACZ;AAED,YAAI,QAAS,SAAQ,WAAW;;aAE7B;AAEL,gBAAS,OAAO;AAChB,kBAAW,QAAQ,EAAE,IAAI;;AAE3B;KAEF,KAAK;AAEH,eAAS,qBAAqB;AAC9B,iBAAW,6BAA6B,EAAE,IAAI;AAC9C,UAAI,QAAS,SAAQ,gBAAgB;AACrC;KAEF,KAAK;AAEH,eAAS,uBAAuB;AAChC,iBAAW,gBAAgB,EAAE,IAAI;AACjC,UAAI,QAAS,SAAQ,cAAc;AACnC;KAEF,KAAK;AAEH,eAAS,QAAQ;AACjB,iBAAW,SAAS,EAAE,IAAI;AAC1B,UAAI,QAAS,SAAQ,cAAc;AACnC;;;;EAKR,MAAM,eAAe,MAAqB;AAExC,kBAAe,QAAQ,OAAO,EAAE,IAAI,aAAa,CAAC;;AAGpD,SAAO,iBAAiB,WAAW,cAAc;AACjD,SAAO,iBAAiB,SAAS,YAAY;AAE7C,eAAa;AACX,UAAO,oBAAoB,WAAW,cAAc;AACpD,UAAO,oBAAoB,SAAS,YAAY;AAEhD,eAAY,OAAO;;IAEpB;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,QAAO;EACL;EACA;EACA;EACD"}
1
+ {"version":3,"file":"useKeyboardControls.js","names":[],"sources":["../../src/hooks/useKeyboardControls.ts"],"sourcesContent":["/**\n * Custom hook for keyboard controls with visual feedback\n * Manages keyboard input for trigram stance switching and combat actions\n *\n * @module hooks/useKeyboardControls\n * @category Input System\n * @korean 키보드 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FOOTWORK_KOREAN_TERMS } from \"../systems/animation/core/types\";\nimport { ControlMapper } from \"../utils/controlMapping\";\n\n// Korean terminology constants (module-level to avoid recreation on every keystroke)\nconst DIAGONAL_KOREAN_TERMS: Record<string, string> = {\n step_forward_left: \"전좌측보법 (Forward-Left)\",\n step_forward_right: \"전우측보법 (Forward-Right)\",\n step_back_left: \"후좌측보법 (Back-Left)\",\n step_back_right: \"후우측보법 (Back-Right)\",\n};\n\nconst STEP_KOREAN_TERMS: Record<string, string> = {\n step_forward: \"전진보법 (Forward Step)\",\n step_back: \"후퇴보법 (Retreat Step)\",\n step_left: \"좌측면보법 (Left Step)\",\n step_right: \"우측면보법 (Right Step)\",\n};\n\n// Footwork pattern Korean terminology - maps animation states to display names\n// Reuses FOOTWORK_KOREAN_TERMS from types.ts but adds specific animation state labels\nconst FOOTWORK_DISPLAY_TERMS: Record<string, string> = {\n footwork_circular_left: `${FOOTWORK_KOREAN_TERMS.circular.korean} 좌 (Circular Left)`,\n footwork_circular_right: `${FOOTWORK_KOREAN_TERMS.circular.korean} 우 (Circular Right)`,\n footwork_pivot_left: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 좌 (Pivot Left)`,\n footwork_pivot_right: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 우 (Pivot Right)`,\n footwork_slide_forward: `${FOOTWORK_KOREAN_TERMS.slide.korean} 전 (Slide Forward)`,\n footwork_slide_back: `${FOOTWORK_KOREAN_TERMS.slide.korean} 후 (Slide Back)`,\n footwork_slide_left: `${FOOTWORK_KOREAN_TERMS.slide.korean} 좌 (Slide Left)`,\n footwork_slide_right: `${FOOTWORK_KOREAN_TERMS.slide.korean} 우 (Slide Right)`,\n footwork_shuffle: `${FOOTWORK_KOREAN_TERMS.shuffle.korean} (Shuffle)`,\n};\n\n// Step direction mapping (all move_* actions covered explicitly)\nconst STEP_DIRECTION_MAP: Record<string, string> = {\n move_up: \"step_forward\",\n move_down: \"step_back\",\n move_left: \"step_left\",\n move_right: \"step_right\",\n};\n\n/**\n * Queued input for input buffer display\n */\nexport interface QueuedInput {\n readonly action: string;\n readonly key: string;\n readonly timestamp: number;\n}\n\n/**\n * Props for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsProps {\n /** Callback when stance changes (receives stance index 0-7) */\n readonly onStanceChange: (stance: number) => void;\n /** Callback for combat actions (attack, block, etc.) */\n readonly onAction: (action: string) => void;\n /** Whether keyboard input is enabled */\n readonly enabled?: boolean;\n /** Current stance index (0-7) for validation */\n readonly currentStance?: number;\n /** Callback when hints toggle is requested */\n readonly onToggleHints?: () => void;\n /** Play sound effect function */\n readonly playSFX?: (soundId: string) => void;\n /** Current animation state for grounded detection */\n readonly currentAnimationState?: string;\n}\n\n/**\n * Return type for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsReturn {\n /** Queued inputs for display */\n readonly queuedInputs: readonly QueuedInput[];\n /** Whether hints are visible */\n readonly showHints: boolean;\n /** Toggle hints visibility */\n readonly toggleHints: () => void;\n}\n\n/**\n * Maximum number of inputs to keep in queue\n */\nconst MAX_QUEUE_SIZE = 3;\n\n/**\n * Time to keep inputs in queue (milliseconds)\n */\nconst QUEUE_RETENTION_TIME = 2000;\n\n/**\n * Custom hook for handling keyboard controls with visual feedback\n *\n * Features:\n * - Trigram stance switching (1-8 keys or custom bindings)\n * - Combat action handling (attack, block, movement)\n * - Input queue visualization\n * - Keyboard hints toggle (F1)\n * - Invalid input prevention\n * - Audio feedback integration\n *\n * @example\n * ```typescript\n * const { queuedInputs, showHints, toggleHints } = useKeyboardControls({\n * onStanceChange: (stance) => handleStanceChange(stance),\n * onAction: (action) => handleAction(action),\n * enabled: !isPaused,\n * currentStance: player.stance,\n * playSFX: audio.playSFX,\n * });\n * ```\n *\n * @public\n * @korean 키보드컨트롤사용\n */\nexport function useKeyboardControls({\n onStanceChange,\n onAction,\n enabled = true,\n currentStance = 0,\n onToggleHints,\n playSFX,\n currentAnimationState,\n}: UseKeyboardControlsProps): UseKeyboardControlsReturn {\n const [queuedInputs, setQueuedInputs] = useState<readonly QueuedInput[]>([]);\n const [showHints, setShowHints] = useState(false);\n const controlMapperRef = useRef<ControlMapper>(new ControlMapper());\n const lastStanceChangeRef = useRef<number>(0);\n\n // Track currently pressed keys for diagonal step detection\n const pressedKeysRef = useRef<Set<string>>(new Set());\n\n /**\n * Toggle hints visibility\n */\n const toggleHints = useCallback(() => {\n setShowHints((prev) => {\n const newState = !prev;\n if (onToggleHints) {\n onToggleHints();\n }\n // Play UI sound\n if (playSFX) {\n playSFX(\"menu_select\");\n }\n return newState;\n });\n }, [onToggleHints, playSFX]);\n\n /**\n * Detect diagonal step direction from pressed keys\n * Returns step action for diagonal or null if not diagonal\n *\n * @korean 대각선발걸음감지\n */\n const getDiagonalStepAction = useCallback((): string | null => {\n const keys = pressedKeysRef.current;\n const mapper = controlMapperRef.current;\n\n // Check for forward diagonals\n if (keys.has(mapper.getBindings().movement.up.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_forward_left\"; // 전좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_forward_right\"; // 전우측보법\n }\n }\n\n // Check for backward diagonals\n if (keys.has(mapper.getBindings().movement.down.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_back_left\"; // 후좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_back_right\"; // 후우측보법\n }\n }\n\n return null;\n }, []);\n\n /**\n * Add input to queue\n */\n const addToQueue = useCallback((action: string, key: string) => {\n const newInput: QueuedInput = {\n action,\n key,\n timestamp: Date.now(),\n };\n\n setQueuedInputs((prev) => {\n const updated = [newInput, ...prev].slice(0, MAX_QUEUE_SIZE);\n return updated;\n });\n }, []);\n\n /**\n * Clean up old queued inputs\n * Single interval for component lifetime to avoid recreating on every input\n */\n useEffect(() => {\n const interval = setInterval(() => {\n const now = Date.now();\n setQueuedInputs((prev) => {\n if (prev.length === 0) return prev;\n return prev.filter(\n (input) => now - input.timestamp < QUEUE_RETENTION_TIME\n );\n });\n }, 500);\n\n return () => clearInterval(interval);\n }, []); // Empty deps - single interval for component lifetime\n\n /**\n * Handle keyboard input\n */\n useEffect(() => {\n if (!enabled) return;\n\n // Copy ref value for cleanup\n const pressedKeys = pressedKeysRef.current;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const mapper = controlMapperRef.current;\n\n // Track pressed keys for diagonal detection\n pressedKeysRef.current.add(e.key.toLowerCase());\n\n // F1: Toggle hints (prevent default browser help)\n if (e.key === \"F1\") {\n e.preventDefault();\n toggleHints();\n return;\n }\n\n // Escape: Close hints if open\n if (e.key === \"Escape\" && showHints) {\n e.preventDefault();\n setShowHints(false);\n return;\n }\n\n // Recovery input detection (기상 입력 감지)\n // When player is in ground state, any button triggers recovery options\n const isGrounded = currentAnimationState?.startsWith(\"ground_\");\n\n if (isGrounded) {\n // Space: Quick/default recovery\n if (e.key === \" \") {\n e.preventDefault();\n onAction(\"recovery_quick\");\n addToQueue(\"기상 (Quick Recovery)\", \"Space\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // R or Enter: Roll recovery (회전기상) - fast but costs stamina\n if (e.key.toLowerCase() === \"r\" || e.key === \"Enter\") {\n e.preventDefault();\n onAction(\"recovery_roll\");\n addToQueue(\"회전기상 (Roll Recovery)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // Shift: Defensive getup (방어기상) - slow but protected\n if (e.shiftKey) {\n e.preventDefault();\n onAction(\"recovery_defensive\");\n addToQueue(\"방어기상 (Defensive Getup)\", \"Shift\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // If grounded, don't process other inputs (can't attack/move while down)\n return;\n }\n\n // Check for stance change (1-8 or custom bindings)\n const stanceIndex = mapper.getStanceForKey(e.key);\n if (stanceIndex !== null) {\n e.preventDefault();\n\n // Prevent switching to the same stance\n if (stanceIndex === currentStance) {\n // Invalid input feedback (shake animation trigger)\n addToQueue(`Stance ${stanceIndex + 1} (already active)`, e.key);\n\n // Play error sound\n if (playSFX) {\n playSFX(\"menu_error\");\n }\n return;\n }\n\n // Throttle stance changes (minimum 8 frames at 60fps = 133ms)\n const now = Date.now();\n if (now - lastStanceChangeRef.current < 133) {\n return;\n }\n lastStanceChangeRef.current = now;\n\n // Execute stance change\n onStanceChange(stanceIndex);\n addToQueue(`Stance ${stanceIndex + 1}`, e.key);\n\n // Play stance change sound\n if (playSFX) {\n playSFX(\"stance_change\");\n }\n return;\n }\n\n // Handle other actions\n const action = mapper.getActionForKey(e.key);\n if (action) {\n e.preventDefault();\n\n switch (action) {\n case \"attack\":\n onAction(\"attack\");\n addToQueue(\"Attack\", e.key);\n if (playSFX) playSFX(\"attack_light\");\n break;\n\n case \"block\":\n onAction(\"block\");\n addToQueue(\"Block\", e.key);\n if (playSFX) playSFX(\"block\");\n break;\n\n case \"move_up\":\n case \"move_down\":\n case \"move_left\":\n case \"move_right\":\n // Check for Alt+A/D modifier for slide left/right (MUST be checked first)\n if (e.altKey) {\n // Alt modifier for slide left/right footwork patterns\n let slideFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Alt+A: Slide left (미끄럼보 좌)\n slideFootwork = \"footwork_slide_left\";\n } else if (action === \"move_right\") {\n // Alt+D: Slide right (미끄럼보 우)\n slideFootwork = \"footwork_slide_right\";\n }\n\n if (slideFootwork) {\n onAction(slideFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[slideFootwork] ?? \"Slide Footwork\",\n `Alt+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey && e.ctrlKey) {\n // Shift+Ctrl modifier for advanced footwork patterns\n let advancedFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Shift+Ctrl+A: Pivot left (축족회전 좌)\n advancedFootwork = \"footwork_pivot_left\";\n } else if (action === \"move_right\") {\n // Shift+Ctrl+D: Pivot right (축족회전 우)\n advancedFootwork = \"footwork_pivot_right\";\n } else if (action === \"move_up\" || action === \"move_down\") {\n // Shift+Ctrl+W or Shift+Ctrl+S: Shuffle step (섞음보)\n advancedFootwork = \"footwork_shuffle\";\n }\n\n if (advancedFootwork) {\n onAction(advancedFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[advancedFootwork] ??\n \"Advanced Footwork\",\n `Shift+Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.ctrlKey) {\n // Footwork patterns with Ctrl+WASD\n let footworkAction: string | null = null;\n\n if (action === \"move_left\") {\n // Ctrl+A: Circular step left (원형보)\n footworkAction = \"footwork_circular_left\";\n } else if (action === \"move_right\") {\n // Ctrl+D: Circular step right (원형보)\n footworkAction = \"footwork_circular_right\";\n } else if (action === \"move_up\") {\n // Ctrl+W: Slide forward (미끄럼보)\n footworkAction = \"footwork_slide_forward\";\n } else if (action === \"move_down\") {\n // Ctrl+S: Slide back (미끄럼보)\n footworkAction = \"footwork_slide_back\";\n }\n\n if (footworkAction) {\n onAction(footworkAction);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[footworkAction] ?? \"Footwork\",\n `Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey) {\n // Check if Shift is held for tactical step instead of walk\n // Check for diagonal step first\n const diagonalStep = getDiagonalStepAction();\n\n if (diagonalStep) {\n // Diagonal tactical step\n onAction(diagonalStep);\n\n addToQueue(\n DIAGONAL_KOREAN_TERMS[diagonalStep] ?? \"Diagonal Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n } else {\n // Cardinal direction tactical step\n // Map move_up/move_down to step_forward/step_back to match AnimationState\n const stepDirection = STEP_DIRECTION_MAP[action];\n onAction(stepDirection);\n\n addToQueue(\n STEP_KOREAN_TERMS[stepDirection] ?? \"Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n }\n } else {\n // Regular movement\n onAction(action);\n addToQueue(action, e.key);\n }\n break;\n\n case \"stance_side_switch\":\n // H key: Switch front foot (stance side switch)\n onAction(\"stance_side_switch\");\n addToQueue(\"발 바꿈 (Switch Stance Side)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n break;\n\n case \"vital_points_overlay\":\n // Vital points overlay toggle\n onAction(\"vital_points_overlay\");\n addToQueue(\"Vital Points\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n\n case \"pause\":\n // Pause menu toggle\n onAction(\"pause\");\n addToQueue(\"Pause\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n }\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n // Remove key from pressed keys set\n pressedKeysRef.current.delete(e.key.toLowerCase());\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n window.addEventListener(\"keyup\", handleKeyUp);\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n window.removeEventListener(\"keyup\", handleKeyUp);\n // Clear pressed keys on cleanup using copied ref value\n pressedKeys.clear();\n };\n }, [\n enabled,\n currentStance,\n onStanceChange,\n onAction,\n addToQueue,\n toggleHints,\n getDiagonalStepAction,\n showHints,\n playSFX,\n currentAnimationState,\n ]);\n\n return {\n queuedInputs,\n showHints,\n toggleHints,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useMatchCountdown.js","names":[],"sources":["../../src/hooks/useMatchCountdown.ts"],"sourcesContent":["/**\n * useMatchCountdown Hook - Manages match start countdown sequence\n *\n * Korean: 매치 시작 카운트다운 훅 (Match Start Countdown Hook)\n *\n * Handles the state machine for match start countdown:\n * - idle: Waiting to start\n * - ready: Showing \"Ready?\" message\n * - counting: Counting down \"3... 2... 1...\"\n * - fight: Showing \"Fight!\" announcement\n * - complete: Countdown finished, combat can begin\n *\n * @module hooks/useMatchCountdown\n * @category Combat Hooks\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\n/**\n * Match countdown states\n *\n * Korean: 매치 카운트다운 상태\n */\nexport type MatchCountdownState =\n | \"idle\"\n | \"ready\"\n | \"counting\"\n | \"fight\"\n | \"complete\";\n\n/**\n * Match countdown configuration\n */\nexport interface MatchCountdownConfig {\n /** Duration of \"Ready?\" display in seconds */\n readonly readyDuration?: number;\n /** Duration of each countdown number in seconds */\n readonly countdownInterval?: number;\n /** Duration of \"Fight!\" display in seconds */\n readonly fightDuration?: number;\n /** Starting countdown number */\n readonly startNumber?: number;\n}\n\n/**\n * Match countdown hook state\n */\nexport interface UseMatchCountdownResult {\n /** Current countdown state */\n readonly state: MatchCountdownState;\n /** Current countdown number (3, 2, 1, or 0) */\n readonly currentNumber: number;\n /** Start countdown sequence */\n readonly startCountdown: () => void;\n /** Skip countdown and proceed immediately */\n readonly skipCountdown: () => void;\n /** Reset countdown to idle state */\n readonly resetCountdown: () => void;\n /** Whether countdown is in progress */\n readonly isActive: boolean;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<MatchCountdownConfig> = {\n readyDuration: 1,\n countdownInterval: 1,\n fightDuration: 1,\n startNumber: 3,\n};\n\n/**\n * useMatchCountdown Hook\n *\n * Manages the complete match start countdown flow:\n * 1. Idle state waiting for match start\n * 2. Ready state shows \"Ready?\" message (1s)\n * 3. Counting state counts down from 3 to 1 (1s intervals)\n * 4. Fight state shows \"Fight!\" message (1s)\n * 5. Complete state signals combat can begin\n *\n * @param config - Configuration for countdown timings\n * @param onComplete - Callback when countdown completes\n * @returns Match countdown state and control functions\n *\n * @example\n * ```typescript\n * const {\n * state,\n * currentNumber,\n * startCountdown,\n * skipCountdown,\n * } = useMatchCountdown(\n * { startNumber: 3 },\n * () => {\n * // Enable combat inputs\n * enableCombatControls();\n * }\n * );\n *\n * // When match initializes\n * startCountdown();\n * ```\n */\nexport function useMatchCountdown(\n config: MatchCountdownConfig = {},\n onComplete?: () => void\n): UseMatchCountdownResult {\n // Memoize with individual config values to avoid reference equality issues\n const mergedConfig = useMemo(\n () => ({\n readyDuration: config.readyDuration ?? DEFAULT_CONFIG.readyDuration,\n countdownInterval:\n config.countdownInterval ?? DEFAULT_CONFIG.countdownInterval,\n fightDuration: config.fightDuration ?? DEFAULT_CONFIG.fightDuration,\n startNumber: config.startNumber ?? DEFAULT_CONFIG.startNumber,\n }),\n [\n config.readyDuration,\n config.countdownInterval,\n config.fightDuration,\n config.startNumber,\n ]\n );\n\n const [state, setState] = useState<MatchCountdownState>(\"idle\");\n const [currentNumber, setCurrentNumber] = useState(mergedConfig.startNumber);\n\n // Use refs to track active timers\n const readyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const countdownTimer = useRef<ReturnType<typeof setInterval> | null>(null);\n const fightTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Use ref to always call the latest callback\n const onCompleteRef = useRef(onComplete);\n useEffect(() => {\n onCompleteRef.current = onComplete;\n }, [onComplete]);\n\n /**\n * Clear all active timers\n */\n const clearTimers = useCallback(() => {\n if (readyTimer.current) {\n clearTimeout(readyTimer.current);\n readyTimer.current = null;\n }\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n if (fightTimer.current) {\n clearTimeout(fightTimer.current);\n fightTimer.current = null;\n }\n }, []);\n\n /**\n * Start the countdown sequence\n */\n const startCountdown = useCallback(() => {\n clearTimers();\n setState(\"ready\");\n setCurrentNumber(mergedConfig.startNumber);\n\n // Show \"Ready?\" message\n readyTimer.current = setTimeout(() => {\n setState(\"counting\");\n setCurrentNumber(mergedConfig.startNumber);\n\n // Countdown timer\n let count = mergedConfig.startNumber;\n countdownTimer.current = setInterval(() => {\n count -= 1;\n setCurrentNumber(count);\n\n if (count <= 0) {\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n\n // Show \"Fight!\" message\n setState(\"fight\");\n\n fightTimer.current = setTimeout(() => {\n setState(\"complete\");\n onCompleteRef.current?.();\n }, mergedConfig.fightDuration * 1000);\n }\n }, mergedConfig.countdownInterval * 1000);\n }, mergedConfig.readyDuration * 1000);\n }, [clearTimers, mergedConfig]);\n\n /**\n * Skip countdown and proceed immediately to fight state\n */\n const skipCountdown = useCallback(() => {\n clearTimers();\n setState(\"complete\");\n onCompleteRef.current?.();\n }, [clearTimers]);\n\n /**\n * Reset countdown to idle state\n */\n const resetCountdown = useCallback(() => {\n clearTimers();\n setState(\"idle\");\n setCurrentNumber(mergedConfig.startNumber);\n }, [clearTimers, mergedConfig.startNumber]);\n\n /**\n * Cleanup on unmount\n */\n useEffect(() => {\n return () => {\n clearTimers();\n };\n }, [clearTimers]);\n\n return {\n state,\n currentNumber,\n startCountdown,\n skipCountdown,\n resetCountdown,\n isActive: state !== \"idle\" && state !== \"complete\",\n };\n}\n\nexport default useMatchCountdown;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAiEA,IAAM,iBAAiD;CACrD,eAAe;CACf,mBAAmB;CACnB,eAAe;CACf,aAAa;CACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCD,SAAgB,kBACd,SAA+B,EAAE,EACjC,YACyB;CAEzB,MAAM,eAAe,eACZ;EACL,eAAe,OAAO,iBAAiB,eAAe;EACtD,mBACE,OAAO,qBAAqB,eAAe;EAC7C,eAAe,OAAO,iBAAiB,eAAe;EACtD,aAAa,OAAO,eAAe,eAAe;EACnD,GACD;EACE,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACR,CACF;CAED,MAAM,CAAC,OAAO,YAAY,SAA8B,OAAO;CAC/D,MAAM,CAAC,eAAe,oBAAoB,SAAS,aAAa,YAAY;CAG5E,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,iBAAiB,OAA8C,KAAK;CAC1E,MAAM,aAAa,OAA6C,KAAK;CAGrE,MAAM,gBAAgB,OAAO,WAAW;AACxC,iBAAgB;AACd,gBAAc,UAAU;IACvB,CAAC,WAAW,CAAC;;;;CAKhB,MAAM,cAAc,kBAAkB;AACpC,MAAI,WAAW,SAAS;AACtB,gBAAa,WAAW,QAAQ;AAChC,cAAW,UAAU;;AAEvB,MAAI,eAAe,SAAS;AAC1B,iBAAc,eAAe,QAAQ;AACrC,kBAAe,UAAU;;AAE3B,MAAI,WAAW,SAAS;AACtB,gBAAa,WAAW,QAAQ;AAChC,cAAW,UAAU;;IAEtB,EAAE,CAAC;;;;CAKN,MAAM,iBAAiB,kBAAkB;AACvC,eAAa;AACb,WAAS,QAAQ;AACjB,mBAAiB,aAAa,YAAY;AAG1C,aAAW,UAAU,iBAAiB;AACpC,YAAS,WAAW;AACpB,oBAAiB,aAAa,YAAY;GAG1C,IAAI,QAAQ,aAAa;AACzB,kBAAe,UAAU,kBAAkB;AACzC,aAAS;AACT,qBAAiB,MAAM;AAEvB,QAAI,SAAS,GAAG;AACd,SAAI,eAAe,SAAS;AAC1B,oBAAc,eAAe,QAAQ;AACrC,qBAAe,UAAU;;AAI3B,cAAS,QAAQ;AAEjB,gBAAW,UAAU,iBAAiB;AACpC,eAAS,WAAW;AACpB,oBAAc,WAAW;QACxB,aAAa,gBAAgB,IAAK;;MAEtC,aAAa,oBAAoB,IAAK;KACxC,aAAa,gBAAgB,IAAK;IACpC,CAAC,aAAa,aAAa,CAAC;;;;CAK/B,MAAM,gBAAgB,kBAAkB;AACtC,eAAa;AACb,WAAS,WAAW;AACpB,gBAAc,WAAW;IACxB,CAAC,YAAY,CAAC;;;;CAKjB,MAAM,iBAAiB,kBAAkB;AACvC,eAAa;AACb,WAAS,OAAO;AAChB,mBAAiB,aAAa,YAAY;IACzC,CAAC,aAAa,aAAa,YAAY,CAAC;;;;AAK3C,iBAAgB;AACd,eAAa;AACX,gBAAa;;IAEd,CAAC,YAAY,CAAC;AAEjB,QAAO;EACL;EACA;EACA;EACA;EACA;EACA,UAAU,UAAU,UAAU,UAAU;EACzC"}
1
+ {"version":3,"file":"useMatchCountdown.js","names":[],"sources":["../../src/hooks/useMatchCountdown.ts"],"sourcesContent":["/**\n * useMatchCountdown Hook - Manages match start countdown sequence\n *\n * Korean: 매치 시작 카운트다운 훅 (Match Start Countdown Hook)\n *\n * Handles the state machine for match start countdown:\n * - idle: Waiting to start\n * - ready: Showing \"Ready?\" message\n * - counting: Counting down \"3... 2... 1...\"\n * - fight: Showing \"Fight!\" announcement\n * - complete: Countdown finished, combat can begin\n *\n * @module hooks/useMatchCountdown\n * @category Combat Hooks\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\n/**\n * Match countdown states\n *\n * Korean: 매치 카운트다운 상태\n */\nexport type MatchCountdownState =\n | \"idle\"\n | \"ready\"\n | \"counting\"\n | \"fight\"\n | \"complete\";\n\n/**\n * Match countdown configuration\n */\nexport interface MatchCountdownConfig {\n /** Duration of \"Ready?\" display in seconds */\n readonly readyDuration?: number;\n /** Duration of each countdown number in seconds */\n readonly countdownInterval?: number;\n /** Duration of \"Fight!\" display in seconds */\n readonly fightDuration?: number;\n /** Starting countdown number */\n readonly startNumber?: number;\n}\n\n/**\n * Match countdown hook state\n */\nexport interface UseMatchCountdownResult {\n /** Current countdown state */\n readonly state: MatchCountdownState;\n /** Current countdown number (3, 2, 1, or 0) */\n readonly currentNumber: number;\n /** Start countdown sequence */\n readonly startCountdown: () => void;\n /** Skip countdown and proceed immediately */\n readonly skipCountdown: () => void;\n /** Reset countdown to idle state */\n readonly resetCountdown: () => void;\n /** Whether countdown is in progress */\n readonly isActive: boolean;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<MatchCountdownConfig> = {\n readyDuration: 1,\n countdownInterval: 1,\n fightDuration: 1,\n startNumber: 3,\n};\n\n/**\n * useMatchCountdown Hook\n *\n * Manages the complete match start countdown flow:\n * 1. Idle state waiting for match start\n * 2. Ready state shows \"Ready?\" message (1s)\n * 3. Counting state counts down from 3 to 1 (1s intervals)\n * 4. Fight state shows \"Fight!\" message (1s)\n * 5. Complete state signals combat can begin\n *\n * @param config - Configuration for countdown timings\n * @param onComplete - Callback when countdown completes\n * @returns Match countdown state and control functions\n *\n * @example\n * ```typescript\n * const {\n * state,\n * currentNumber,\n * startCountdown,\n * skipCountdown,\n * } = useMatchCountdown(\n * { startNumber: 3 },\n * () => {\n * // Enable combat inputs\n * enableCombatControls();\n * }\n * );\n *\n * // When match initializes\n * startCountdown();\n * ```\n */\nexport function useMatchCountdown(\n config: MatchCountdownConfig = {},\n onComplete?: () => void\n): UseMatchCountdownResult {\n // Memoize with individual config values to avoid reference equality issues\n const mergedConfig = useMemo(\n () => ({\n readyDuration: config.readyDuration ?? DEFAULT_CONFIG.readyDuration,\n countdownInterval:\n config.countdownInterval ?? DEFAULT_CONFIG.countdownInterval,\n fightDuration: config.fightDuration ?? DEFAULT_CONFIG.fightDuration,\n startNumber: config.startNumber ?? DEFAULT_CONFIG.startNumber,\n }),\n [\n config.readyDuration,\n config.countdownInterval,\n config.fightDuration,\n config.startNumber,\n ]\n );\n\n const [state, setState] = useState<MatchCountdownState>(\"idle\");\n const [currentNumber, setCurrentNumber] = useState(mergedConfig.startNumber);\n\n // Use refs to track active timers\n const readyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const countdownTimer = useRef<ReturnType<typeof setInterval> | null>(null);\n const fightTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Use ref to always call the latest callback\n const onCompleteRef = useRef(onComplete);\n useEffect(() => {\n onCompleteRef.current = onComplete;\n }, [onComplete]);\n\n /**\n * Clear all active timers\n */\n const clearTimers = useCallback(() => {\n if (readyTimer.current) {\n clearTimeout(readyTimer.current);\n readyTimer.current = null;\n }\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n if (fightTimer.current) {\n clearTimeout(fightTimer.current);\n fightTimer.current = null;\n }\n }, []);\n\n /**\n * Start the countdown sequence\n */\n const startCountdown = useCallback(() => {\n clearTimers();\n setState(\"ready\");\n setCurrentNumber(mergedConfig.startNumber);\n\n // Show \"Ready?\" message\n readyTimer.current = setTimeout(() => {\n setState(\"counting\");\n setCurrentNumber(mergedConfig.startNumber);\n\n // Countdown timer\n let count = mergedConfig.startNumber;\n countdownTimer.current = setInterval(() => {\n count -= 1;\n setCurrentNumber(count);\n\n if (count <= 0) {\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n\n // Show \"Fight!\" message\n setState(\"fight\");\n\n fightTimer.current = setTimeout(() => {\n setState(\"complete\");\n onCompleteRef.current?.();\n }, mergedConfig.fightDuration * 1000);\n }\n }, mergedConfig.countdownInterval * 1000);\n }, mergedConfig.readyDuration * 1000);\n }, [clearTimers, mergedConfig]);\n\n /**\n * Skip countdown and proceed immediately to fight state\n */\n const skipCountdown = useCallback(() => {\n clearTimers();\n setState(\"complete\");\n onCompleteRef.current?.();\n }, [clearTimers]);\n\n /**\n * Reset countdown to idle state\n */\n const resetCountdown = useCallback(() => {\n clearTimers();\n setState(\"idle\");\n setCurrentNumber(mergedConfig.startNumber);\n }, [clearTimers, mergedConfig.startNumber]);\n\n /**\n * Cleanup on unmount\n */\n useEffect(() => {\n return () => {\n clearTimers();\n };\n }, [clearTimers]);\n\n return {\n state,\n currentNumber,\n startCountdown,\n skipCountdown,\n resetCountdown,\n isActive: state !== \"idle\" && state !== \"complete\",\n };\n}\n\nexport default useMatchCountdown;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAiEA,IAAM,iBAAiD;CACrD,eAAe;CACf,mBAAmB;CACnB,eAAe;CACf,aAAa;CACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCD,SAAgB,kBACd,SAA+B,EAAE,EACjC,YACyB;CAEzB,MAAM,eAAe,eACZ;EACL,eAAe,OAAO,iBAAiB,eAAe;EACtD,mBACE,OAAO,qBAAqB,eAAe;EAC7C,eAAe,OAAO,iBAAiB,eAAe;EACtD,aAAa,OAAO,eAAe,eAAe;EACnD,GACD;EACE,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACR,CACF;CAED,MAAM,CAAC,OAAO,YAAY,SAA8B,OAAO;CAC/D,MAAM,CAAC,eAAe,oBAAoB,SAAS,aAAa,YAAY;CAG5E,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,iBAAiB,OAA8C,KAAK;CAC1E,MAAM,aAAa,OAA6C,KAAK;CAGrE,MAAM,gBAAgB,OAAO,WAAW;CACxC,gBAAgB;EACd,cAAc,UAAU;IACvB,CAAC,WAAW,CAAC;;;;CAKhB,MAAM,cAAc,kBAAkB;EACpC,IAAI,WAAW,SAAS;GACtB,aAAa,WAAW,QAAQ;GAChC,WAAW,UAAU;;EAEvB,IAAI,eAAe,SAAS;GAC1B,cAAc,eAAe,QAAQ;GACrC,eAAe,UAAU;;EAE3B,IAAI,WAAW,SAAS;GACtB,aAAa,WAAW,QAAQ;GAChC,WAAW,UAAU;;IAEtB,EAAE,CAAC;;;;CAKN,MAAM,iBAAiB,kBAAkB;EACvC,aAAa;EACb,SAAS,QAAQ;EACjB,iBAAiB,aAAa,YAAY;EAG1C,WAAW,UAAU,iBAAiB;GACpC,SAAS,WAAW;GACpB,iBAAiB,aAAa,YAAY;GAG1C,IAAI,QAAQ,aAAa;GACzB,eAAe,UAAU,kBAAkB;IACzC,SAAS;IACT,iBAAiB,MAAM;IAEvB,IAAI,SAAS,GAAG;KACd,IAAI,eAAe,SAAS;MAC1B,cAAc,eAAe,QAAQ;MACrC,eAAe,UAAU;;KAI3B,SAAS,QAAQ;KAEjB,WAAW,UAAU,iBAAiB;MACpC,SAAS,WAAW;MACpB,cAAc,WAAW;QACxB,aAAa,gBAAgB,IAAK;;MAEtC,aAAa,oBAAoB,IAAK;KACxC,aAAa,gBAAgB,IAAK;IACpC,CAAC,aAAa,aAAa,CAAC;;;;CAK/B,MAAM,gBAAgB,kBAAkB;EACtC,aAAa;EACb,SAAS,WAAW;EACpB,cAAc,WAAW;IACxB,CAAC,YAAY,CAAC;;;;CAKjB,MAAM,iBAAiB,kBAAkB;EACvC,aAAa;EACb,SAAS,OAAO;EAChB,iBAAiB,aAAa,YAAY;IACzC,CAAC,aAAa,aAAa,YAAY,CAAC;;;;CAK3C,gBAAgB;EACd,aAAa;GACX,aAAa;;IAEd,CAAC,YAAY,CAAC;CAEjB,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,UAAU,UAAU,UAAU,UAAU;EACzC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useMuscleActivation.js","names":[],"sources":["../../src/hooks/useMuscleActivation.ts"],"sourcesContent":["/**\n * useMuscleActivation - Shared hook for muscle activation management\n *\n * Manages muscle activation state based on current actions and stamina.\n * Reduces code duplication in skeletal animation components.\n * Includes stance-based leg muscle tension for isometric holds.\n *\n * @module hooks/useMuscleActivation\n * @category Hooks\n * @korean 근육활성화훅\n */\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n MuscleActivationManager,\n getMuscleTensionForStance,\n} from \"../systems/animation\";\nimport type { TrigramStance } from \"../types/common\";\nimport { DEFAULT_MUSCLE_CONFIG } from \"../types/muscle\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\n\n/**\n * Simple lerp function for muscle tension interpolation\n * Defined at module level to avoid recreation\n * @korean 선형보간함수\n */\nconst lerp = (start: number, end: number, t: number): number =>\n start + (end - start) * t;\n\n/**\n * Options for useMuscleActivation hook\n * @korean 근육활성화훅옵션\n */\nexport interface UseMuscleActivationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current stamina level (0-100) */\n readonly stamina: number;\n /** Current trigram stance (for leg muscle tension) */\n readonly currentStance?: TrigramStance;\n}\n\n/**\n * Return type for useMuscleActivation hook\n * @korean 근육활성화훅반환타입\n */\nexport interface UseMuscleActivationReturn {\n /** Current muscle activation states (bone name -> activation 0-1) */\n readonly muscleStates: Map<string, number>;\n /** Update muscle activations (call in useFrame) */\n readonly updateMuscleActivations: (\n delta: number,\n frameCounter: number\n ) => void;\n}\n\n/**\n * useMuscleActivation hook\n *\n * Manages muscle activation based on current actions (attack, defend, movement).\n * Updates at 60fps with periodic state syncs to reduce re-renders.\n *\n * @param options - Muscle activation options\n * @returns Muscle states and update function\n *\n * @example\n * ```tsx\n * const { muscleStates, updateMuscleActivations } = useMuscleActivation({\n * currentAnimation: \"attack\",\n * attackAnimation: \"jab\",\n * isBlocking: false,\n * stamina: 85,\n * });\n *\n * // In useFrame callback\n * let frameCounter = 0;\n * useFrame((_, delta) => {\n * frameCounter = (frameCounter + 1) % 10;\n * updateMuscleActivations(delta, frameCounter);\n * });\n *\n * // Use muscle states in rendering\n * <BoneRenderer\n * rig={rig}\n * muscleStates={muscleStates}\n * isExhausted={stamina < 20}\n * />\n * ```\n *\n * @korean 근육활성화훅\n */\nexport function useMuscleActivation(\n options: UseMuscleActivationOptions\n): UseMuscleActivationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stamina,\n currentStance,\n } = options;\n\n // Muscle activation manager\n const muscleManager = useRef(new MuscleActivationManager());\n const [muscleStates, setMuscleStates] = useState<Map<string, number>>(\n new Map()\n );\n\n // Cleanup muscle manager on unmount\n useEffect(() => {\n const manager = muscleManager.current;\n return () => {\n try {\n manager.reset();\n } catch (error) {\n console.warn(\"MuscleActivationManager reset failed:\", error);\n }\n };\n }, []);\n\n // Memoize stance tension to avoid recalculation at 60fps\n const stanceTension = useMemo(() => {\n return currentStance ? getMuscleTensionForStance(currentStance) : null;\n }, [currentStance]);\n\n // Update muscle activations (called at 60fps in useFrame)\n const updateMuscleActivations = (\n delta: number,\n frameCounter: number\n ): void => {\n // Update muscle system based on current action\n if (currentAnimation === \"attack\" && attackAnimation) {\n muscleManager.current.update(attackAnimation, stamina, delta);\n } else if (currentAnimation === \"defend\" || isBlocking) {\n muscleManager.current.update(\"block\", stamina, delta);\n } else if (\n currentAnimation === \"walk\" ||\n currentAnimation === \"stance_change\"\n ) {\n // Engage stance/leg/core muscles during movement and stance changes\n muscleManager.current.update(\"stance_change\", stamina, delta);\n } else if (currentAnimation === \"idle\" && stanceTension) {\n // Apply stance-based leg muscle tension for idle/guard positions\n // This provides realistic isometric contraction visualization\n\n // Get all activation states once (not in the loop)\n const allActivations = muscleManager.current.getAllActivations();\n\n // Update manager with stance muscle activations\n stanceTension.forEach((tension, muscleGroup) => {\n // Get existing activation state\n const state = allActivations.get(muscleGroup);\n\n if (state) {\n // Set target tension for smooth interpolation\n state.targetTension = tension;\n\n // Smoothly interpolate to target (using config activation speed)\n state.tension = lerp(\n state.tension,\n state.targetTension,\n DEFAULT_MUSCLE_CONFIG.activationSpeed * delta\n );\n }\n });\n } else {\n muscleManager.current.relaxAllMuscles(delta);\n }\n\n // Sync muscle states to React state deterministically\n // (every 10 frames at 60fps = ~6 times/sec)\n // Balances animation smoothness with performance and reduces GC pressure\n if (frameCounter === 0) {\n // Reuse scratch map from manager to avoid repeated allocations\n const scratchMap = muscleManager.current.getScratchMapForSync();\n setMuscleStates(scratchMap);\n }\n };\n\n return {\n muscleStates,\n updateMuscleActivations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,IAAM,QAAQ,OAAe,KAAa,MACxC,SAAS,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoE1B,SAAgB,oBACd,SAC2B;CAC3B,MAAM,EACJ,kBACA,iBACA,aAAa,OACb,SACA,kBACE;CAGJ,MAAM,gBAAgB,OAAO,IAAI,yBAAyB,CAAC;CAC3D,MAAM,CAAC,cAAc,mBAAmB,yBACtC,IAAI,KAAK,CACV;AAGD,iBAAgB;EACd,MAAM,UAAU,cAAc;AAC9B,eAAa;AACX,OAAI;AACF,YAAQ,OAAO;YACR,OAAO;AACd,YAAQ,KAAK,yCAAyC,MAAM;;;IAG/D,EAAE,CAAC;CAGN,MAAM,gBAAgB,cAAc;AAClC,SAAO,gBAAgB,0BAA0B,cAAc,GAAG;IACjE,CAAC,cAAc,CAAC;CAGnB,MAAM,2BACJ,OACA,iBACS;AAET,MAAI,qBAAqB,YAAY,gBACnC,eAAc,QAAQ,OAAO,iBAAiB,SAAS,MAAM;WACpD,qBAAqB,YAAY,WAC1C,eAAc,QAAQ,OAAO,SAAS,SAAS,MAAM;WAErD,qBAAqB,UACrB,qBAAqB,gBAGrB,eAAc,QAAQ,OAAO,iBAAiB,SAAS,MAAM;WACpD,qBAAqB,UAAU,eAAe;GAKvD,MAAM,iBAAiB,cAAc,QAAQ,mBAAmB;AAGhE,iBAAc,SAAS,SAAS,gBAAgB;IAE9C,MAAM,QAAQ,eAAe,IAAI,YAAY;AAE7C,QAAI,OAAO;AAET,WAAM,gBAAgB;AAGtB,WAAM,UAAU,KACd,MAAM,SACN,MAAM,eACN,sBAAsB,kBAAkB,MACzC;;KAEH;QAEF,eAAc,QAAQ,gBAAgB,MAAM;AAM9C,MAAI,iBAAiB,EAGnB,iBADmB,cAAc,QAAQ,sBACzB,CAAW;;AAI/B,QAAO;EACL;EACA;EACD"}
1
+ {"version":3,"file":"useMuscleActivation.js","names":[],"sources":["../../src/hooks/useMuscleActivation.ts"],"sourcesContent":["/**\n * useMuscleActivation - Shared hook for muscle activation management\n *\n * Manages muscle activation state based on current actions and stamina.\n * Reduces code duplication in skeletal animation components.\n * Includes stance-based leg muscle tension for isometric holds.\n *\n * @module hooks/useMuscleActivation\n * @category Hooks\n * @korean 근육활성화훅\n */\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n MuscleActivationManager,\n getMuscleTensionForStance,\n} from \"../systems/animation\";\nimport type { TrigramStance } from \"../types/common\";\nimport { DEFAULT_MUSCLE_CONFIG } from \"../types/muscle\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\n\n/**\n * Simple lerp function for muscle tension interpolation\n * Defined at module level to avoid recreation\n * @korean 선형보간함수\n */\nconst lerp = (start: number, end: number, t: number): number =>\n start + (end - start) * t;\n\n/**\n * Options for useMuscleActivation hook\n * @korean 근육활성화훅옵션\n */\nexport interface UseMuscleActivationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current stamina level (0-100) */\n readonly stamina: number;\n /** Current trigram stance (for leg muscle tension) */\n readonly currentStance?: TrigramStance;\n}\n\n/**\n * Return type for useMuscleActivation hook\n * @korean 근육활성화훅반환타입\n */\nexport interface UseMuscleActivationReturn {\n /** Current muscle activation states (bone name -> activation 0-1) */\n readonly muscleStates: Map<string, number>;\n /** Update muscle activations (call in useFrame) */\n readonly updateMuscleActivations: (\n delta: number,\n frameCounter: number\n ) => void;\n}\n\n/**\n * useMuscleActivation hook\n *\n * Manages muscle activation based on current actions (attack, defend, movement).\n * Updates at 60fps with periodic state syncs to reduce re-renders.\n *\n * @param options - Muscle activation options\n * @returns Muscle states and update function\n *\n * @example\n * ```tsx\n * const { muscleStates, updateMuscleActivations } = useMuscleActivation({\n * currentAnimation: \"attack\",\n * attackAnimation: \"jab\",\n * isBlocking: false,\n * stamina: 85,\n * });\n *\n * // In useFrame callback\n * let frameCounter = 0;\n * useFrame((_, delta) => {\n * frameCounter = (frameCounter + 1) % 10;\n * updateMuscleActivations(delta, frameCounter);\n * });\n *\n * // Use muscle states in rendering\n * <BoneRenderer\n * rig={rig}\n * muscleStates={muscleStates}\n * isExhausted={stamina < 20}\n * />\n * ```\n *\n * @korean 근육활성화훅\n */\nexport function useMuscleActivation(\n options: UseMuscleActivationOptions\n): UseMuscleActivationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stamina,\n currentStance,\n } = options;\n\n // Muscle activation manager\n const muscleManager = useRef(new MuscleActivationManager());\n const [muscleStates, setMuscleStates] = useState<Map<string, number>>(\n new Map()\n );\n\n // Cleanup muscle manager on unmount\n useEffect(() => {\n const manager = muscleManager.current;\n return () => {\n try {\n manager.reset();\n } catch (error) {\n console.warn(\"MuscleActivationManager reset failed:\", error);\n }\n };\n }, []);\n\n // Memoize stance tension to avoid recalculation at 60fps\n const stanceTension = useMemo(() => {\n return currentStance ? getMuscleTensionForStance(currentStance) : null;\n }, [currentStance]);\n\n // Update muscle activations (called at 60fps in useFrame)\n const updateMuscleActivations = (\n delta: number,\n frameCounter: number\n ): void => {\n // Update muscle system based on current action\n if (currentAnimation === \"attack\" && attackAnimation) {\n muscleManager.current.update(attackAnimation, stamina, delta);\n } else if (currentAnimation === \"defend\" || isBlocking) {\n muscleManager.current.update(\"block\", stamina, delta);\n } else if (\n currentAnimation === \"walk\" ||\n currentAnimation === \"stance_change\"\n ) {\n // Engage stance/leg/core muscles during movement and stance changes\n muscleManager.current.update(\"stance_change\", stamina, delta);\n } else if (currentAnimation === \"idle\" && stanceTension) {\n // Apply stance-based leg muscle tension for idle/guard positions\n // This provides realistic isometric contraction visualization\n\n // Get all activation states once (not in the loop)\n const allActivations = muscleManager.current.getAllActivations();\n\n // Update manager with stance muscle activations\n stanceTension.forEach((tension, muscleGroup) => {\n // Get existing activation state\n const state = allActivations.get(muscleGroup);\n\n if (state) {\n // Set target tension for smooth interpolation\n state.targetTension = tension;\n\n // Smoothly interpolate to target (using config activation speed)\n state.tension = lerp(\n state.tension,\n state.targetTension,\n DEFAULT_MUSCLE_CONFIG.activationSpeed * delta\n );\n }\n });\n } else {\n muscleManager.current.relaxAllMuscles(delta);\n }\n\n // Sync muscle states to React state deterministically\n // (every 10 frames at 60fps = ~6 times/sec)\n // Balances animation smoothness with performance and reduces GC pressure\n if (frameCounter === 0) {\n // Reuse scratch map from manager to avoid repeated allocations\n const scratchMap = muscleManager.current.getScratchMapForSync();\n setMuscleStates(scratchMap);\n }\n };\n\n return {\n muscleStates,\n updateMuscleActivations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,IAAM,QAAQ,OAAe,KAAa,MACxC,SAAS,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoE1B,SAAgB,oBACd,SAC2B;CAC3B,MAAM,EACJ,kBACA,iBACA,aAAa,OACb,SACA,kBACE;CAGJ,MAAM,gBAAgB,OAAO,IAAI,yBAAyB,CAAC;CAC3D,MAAM,CAAC,cAAc,mBAAmB,yBACtC,IAAI,KAAK,CACV;CAGD,gBAAgB;EACd,MAAM,UAAU,cAAc;EAC9B,aAAa;GACX,IAAI;IACF,QAAQ,OAAO;YACR,OAAO;IACd,QAAQ,KAAK,yCAAyC,MAAM;;;IAG/D,EAAE,CAAC;CAGN,MAAM,gBAAgB,cAAc;EAClC,OAAO,gBAAgB,0BAA0B,cAAc,GAAG;IACjE,CAAC,cAAc,CAAC;CAGnB,MAAM,2BACJ,OACA,iBACS;EAET,IAAI,qBAAqB,YAAY,iBACnC,cAAc,QAAQ,OAAO,iBAAiB,SAAS,MAAM;OACxD,IAAI,qBAAqB,YAAY,YAC1C,cAAc,QAAQ,OAAO,SAAS,SAAS,MAAM;OAChD,IACL,qBAAqB,UACrB,qBAAqB,iBAGrB,cAAc,QAAQ,OAAO,iBAAiB,SAAS,MAAM;OACxD,IAAI,qBAAqB,UAAU,eAAe;GAKvD,MAAM,iBAAiB,cAAc,QAAQ,mBAAmB;GAGhE,cAAc,SAAS,SAAS,gBAAgB;IAE9C,MAAM,QAAQ,eAAe,IAAI,YAAY;IAE7C,IAAI,OAAO;KAET,MAAM,gBAAgB;KAGtB,MAAM,UAAU,KACd,MAAM,SACN,MAAM,eACN,sBAAsB,kBAAkB,MACzC;;KAEH;SAEF,cAAc,QAAQ,gBAAgB,MAAM;EAM9C,IAAI,iBAAiB,GAGnB,gBADmB,cAAc,QAAQ,sBACzB,CAAW;;CAI/B,OAAO;EACL;EACA;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"usePauseMenu.js","names":[],"sources":["../../src/hooks/usePauseMenu.ts"],"sourcesContent":["/**\n * usePauseMenu Hook - State management for pause menu\n * \n * Manages:\n * - Pause menu visibility\n * - Active submenu (controls, settings)\n * - Confirmation dialog state\n */\n\nimport { useCallback, useState } from \"react\";\n\nexport type PauseSubmenu = \"main\" | \"controls\" | \"settings\" | null;\n\nexport interface ConfirmDialogState {\n readonly isOpen: boolean;\n readonly title: string;\n readonly titleKorean: string;\n readonly message: string;\n readonly messageKorean: string;\n readonly onConfirm: () => void;\n}\n\nexport interface UsePauseMenuResult {\n readonly activeSubmenu: PauseSubmenu;\n readonly confirmDialog: ConfirmDialogState;\n readonly showControls: () => void;\n readonly showSettings: () => void;\n readonly closeSubmenu: () => void;\n readonly openConfirmDialog: (config: Omit<ConfirmDialogState, \"isOpen\">) => void;\n readonly closeConfirmDialog: () => void;\n readonly confirmAndClose: () => void;\n}\n\nconst defaultConfirmDialog: ConfirmDialogState = {\n isOpen: false,\n title: \"\",\n titleKorean: \"\",\n message: \"\",\n messageKorean: \"\",\n onConfirm: () => {},\n};\n\n/**\n * Hook for managing pause menu state\n */\nexport function usePauseMenu(): UsePauseMenuResult {\n const [activeSubmenu, setActiveSubmenu] = useState<PauseSubmenu>(\"main\");\n const [confirmDialog, setConfirmDialog] = useState<ConfirmDialogState>(defaultConfirmDialog);\n\n const showControls = useCallback(() => {\n setActiveSubmenu(\"controls\");\n }, []);\n\n const showSettings = useCallback(() => {\n setActiveSubmenu(\"settings\");\n }, []);\n\n const closeSubmenu = useCallback(() => {\n setActiveSubmenu(\"main\");\n }, []);\n\n const openConfirmDialog = useCallback(\n (config: Omit<ConfirmDialogState, \"isOpen\">) => {\n setConfirmDialog({\n ...config,\n isOpen: true,\n });\n },\n []\n );\n\n const closeConfirmDialog = useCallback(() => {\n setConfirmDialog(defaultConfirmDialog);\n }, []);\n\n const confirmAndClose = useCallback(() => {\n if (confirmDialog.onConfirm) {\n confirmDialog.onConfirm();\n }\n closeConfirmDialog();\n }, [confirmDialog, closeConfirmDialog]);\n\n return {\n activeSubmenu,\n confirmDialog,\n showControls,\n showSettings,\n closeSubmenu,\n openConfirmDialog,\n closeConfirmDialog,\n confirmAndClose,\n };\n}\n"],"mappings":";;;;;;;;;;AAiCA,IAAM,uBAA2C;CAC/C,QAAQ;CACR,OAAO;CACP,aAAa;CACb,SAAS;CACT,eAAe;CACf,iBAAiB;CAClB;;;;AAKD,SAAgB,eAAmC;CACjD,MAAM,CAAC,eAAe,oBAAoB,SAAuB,OAAO;CACxE,MAAM,CAAC,eAAe,oBAAoB,SAA6B,qBAAqB;CAE5F,MAAM,eAAe,kBAAkB;AACrC,mBAAiB,WAAW;IAC3B,EAAE,CAAC;CAEN,MAAM,eAAe,kBAAkB;AACrC,mBAAiB,WAAW;IAC3B,EAAE,CAAC;CAEN,MAAM,eAAe,kBAAkB;AACrC,mBAAiB,OAAO;IACvB,EAAE,CAAC;CAEN,MAAM,oBAAoB,aACvB,WAA+C;AAC9C,mBAAiB;GACf,GAAG;GACH,QAAQ;GACT,CAAC;IAEJ,EAAE,CACH;CAED,MAAM,qBAAqB,kBAAkB;AAC3C,mBAAiB,qBAAqB;IACrC,EAAE,CAAC;AASN,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,iBAfsB,kBAAkB;AACxC,OAAI,cAAc,UAChB,eAAc,WAAW;AAE3B,uBAAoB;KACnB,CAAC,eAAe,mBAAmB,CAUpC;EACD"}
1
+ {"version":3,"file":"usePauseMenu.js","names":[],"sources":["../../src/hooks/usePauseMenu.ts"],"sourcesContent":["/**\n * usePauseMenu Hook - State management for pause menu\n * \n * Manages:\n * - Pause menu visibility\n * - Active submenu (controls, settings)\n * - Confirmation dialog state\n */\n\nimport { useCallback, useState } from \"react\";\n\nexport type PauseSubmenu = \"main\" | \"controls\" | \"settings\" | null;\n\nexport interface ConfirmDialogState {\n readonly isOpen: boolean;\n readonly title: string;\n readonly titleKorean: string;\n readonly message: string;\n readonly messageKorean: string;\n readonly onConfirm: () => void;\n}\n\nexport interface UsePauseMenuResult {\n readonly activeSubmenu: PauseSubmenu;\n readonly confirmDialog: ConfirmDialogState;\n readonly showControls: () => void;\n readonly showSettings: () => void;\n readonly closeSubmenu: () => void;\n readonly openConfirmDialog: (config: Omit<ConfirmDialogState, \"isOpen\">) => void;\n readonly closeConfirmDialog: () => void;\n readonly confirmAndClose: () => void;\n}\n\nconst defaultConfirmDialog: ConfirmDialogState = {\n isOpen: false,\n title: \"\",\n titleKorean: \"\",\n message: \"\",\n messageKorean: \"\",\n onConfirm: () => {},\n};\n\n/**\n * Hook for managing pause menu state\n */\nexport function usePauseMenu(): UsePauseMenuResult {\n const [activeSubmenu, setActiveSubmenu] = useState<PauseSubmenu>(\"main\");\n const [confirmDialog, setConfirmDialog] = useState<ConfirmDialogState>(defaultConfirmDialog);\n\n const showControls = useCallback(() => {\n setActiveSubmenu(\"controls\");\n }, []);\n\n const showSettings = useCallback(() => {\n setActiveSubmenu(\"settings\");\n }, []);\n\n const closeSubmenu = useCallback(() => {\n setActiveSubmenu(\"main\");\n }, []);\n\n const openConfirmDialog = useCallback(\n (config: Omit<ConfirmDialogState, \"isOpen\">) => {\n setConfirmDialog({\n ...config,\n isOpen: true,\n });\n },\n []\n );\n\n const closeConfirmDialog = useCallback(() => {\n setConfirmDialog(defaultConfirmDialog);\n }, []);\n\n const confirmAndClose = useCallback(() => {\n if (confirmDialog.onConfirm) {\n confirmDialog.onConfirm();\n }\n closeConfirmDialog();\n }, [confirmDialog, closeConfirmDialog]);\n\n return {\n activeSubmenu,\n confirmDialog,\n showControls,\n showSettings,\n closeSubmenu,\n openConfirmDialog,\n closeConfirmDialog,\n confirmAndClose,\n };\n}\n"],"mappings":";;;;;;;;;;AAiCA,IAAM,uBAA2C;CAC/C,QAAQ;CACR,OAAO;CACP,aAAa;CACb,SAAS;CACT,eAAe;CACf,iBAAiB;CAClB;;;;AAKD,SAAgB,eAAmC;CACjD,MAAM,CAAC,eAAe,oBAAoB,SAAuB,OAAO;CACxE,MAAM,CAAC,eAAe,oBAAoB,SAA6B,qBAAqB;CAE5F,MAAM,eAAe,kBAAkB;EACrC,iBAAiB,WAAW;IAC3B,EAAE,CAAC;CAEN,MAAM,eAAe,kBAAkB;EACrC,iBAAiB,WAAW;IAC3B,EAAE,CAAC;CAEN,MAAM,eAAe,kBAAkB;EACrC,iBAAiB,OAAO;IACvB,EAAE,CAAC;CAEN,MAAM,oBAAoB,aACvB,WAA+C;EAC9C,iBAAiB;GACf,GAAG;GACH,QAAQ;GACT,CAAC;IAEJ,EAAE,CACH;CAED,MAAM,qBAAqB,kBAAkB;EAC3C,iBAAiB,qBAAqB;IACrC,EAAE,CAAC;CASN,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,iBAfsB,kBAAkB;GACxC,IAAI,cAAc,WAChB,cAAc,WAAW;GAE3B,oBAAoB;KACnB,CAAC,eAAe,mBAAmB,CAUpC;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"usePlayerAnimation.js","names":[],"sources":["../../src/hooks/usePlayerAnimation.ts"],"sourcesContent":["/**\n * usePlayerAnimation - React hook for player animation state\n *\n * Provides a React interface to the PlayerAnimationStateMachine\n * with automatic cleanup and event handling.\n *\n * @module hooks/usePlayerAnimation\n * @category Hooks\n * @korean 플레이어애니메이션훅\n */\n\nimport { useCallback, useMemo, useState } from \"react\";\nimport {\n AnimationEvents,\n AnimationState,\n DEFAULT_ANIMATION_CONFIGS,\n PlayerAnimationStateMachine,\n} from \"../systems/animation\";\nimport type {\n AnimationConfig,\n AnimationUpdateResult,\n} from \"../systems/animation/core/types\";\nimport type { TrigramStance } from \"../types/common\";\n\n/**\n * Options for usePlayerAnimation hook\n *\n * @korean 플레이어애니메이션훅옵션\n */\nexport interface UsePlayerAnimationOptions {\n /**\n * Custom animation configurations\n * If not provided, uses DEFAULT_ANIMATION_CONFIGS\n *\n * @korean 커스텀애니메이션설정\n */\n readonly customConfigs?: Map<AnimationState, AnimationConfig>;\n\n /**\n * Animation event callbacks\n *\n * **IMPORTANT**: The events object should be stable (memoized) to prevent\n * unnecessary re-initialization of the animation system. Changes to event\n * callbacks after the hook is initialized will NOT be reflected in the\n * animation system. Use `useMemo` or define events outside the component\n * to ensure stability.\n *\n * @korean 이벤트콜백\n */\n readonly events?: AnimationEvents;\n\n /**\n * Initial animation state (defaults to \"idle\")\n *\n * @korean 초기상태\n */\n readonly initialState?: AnimationState;\n}\n\n/**\n * Return type for usePlayerAnimation hook\n *\n * @korean 플레이어애니메이션훅반환타입\n */\nexport interface UsePlayerAnimationReturn {\n /**\n * Current animation state\n *\n * @korean 현재상태\n */\n readonly currentState: AnimationState;\n\n /**\n * Current frame index\n *\n * @korean 현재프레임\n */\n readonly currentFrame: number;\n\n /**\n * Update animation state (call in useFrame)\n *\n * @param deltaTime - Time elapsed since last update in seconds\n * @returns Animation update result\n *\n * @korean 업데이트\n */\n readonly update: (deltaTime: number) => AnimationUpdateResult;\n\n /**\n * Transition to a new animation state\n *\n * @param newState - Target animation state\n * @returns Whether transition was successful\n *\n * @korean 상태전환\n */\n readonly transitionTo: (newState: AnimationState) => boolean;\n\n /**\n * Transition to ATTACK state with technique-specific duration.\n *\n * The default ATTACK config is 200ms (12 frames), but real techniques\n * range from 350ms to 1200ms. This method overrides the duration so\n * the state machine stays in ATTACK for the full technique animation.\n *\n * @param durationSeconds - The skeletal animation duration in seconds\n * @returns Whether transition was successful\n *\n * @korean 공격전환 (기술별 지속시간)\n */\n readonly transitionToAttack: (durationSeconds: number) => boolean;\n\n /**\n * Transition to stance-specific guard animation\n *\n * @param stance - Trigram stance\n * @returns Whether transition was successful\n *\n * @korean 자세가드전환\n */\n readonly transitionToStanceGuard: (stance: TrigramStance) => boolean;\n\n /**\n * Transition to stance_change animation with specific transition data\n *\n * **Korean**: 자세 전환 애니메이션 시작\n *\n * Initiates a stance change animation with the specific transition data\n * from the 64-transition matrix. This provides stance-specific keyframes\n * and blend weights for smooth interpolation.\n *\n * @param fromStance - Source trigram stance\n * @param toStance - Target trigram stance\n * @returns Whether transition was successful\n *\n * @korean 자세전환애니메이션시작\n */\n readonly transitionToStanceChange: (\n fromStance: TrigramStance,\n toStance: TrigramStance,\n ) => boolean;\n\n /**\n * Check if currently in a stance guard animation\n *\n * @returns True if in any stance guard state\n *\n * @korean 자세가드확인\n */\n readonly isInStanceGuard: () => boolean;\n\n /**\n * Get current guard stance (if in guard animation)\n *\n * @returns Trigram stance or null\n *\n * @korean 현재가드자세\n */\n readonly getCurrentGuardStance: () => TrigramStance | null;\n\n /**\n * Reset animation to idle state\n *\n * @korean 초기화\n */\n readonly reset: () => void;\n}\n\n/**\n * React hook for player animation state management\n *\n * Provides frame-accurate animation control with priority system\n * and event callbacks. Integrates seamlessly with useFrame for\n * 60fps updates.\n *\n * @param options - Animation options\n * @returns Animation control interface\n *\n * @example\n * ```typescript\n * // Basic usage\n * const { currentState, currentFrame, update, transitionTo } = usePlayerAnimation({\n * events: {\n * onAnimationStart: (state) => console.log(`Started ${state}`),\n * onAnimationComplete: (state) => console.log(`Completed ${state}`),\n * onFrame: (frame, state) => {\n * if (state === \"attack\" && frame === 6) {\n * // Execute attack at midpoint\n * executeAttack();\n * }\n * }\n * }\n * });\n *\n * // In useFrame callback\n * useFrame((state, delta) => {\n * const result = update(delta);\n * // Update visuals based on result.state and result.frame\n * });\n *\n * // Trigger animations\n * const handleAttackInput = () => {\n * transitionTo(\"attack\");\n * };\n *\n * const handleMovement = (isMoving: boolean) => {\n * transitionTo(isMoving ? \"walk\" : \"idle\");\n * };\n * ```\n *\n * @korean 플레이어애니메이션훅\n */\nexport function usePlayerAnimation(\n options: UsePlayerAnimationOptions = {},\n): UsePlayerAnimationReturn {\n const { customConfigs, events, initialState = \"idle\" } = options;\n\n // Create animation configs (memoized)\n const configs = useMemo(\n () => customConfigs ?? DEFAULT_ANIMATION_CONFIGS,\n [customConfigs],\n );\n\n // Create animation state machine (persistent across renders via useMemo)\n const stateMachine = useMemo(() => {\n const machine = new PlayerAnimationStateMachine(configs, events);\n // Set initial state if not \"idle\"\n if (initialState !== \"idle\") {\n machine.transitionTo(initialState);\n }\n return machine;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []); // Empty deps - only create once\n\n // Track current state/frame as React state so they can be safely returned\n // during render. Previously these were refs paired with a forceUpdate trick,\n // but react-hooks/refs forbids reading .current during render.\n const [currentState, setCurrentState] = useState<AnimationState>(() =>\n stateMachine.getCurrentState(),\n );\n const [currentFrame, setCurrentFrame] = useState<number>(() =>\n stateMachine.getCurrentFrame(),\n );\n\n // Memoized callbacks with selective state updates\n const update = useCallback(\n (deltaTime: number) => {\n const result = stateMachine.update(deltaTime);\n const nextState = stateMachine.getCurrentState();\n const nextFrame = stateMachine.getCurrentFrame();\n\n // Only trigger re-render if state or frame changed\n setCurrentState((prev) => (prev !== nextState ? nextState : prev));\n setCurrentFrame((prev) => (prev !== nextFrame ? nextFrame : prev));\n\n return result;\n },\n [stateMachine],\n );\n\n const transitionTo = useCallback(\n (newState: AnimationState) => {\n const success = stateMachine.transitionTo(newState);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const reset = useCallback(() => {\n stateMachine.reset();\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }, [stateMachine]);\n\n const transitionToStanceGuard = useCallback(\n (stance: TrigramStance) => {\n const success = stateMachine.transitionToStanceGuard(stance);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const transitionToAttack = useCallback(\n (durationSeconds: number) => {\n const success = stateMachine.transitionToAttack(durationSeconds);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const transitionToStanceChange = useCallback(\n (fromStance: TrigramStance, toStance: TrigramStance) => {\n const success = stateMachine.transitionToStanceChange(\n fromStance,\n toStance,\n );\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const isInStanceGuard = useCallback(() => {\n return stateMachine.isInStanceGuard();\n }, [stateMachine]);\n\n const getCurrentGuardStance = useCallback(() => {\n return stateMachine.getCurrentGuardStance();\n }, [stateMachine]);\n\n // Return a fresh object each render so consumers see the latest state/frame\n return {\n currentState,\n currentFrame,\n update,\n transitionTo,\n transitionToAttack,\n transitionToStanceGuard,\n transitionToStanceChange,\n isInStanceGuard,\n getCurrentGuardStance,\n reset,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqNA,SAAgB,mBACd,UAAqC,EAAE,EACb;CAC1B,MAAM,EAAE,eAAe,QAAQ,eAAe,WAAW;CAGzD,MAAM,UAAU,cACR,iBAAiB,2BACvB,CAAC,cAAc,CAChB;CAGD,MAAM,eAAe,cAAc;EACjC,MAAM,UAAU,IAAI,4BAA4B,SAAS,OAAO;AAEhE,MAAI,iBAAiB,OACnB,SAAQ,aAAa,aAAa;AAEpC,SAAO;IAEN,EAAE,CAAC;CAKN,MAAM,CAAC,cAAc,mBAAmB,eACtC,aAAa,iBAAiB,CAC/B;CACD,MAAM,CAAC,cAAc,mBAAmB,eACtC,aAAa,iBAAiB,CAC/B;CAGD,MAAM,SAAS,aACZ,cAAsB;EACrB,MAAM,SAAS,aAAa,OAAO,UAAU;EAC7C,MAAM,YAAY,aAAa,iBAAiB;EAChD,MAAM,YAAY,aAAa,iBAAiB;AAGhD,mBAAiB,SAAU,SAAS,YAAY,YAAY,KAAM;AAClE,mBAAiB,SAAU,SAAS,YAAY,YAAY,KAAM;AAElE,SAAO;IAET,CAAC,aAAa,CACf;CAED,MAAM,eAAe,aAClB,aAA6B;EAC5B,MAAM,UAAU,aAAa,aAAa,SAAS;AACnD,MAAI,SAAS;AACX,mBAAgB,aAAa,iBAAiB,CAAC;AAC/C,mBAAgB,aAAa,iBAAiB,CAAC;;AAEjD,SAAO;IAET,CAAC,aAAa,CACf;CAED,MAAM,QAAQ,kBAAkB;AAC9B,eAAa,OAAO;AACpB,kBAAgB,aAAa,iBAAiB,CAAC;AAC/C,kBAAgB,aAAa,iBAAiB,CAAC;IAC9C,CAAC,aAAa,CAAC;CAElB,MAAM,0BAA0B,aAC7B,WAA0B;EACzB,MAAM,UAAU,aAAa,wBAAwB,OAAO;AAC5D,MAAI,SAAS;AACX,mBAAgB,aAAa,iBAAiB,CAAC;AAC/C,mBAAgB,aAAa,iBAAiB,CAAC;;AAEjD,SAAO;IAET,CAAC,aAAa,CACf;AAsCD,QAAO;EACL;EACA;EACA;EACA;EACA,oBAzCyB,aACxB,oBAA4B;GAC3B,MAAM,UAAU,aAAa,mBAAmB,gBAAgB;AAChE,OAAI,SAAS;AACX,oBAAgB,aAAa,iBAAiB,CAAC;AAC/C,oBAAgB,aAAa,iBAAiB,CAAC;;AAEjD,UAAO;KAET,CAAC,aAAa,CAgCd;EACA;EACA,0BA/B+B,aAC9B,YAA2B,aAA4B;GACtD,MAAM,UAAU,aAAa,yBAC3B,YACA,SACD;AACD,OAAI,SAAS;AACX,oBAAgB,aAAa,iBAAiB,CAAC;AAC/C,oBAAgB,aAAa,iBAAiB,CAAC;;AAEjD,UAAO;KAET,CAAC,aAAa,CAmBd;EACA,iBAjBsB,kBAAkB;AACxC,UAAO,aAAa,iBAAiB;KACpC,CAAC,aAAa,CAef;EACA,uBAd4B,kBAAkB;AAC9C,UAAO,aAAa,uBAAuB;KAC1C,CAAC,aAAa,CAYf;EACA;EACD"}
1
+ {"version":3,"file":"usePlayerAnimation.js","names":[],"sources":["../../src/hooks/usePlayerAnimation.ts"],"sourcesContent":["/**\n * usePlayerAnimation - React hook for player animation state\n *\n * Provides a React interface to the PlayerAnimationStateMachine\n * with automatic cleanup and event handling.\n *\n * @module hooks/usePlayerAnimation\n * @category Hooks\n * @korean 플레이어애니메이션훅\n */\n\nimport { useCallback, useMemo, useState } from \"react\";\nimport {\n AnimationEvents,\n AnimationState,\n DEFAULT_ANIMATION_CONFIGS,\n PlayerAnimationStateMachine,\n} from \"../systems/animation\";\nimport type {\n AnimationConfig,\n AnimationUpdateResult,\n} from \"../systems/animation/core/types\";\nimport type { TrigramStance } from \"../types/common\";\n\n/**\n * Options for usePlayerAnimation hook\n *\n * @korean 플레이어애니메이션훅옵션\n */\nexport interface UsePlayerAnimationOptions {\n /**\n * Custom animation configurations\n * If not provided, uses DEFAULT_ANIMATION_CONFIGS\n *\n * @korean 커스텀애니메이션설정\n */\n readonly customConfigs?: Map<AnimationState, AnimationConfig>;\n\n /**\n * Animation event callbacks\n *\n * **IMPORTANT**: The events object should be stable (memoized) to prevent\n * unnecessary re-initialization of the animation system. Changes to event\n * callbacks after the hook is initialized will NOT be reflected in the\n * animation system. Use `useMemo` or define events outside the component\n * to ensure stability.\n *\n * @korean 이벤트콜백\n */\n readonly events?: AnimationEvents;\n\n /**\n * Initial animation state (defaults to \"idle\")\n *\n * @korean 초기상태\n */\n readonly initialState?: AnimationState;\n}\n\n/**\n * Return type for usePlayerAnimation hook\n *\n * @korean 플레이어애니메이션훅반환타입\n */\nexport interface UsePlayerAnimationReturn {\n /**\n * Current animation state\n *\n * @korean 현재상태\n */\n readonly currentState: AnimationState;\n\n /**\n * Current frame index\n *\n * @korean 현재프레임\n */\n readonly currentFrame: number;\n\n /**\n * Update animation state (call in useFrame)\n *\n * @param deltaTime - Time elapsed since last update in seconds\n * @returns Animation update result\n *\n * @korean 업데이트\n */\n readonly update: (deltaTime: number) => AnimationUpdateResult;\n\n /**\n * Transition to a new animation state\n *\n * @param newState - Target animation state\n * @returns Whether transition was successful\n *\n * @korean 상태전환\n */\n readonly transitionTo: (newState: AnimationState) => boolean;\n\n /**\n * Transition to ATTACK state with technique-specific duration.\n *\n * The default ATTACK config is 200ms (12 frames), but real techniques\n * range from 350ms to 1200ms. This method overrides the duration so\n * the state machine stays in ATTACK for the full technique animation.\n *\n * @param durationSeconds - The skeletal animation duration in seconds\n * @returns Whether transition was successful\n *\n * @korean 공격전환 (기술별 지속시간)\n */\n readonly transitionToAttack: (durationSeconds: number) => boolean;\n\n /**\n * Transition to stance-specific guard animation\n *\n * @param stance - Trigram stance\n * @returns Whether transition was successful\n *\n * @korean 자세가드전환\n */\n readonly transitionToStanceGuard: (stance: TrigramStance) => boolean;\n\n /**\n * Transition to stance_change animation with specific transition data\n *\n * **Korean**: 자세 전환 애니메이션 시작\n *\n * Initiates a stance change animation with the specific transition data\n * from the 64-transition matrix. This provides stance-specific keyframes\n * and blend weights for smooth interpolation.\n *\n * @param fromStance - Source trigram stance\n * @param toStance - Target trigram stance\n * @returns Whether transition was successful\n *\n * @korean 자세전환애니메이션시작\n */\n readonly transitionToStanceChange: (\n fromStance: TrigramStance,\n toStance: TrigramStance,\n ) => boolean;\n\n /**\n * Check if currently in a stance guard animation\n *\n * @returns True if in any stance guard state\n *\n * @korean 자세가드확인\n */\n readonly isInStanceGuard: () => boolean;\n\n /**\n * Get current guard stance (if in guard animation)\n *\n * @returns Trigram stance or null\n *\n * @korean 현재가드자세\n */\n readonly getCurrentGuardStance: () => TrigramStance | null;\n\n /**\n * Reset animation to idle state\n *\n * @korean 초기화\n */\n readonly reset: () => void;\n}\n\n/**\n * React hook for player animation state management\n *\n * Provides frame-accurate animation control with priority system\n * and event callbacks. Integrates seamlessly with useFrame for\n * 60fps updates.\n *\n * @param options - Animation options\n * @returns Animation control interface\n *\n * @example\n * ```typescript\n * // Basic usage\n * const { currentState, currentFrame, update, transitionTo } = usePlayerAnimation({\n * events: {\n * onAnimationStart: (state) => console.log(`Started ${state}`),\n * onAnimationComplete: (state) => console.log(`Completed ${state}`),\n * onFrame: (frame, state) => {\n * if (state === \"attack\" && frame === 6) {\n * // Execute attack at midpoint\n * executeAttack();\n * }\n * }\n * }\n * });\n *\n * // In useFrame callback\n * useFrame((state, delta) => {\n * const result = update(delta);\n * // Update visuals based on result.state and result.frame\n * });\n *\n * // Trigger animations\n * const handleAttackInput = () => {\n * transitionTo(\"attack\");\n * };\n *\n * const handleMovement = (isMoving: boolean) => {\n * transitionTo(isMoving ? \"walk\" : \"idle\");\n * };\n * ```\n *\n * @korean 플레이어애니메이션훅\n */\nexport function usePlayerAnimation(\n options: UsePlayerAnimationOptions = {},\n): UsePlayerAnimationReturn {\n const { customConfigs, events, initialState = \"idle\" } = options;\n\n // Create animation configs (memoized)\n const configs = useMemo(\n () => customConfigs ?? DEFAULT_ANIMATION_CONFIGS,\n [customConfigs],\n );\n\n // Create animation state machine (persistent across renders via useMemo)\n const stateMachine = useMemo(() => {\n const machine = new PlayerAnimationStateMachine(configs, events);\n // Set initial state if not \"idle\"\n if (initialState !== \"idle\") {\n machine.transitionTo(initialState);\n }\n return machine;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []); // Empty deps - only create once\n\n // Track current state/frame as React state so they can be safely returned\n // during render. Previously these were refs paired with a forceUpdate trick,\n // but react-hooks/refs forbids reading .current during render.\n const [currentState, setCurrentState] = useState<AnimationState>(() =>\n stateMachine.getCurrentState(),\n );\n const [currentFrame, setCurrentFrame] = useState<number>(() =>\n stateMachine.getCurrentFrame(),\n );\n\n // Memoized callbacks with selective state updates\n const update = useCallback(\n (deltaTime: number) => {\n const result = stateMachine.update(deltaTime);\n const nextState = stateMachine.getCurrentState();\n const nextFrame = stateMachine.getCurrentFrame();\n\n // Only trigger re-render if state or frame changed\n setCurrentState((prev) => (prev !== nextState ? nextState : prev));\n setCurrentFrame((prev) => (prev !== nextFrame ? nextFrame : prev));\n\n return result;\n },\n [stateMachine],\n );\n\n const transitionTo = useCallback(\n (newState: AnimationState) => {\n const success = stateMachine.transitionTo(newState);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const reset = useCallback(() => {\n stateMachine.reset();\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }, [stateMachine]);\n\n const transitionToStanceGuard = useCallback(\n (stance: TrigramStance) => {\n const success = stateMachine.transitionToStanceGuard(stance);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const transitionToAttack = useCallback(\n (durationSeconds: number) => {\n const success = stateMachine.transitionToAttack(durationSeconds);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const transitionToStanceChange = useCallback(\n (fromStance: TrigramStance, toStance: TrigramStance) => {\n const success = stateMachine.transitionToStanceChange(\n fromStance,\n toStance,\n );\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const isInStanceGuard = useCallback(() => {\n return stateMachine.isInStanceGuard();\n }, [stateMachine]);\n\n const getCurrentGuardStance = useCallback(() => {\n return stateMachine.getCurrentGuardStance();\n }, [stateMachine]);\n\n // Return a fresh object each render so consumers see the latest state/frame\n return {\n currentState,\n currentFrame,\n update,\n transitionTo,\n transitionToAttack,\n transitionToStanceGuard,\n transitionToStanceChange,\n isInStanceGuard,\n getCurrentGuardStance,\n reset,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqNA,SAAgB,mBACd,UAAqC,EAAE,EACb;CAC1B,MAAM,EAAE,eAAe,QAAQ,eAAe,WAAW;CAGzD,MAAM,UAAU,cACR,iBAAiB,2BACvB,CAAC,cAAc,CAChB;CAGD,MAAM,eAAe,cAAc;EACjC,MAAM,UAAU,IAAI,4BAA4B,SAAS,OAAO;EAEhE,IAAI,iBAAiB,QACnB,QAAQ,aAAa,aAAa;EAEpC,OAAO;IAEN,EAAE,CAAC;CAKN,MAAM,CAAC,cAAc,mBAAmB,eACtC,aAAa,iBAAiB,CAC/B;CACD,MAAM,CAAC,cAAc,mBAAmB,eACtC,aAAa,iBAAiB,CAC/B;CAGD,MAAM,SAAS,aACZ,cAAsB;EACrB,MAAM,SAAS,aAAa,OAAO,UAAU;EAC7C,MAAM,YAAY,aAAa,iBAAiB;EAChD,MAAM,YAAY,aAAa,iBAAiB;EAGhD,iBAAiB,SAAU,SAAS,YAAY,YAAY,KAAM;EAClE,iBAAiB,SAAU,SAAS,YAAY,YAAY,KAAM;EAElE,OAAO;IAET,CAAC,aAAa,CACf;CAED,MAAM,eAAe,aAClB,aAA6B;EAC5B,MAAM,UAAU,aAAa,aAAa,SAAS;EACnD,IAAI,SAAS;GACX,gBAAgB,aAAa,iBAAiB,CAAC;GAC/C,gBAAgB,aAAa,iBAAiB,CAAC;;EAEjD,OAAO;IAET,CAAC,aAAa,CACf;CAED,MAAM,QAAQ,kBAAkB;EAC9B,aAAa,OAAO;EACpB,gBAAgB,aAAa,iBAAiB,CAAC;EAC/C,gBAAgB,aAAa,iBAAiB,CAAC;IAC9C,CAAC,aAAa,CAAC;CAElB,MAAM,0BAA0B,aAC7B,WAA0B;EACzB,MAAM,UAAU,aAAa,wBAAwB,OAAO;EAC5D,IAAI,SAAS;GACX,gBAAgB,aAAa,iBAAiB,CAAC;GAC/C,gBAAgB,aAAa,iBAAiB,CAAC;;EAEjD,OAAO;IAET,CAAC,aAAa,CACf;CAsCD,OAAO;EACL;EACA;EACA;EACA;EACA,oBAzCyB,aACxB,oBAA4B;GAC3B,MAAM,UAAU,aAAa,mBAAmB,gBAAgB;GAChE,IAAI,SAAS;IACX,gBAAgB,aAAa,iBAAiB,CAAC;IAC/C,gBAAgB,aAAa,iBAAiB,CAAC;;GAEjD,OAAO;KAET,CAAC,aAAa,CAgCd;EACA;EACA,0BA/B+B,aAC9B,YAA2B,aAA4B;GACtD,MAAM,UAAU,aAAa,yBAC3B,YACA,SACD;GACD,IAAI,SAAS;IACX,gBAAgB,aAAa,iBAAiB,CAAC;IAC/C,gBAAgB,aAAa,iBAAiB,CAAC;;GAEjD,OAAO;KAET,CAAC,aAAa,CAmBd;EACA,iBAjBsB,kBAAkB;GACxC,OAAO,aAAa,iBAAiB;KACpC,CAAC,aAAa,CAef;EACA,uBAd4B,kBAAkB;GAC9C,OAAO,aAAa,uBAAuB;KAC1C,CAAC,aAAa,CAYf;EACA;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"useResponsiveLayout.js","names":[],"sources":["../../src/hooks/useResponsiveLayout.ts"],"sourcesContent":["/**\n * useResponsiveLayout Hook - Enhanced Responsive Layout System\n * \n * Provides comprehensive responsive layout values for all screen sizes with:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional font scaling (0.8x-1.4x) with 14-24px readability constraints\n * - Proportional spacing scaling (0.5x-1.5x)\n * - Smooth transitions for resize operations (300ms ease-in-out)\n * - Touch-friendly element sizing following iOS Human Interface Guidelines\n * - Safe area insets for notch and home indicator\n * \n * Uses robust device detection combining user-agent and screen size to ensure\n * mobile controls are shown on all mobile devices, including high-resolution phones.\n * \n * Features:\n * - Touch target minimum: 44x44px (iOS guideline)\n * - Safe area insets for notch and home indicator\n * - Responsive font sizes (14px minimum body text)\n * - Optimized spacing for mobile vs desktop\n * - Portrait and landscape mode support\n * - Tablet breakpoint support (768-1024px)\n * \n * @module hooks/useResponsiveLayout\n * @category Mobile UI\n * @korean 반응형레이아웃훅\n */\n\nimport { useMemo } from 'react';\nimport { shouldUseMobileControls, getSafeAreaInsets as getDeviceSafeAreaInsets } from '../utils/deviceDetection';\nimport {\n RESPONSIVE_BREAKPOINTS,\n getScreenSize,\n calculateResponsiveValues,\n createTransitionString,\n} from '../systems/ResponsiveScaling';\n\nimport type { ScreenSize } from '../systems/ResponsiveScaling';\n\n/**\n * Width breakpoint (in CSS pixels) below which devices are treated as\n * extra-small mobile (e.g. iPhone SE). Kept local to this hook because the\n * wider responsive scaling system uses a different bucketing scheme\n * (mobile / tablet / desktop / large / xlarge) that does not expose this\n * sub-mobile threshold.\n */\nconst SMALL_MOBILE_MAX_WIDTH = 375;\n\n/**\n * Breakpoints for responsive design exposed by this hook.\n *\n * @deprecated Prefer `RESPONSIVE_BREAKPOINTS` from `../systems/ResponsiveScaling`\n * for the standard mobile / tablet / desktop tiers. This object is retained as\n * a backward-compatible shim for existing tests and external consumers and\n * additionally exposes the hook-local `MOBILE_SMALL` (iPhone SE) threshold.\n */\nexport const BREAKPOINTS = Object.freeze({\n /** Extra small mobile devices (iPhone SE) */\n MOBILE_SMALL: SMALL_MOBILE_MAX_WIDTH,\n /** Standard mobile devices */\n MOBILE: RESPONSIVE_BREAKPOINTS.MOBILE,\n /** Tablet devices */\n TABLET: RESPONSIVE_BREAKPOINTS.TABLET,\n /** Desktop devices */\n DESKTOP: RESPONSIVE_BREAKPOINTS.LARGE,\n}) as {\n readonly MOBILE_SMALL: number;\n readonly MOBILE: number;\n readonly TABLET: number;\n readonly DESKTOP: number;\n};\n\n/**\n * Touch target sizes following iOS Human Interface Guidelines\n */\nexport interface TouchTargets {\n /** Minimum touch target (44x44px iOS guideline) */\n readonly small: number;\n /** Medium touch target for primary actions */\n readonly medium: number;\n /** Large touch target for critical actions */\n readonly large: number;\n}\n\n/**\n * Safe area insets for mobile devices\n * Accounts for notch, status bar, and home indicator\n */\nexport interface SafeAreaInsets {\n /** Top inset (status bar, notch) */\n readonly top: number;\n /** Bottom inset (home indicator) */\n readonly bottom: number;\n /** Left inset (landscape mode) */\n readonly left: number;\n /** Right inset (landscape mode) */\n readonly right: number;\n}\n\n/**\n * Responsive font sizes\n */\nexport interface FontSizes {\n /** Small text (12-14px) */\n readonly small: number;\n /** Body text (14-16px minimum for mobile) */\n readonly body: number;\n /** Title text (18-24px) */\n readonly title: number;\n /** Hero text (24-36px) */\n readonly hero: number;\n /** HUD text (16-20px for important combat info) */\n readonly hud: number;\n}\n\n/**\n * Responsive spacing scale\n */\nexport interface Spacing {\n /** Extra small (4-8px) */\n readonly xs: number;\n /** Small (8-12px) */\n readonly sm: number;\n /** Medium (12-16px) */\n readonly md: number;\n /** Large (16-24px) */\n readonly lg: number;\n /** Extra large (24-32px) */\n readonly xl: number;\n}\n\n/**\n * Complete responsive layout configuration\n */\nexport interface ResponsiveLayout {\n /** Whether current viewport is mobile-sized */\n readonly isMobile: boolean;\n /** Whether current viewport is small mobile (< 400px) */\n readonly isSmallMobile: boolean;\n /** Whether current viewport is tablet-sized */\n readonly isTablet: boolean;\n /** Whether current viewport is desktop or larger */\n readonly isDesktop: boolean;\n /** Whether in landscape orientation */\n readonly isLandscape: boolean;\n /** Current screen size category (mobile, tablet, desktop, large, xlarge) */\n readonly screenSize: ScreenSize;\n /** Safe area insets for mobile devices */\n readonly safeArea: SafeAreaInsets;\n /** Touch target sizes */\n readonly touchTarget: TouchTargets;\n /** Font sizes */\n readonly fontSize: FontSizes;\n /** Spacing scale */\n readonly spacing: Spacing;\n /** CSS transition string for smooth resizing */\n readonly transition: string;\n /** Current viewport dimensions */\n readonly viewport: {\n readonly width: number;\n readonly height: number;\n };\n}\n\n/**\n * Calculate responsive layout values based on viewport dimensions\n * \n * Enhanced with centralized responsive scaling system:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional font scaling (0.8x-1.4x) with 14-24px constraints\n * - Proportional spacing scaling (0.5x-1.5x)\n * - Smooth CSS transitions for resize operations\n * \n * Optimized for:\n * - iPhone SE (375x667) - mobile\n * - iPhone 11/12/13 (414x896) - mobile\n * - iPhone 14 Pro Max (430x932) - mobile\n * - iPad (768x1024) - tablet\n * - iPad Pro (1024x1366) - desktop\n * - Standard Desktop (1280x800) - desktop\n * - HD Display (1920x1080) - xlarge\n * - 4K Display (2560x1440) - xlarge\n * \n * @param width - Viewport width in pixels\n * @param height - Viewport height in pixels\n * @returns Complete responsive layout configuration\n * \n * @example\n * ```tsx\n * const layout = useResponsiveLayout(375, 667);\n * \n * <div style={{\n * padding: layout.spacing.md,\n * fontSize: layout.fontSize.body,\n * minHeight: layout.touchTarget.small,\n * transition: layout.transition,\n * }}>\n * Responsive content\n * </div>\n * ```\n * \n * @public\n * @korean 반응형레이아웃사용\n */\nexport function useResponsiveLayout(\n width: number,\n height: number\n): ResponsiveLayout {\n return useMemo(() => {\n // Determine screen size category using centralized scaling system\n const screenSize = getScreenSize(width);\n \n // Device type detection using robust device detection utility\n // Combines user-agent and screen size for reliable mobile detection\n const isMobile = shouldUseMobileControls();\n // Extra-small mobile (e.g. iPhone SE) detection\n const isSmallMobile = width <= SMALL_MOBILE_MAX_WIDTH;\n const isTablet = screenSize === 'tablet';\n const isDesktop = screenSize === 'desktop' || screenSize === 'large' || screenSize === 'xlarge';\n const isLandscape = width > height;\n\n // Safe area insets from device detection utility\n const deviceInsets = getDeviceSafeAreaInsets();\n const safeArea: SafeAreaInsets = {\n top: deviceInsets.top,\n bottom: deviceInsets.bottom,\n left: isLandscape ? deviceInsets.left : 0,\n right: isLandscape ? deviceInsets.right : 0,\n };\n\n // Touch target sizes (iOS Human Interface Guidelines: 44x44px minimum)\n const touchTarget: TouchTargets = {\n small: 44, // Minimum iOS guideline\n medium: isSmallMobile ? 50 : 60,\n large: isSmallMobile ? 60 : 80,\n };\n\n // Calculate responsive values using centralized scaling system\n const responsiveValues = calculateResponsiveValues(width);\n\n // Map to existing font size structure for backward compatibility\n const fontSize: FontSizes = {\n small: responsiveValues.fontSize.small,\n body: responsiveValues.fontSize.body,\n title: responsiveValues.fontSize.title,\n hero: responsiveValues.fontSize.hero,\n hud: responsiveValues.fontSize.hud,\n };\n\n // Map to existing spacing structure for backward compatibility\n const spacing: Spacing = {\n xs: responsiveValues.spacing.xs,\n sm: responsiveValues.spacing.sm,\n md: responsiveValues.spacing.md,\n lg: responsiveValues.spacing.lg,\n xl: responsiveValues.spacing.xl,\n };\n\n // Get transition string for smooth resizing\n const transition = createTransitionString();\n\n return {\n isMobile,\n isSmallMobile,\n isTablet,\n isDesktop,\n isLandscape,\n screenSize,\n safeArea,\n touchTarget,\n fontSize,\n spacing,\n transition,\n viewport: { width, height },\n };\n }, [width, height]);\n}\n\n/**\n * Calculate available content area after safe areas and HUD\n * \n * @param layout - Responsive layout configuration\n * @param hudHeight - Height reserved for HUD elements\n * @param controlsHeight - Height reserved for control elements\n * @returns Available content dimensions\n */\nexport function useContentArea(\n layout: ResponsiveLayout,\n hudHeight: number = 0,\n controlsHeight: number = 0\n) {\n return useMemo(() => {\n const totalVerticalReserved =\n layout.safeArea.top +\n layout.safeArea.bottom +\n hudHeight +\n controlsHeight +\n layout.spacing.md * 2; // Padding\n\n const totalHorizontalReserved =\n layout.safeArea.left + layout.safeArea.right + layout.spacing.md * 2;\n\n return {\n width: layout.viewport.width - totalHorizontalReserved,\n height: layout.viewport.height - totalVerticalReserved,\n x: layout.safeArea.left + layout.spacing.md,\n y: layout.safeArea.top + hudHeight + layout.spacing.md,\n };\n }, [layout, hudHeight, controlsHeight]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAM,yBAAyB;;;;;;;;;AAU/B,IAAa,cAAc,OAAO,OAAO;;CAEvC,cAAc;;CAEd,QAAQ,uBAAuB;;CAE/B,QAAQ,uBAAuB;;CAE/B,SAAS,uBAAuB;CACjC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2IF,SAAgB,oBACd,OACA,QACkB;AAClB,QAAO,cAAc;EAEnB,MAAM,aAAa,cAAc,MAAM;EAIvC,MAAM,WAAW,yBAAyB;EAE1C,MAAM,gBAAgB,SAAS;EAC/B,MAAM,WAAW,eAAe;EAChC,MAAM,YAAY,eAAe,aAAa,eAAe,WAAW,eAAe;EACvF,MAAM,cAAc,QAAQ;EAG5B,MAAM,eAAe,mBAAyB;EAC9C,MAAM,WAA2B;GAC/B,KAAK,aAAa;GAClB,QAAQ,aAAa;GACrB,MAAM,cAAc,aAAa,OAAO;GACxC,OAAO,cAAc,aAAa,QAAQ;GAC3C;EAGD,MAAM,cAA4B;GAChC,OAAO;GACP,QAAQ,gBAAgB,KAAK;GAC7B,OAAO,gBAAgB,KAAK;GAC7B;EAGD,MAAM,mBAAmB,0BAA0B,MAAM;AAuBzD,SAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,UAAA;IA5BA,OAAO,iBAAiB,SAAS;IACjC,MAAM,iBAAiB,SAAS;IAChC,OAAO,iBAAiB,SAAS;IACjC,MAAM,iBAAiB,SAAS;IAChC,KAAK,iBAAiB,SAAS;IAwB/B;GACA,SAAA;IApBA,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAgB7B;GACA,YAbiB,wBAajB;GACA,UAAU;IAAE;IAAO;IAAQ;GAC5B;IACA,CAAC,OAAO,OAAO,CAAC;;;;;;;;;;AAWrB,SAAgB,eACd,QACA,YAAoB,GACpB,iBAAyB,GACzB;AACA,QAAO,cAAc;EACnB,MAAM,wBACJ,OAAO,SAAS,MAChB,OAAO,SAAS,SAChB,YACA,iBACA,OAAO,QAAQ,KAAK;EAEtB,MAAM,0BACJ,OAAO,SAAS,OAAO,OAAO,SAAS,QAAQ,OAAO,QAAQ,KAAK;AAErE,SAAO;GACL,OAAO,OAAO,SAAS,QAAQ;GAC/B,QAAQ,OAAO,SAAS,SAAS;GACjC,GAAG,OAAO,SAAS,OAAO,OAAO,QAAQ;GACzC,GAAG,OAAO,SAAS,MAAM,YAAY,OAAO,QAAQ;GACrD;IACA;EAAC;EAAQ;EAAW;EAAe,CAAC"}
1
+ {"version":3,"file":"useResponsiveLayout.js","names":[],"sources":["../../src/hooks/useResponsiveLayout.ts"],"sourcesContent":["/**\n * useResponsiveLayout Hook - Enhanced Responsive Layout System\n * \n * Provides comprehensive responsive layout values for all screen sizes with:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional font scaling (0.8x-1.4x) with 14-24px readability constraints\n * - Proportional spacing scaling (0.5x-1.5x)\n * - Smooth transitions for resize operations (300ms ease-in-out)\n * - Touch-friendly element sizing following iOS Human Interface Guidelines\n * - Safe area insets for notch and home indicator\n * \n * Uses robust device detection combining user-agent and screen size to ensure\n * mobile controls are shown on all mobile devices, including high-resolution phones.\n * \n * Features:\n * - Touch target minimum: 44x44px (iOS guideline)\n * - Safe area insets for notch and home indicator\n * - Responsive font sizes (14px minimum body text)\n * - Optimized spacing for mobile vs desktop\n * - Portrait and landscape mode support\n * - Tablet breakpoint support (768-1024px)\n * \n * @module hooks/useResponsiveLayout\n * @category Mobile UI\n * @korean 반응형레이아웃훅\n */\n\nimport { useMemo } from 'react';\nimport { shouldUseMobileControls, getSafeAreaInsets as getDeviceSafeAreaInsets } from '../utils/deviceDetection';\nimport {\n RESPONSIVE_BREAKPOINTS,\n getScreenSize,\n calculateResponsiveValues,\n createTransitionString,\n} from '../systems/ResponsiveScaling';\n\nimport type { ScreenSize } from '../systems/ResponsiveScaling';\n\n/**\n * Width breakpoint (in CSS pixels) below which devices are treated as\n * extra-small mobile (e.g. iPhone SE). Kept local to this hook because the\n * wider responsive scaling system uses a different bucketing scheme\n * (mobile / tablet / desktop / large / xlarge) that does not expose this\n * sub-mobile threshold.\n */\nconst SMALL_MOBILE_MAX_WIDTH = 375;\n\n/**\n * Breakpoints for responsive design exposed by this hook.\n *\n * @deprecated Prefer `RESPONSIVE_BREAKPOINTS` from `../systems/ResponsiveScaling`\n * for the standard mobile / tablet / desktop tiers. This object is retained as\n * a backward-compatible shim for existing tests and external consumers and\n * additionally exposes the hook-local `MOBILE_SMALL` (iPhone SE) threshold.\n */\nexport const BREAKPOINTS = Object.freeze({\n /** Extra small mobile devices (iPhone SE) */\n MOBILE_SMALL: SMALL_MOBILE_MAX_WIDTH,\n /** Standard mobile devices */\n MOBILE: RESPONSIVE_BREAKPOINTS.MOBILE,\n /** Tablet devices */\n TABLET: RESPONSIVE_BREAKPOINTS.TABLET,\n /** Desktop devices */\n DESKTOP: RESPONSIVE_BREAKPOINTS.LARGE,\n}) as {\n readonly MOBILE_SMALL: number;\n readonly MOBILE: number;\n readonly TABLET: number;\n readonly DESKTOP: number;\n};\n\n/**\n * Touch target sizes following iOS Human Interface Guidelines\n */\nexport interface TouchTargets {\n /** Minimum touch target (44x44px iOS guideline) */\n readonly small: number;\n /** Medium touch target for primary actions */\n readonly medium: number;\n /** Large touch target for critical actions */\n readonly large: number;\n}\n\n/**\n * Safe area insets for mobile devices\n * Accounts for notch, status bar, and home indicator\n */\nexport interface SafeAreaInsets {\n /** Top inset (status bar, notch) */\n readonly top: number;\n /** Bottom inset (home indicator) */\n readonly bottom: number;\n /** Left inset (landscape mode) */\n readonly left: number;\n /** Right inset (landscape mode) */\n readonly right: number;\n}\n\n/**\n * Responsive font sizes\n */\nexport interface FontSizes {\n /** Small text (12-14px) */\n readonly small: number;\n /** Body text (14-16px minimum for mobile) */\n readonly body: number;\n /** Title text (18-24px) */\n readonly title: number;\n /** Hero text (24-36px) */\n readonly hero: number;\n /** HUD text (16-20px for important combat info) */\n readonly hud: number;\n}\n\n/**\n * Responsive spacing scale\n */\nexport interface Spacing {\n /** Extra small (4-8px) */\n readonly xs: number;\n /** Small (8-12px) */\n readonly sm: number;\n /** Medium (12-16px) */\n readonly md: number;\n /** Large (16-24px) */\n readonly lg: number;\n /** Extra large (24-32px) */\n readonly xl: number;\n}\n\n/**\n * Complete responsive layout configuration\n */\nexport interface ResponsiveLayout {\n /** Whether current viewport is mobile-sized */\n readonly isMobile: boolean;\n /** Whether current viewport is small mobile (< 400px) */\n readonly isSmallMobile: boolean;\n /** Whether current viewport is tablet-sized */\n readonly isTablet: boolean;\n /** Whether current viewport is desktop or larger */\n readonly isDesktop: boolean;\n /** Whether in landscape orientation */\n readonly isLandscape: boolean;\n /** Current screen size category (mobile, tablet, desktop, large, xlarge) */\n readonly screenSize: ScreenSize;\n /** Safe area insets for mobile devices */\n readonly safeArea: SafeAreaInsets;\n /** Touch target sizes */\n readonly touchTarget: TouchTargets;\n /** Font sizes */\n readonly fontSize: FontSizes;\n /** Spacing scale */\n readonly spacing: Spacing;\n /** CSS transition string for smooth resizing */\n readonly transition: string;\n /** Current viewport dimensions */\n readonly viewport: {\n readonly width: number;\n readonly height: number;\n };\n}\n\n/**\n * Calculate responsive layout values based on viewport dimensions\n * \n * Enhanced with centralized responsive scaling system:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional font scaling (0.8x-1.4x) with 14-24px constraints\n * - Proportional spacing scaling (0.5x-1.5x)\n * - Smooth CSS transitions for resize operations\n * \n * Optimized for:\n * - iPhone SE (375x667) - mobile\n * - iPhone 11/12/13 (414x896) - mobile\n * - iPhone 14 Pro Max (430x932) - mobile\n * - iPad (768x1024) - tablet\n * - iPad Pro (1024x1366) - desktop\n * - Standard Desktop (1280x800) - desktop\n * - HD Display (1920x1080) - xlarge\n * - 4K Display (2560x1440) - xlarge\n * \n * @param width - Viewport width in pixels\n * @param height - Viewport height in pixels\n * @returns Complete responsive layout configuration\n * \n * @example\n * ```tsx\n * const layout = useResponsiveLayout(375, 667);\n * \n * <div style={{\n * padding: layout.spacing.md,\n * fontSize: layout.fontSize.body,\n * minHeight: layout.touchTarget.small,\n * transition: layout.transition,\n * }}>\n * Responsive content\n * </div>\n * ```\n * \n * @public\n * @korean 반응형레이아웃사용\n */\nexport function useResponsiveLayout(\n width: number,\n height: number\n): ResponsiveLayout {\n return useMemo(() => {\n // Determine screen size category using centralized scaling system\n const screenSize = getScreenSize(width);\n \n // Device type detection using robust device detection utility\n // Combines user-agent and screen size for reliable mobile detection\n const isMobile = shouldUseMobileControls();\n // Extra-small mobile (e.g. iPhone SE) detection\n const isSmallMobile = width <= SMALL_MOBILE_MAX_WIDTH;\n const isTablet = screenSize === 'tablet';\n const isDesktop = screenSize === 'desktop' || screenSize === 'large' || screenSize === 'xlarge';\n const isLandscape = width > height;\n\n // Safe area insets from device detection utility\n const deviceInsets = getDeviceSafeAreaInsets();\n const safeArea: SafeAreaInsets = {\n top: deviceInsets.top,\n bottom: deviceInsets.bottom,\n left: isLandscape ? deviceInsets.left : 0,\n right: isLandscape ? deviceInsets.right : 0,\n };\n\n // Touch target sizes (iOS Human Interface Guidelines: 44x44px minimum)\n const touchTarget: TouchTargets = {\n small: 44, // Minimum iOS guideline\n medium: isSmallMobile ? 50 : 60,\n large: isSmallMobile ? 60 : 80,\n };\n\n // Calculate responsive values using centralized scaling system\n const responsiveValues = calculateResponsiveValues(width);\n\n // Map to existing font size structure for backward compatibility\n const fontSize: FontSizes = {\n small: responsiveValues.fontSize.small,\n body: responsiveValues.fontSize.body,\n title: responsiveValues.fontSize.title,\n hero: responsiveValues.fontSize.hero,\n hud: responsiveValues.fontSize.hud,\n };\n\n // Map to existing spacing structure for backward compatibility\n const spacing: Spacing = {\n xs: responsiveValues.spacing.xs,\n sm: responsiveValues.spacing.sm,\n md: responsiveValues.spacing.md,\n lg: responsiveValues.spacing.lg,\n xl: responsiveValues.spacing.xl,\n };\n\n // Get transition string for smooth resizing\n const transition = createTransitionString();\n\n return {\n isMobile,\n isSmallMobile,\n isTablet,\n isDesktop,\n isLandscape,\n screenSize,\n safeArea,\n touchTarget,\n fontSize,\n spacing,\n transition,\n viewport: { width, height },\n };\n }, [width, height]);\n}\n\n/**\n * Calculate available content area after safe areas and HUD\n * \n * @param layout - Responsive layout configuration\n * @param hudHeight - Height reserved for HUD elements\n * @param controlsHeight - Height reserved for control elements\n * @returns Available content dimensions\n */\nexport function useContentArea(\n layout: ResponsiveLayout,\n hudHeight: number = 0,\n controlsHeight: number = 0\n) {\n return useMemo(() => {\n const totalVerticalReserved =\n layout.safeArea.top +\n layout.safeArea.bottom +\n hudHeight +\n controlsHeight +\n layout.spacing.md * 2; // Padding\n\n const totalHorizontalReserved =\n layout.safeArea.left + layout.safeArea.right + layout.spacing.md * 2;\n\n return {\n width: layout.viewport.width - totalHorizontalReserved,\n height: layout.viewport.height - totalVerticalReserved,\n x: layout.safeArea.left + layout.spacing.md,\n y: layout.safeArea.top + hudHeight + layout.spacing.md,\n };\n }, [layout, hudHeight, controlsHeight]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAM,yBAAyB;;;;;;;;;AAU/B,IAAa,cAAc,OAAO,OAAO;;CAEvC,cAAc;;CAEd,QAAQ,uBAAuB;;CAE/B,QAAQ,uBAAuB;;CAE/B,SAAS,uBAAuB;CACjC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2IF,SAAgB,oBACd,OACA,QACkB;CAClB,OAAO,cAAc;EAEnB,MAAM,aAAa,cAAc,MAAM;EAIvC,MAAM,WAAW,yBAAyB;EAE1C,MAAM,gBAAgB,SAAS;EAC/B,MAAM,WAAW,eAAe;EAChC,MAAM,YAAY,eAAe,aAAa,eAAe,WAAW,eAAe;EACvF,MAAM,cAAc,QAAQ;EAG5B,MAAM,eAAe,mBAAyB;EAC9C,MAAM,WAA2B;GAC/B,KAAK,aAAa;GAClB,QAAQ,aAAa;GACrB,MAAM,cAAc,aAAa,OAAO;GACxC,OAAO,cAAc,aAAa,QAAQ;GAC3C;EAGD,MAAM,cAA4B;GAChC,OAAO;GACP,QAAQ,gBAAgB,KAAK;GAC7B,OAAO,gBAAgB,KAAK;GAC7B;EAGD,MAAM,mBAAmB,0BAA0B,MAAM;EAuBzD,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,UAAA;IA5BA,OAAO,iBAAiB,SAAS;IACjC,MAAM,iBAAiB,SAAS;IAChC,OAAO,iBAAiB,SAAS;IACjC,MAAM,iBAAiB,SAAS;IAChC,KAAK,iBAAiB,SAAS;IAwB/B;GACA,SAAA;IApBA,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAgB7B;GACA,YAbiB,wBAajB;GACA,UAAU;IAAE;IAAO;IAAQ;GAC5B;IACA,CAAC,OAAO,OAAO,CAAC;;;;;;;;;;AAWrB,SAAgB,eACd,QACA,YAAoB,GACpB,iBAAyB,GACzB;CACA,OAAO,cAAc;EACnB,MAAM,wBACJ,OAAO,SAAS,MAChB,OAAO,SAAS,SAChB,YACA,iBACA,OAAO,QAAQ,KAAK;EAEtB,MAAM,0BACJ,OAAO,SAAS,OAAO,OAAO,SAAS,QAAQ,OAAO,QAAQ,KAAK;EAErE,OAAO;GACL,OAAO,OAAO,SAAS,QAAQ;GAC/B,QAAQ,OAAO,SAAS,SAAS;GACjC,GAAG,OAAO,SAAS,OAAO,OAAO,QAAQ;GACzC,GAAG,OAAO,SAAS,MAAM,YAAY,OAAO,QAAQ;GACrD;IACA;EAAC;EAAQ;EAAW;EAAe,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useRoundTransition.js","names":[],"sources":["../../src/hooks/useRoundTransition.ts"],"sourcesContent":["/**\n * useRoundTransition Hook - Manages round transition state and timing\n *\n * Korean: 라운드 전환 훅 (Round Transition Hook)\n *\n * Handles the state machine for round transitions:\n * - idle: Normal combat state\n * - announcing: Showing round announcement\n * - countdown: Counting down to next round\n * - transitioning: Brief transition to next round\n *\n * @module hooks/useRoundTransition\n * @category Combat Hooks\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { PlayerState } from \"../systems\";\n\n/**\n * Round transition states\n *\n * Korean: 라운드 전환 상태\n */\nexport type RoundTransitionState =\n | \"idle\"\n | \"announcing\"\n | \"countdown\"\n | \"transitioning\";\n\n/**\n * Round transition configuration\n */\nexport interface RoundTransitionConfig {\n /** Duration of announcement display in seconds */\n readonly announcementDuration?: number;\n /** Duration of countdown in seconds */\n readonly countdownDuration?: number;\n /** Duration of transition phase in milliseconds */\n readonly transitionDuration?: number;\n}\n\n/**\n * Round transition hook state\n */\nexport interface UseRoundTransitionResult {\n /** Current transition state */\n readonly transitionState: RoundTransitionState;\n /** Whether announcement should be visible */\n readonly showAnnouncement: boolean;\n /** Start round transition sequence */\n readonly startTransition: (\n winner: PlayerState | null,\n roundNumber: number\n ) => void;\n /** Skip countdown and proceed immediately */\n readonly skipCountdown: () => void;\n /** Reset transition state to idle */\n readonly resetTransition: () => void;\n /** Round winner for current transition */\n readonly roundWinner: PlayerState | null;\n /** Round number for current transition */\n readonly currentRoundNumber: number;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<RoundTransitionConfig> = {\n announcementDuration: 2,\n countdownDuration: 3,\n transitionDuration: 500,\n};\n\n/**\n * useRoundTransition Hook\n *\n * Manages the complete round transition flow:\n * 1. Idle state during normal combat\n * 2. Announcing state shows round results\n * 3. Countdown state counts down to next round\n * 4. Transitioning state briefly transitions to next round\n * 5. Returns to idle for next round\n *\n * @param config - Configuration for transition timings\n * @param onTransitionComplete - Callback when transition completes\n * @returns Round transition state and control functions\n *\n * @example\n * ```typescript\n * const {\n * transitionState,\n * showAnnouncement,\n * startTransition,\n * skipCountdown,\n * } = useRoundTransition(\n * { countdownDuration: 3 },\n * () => {\n * // Start next round\n * initializeNextRound();\n * }\n * );\n *\n * // When round ends\n * startTransition(winner, roundNumber);\n * ```\n */\nexport function useRoundTransition(\n config: RoundTransitionConfig = {},\n onTransitionComplete?: () => void\n): UseRoundTransitionResult {\n const mergedConfig = useMemo(\n () => ({ ...DEFAULT_CONFIG, ...config }),\n [config]\n );\n\n const [transitionState, setTransitionState] =\n useState<RoundTransitionState>(\"idle\");\n const [roundWinner, setRoundWinner] = useState<PlayerState | null>(null);\n const [currentRoundNumber, setCurrentRoundNumber] = useState(0);\n\n // Use ref to track if we should continue countdown\n const countdownActive = useRef(false);\n const countdownTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const transitionTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n /**\n * Clear all active timers\n */\n const clearTimers = useCallback(() => {\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n if (transitionTimer.current) {\n clearTimeout(transitionTimer.current);\n transitionTimer.current = null;\n }\n }, []);\n\n /**\n * Start the transition sequence\n */\n const startTransition = useCallback(\n (winner: PlayerState | null, roundNumber: number) => {\n clearTimers();\n setRoundWinner(winner);\n setCurrentRoundNumber(roundNumber);\n setTransitionState(\"announcing\");\n\n // Move to countdown after announcement\n const announcementTimer = setTimeout(() => {\n setTransitionState(\"countdown\");\n countdownActive.current = true;\n\n // Countdown will be managed by the RoundAnnouncement component\n // After countdownDuration, move to transitioning state\n countdownTimer.current = setTimeout(() => {\n countdownActive.current = false;\n setTransitionState(\"transitioning\");\n\n // Complete transition after brief delay\n transitionTimer.current = setTimeout(() => {\n setTransitionState(\"idle\");\n onTransitionComplete?.();\n }, mergedConfig.transitionDuration);\n }, mergedConfig.countdownDuration * 1000);\n }, mergedConfig.announcementDuration * 1000);\n\n // Store timer ref for cleanup\n transitionTimer.current = announcementTimer;\n },\n [clearTimers, mergedConfig, onTransitionComplete]\n );\n\n /**\n * Skip countdown and proceed immediately to next round\n */\n const skipCountdown = useCallback(() => {\n if (transitionState === \"announcing\" || transitionState === \"countdown\") {\n clearTimers();\n countdownActive.current = false;\n setTransitionState(\"transitioning\");\n\n // Complete transition after brief delay\n transitionTimer.current = setTimeout(() => {\n setTransitionState(\"idle\");\n onTransitionComplete?.();\n }, mergedConfig.transitionDuration);\n }\n }, [\n transitionState,\n clearTimers,\n mergedConfig.transitionDuration,\n onTransitionComplete,\n ]);\n\n /**\n * Reset transition to idle state\n */\n const resetTransition = useCallback(() => {\n clearTimers();\n countdownActive.current = false;\n setTransitionState(\"idle\");\n setRoundWinner(null);\n setCurrentRoundNumber(0);\n }, [clearTimers]);\n\n /**\n * Cleanup on unmount\n */\n useEffect(() => {\n return () => {\n clearTimers();\n countdownActive.current = false;\n };\n }, [clearTimers]);\n\n return {\n transitionState,\n showAnnouncement:\n transitionState === \"announcing\" || transitionState === \"countdown\",\n startTransition,\n skipCountdown,\n resetTransition,\n roundWinner,\n currentRoundNumber,\n };\n}\n\nexport default useRoundTransition;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmEA,IAAM,iBAAkD;CACtD,sBAAsB;CACtB,mBAAmB;CACnB,oBAAoB;CACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCD,SAAgB,mBACd,SAAgC,EAAE,EAClC,sBAC0B;CAC1B,MAAM,eAAe,eACZ;EAAE,GAAG;EAAgB,GAAG;EAAQ,GACvC,CAAC,OAAO,CACT;CAED,MAAM,CAAC,iBAAiB,sBACtB,SAA+B,OAAO;CACxC,MAAM,CAAC,aAAa,kBAAkB,SAA6B,KAAK;CACxE,MAAM,CAAC,oBAAoB,yBAAyB,SAAS,EAAE;CAG/D,MAAM,kBAAkB,OAAO,MAAM;CACrC,MAAM,iBAAiB,OAA6C,KAAK;CACzE,MAAM,kBAAkB,OAA6C,KAAK;;;;CAK1E,MAAM,cAAc,kBAAkB;AACpC,MAAI,eAAe,SAAS;AAC1B,iBAAc,eAAe,QAAQ;AACrC,kBAAe,UAAU;;AAE3B,MAAI,gBAAgB,SAAS;AAC3B,gBAAa,gBAAgB,QAAQ;AACrC,mBAAgB,UAAU;;IAE3B,EAAE,CAAC;;;;CAKN,MAAM,kBAAkB,aACrB,QAA4B,gBAAwB;AACnD,eAAa;AACb,iBAAe,OAAO;AACtB,wBAAsB,YAAY;AAClC,qBAAmB,aAAa;AAsBhC,kBAAgB,UAnBU,iBAAiB;AACzC,sBAAmB,YAAY;AAC/B,mBAAgB,UAAU;AAI1B,kBAAe,UAAU,iBAAiB;AACxC,oBAAgB,UAAU;AAC1B,uBAAmB,gBAAgB;AAGnC,oBAAgB,UAAU,iBAAiB;AACzC,wBAAmB,OAAO;AAC1B,6BAAwB;OACvB,aAAa,mBAAmB;MAClC,aAAa,oBAAoB,IAAK;KACxC,aAAa,uBAAuB,IAGb;IAE5B;EAAC;EAAa;EAAc;EAAqB,CAClD;;;;CAKD,MAAM,gBAAgB,kBAAkB;AACtC,MAAI,oBAAoB,gBAAgB,oBAAoB,aAAa;AACvE,gBAAa;AACb,mBAAgB,UAAU;AAC1B,sBAAmB,gBAAgB;AAGnC,mBAAgB,UAAU,iBAAiB;AACzC,uBAAmB,OAAO;AAC1B,4BAAwB;MACvB,aAAa,mBAAmB;;IAEpC;EACD;EACA;EACA,aAAa;EACb;EACD,CAAC;;;;CAKF,MAAM,kBAAkB,kBAAkB;AACxC,eAAa;AACb,kBAAgB,UAAU;AAC1B,qBAAmB,OAAO;AAC1B,iBAAe,KAAK;AACpB,wBAAsB,EAAE;IACvB,CAAC,YAAY,CAAC;;;;AAKjB,iBAAgB;AACd,eAAa;AACX,gBAAa;AACb,mBAAgB,UAAU;;IAE3B,CAAC,YAAY,CAAC;AAEjB,QAAO;EACL;EACA,kBACE,oBAAoB,gBAAgB,oBAAoB;EAC1D;EACA;EACA;EACA;EACA;EACD"}
1
+ {"version":3,"file":"useRoundTransition.js","names":[],"sources":["../../src/hooks/useRoundTransition.ts"],"sourcesContent":["/**\n * useRoundTransition Hook - Manages round transition state and timing\n *\n * Korean: 라운드 전환 훅 (Round Transition Hook)\n *\n * Handles the state machine for round transitions:\n * - idle: Normal combat state\n * - announcing: Showing round announcement\n * - countdown: Counting down to next round\n * - transitioning: Brief transition to next round\n *\n * @module hooks/useRoundTransition\n * @category Combat Hooks\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { PlayerState } from \"../systems\";\n\n/**\n * Round transition states\n *\n * Korean: 라운드 전환 상태\n */\nexport type RoundTransitionState =\n | \"idle\"\n | \"announcing\"\n | \"countdown\"\n | \"transitioning\";\n\n/**\n * Round transition configuration\n */\nexport interface RoundTransitionConfig {\n /** Duration of announcement display in seconds */\n readonly announcementDuration?: number;\n /** Duration of countdown in seconds */\n readonly countdownDuration?: number;\n /** Duration of transition phase in milliseconds */\n readonly transitionDuration?: number;\n}\n\n/**\n * Round transition hook state\n */\nexport interface UseRoundTransitionResult {\n /** Current transition state */\n readonly transitionState: RoundTransitionState;\n /** Whether announcement should be visible */\n readonly showAnnouncement: boolean;\n /** Start round transition sequence */\n readonly startTransition: (\n winner: PlayerState | null,\n roundNumber: number\n ) => void;\n /** Skip countdown and proceed immediately */\n readonly skipCountdown: () => void;\n /** Reset transition state to idle */\n readonly resetTransition: () => void;\n /** Round winner for current transition */\n readonly roundWinner: PlayerState | null;\n /** Round number for current transition */\n readonly currentRoundNumber: number;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<RoundTransitionConfig> = {\n announcementDuration: 2,\n countdownDuration: 3,\n transitionDuration: 500,\n};\n\n/**\n * useRoundTransition Hook\n *\n * Manages the complete round transition flow:\n * 1. Idle state during normal combat\n * 2. Announcing state shows round results\n * 3. Countdown state counts down to next round\n * 4. Transitioning state briefly transitions to next round\n * 5. Returns to idle for next round\n *\n * @param config - Configuration for transition timings\n * @param onTransitionComplete - Callback when transition completes\n * @returns Round transition state and control functions\n *\n * @example\n * ```typescript\n * const {\n * transitionState,\n * showAnnouncement,\n * startTransition,\n * skipCountdown,\n * } = useRoundTransition(\n * { countdownDuration: 3 },\n * () => {\n * // Start next round\n * initializeNextRound();\n * }\n * );\n *\n * // When round ends\n * startTransition(winner, roundNumber);\n * ```\n */\nexport function useRoundTransition(\n config: RoundTransitionConfig = {},\n onTransitionComplete?: () => void\n): UseRoundTransitionResult {\n const mergedConfig = useMemo(\n () => ({ ...DEFAULT_CONFIG, ...config }),\n [config]\n );\n\n const [transitionState, setTransitionState] =\n useState<RoundTransitionState>(\"idle\");\n const [roundWinner, setRoundWinner] = useState<PlayerState | null>(null);\n const [currentRoundNumber, setCurrentRoundNumber] = useState(0);\n\n // Use ref to track if we should continue countdown\n const countdownActive = useRef(false);\n const countdownTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const transitionTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n /**\n * Clear all active timers\n */\n const clearTimers = useCallback(() => {\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n if (transitionTimer.current) {\n clearTimeout(transitionTimer.current);\n transitionTimer.current = null;\n }\n }, []);\n\n /**\n * Start the transition sequence\n */\n const startTransition = useCallback(\n (winner: PlayerState | null, roundNumber: number) => {\n clearTimers();\n setRoundWinner(winner);\n setCurrentRoundNumber(roundNumber);\n setTransitionState(\"announcing\");\n\n // Move to countdown after announcement\n const announcementTimer = setTimeout(() => {\n setTransitionState(\"countdown\");\n countdownActive.current = true;\n\n // Countdown will be managed by the RoundAnnouncement component\n // After countdownDuration, move to transitioning state\n countdownTimer.current = setTimeout(() => {\n countdownActive.current = false;\n setTransitionState(\"transitioning\");\n\n // Complete transition after brief delay\n transitionTimer.current = setTimeout(() => {\n setTransitionState(\"idle\");\n onTransitionComplete?.();\n }, mergedConfig.transitionDuration);\n }, mergedConfig.countdownDuration * 1000);\n }, mergedConfig.announcementDuration * 1000);\n\n // Store timer ref for cleanup\n transitionTimer.current = announcementTimer;\n },\n [clearTimers, mergedConfig, onTransitionComplete]\n );\n\n /**\n * Skip countdown and proceed immediately to next round\n */\n const skipCountdown = useCallback(() => {\n if (transitionState === \"announcing\" || transitionState === \"countdown\") {\n clearTimers();\n countdownActive.current = false;\n setTransitionState(\"transitioning\");\n\n // Complete transition after brief delay\n transitionTimer.current = setTimeout(() => {\n setTransitionState(\"idle\");\n onTransitionComplete?.();\n }, mergedConfig.transitionDuration);\n }\n }, [\n transitionState,\n clearTimers,\n mergedConfig.transitionDuration,\n onTransitionComplete,\n ]);\n\n /**\n * Reset transition to idle state\n */\n const resetTransition = useCallback(() => {\n clearTimers();\n countdownActive.current = false;\n setTransitionState(\"idle\");\n setRoundWinner(null);\n setCurrentRoundNumber(0);\n }, [clearTimers]);\n\n /**\n * Cleanup on unmount\n */\n useEffect(() => {\n return () => {\n clearTimers();\n countdownActive.current = false;\n };\n }, [clearTimers]);\n\n return {\n transitionState,\n showAnnouncement:\n transitionState === \"announcing\" || transitionState === \"countdown\",\n startTransition,\n skipCountdown,\n resetTransition,\n roundWinner,\n currentRoundNumber,\n };\n}\n\nexport default useRoundTransition;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmEA,IAAM,iBAAkD;CACtD,sBAAsB;CACtB,mBAAmB;CACnB,oBAAoB;CACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCD,SAAgB,mBACd,SAAgC,EAAE,EAClC,sBAC0B;CAC1B,MAAM,eAAe,eACZ;EAAE,GAAG;EAAgB,GAAG;EAAQ,GACvC,CAAC,OAAO,CACT;CAED,MAAM,CAAC,iBAAiB,sBACtB,SAA+B,OAAO;CACxC,MAAM,CAAC,aAAa,kBAAkB,SAA6B,KAAK;CACxE,MAAM,CAAC,oBAAoB,yBAAyB,SAAS,EAAE;CAG/D,MAAM,kBAAkB,OAAO,MAAM;CACrC,MAAM,iBAAiB,OAA6C,KAAK;CACzE,MAAM,kBAAkB,OAA6C,KAAK;;;;CAK1E,MAAM,cAAc,kBAAkB;EACpC,IAAI,eAAe,SAAS;GAC1B,cAAc,eAAe,QAAQ;GACrC,eAAe,UAAU;;EAE3B,IAAI,gBAAgB,SAAS;GAC3B,aAAa,gBAAgB,QAAQ;GACrC,gBAAgB,UAAU;;IAE3B,EAAE,CAAC;;;;CAKN,MAAM,kBAAkB,aACrB,QAA4B,gBAAwB;EACnD,aAAa;EACb,eAAe,OAAO;EACtB,sBAAsB,YAAY;EAClC,mBAAmB,aAAa;EAsBhC,gBAAgB,UAnBU,iBAAiB;GACzC,mBAAmB,YAAY;GAC/B,gBAAgB,UAAU;GAI1B,eAAe,UAAU,iBAAiB;IACxC,gBAAgB,UAAU;IAC1B,mBAAmB,gBAAgB;IAGnC,gBAAgB,UAAU,iBAAiB;KACzC,mBAAmB,OAAO;KAC1B,wBAAwB;OACvB,aAAa,mBAAmB;MAClC,aAAa,oBAAoB,IAAK;KACxC,aAAa,uBAAuB,IAGb;IAE5B;EAAC;EAAa;EAAc;EAAqB,CAClD;;;;CAKD,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,oBAAoB,gBAAgB,oBAAoB,aAAa;GACvE,aAAa;GACb,gBAAgB,UAAU;GAC1B,mBAAmB,gBAAgB;GAGnC,gBAAgB,UAAU,iBAAiB;IACzC,mBAAmB,OAAO;IAC1B,wBAAwB;MACvB,aAAa,mBAAmB;;IAEpC;EACD;EACA;EACA,aAAa;EACb;EACD,CAAC;;;;CAKF,MAAM,kBAAkB,kBAAkB;EACxC,aAAa;EACb,gBAAgB,UAAU;EAC1B,mBAAmB,OAAO;EAC1B,eAAe,KAAK;EACpB,sBAAsB,EAAE;IACvB,CAAC,YAAY,CAAC;;;;CAKjB,gBAAgB;EACd,aAAa;GACX,aAAa;GACb,gBAAgB,UAAU;;IAE3B,CAAC,YAAY,CAAC;CAEjB,OAAO;EACL;EACA,kBACE,oBAAoB,gBAAgB,oBAAoB;EAC1D;EACA;EACA;EACA;EACA;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"useSkeletalAnimation.js","names":[],"sources":["../../src/hooks/useSkeletalAnimation.ts"],"sourcesContent":["/**\n * useSkeletalAnimation - Shared hook for skeletal animation management\n *\n * Centralizes skeletal animation state and frame updates for player characters.\n * Reduces code duplication across SkeletalPlayer3D, Player3DWithTransitions,\n * and screen components.\n *\n * PHASE 2: Now uses cached interpolation and batch bone updates for 60fps performance\n *\n * @module hooks/useSkeletalAnimation\n * @category Hooks\n * @korean 골격애니메이션훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n batchUpdateBones,\n getAnimation,\n getAnimationByName,\n getAttackAnimation,\n getDefensiveAnimation,\n getFootworkAnimation,\n getStepAnimation,\n interpolateKeyframeCached,\n performanceMonitor,\n} from \"../systems/animation\";\nimport { applyLaterality } from \"../systems/animation/core/LateralityTransform\";\nimport type { TrigramStance } from \"../types/common\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\nimport type {\n SkeletalAnimation,\n SkeletalAnimationState,\n SkeletalRig,\n} from \"../types/skeletal\";\n\n/**\n * Options for useSkeletalAnimation hook\n * @korean 골격애니메이션훅옵션\n */\nexport interface UseSkeletalAnimationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current player stance for trigram-specific idle animations */\n readonly stance?: TrigramStance;\n /**\n * Stance laterality (left or right foot forward)\n *\n * - \"left\": Left foot forward (왼발서기 - Oenbal Seogi)\n * - \"right\": Right foot forward (오른발서기 - Oreun Bal Seogi)\n *\n * This affects animation mirroring - techniques will be mirrored\n * appropriately based on the laterality, creating 16 distinct stance\n * configurations (8 trigrams × 2 laterality).\n *\n * **Korean**: 측면성 (Cheugmyeonseong - Laterality/Sidedness)\n */\n readonly laterality?: \"left\" | \"right\";\n /** Callback when animation completes */\n readonly onAnimationComplete?: () => void;\n}\n\n/**\n * Return type for useSkeletalAnimation hook\n * @korean 골격애니메이션훅반환타입\n */\nexport interface UseSkeletalAnimationReturn {\n /** Current animation state */\n readonly animState: SkeletalAnimationState;\n /** Animation time reference (seconds) */\n readonly animTimeRef: React.MutableRefObject<number>;\n /** Update animation and apply to rig (call in useFrame) */\n readonly updateRigAnimation: (rig: SkeletalRig, delta: number) => void;\n /** Diagonal rotation override for step animations */\n readonly diagonalRotationY: number | null;\n}\n\n/**\n * Set of diagonal step animations for O(1) lookup\n * @korean 대각선스텝애니메이션집합\n */\nconst DIAGONAL_STEP_ANIMATIONS = new Set([\n \"step_forward_left\",\n \"step_forward_right\",\n \"step_back_left\",\n \"step_back_right\",\n]);\n\n/**\n * useSkeletalAnimation hook\n *\n * Manages skeletal animation state and frame updates for player characters.\n * Handles animation selection based on player state (idle, walk, attack, etc.)\n * and applies keyframes to the skeletal rig.\n *\n * @param options - Animation options\n * @returns Animation state and update function\n *\n * @example\n * ```tsx\n * const { animState, animTimeRef, updateRigAnimation, diagonalRotationY } =\n * useSkeletalAnimation({\n * currentAnimation: \"walk\",\n * isBlocking: false,\n * onAnimationComplete: () => console.log(\"Animation completed\"),\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateRigAnimation(rig, delta);\n * });\n * ```\n *\n * @korean 골격애니메이션훅\n */\nexport function useSkeletalAnimation(\n options: UseSkeletalAnimationOptions,\n): UseSkeletalAnimationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stance,\n laterality = \"right\",\n onAnimationComplete,\n } = options;\n\n // Animation state\n const [animState, setAnimState] = useState<SkeletalAnimationState>({\n currentAnimation: null,\n currentTime: 0,\n isPlaying: false,\n playbackSpeed: 1.0,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n\n // Animation time ref (updated at 60fps without triggering re-renders)\n const animTimeRef = useRef(0);\n\n // Diagonal step rotation override (Y-axis rotation in radians)\n const [diagonalRotationY, setDiagonalRotationY] = useState<number | null>(\n null,\n );\n\n // Load animation when currentAnimation, blocking state, or laterality changes\n useEffect(() => {\n // Reset animation time whenever animation changes\n animTimeRef.current = 0;\n\n let selectedAnim: SkeletalAnimation | null = null;\n let playbackSpeed: number;\n let shouldClearDiagonalRotation = true;\n\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - first check stance-specific attacks, then generic\n selectedAnim =\n getAttackAnimation(attackAnimation) ??\n getAnimation(attackAnimation) ??\n null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Block/defend animation - check stance-specific defensive animations first\n // If attackAnimation contains a defensive animation name, use it\n if (attackAnimation) {\n selectedAnim = getDefensiveAnimation(attackAnimation) ?? null;\n }\n // Fall back to generic block animation\n selectedAnim ??= getAnimation(\"block\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"idle\") {\n // Idle animation - use trigram-specific stance idle if stance is provided\n // Otherwise fall back to generic idle breathing animation\n if (stance) {\n const stanceIdleAnim = `stance_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceIdleAnim) ?? null;\n }\n // Fall back to generic idle if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"idle\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n } else if (currentAnimation === \"walk\") {\n // Walking animation - use trigram-specific walk if stance is provided\n if (stance) {\n const stanceWalkAnim = `walk_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceWalkAnim) ?? null;\n }\n // Fall back to generic walk if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"walk\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"run\") {\n // Running animation - use trigram-specific run if stance is provided\n if (stance) {\n const stanceRunAnim = `run_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceRunAnim) ?? null;\n }\n // Fall back to generic run if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"run\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"fall_\")) {\n // Fall animations - directional falls from BasicAnimations\n selectedAnim = getAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"stance_change\") {\n // Stance change animation\n selectedAnim = getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 1.2; // Slightly faster for responsiveness\n } else if (currentAnimation === \"hit\") {\n // Hit reaction - stop animation\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n return;\n } else if (currentAnimation?.startsWith(\"step_\")) {\n // Tactical step animation\n selectedAnim = getStepAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n\n // Handle diagonal step rotation\n if (DIAGONAL_STEP_ANIMATIONS.has(currentAnimation)) {\n shouldClearDiagonalRotation = false;\n // Diagonal rotation will be handled by parent component\n // This hook only manages the flag\n }\n } else if (currentAnimation?.startsWith(\"footwork_\")) {\n // Footwork pattern animation\n selectedAnim = getFootworkAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"stance_\")) {\n // Stance-specific idle animation with proper biomechanics\n // Use getAnimationByName which searches ALL_ANIMATIONS (includes STANCE_ANIMATIONS)\n selectedAnim = getAnimationByName(currentAnimation) ?? null;\n playbackSpeed = 0.5; // Slow breathing animation for stance idle\n } else {\n // Idle animation (fallback)\n selectedAnim = getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n }\n\n // Apply laterality transformation if selectedAnim exists\n // laterality directly affects animation mirroring:\n // \"left\" = left foot forward (왼발서기) → animations mirrored\n // \"right\" = right foot forward (오른발서기) → base animations (default)\n if (selectedAnim) {\n selectedAnim = applyLaterality(selectedAnim, laterality);\n }\n\n // Clear diagonal rotation for non-diagonal animations\n if (shouldClearDiagonalRotation) {\n setDiagonalRotationY(null);\n }\n\n setAnimState({\n currentAnimation: selectedAnim,\n currentTime: 0,\n isPlaying: true,\n playbackSpeed,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n }, [currentAnimation, attackAnimation, isBlocking, stance, laterality]);\n\n // Update animation and apply to rig (called at 60fps in useFrame)\n // PHASE 2: Now uses cached interpolation and batch bone updates\n const updateRigAnimation = useCallback(\n (targetRig: SkeletalRig, delta: number) => {\n if (animState.isPlaying && animState.currentAnimation) {\n const frameStartTime = performance.now();\n\n // Advance animation time\n let newTime = animTimeRef.current + delta * animState.playbackSpeed;\n let completed = false;\n\n // Handle looping or completion\n if (newTime >= animState.currentAnimation.duration) {\n if (animState.currentAnimation.loop) {\n newTime = newTime % animState.currentAnimation.duration;\n } else {\n newTime = animState.currentAnimation.duration;\n completed = true;\n }\n }\n\n // Use cached interpolation for 90%+ cache hit rate\n // Use animation.name as the unique identifier\n const keyframe = interpolateKeyframeCached(\n animState.currentAnimation.name,\n animState.currentAnimation,\n newTime,\n );\n\n if (keyframe) {\n // Batch update bones (60% faster than individual updates)\n batchUpdateBones(targetRig, keyframe);\n }\n\n // Update time ref\n animTimeRef.current = newTime;\n\n // Record performance metrics\n const frameTime = performance.now() - frameStartTime;\n performanceMonitor.recordFrame(frameTime);\n\n // Handle animation completion\n if (completed) {\n animTimeRef.current = 0;\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n\n // Trigger callback\n if (onAnimationComplete) {\n onAnimationComplete();\n }\n }\n }\n },\n [animState, onAnimationComplete],\n );\n\n return {\n animState,\n animTimeRef,\n updateRigAnimation,\n diagonalRotationY,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useSkeletalAnimation.js","names":[],"sources":["../../src/hooks/useSkeletalAnimation.ts"],"sourcesContent":["/**\n * useSkeletalAnimation - Shared hook for skeletal animation management\n *\n * Centralizes skeletal animation state and frame updates for player characters.\n * Reduces code duplication across SkeletalPlayer3D, Player3DWithTransitions,\n * and screen components.\n *\n * PHASE 2: Now uses cached interpolation and batch bone updates for 60fps performance\n *\n * @module hooks/useSkeletalAnimation\n * @category Hooks\n * @korean 골격애니메이션훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n batchUpdateBones,\n getAnimation,\n getAnimationByName,\n getAttackAnimation,\n getDefensiveAnimation,\n getFootworkAnimation,\n getStepAnimation,\n interpolateKeyframeCached,\n performanceMonitor,\n} from \"../systems/animation\";\nimport { applyLaterality } from \"../systems/animation/core/LateralityTransform\";\nimport type { TrigramStance } from \"../types/common\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\nimport type {\n SkeletalAnimation,\n SkeletalAnimationState,\n SkeletalRig,\n} from \"../types/skeletal\";\n\n/**\n * Options for useSkeletalAnimation hook\n * @korean 골격애니메이션훅옵션\n */\nexport interface UseSkeletalAnimationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current player stance for trigram-specific idle animations */\n readonly stance?: TrigramStance;\n /**\n * Stance laterality (left or right foot forward)\n *\n * - \"left\": Left foot forward (왼발서기 - Oenbal Seogi)\n * - \"right\": Right foot forward (오른발서기 - Oreun Bal Seogi)\n *\n * This affects animation mirroring - techniques will be mirrored\n * appropriately based on the laterality, creating 16 distinct stance\n * configurations (8 trigrams × 2 laterality).\n *\n * **Korean**: 측면성 (Cheugmyeonseong - Laterality/Sidedness)\n */\n readonly laterality?: \"left\" | \"right\";\n /** Callback when animation completes */\n readonly onAnimationComplete?: () => void;\n}\n\n/**\n * Return type for useSkeletalAnimation hook\n * @korean 골격애니메이션훅반환타입\n */\nexport interface UseSkeletalAnimationReturn {\n /** Current animation state */\n readonly animState: SkeletalAnimationState;\n /** Animation time reference (seconds) */\n readonly animTimeRef: React.MutableRefObject<number>;\n /** Update animation and apply to rig (call in useFrame) */\n readonly updateRigAnimation: (rig: SkeletalRig, delta: number) => void;\n /** Diagonal rotation override for step animations */\n readonly diagonalRotationY: number | null;\n}\n\n/**\n * Set of diagonal step animations for O(1) lookup\n * @korean 대각선스텝애니메이션집합\n */\nconst DIAGONAL_STEP_ANIMATIONS = new Set([\n \"step_forward_left\",\n \"step_forward_right\",\n \"step_back_left\",\n \"step_back_right\",\n]);\n\n/**\n * useSkeletalAnimation hook\n *\n * Manages skeletal animation state and frame updates for player characters.\n * Handles animation selection based on player state (idle, walk, attack, etc.)\n * and applies keyframes to the skeletal rig.\n *\n * @param options - Animation options\n * @returns Animation state and update function\n *\n * @example\n * ```tsx\n * const { animState, animTimeRef, updateRigAnimation, diagonalRotationY } =\n * useSkeletalAnimation({\n * currentAnimation: \"walk\",\n * isBlocking: false,\n * onAnimationComplete: () => console.log(\"Animation completed\"),\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateRigAnimation(rig, delta);\n * });\n * ```\n *\n * @korean 골격애니메이션훅\n */\nexport function useSkeletalAnimation(\n options: UseSkeletalAnimationOptions,\n): UseSkeletalAnimationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stance,\n laterality = \"right\",\n onAnimationComplete,\n } = options;\n\n // Animation state\n const [animState, setAnimState] = useState<SkeletalAnimationState>({\n currentAnimation: null,\n currentTime: 0,\n isPlaying: false,\n playbackSpeed: 1.0,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n\n // Animation time ref (updated at 60fps without triggering re-renders)\n const animTimeRef = useRef(0);\n\n // Diagonal step rotation override (Y-axis rotation in radians)\n const [diagonalRotationY, setDiagonalRotationY] = useState<number | null>(\n null,\n );\n\n // Load animation when currentAnimation, blocking state, or laterality changes\n useEffect(() => {\n // Reset animation time whenever animation changes\n animTimeRef.current = 0;\n\n let selectedAnim: SkeletalAnimation | null = null;\n let playbackSpeed: number;\n let shouldClearDiagonalRotation = true;\n\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - first check stance-specific attacks, then generic\n selectedAnim =\n getAttackAnimation(attackAnimation) ??\n getAnimation(attackAnimation) ??\n null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Block/defend animation - check stance-specific defensive animations first\n // If attackAnimation contains a defensive animation name, use it\n if (attackAnimation) {\n selectedAnim = getDefensiveAnimation(attackAnimation) ?? null;\n }\n // Fall back to generic block animation\n selectedAnim ??= getAnimation(\"block\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"idle\") {\n // Idle animation - use trigram-specific stance idle if stance is provided\n // Otherwise fall back to generic idle breathing animation\n if (stance) {\n const stanceIdleAnim = `stance_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceIdleAnim) ?? null;\n }\n // Fall back to generic idle if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"idle\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n } else if (currentAnimation === \"walk\") {\n // Walking animation - use trigram-specific walk if stance is provided\n if (stance) {\n const stanceWalkAnim = `walk_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceWalkAnim) ?? null;\n }\n // Fall back to generic walk if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"walk\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"run\") {\n // Running animation - use trigram-specific run if stance is provided\n if (stance) {\n const stanceRunAnim = `run_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceRunAnim) ?? null;\n }\n // Fall back to generic run if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"run\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"fall_\")) {\n // Fall animations - directional falls from BasicAnimations\n selectedAnim = getAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"stance_change\") {\n // Stance change animation\n selectedAnim = getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 1.2; // Slightly faster for responsiveness\n } else if (currentAnimation === \"hit\") {\n // Hit reaction - stop animation\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n return;\n } else if (currentAnimation?.startsWith(\"step_\")) {\n // Tactical step animation\n selectedAnim = getStepAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n\n // Handle diagonal step rotation\n if (DIAGONAL_STEP_ANIMATIONS.has(currentAnimation)) {\n shouldClearDiagonalRotation = false;\n // Diagonal rotation will be handled by parent component\n // This hook only manages the flag\n }\n } else if (currentAnimation?.startsWith(\"footwork_\")) {\n // Footwork pattern animation\n selectedAnim = getFootworkAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"stance_\")) {\n // Stance-specific idle animation with proper biomechanics\n // Use getAnimationByName which searches ALL_ANIMATIONS (includes STANCE_ANIMATIONS)\n selectedAnim = getAnimationByName(currentAnimation) ?? null;\n playbackSpeed = 0.5; // Slow breathing animation for stance idle\n } else {\n // Idle animation (fallback)\n selectedAnim = getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n }\n\n // Apply laterality transformation if selectedAnim exists\n // laterality directly affects animation mirroring:\n // \"left\" = left foot forward (왼발서기) → animations mirrored\n // \"right\" = right foot forward (오른발서기) → base animations (default)\n if (selectedAnim) {\n selectedAnim = applyLaterality(selectedAnim, laterality);\n }\n\n // Clear diagonal rotation for non-diagonal animations\n if (shouldClearDiagonalRotation) {\n setDiagonalRotationY(null);\n }\n\n setAnimState({\n currentAnimation: selectedAnim,\n currentTime: 0,\n isPlaying: true,\n playbackSpeed,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n }, [currentAnimation, attackAnimation, isBlocking, stance, laterality]);\n\n // Update animation and apply to rig (called at 60fps in useFrame)\n // PHASE 2: Now uses cached interpolation and batch bone updates\n const updateRigAnimation = useCallback(\n (targetRig: SkeletalRig, delta: number) => {\n if (animState.isPlaying && animState.currentAnimation) {\n const frameStartTime = performance.now();\n\n // Advance animation time\n let newTime = animTimeRef.current + delta * animState.playbackSpeed;\n let completed = false;\n\n // Handle looping or completion\n if (newTime >= animState.currentAnimation.duration) {\n if (animState.currentAnimation.loop) {\n newTime = newTime % animState.currentAnimation.duration;\n } else {\n newTime = animState.currentAnimation.duration;\n completed = true;\n }\n }\n\n // Use cached interpolation for 90%+ cache hit rate\n // Use animation.name as the unique identifier\n const keyframe = interpolateKeyframeCached(\n animState.currentAnimation.name,\n animState.currentAnimation,\n newTime,\n );\n\n if (keyframe) {\n // Batch update bones (60% faster than individual updates)\n batchUpdateBones(targetRig, keyframe);\n }\n\n // Update time ref\n animTimeRef.current = newTime;\n\n // Record performance metrics\n const frameTime = performance.now() - frameStartTime;\n performanceMonitor.recordFrame(frameTime);\n\n // Handle animation completion\n if (completed) {\n animTimeRef.current = 0;\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n\n // Trigger callback\n if (onAnimationComplete) {\n onAnimationComplete();\n }\n }\n }\n },\n [animState, onAnimationComplete],\n );\n\n return {\n animState,\n animTimeRef,\n updateRigAnimation,\n diagonalRotationY,\n };\n}\n"],"mappings":"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"}