blacktrigram 0.7.45 → 0.7.48

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 (450) 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.d.ts.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +22 -11
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  21. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  22. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  31. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  32. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  36. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  39. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  42. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  44. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  45. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  46. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  47. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  48. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  49. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  50. package/lib/components/screens/combat/helpers/AnimationUpdater.d.ts.map +1 -1
  51. package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
  52. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  53. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  54. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  55. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  56. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  57. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  58. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  59. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  60. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  61. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  62. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  63. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  64. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  65. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  66. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  67. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  68. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  69. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  70. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  71. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  72. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  74. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  75. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  76. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  77. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  78. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  79. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  84. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  85. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  86. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  87. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  88. package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
  89. package/lib/components/screens/training/TrainingScreen3D.js +1 -0
  90. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  91. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  93. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  94. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  96. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  97. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  98. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  99. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  100. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  101. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  102. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  103. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  104. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  105. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  106. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  107. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  108. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  109. package/lib/components/screens/training/hooks/useTrainingActions.d.ts +1 -0
  110. package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
  111. package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
  112. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  113. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  114. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  115. package/lib/components/shared/base/BaseButton.js.map +1 -1
  116. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  117. package/lib/components/shared/base/BasePanel.js.map +1 -1
  118. package/lib/components/shared/base/BaseText.js.map +1 -1
  119. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  120. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  121. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  122. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  123. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  124. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  125. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  126. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  127. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  128. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  129. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  130. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  131. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  132. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  133. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  134. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  135. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  136. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  137. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  138. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  139. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  140. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  141. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  142. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  143. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  144. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  145. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  146. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  147. package/lib/components/shared/three/models/SkeletalPlayer3D.d.ts.map +1 -1
  148. package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
  149. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  150. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  151. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  152. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  153. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  154. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  155. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  156. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  157. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  158. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  159. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  160. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  161. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  162. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  163. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  164. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  165. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  166. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  167. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  168. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  169. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  170. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  171. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  172. package/lib/components/shared/ui/BackButton.js.map +1 -1
  173. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  174. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  175. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  176. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  177. package/lib/components/shared/ui/SplashScreen.js +2 -2
  178. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  179. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  180. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  181. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  182. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  183. package/lib/constants/bodyDimensions.js.map +1 -1
  184. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  185. package/lib/data/archetypeClothing.js.map +1 -1
  186. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  187. package/lib/data/techniqueMappings.js.map +1 -1
  188. package/lib/data/techniques.js.map +1 -1
  189. package/lib/hooks/useActionFeedback.js.map +1 -1
  190. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  191. package/lib/hooks/useCombatTimer.js.map +1 -1
  192. package/lib/hooks/useDebounce.js.map +1 -1
  193. package/lib/hooks/useHUDLayout.js.map +1 -1
  194. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  195. package/lib/hooks/useKeyboardControls.js.map +1 -1
  196. package/lib/hooks/useMatchCountdown.js.map +1 -1
  197. package/lib/hooks/useMuscleActivation.js.map +1 -1
  198. package/lib/hooks/usePauseMenu.js.map +1 -1
  199. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  200. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  201. package/lib/hooks/useRoundTransition.js.map +1 -1
  202. package/lib/hooks/useSkeletalAnimation.d.ts.map +1 -1
  203. package/lib/hooks/useSkeletalAnimation.js +1 -1
  204. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  205. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  206. package/lib/hooks/useThrottle.js.map +1 -1
  207. package/lib/hooks/useTouchControls.js.map +1 -1
  208. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  209. package/lib/hooks/useWindowSize.js.map +1 -1
  210. package/lib/systems/CombatSystem.js.map +1 -1
  211. package/lib/systems/EffectCalculator.js.map +1 -1
  212. package/lib/systems/LayoutSystem.js.map +1 -1
  213. package/lib/systems/PlayerEffectManager.js.map +1 -1
  214. package/lib/systems/ResponsiveScaling.js.map +1 -1
  215. package/lib/systems/TrigramSystem.js.map +1 -1
  216. package/lib/systems/VitalPointSystem.js.map +1 -1
  217. package/lib/systems/ai/AIPersonality.js.map +1 -1
  218. package/lib/systems/ai/AdaptiveDifficulty.js +16 -16
  219. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  220. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  221. package/lib/systems/ai/ComboSystem.js.map +1 -1
  222. package/lib/systems/ai/DecisionTree.js.map +1 -1
  223. package/lib/systems/ai/TrainingAI.js.map +1 -1
  224. package/lib/systems/ai/types.js.map +1 -1
  225. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  226. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  227. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  228. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  229. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  230. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts +6 -0
  231. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
  232. package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
  233. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  234. package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
  235. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  236. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
  237. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
  238. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
  239. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  240. package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
  241. package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
  242. package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
  243. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  244. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  245. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  246. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  247. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  248. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  249. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  250. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  251. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  252. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  253. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  254. package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
  255. package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +1 -1
  256. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  257. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  258. package/lib/systems/animation/catalogs/GamStanceAnimations.js +21 -0
  259. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
  260. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
  261. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  262. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  263. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  264. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  265. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
  266. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
  267. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
  268. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
  269. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  270. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  271. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  272. package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
  273. package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
  274. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  275. package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -1
  276. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  277. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  278. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  279. package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
  280. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  281. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  282. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  283. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  284. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  285. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  286. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  287. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  288. package/lib/systems/animation/catalogs/StanceGuardPoses.d.ts +6 -6
  289. package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
  290. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  291. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  292. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  293. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  294. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  295. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  296. package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
  297. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  298. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  299. package/lib/systems/animation/core/AnimationPriority.js +15 -15
  300. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  301. package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
  302. package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
  303. package/lib/systems/animation/core/AnimationRegistry.js +74 -12
  304. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  305. package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
  306. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  307. package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
  308. package/lib/systems/animation/core/AnimationTransitions.js +34 -0
  309. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  310. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  311. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  312. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  313. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  314. package/lib/systems/animation/core/index.d.ts +1 -1
  315. package/lib/systems/animation/core/index.d.ts.map +1 -1
  316. package/lib/systems/animation/core/types.d.ts +24 -0
  317. package/lib/systems/animation/core/types.d.ts.map +1 -1
  318. package/lib/systems/animation/core/types.js +27 -11
  319. package/lib/systems/animation/core/types.js.map +1 -1
  320. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  321. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  322. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  323. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  324. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  325. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  326. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  327. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  328. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  329. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  330. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  331. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  332. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  333. package/lib/systems/bodypart/types.js.map +1 -1
  334. package/lib/systems/breathing/BreathingDisruptionSystem.js +19 -19
  335. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  336. package/lib/systems/breathing/feedback.js.map +1 -1
  337. package/lib/systems/breathing/integration.js.map +1 -1
  338. package/lib/systems/combat/BalanceSystem.js +19 -19
  339. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  340. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  341. package/lib/systems/combat/CombatStateSystem.js +17 -17
  342. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  343. package/lib/systems/combat/ConsciousnessSystem.js +24 -24
  344. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  345. package/lib/systems/combat/FallIntegration.js.map +1 -1
  346. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  347. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  348. package/lib/systems/combat/PainResponseSystem.js +21 -21
  349. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  350. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  351. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  352. package/lib/systems/combat/typeGuards.js.map +1 -1
  353. package/lib/systems/effects.js.map +1 -1
  354. package/lib/systems/game.js.map +1 -1
  355. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  356. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  357. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  358. package/lib/systems/movement/integration.js.map +1 -1
  359. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  360. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  361. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  362. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  363. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  364. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  365. package/lib/systems/physics/SpeedModifierSystem.js +6 -6
  366. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  367. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  368. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  369. package/lib/systems/trigram/StanceManager.js.map +1 -1
  370. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  371. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  372. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  373. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  374. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  375. package/lib/systems/trigram/techniques/GeonTechniques.js.map +1 -1
  376. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  377. package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
  378. package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
  379. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  380. package/lib/systems/trigram/techniques/TaeTechniques.js.map +1 -1
  381. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  382. package/lib/systems/trigram/techniques/index.js.map +1 -1
  383. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  384. package/lib/systems/trigram/types.js.map +1 -1
  385. package/lib/systems/types.js.map +1 -1
  386. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  387. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  388. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  389. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  390. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  391. package/lib/systems/vitalpoint/VitalPointsData.js.map +1 -1
  392. package/lib/types/AccessibilityTypes.js.map +1 -1
  393. package/lib/types/LayoutTypes.js.map +1 -1
  394. package/lib/types/PhysicsTypes.js.map +1 -1
  395. package/lib/types/common.js.map +1 -1
  396. package/lib/types/constants/animations.js.map +1 -1
  397. package/lib/types/constants/colors.js.map +1 -1
  398. package/lib/types/constants/designSystem.js.map +1 -1
  399. package/lib/types/constants/index.js.map +1 -1
  400. package/lib/types/constants/layout.js.map +1 -1
  401. package/lib/types/constants/performance.js.map +1 -1
  402. package/lib/types/constants/typography.js.map +1 -1
  403. package/lib/types/constants/ui.js.map +1 -1
  404. package/lib/types/facial.js +19 -19
  405. package/lib/types/facial.js.map +1 -1
  406. package/lib/types/hand-animation.js.map +1 -1
  407. package/lib/types/injury.js.map +1 -1
  408. package/lib/types/muscle.js.map +1 -1
  409. package/lib/types/physics.js.map +1 -1
  410. package/lib/types/physicsConstants.js.map +1 -1
  411. package/lib/types/player-visual.d.ts +1 -1
  412. package/lib/types/player-visual.d.ts.map +1 -1
  413. package/lib/types/skeletal.js.map +1 -1
  414. package/lib/types/techniqueId.js.map +1 -1
  415. package/lib/utils/accessibility.js.map +1 -1
  416. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  417. package/lib/utils/assetConfig.js.map +1 -1
  418. package/lib/utils/characterScaling.js.map +1 -1
  419. package/lib/utils/colorHelpers.js.map +1 -1
  420. package/lib/utils/colorUtils.js.map +1 -1
  421. package/lib/utils/combatReadiness.js.map +1 -1
  422. package/lib/utils/controlMapping.js.map +1 -1
  423. package/lib/utils/deviceDetection.js +6 -7
  424. package/lib/utils/deviceDetection.js.map +1 -1
  425. package/lib/utils/effectUtils.js.map +1 -1
  426. package/lib/utils/fabricTextures.js.map +1 -1
  427. package/lib/utils/hapticFeedback.js.map +1 -1
  428. package/lib/utils/haptics.js.map +1 -1
  429. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  430. package/lib/utils/inputSystem.js.map +1 -1
  431. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  432. package/lib/utils/math.js.map +1 -1
  433. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  434. package/lib/utils/mobileUIUtils.js.map +1 -1
  435. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  436. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  437. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  438. package/lib/utils/performanceOptimization.js.map +1 -1
  439. package/lib/utils/player3DHelpers.js.map +1 -1
  440. package/lib/utils/playerUtils.js.map +1 -1
  441. package/lib/utils/responsiveLayout.js.map +1 -1
  442. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  443. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  444. package/lib/utils/safeAreaUtils.js.map +1 -1
  445. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  446. package/lib/utils/skeletonScaling.js.map +1 -1
  447. package/lib/utils/stanceHelpers.js.map +1 -1
  448. package/lib/utils/threeObjectPool.js.map +1 -1
  449. package/lib/utils/visualEffects.js.map +1 -1
  450. package/package.json +4 -4
@@ -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;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
+ {"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;AACtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,mBACd,SAAgC,CAAC,GACjC,sBAC0B;CAC1B,MAAM,eAAe,eACZ;EAAE,GAAG;EAAgB,GAAG;CAAO,IACtC,CAAC,MAAM,CACT;CAEA,MAAM,CAAC,iBAAiB,sBACtB,SAA+B,MAAM;CACvC,MAAM,CAAC,aAAa,kBAAkB,SAA6B,IAAI;CACvE,MAAM,CAAC,oBAAoB,yBAAyB,SAAS,CAAC;CAG9D,MAAM,kBAAkB,OAAO,KAAK;CACpC,MAAM,iBAAiB,OAA6C,IAAI;CACxE,MAAM,kBAAkB,OAA6C,IAAI;;;;CAKzE,MAAM,cAAc,kBAAkB;EACpC,IAAI,eAAe,SAAS;GAC1B,cAAc,eAAe,OAAO;GACpC,eAAe,UAAU;EAC3B;EACA,IAAI,gBAAgB,SAAS;GAC3B,aAAa,gBAAgB,OAAO;GACpC,gBAAgB,UAAU;EAC5B;CACF,GAAG,CAAC,CAAC;;;;CAKL,MAAM,kBAAkB,aACrB,QAA4B,gBAAwB;EACnD,YAAY;EACZ,eAAe,MAAM;EACrB,sBAAsB,WAAW;EACjC,mBAAmB,YAAY;EAsB/B,gBAAgB,UAnBU,iBAAiB;GACzC,mBAAmB,WAAW;GAC9B,gBAAgB,UAAU;GAI1B,eAAe,UAAU,iBAAiB;IACxC,gBAAgB,UAAU;IAC1B,mBAAmB,eAAe;IAGlC,gBAAgB,UAAU,iBAAiB;KACzC,mBAAmB,MAAM;KACzB,uBAAuB;IACzB,GAAG,aAAa,kBAAkB;GACpC,GAAG,aAAa,oBAAoB,GAAI;EAC1C,GAAG,aAAa,uBAAuB,GAGb;CAC5B,GACA;EAAC;EAAa;EAAc;CAAoB,CAClD;;;;CAKA,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,oBAAoB,gBAAgB,oBAAoB,aAAa;GACvE,YAAY;GACZ,gBAAgB,UAAU;GAC1B,mBAAmB,eAAe;GAGlC,gBAAgB,UAAU,iBAAiB;IACzC,mBAAmB,MAAM;IACzB,uBAAuB;GACzB,GAAG,aAAa,kBAAkB;EACpC;CACF,GAAG;EACD;EACA;EACA,aAAa;EACb;CACF,CAAC;;;;CAKD,MAAM,kBAAkB,kBAAkB;EACxC,YAAY;EACZ,gBAAgB,UAAU;EAC1B,mBAAmB,MAAM;EACzB,eAAe,IAAI;EACnB,sBAAsB,CAAC;CACzB,GAAG,CAAC,WAAW,CAAC;;;;CAKhB,gBAAgB;EACd,aAAa;GACX,YAAY;GACZ,gBAAgB,UAAU;EAC5B;CACF,GAAG,CAAC,WAAW,CAAC;CAEhB,OAAO;EACL;EACA,kBACE,oBAAoB,gBAAgB,oBAAoB;EAC1D;EACA;EACA;EACA;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useSkeletalAnimation.d.ts","sourceRoot":"","sources":["../../src/hooks/useSkeletalAnimation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAEV,sBAAsB,EACtB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,6BAA6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,eAAe,CAAC;IAC3C,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,iCAAiC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,wCAAwC;IACxC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,8BAA8B;IAC9B,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACrD,2DAA2D;IAC3D,QAAQ,CAAC,kBAAkB,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvE,qDAAqD;IACrD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,2BAA2B,GACnC,0BAA0B,CAoN5B"}
1
+ {"version":3,"file":"useSkeletalAnimation.d.ts","sourceRoot":"","sources":["../../src/hooks/useSkeletalAnimation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAEV,sBAAsB,EACtB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,6BAA6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,eAAe,CAAC;IAC3C,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,iCAAiC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,wCAAwC;IACxC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,8BAA8B;IAC9B,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACrD,2DAA2D;IAC3D,QAAQ,CAAC,kBAAkB,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvE,qDAAqD;IACrD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,2BAA2B,GACnC,0BAA0B,CAqN5B"}
@@ -97,7 +97,7 @@ function useSkeletalAnimation(options) {
97
97
  selectedAnim = getAnimation(currentAnimation) ?? null;
98
98
  playbackSpeed = 1;
99
99
  } else if (currentAnimation === "stance_change") {
100
- selectedAnim = getAnimation("idle_stance") ?? null;
100
+ selectedAnim = getAnimation("stance_change") ?? getAnimation("idle_stance") ?? null;
101
101
  playbackSpeed = 1.2;
102
102
  } else if (currentAnimation === "hit") {
103
103
  setAnimState((prev) => ({
@@ -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 =\n getAnimation(\"stance_change\") ?? 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 +1 @@
1
- {"version":3,"file":"useTechniqueSelection.js","names":[],"sources":["../../src/hooks/useTechniqueSelection.ts"],"sourcesContent":["/**\n * Custom hook for managing technique selection and execution.\n *\n * **Korean**: 기술 선택 관리 (Technique Selection Management)\n *\n * Handles technique selection state, keyboard shortcuts, cooldown tracking,\n * and validation of technique execution based on player resources and stance.\n *\n * @module hooks/useTechniqueSelection\n * @category Combat Hooks\n * @korean 기술선택훅\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { getTechniquesForStanceAndArchetype } from \"../data/techniques\";\nimport { PlayerState } from \"../systems/player\";\nimport {\n Technique,\n TechniqueCooldown,\n TechniqueValidation,\n} from \"../types\";\n\n/**\n * Configuration for technique selection hook.\n */\nexport interface UseTechniqueSelectionConfig {\n /** Player state with resources and stance */\n readonly player: PlayerState;\n\n /** Whether technique selection is enabled */\n readonly enabled?: boolean;\n\n /** Callback when technique is selected */\n readonly onTechniqueSelected?: (technique: Technique) => void;\n\n /** Callback when technique execution is attempted */\n readonly onTechniqueExecute?: (technique: Technique) => void;\n}\n\n/**\n * Technique selection state and actions.\n */\nexport interface UseTechniqueSelectionResult {\n /** Available techniques for player archetype */\n readonly availableTechniques: readonly Technique[];\n\n /** Currently selected technique index */\n readonly selectedIndex: number;\n\n /** Active cooldowns for techniques */\n readonly activeCooldowns: readonly TechniqueCooldown[];\n\n /** Select technique by index */\n readonly selectTechnique: (index: number) => void;\n\n /** Execute currently selected technique */\n readonly executeTechnique: (indexOverride?: number) => void;\n\n /** Check if technique can be executed */\n readonly validateTechnique: (technique: Technique) => TechniqueValidation;\n\n /** Check if technique is on cooldown */\n readonly isOnCooldown: (techniqueId: string) => boolean;\n\n /** Get remaining cooldown time in ms */\n readonly getRemainingCooldown: (techniqueId: string) => number;\n\n /** Check if player has sufficient resources */\n readonly hasResources: (technique: Technique) => boolean;\n}\n\n/**\n * Custom hook for managing technique selection and execution.\n *\n * @param config - Configuration options\n * @returns Technique selection state and actions\n *\n * @example\n * ```typescript\n * const techniqueSelection = useTechniqueSelection({\n * player: playerState,\n * enabled: !isPaused && combatActive,\n * onTechniqueExecute: (technique) => {\n * // Execute technique logic\n * executeCombatTechnique(playerState, opponent, technique);\n * }\n * });\n * ```\n *\n * @public\n */\nexport function useTechniqueSelection(\n config: UseTechniqueSelectionConfig\n): UseTechniqueSelectionResult {\n const {\n player,\n enabled = true,\n onTechniqueSelected,\n onTechniqueExecute,\n } = config;\n\n // Get available techniques based on player's current stance and archetype\n const availableTechniques = useMemo(\n () =>\n getTechniquesForStanceAndArchetype(\n player.currentStance,\n player.archetype\n ),\n [player.currentStance, player.archetype]\n );\n\n // Selected technique state\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Cooldown tracking\n const [activeCooldowns, setActiveCooldowns] = useState<TechniqueCooldown[]>(\n []\n );\n\n // Ref for cleanup\n const cooldownUpdateIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n // Update cooldowns every 100ms\n useEffect(() => {\n if (activeCooldowns.length === 0) {\n // Clear any existing interval when no cooldowns\n if (cooldownUpdateIntervalRef.current) {\n clearInterval(cooldownUpdateIntervalRef.current);\n cooldownUpdateIntervalRef.current = null;\n }\n return;\n }\n\n let isMounted = true;\n cooldownUpdateIntervalRef.current = setInterval(() => {\n if (!isMounted) return;\n const now = Date.now();\n setActiveCooldowns((prev) => {\n return prev\n .map((cd) => ({\n ...cd,\n remaining: Math.max(0, cd.startTime + cd.duration - now),\n }))\n .filter((cd) => cd.remaining > 0);\n });\n }, 100);\n\n return () => {\n isMounted = false;\n if (cooldownUpdateIntervalRef.current) {\n clearInterval(cooldownUpdateIntervalRef.current);\n cooldownUpdateIntervalRef.current = null;\n }\n };\n }, [activeCooldowns.length]);\n\n // Check if technique is on cooldown\n const isOnCooldown = useCallback(\n (techniqueId: string): boolean => {\n return activeCooldowns.some(\n (cd) => cd.techniqueId === techniqueId && cd.remaining > 0\n );\n },\n [activeCooldowns]\n );\n\n // Get remaining cooldown time\n const getRemainingCooldown = useCallback(\n (techniqueId: string): number => {\n const cooldown = activeCooldowns.find(\n (cd) => cd.techniqueId === techniqueId\n );\n return cooldown?.remaining ?? 0;\n },\n [activeCooldowns]\n );\n\n // Check if player has sufficient resources\n const hasResources = useCallback(\n (technique: Technique): boolean => {\n return (\n player.stamina >= technique.staminaCost && player.ki >= technique.kiCost\n );\n },\n [player.stamina, player.ki]\n );\n\n // Validate technique execution\n const validateTechnique = useCallback(\n (technique: Technique): TechniqueValidation => {\n // Check stamina\n if (player.stamina < technique.staminaCost) {\n return {\n canExecute: false,\n reason: \"Insufficient stamina\",\n insufficientStamina: true,\n };\n }\n\n // Check Ki\n if (player.ki < technique.kiCost) {\n return {\n canExecute: false,\n reason: \"Insufficient Ki\",\n insufficientKi: true,\n };\n }\n\n // Check cooldown\n if (isOnCooldown(technique.id)) {\n return {\n canExecute: false,\n reason: \"Technique on cooldown\",\n onCooldown: true,\n };\n }\n\n // Check required stance\n if (\n technique.requiredStance &&\n player.currentStance !== technique.requiredStance\n ) {\n return {\n canExecute: false,\n reason: `Requires ${technique.requiredStance} stance`,\n wrongStance: true,\n };\n }\n\n return {\n canExecute: true,\n };\n },\n [player.stamina, player.ki, player.currentStance, isOnCooldown]\n );\n\n // Select technique by index\n const selectTechnique = useCallback(\n (index: number) => {\n if (index < 0 || index >= availableTechniques.length) return;\n if (!enabled) return;\n\n setSelectedIndex(index);\n const technique = availableTechniques[index];\n onTechniqueSelected?.(technique);\n },\n [availableTechniques, enabled, onTechniqueSelected]\n );\n\n // Execute currently selected technique\n const executeTechnique = useCallback(\n (indexOverride?: number) => {\n if (!enabled) return;\n\n const index = indexOverride ?? selectedIndex;\n const technique = availableTechniques[index];\n if (!technique) return;\n\n // Validate technique execution\n const validation = validateTechnique(technique);\n if (!validation.canExecute) {\n console.warn(`Cannot execute technique: ${validation.reason}`);\n return;\n }\n\n // Start cooldown\n const now = Date.now();\n const cooldown: TechniqueCooldown = {\n techniqueId: technique.id,\n startTime: now,\n duration: technique.cooldown,\n remaining: technique.cooldown,\n };\n setActiveCooldowns((prev) => [...prev, cooldown]);\n\n // Execute technique\n onTechniqueExecute?.(technique);\n },\n [\n enabled,\n availableTechniques,\n selectedIndex,\n validateTechnique,\n onTechniqueExecute,\n ]\n );\n\n // Keyboard shortcuts for technique selection (Q-E-R-T-Y-F-G-Z-X-C)\n useEffect(() => {\n if (!enabled) return;\n\n const handleKeyPress = (e: KeyboardEvent) => {\n // Ignore keypresses when typing in input fields\n const target = e.target as HTMLElement;\n if (\n target.tagName === \"INPUT\" ||\n target.tagName === \"TEXTAREA\" ||\n target.isContentEditable\n ) {\n return;\n }\n\n const key = e.key.toUpperCase();\n \n // Technique keys: Q, E, R, T, Y, F, G, Z, X, C (10 keys around WASD)\n const techniqueKeys = [\"Q\", \"E\", \"R\", \"T\", \"Y\", \"F\", \"G\", \"Z\", \"X\", \"C\"];\n\n // Prevent default for all technique keys during combat\n if (techniqueKeys.includes(key)) {\n e.preventDefault();\n }\n\n // Map keys to technique indices\n const techniqueIndex = techniqueKeys.indexOf(key);\n if (techniqueIndex !== -1 && techniqueIndex < availableTechniques.length) {\n selectTechnique(techniqueIndex);\n executeTechnique(techniqueIndex);\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyPress);\n return () => window.removeEventListener(\"keydown\", handleKeyPress);\n }, [enabled, availableTechniques, selectTechnique, executeTechnique]);\n\n return {\n availableTechniques,\n selectedIndex,\n activeCooldowns,\n selectTechnique,\n executeTechnique,\n validateTechnique,\n isOnCooldown,\n getRemainingCooldown,\n hasResources,\n };\n}\n\nexport default useTechniqueSelection;\n"],"mappings":"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"}
1
+ {"version":3,"file":"useTechniqueSelection.js","names":[],"sources":["../../src/hooks/useTechniqueSelection.ts"],"sourcesContent":["/**\n * Custom hook for managing technique selection and execution.\n *\n * **Korean**: 기술 선택 관리 (Technique Selection Management)\n *\n * Handles technique selection state, keyboard shortcuts, cooldown tracking,\n * and validation of technique execution based on player resources and stance.\n *\n * @module hooks/useTechniqueSelection\n * @category Combat Hooks\n * @korean 기술선택훅\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { getTechniquesForStanceAndArchetype } from \"../data/techniques\";\nimport { PlayerState } from \"../systems/player\";\nimport {\n Technique,\n TechniqueCooldown,\n TechniqueValidation,\n} from \"../types\";\n\n/**\n * Configuration for technique selection hook.\n */\nexport interface UseTechniqueSelectionConfig {\n /** Player state with resources and stance */\n readonly player: PlayerState;\n\n /** Whether technique selection is enabled */\n readonly enabled?: boolean;\n\n /** Callback when technique is selected */\n readonly onTechniqueSelected?: (technique: Technique) => void;\n\n /** Callback when technique execution is attempted */\n readonly onTechniqueExecute?: (technique: Technique) => void;\n}\n\n/**\n * Technique selection state and actions.\n */\nexport interface UseTechniqueSelectionResult {\n /** Available techniques for player archetype */\n readonly availableTechniques: readonly Technique[];\n\n /** Currently selected technique index */\n readonly selectedIndex: number;\n\n /** Active cooldowns for techniques */\n readonly activeCooldowns: readonly TechniqueCooldown[];\n\n /** Select technique by index */\n readonly selectTechnique: (index: number) => void;\n\n /** Execute currently selected technique */\n readonly executeTechnique: (indexOverride?: number) => void;\n\n /** Check if technique can be executed */\n readonly validateTechnique: (technique: Technique) => TechniqueValidation;\n\n /** Check if technique is on cooldown */\n readonly isOnCooldown: (techniqueId: string) => boolean;\n\n /** Get remaining cooldown time in ms */\n readonly getRemainingCooldown: (techniqueId: string) => number;\n\n /** Check if player has sufficient resources */\n readonly hasResources: (technique: Technique) => boolean;\n}\n\n/**\n * Custom hook for managing technique selection and execution.\n *\n * @param config - Configuration options\n * @returns Technique selection state and actions\n *\n * @example\n * ```typescript\n * const techniqueSelection = useTechniqueSelection({\n * player: playerState,\n * enabled: !isPaused && combatActive,\n * onTechniqueExecute: (technique) => {\n * // Execute technique logic\n * executeCombatTechnique(playerState, opponent, technique);\n * }\n * });\n * ```\n *\n * @public\n */\nexport function useTechniqueSelection(\n config: UseTechniqueSelectionConfig\n): UseTechniqueSelectionResult {\n const {\n player,\n enabled = true,\n onTechniqueSelected,\n onTechniqueExecute,\n } = config;\n\n // Get available techniques based on player's current stance and archetype\n const availableTechniques = useMemo(\n () =>\n getTechniquesForStanceAndArchetype(\n player.currentStance,\n player.archetype\n ),\n [player.currentStance, player.archetype]\n );\n\n // Selected technique state\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Cooldown tracking\n const [activeCooldowns, setActiveCooldowns] = useState<TechniqueCooldown[]>(\n []\n );\n\n // Ref for cleanup\n const cooldownUpdateIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n // Update cooldowns every 100ms\n useEffect(() => {\n if (activeCooldowns.length === 0) {\n // Clear any existing interval when no cooldowns\n if (cooldownUpdateIntervalRef.current) {\n clearInterval(cooldownUpdateIntervalRef.current);\n cooldownUpdateIntervalRef.current = null;\n }\n return;\n }\n\n let isMounted = true;\n cooldownUpdateIntervalRef.current = setInterval(() => {\n if (!isMounted) return;\n const now = Date.now();\n setActiveCooldowns((prev) => {\n return prev\n .map((cd) => ({\n ...cd,\n remaining: Math.max(0, cd.startTime + cd.duration - now),\n }))\n .filter((cd) => cd.remaining > 0);\n });\n }, 100);\n\n return () => {\n isMounted = false;\n if (cooldownUpdateIntervalRef.current) {\n clearInterval(cooldownUpdateIntervalRef.current);\n cooldownUpdateIntervalRef.current = null;\n }\n };\n }, [activeCooldowns.length]);\n\n // Check if technique is on cooldown\n const isOnCooldown = useCallback(\n (techniqueId: string): boolean => {\n return activeCooldowns.some(\n (cd) => cd.techniqueId === techniqueId && cd.remaining > 0\n );\n },\n [activeCooldowns]\n );\n\n // Get remaining cooldown time\n const getRemainingCooldown = useCallback(\n (techniqueId: string): number => {\n const cooldown = activeCooldowns.find(\n (cd) => cd.techniqueId === techniqueId\n );\n return cooldown?.remaining ?? 0;\n },\n [activeCooldowns]\n );\n\n // Check if player has sufficient resources\n const hasResources = useCallback(\n (technique: Technique): boolean => {\n return (\n player.stamina >= technique.staminaCost && player.ki >= technique.kiCost\n );\n },\n [player.stamina, player.ki]\n );\n\n // Validate technique execution\n const validateTechnique = useCallback(\n (technique: Technique): TechniqueValidation => {\n // Check stamina\n if (player.stamina < technique.staminaCost) {\n return {\n canExecute: false,\n reason: \"Insufficient stamina\",\n insufficientStamina: true,\n };\n }\n\n // Check Ki\n if (player.ki < technique.kiCost) {\n return {\n canExecute: false,\n reason: \"Insufficient Ki\",\n insufficientKi: true,\n };\n }\n\n // Check cooldown\n if (isOnCooldown(technique.id)) {\n return {\n canExecute: false,\n reason: \"Technique on cooldown\",\n onCooldown: true,\n };\n }\n\n // Check required stance\n if (\n technique.requiredStance &&\n player.currentStance !== technique.requiredStance\n ) {\n return {\n canExecute: false,\n reason: `Requires ${technique.requiredStance} stance`,\n wrongStance: true,\n };\n }\n\n return {\n canExecute: true,\n };\n },\n [player.stamina, player.ki, player.currentStance, isOnCooldown]\n );\n\n // Select technique by index\n const selectTechnique = useCallback(\n (index: number) => {\n if (index < 0 || index >= availableTechniques.length) return;\n if (!enabled) return;\n\n setSelectedIndex(index);\n const technique = availableTechniques[index];\n onTechniqueSelected?.(technique);\n },\n [availableTechniques, enabled, onTechniqueSelected]\n );\n\n // Execute currently selected technique\n const executeTechnique = useCallback(\n (indexOverride?: number) => {\n if (!enabled) return;\n\n const index = indexOverride ?? selectedIndex;\n const technique = availableTechniques[index];\n if (!technique) return;\n\n // Validate technique execution\n const validation = validateTechnique(technique);\n if (!validation.canExecute) {\n console.warn(`Cannot execute technique: ${validation.reason}`);\n return;\n }\n\n // Start cooldown\n const now = Date.now();\n const cooldown: TechniqueCooldown = {\n techniqueId: technique.id,\n startTime: now,\n duration: technique.cooldown,\n remaining: technique.cooldown,\n };\n setActiveCooldowns((prev) => [...prev, cooldown]);\n\n // Execute technique\n onTechniqueExecute?.(technique);\n },\n [\n enabled,\n availableTechniques,\n selectedIndex,\n validateTechnique,\n onTechniqueExecute,\n ]\n );\n\n // Keyboard shortcuts for technique selection (Q-E-R-T-Y-F-G-Z-X-C)\n useEffect(() => {\n if (!enabled) return;\n\n const handleKeyPress = (e: KeyboardEvent) => {\n // Ignore keypresses when typing in input fields\n const target = e.target as HTMLElement;\n if (\n target.tagName === \"INPUT\" ||\n target.tagName === \"TEXTAREA\" ||\n target.isContentEditable\n ) {\n return;\n }\n\n const key = e.key.toUpperCase();\n \n // Technique keys: Q, E, R, T, Y, F, G, Z, X, C (10 keys around WASD)\n const techniqueKeys = [\"Q\", \"E\", \"R\", \"T\", \"Y\", \"F\", \"G\", \"Z\", \"X\", \"C\"];\n\n // Prevent default for all technique keys during combat\n if (techniqueKeys.includes(key)) {\n e.preventDefault();\n }\n\n // Map keys to technique indices\n const techniqueIndex = techniqueKeys.indexOf(key);\n if (techniqueIndex !== -1 && techniqueIndex < availableTechniques.length) {\n selectTechnique(techniqueIndex);\n executeTechnique(techniqueIndex);\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyPress);\n return () => window.removeEventListener(\"keydown\", handleKeyPress);\n }, [enabled, availableTechniques, selectTechnique, executeTechnique]);\n\n return {\n availableTechniques,\n selectedIndex,\n activeCooldowns,\n selectTechnique,\n executeTechnique,\n validateTechnique,\n isOnCooldown,\n getRemainingCooldown,\n hasResources,\n };\n}\n\nexport default useTechniqueSelection;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useThrottle.js","names":[],"sources":["../../src/hooks/useThrottle.ts"],"sourcesContent":["/**\n * useThrottle Hook\n * \n * Throttles a function to execute at most once per specified interval.\n * Useful for high-frequency events like scroll, resize, or touch move.\n * \n * Uses a ref pattern to ensure the latest callback is always called\n * without recreating the throttled function on every render.\n * \n * @module hooks/useThrottle\n * @category Performance\n * @korean 쓰로틀 훅\n */\n\nimport { useCallback, useRef, useLayoutEffect, useEffect } from 'react';\n\n/**\n * Hook to throttle a callback function\n * \n * @param callback - Function to throttle\n * @param delay - Minimum delay between executions in milliseconds\n * @returns Throttled function\n * \n * @example\n * ```tsx\n * const handleTouchMove = useThrottle((event: TouchEvent) => {\n * // Handle touch move\n * }, 16); // ~60fps\n * ```\n */\nexport function useThrottle<T extends (...args: never[]) => void>(\n callback: T,\n delay: number\n): T {\n const lastRunRef = useRef<number>(0);\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 pending timeout 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 const now = Date.now();\n const timeSinceLastRun = now - lastRunRef.current;\n\n if (timeSinceLastRun >= delay) {\n // Execute immediately if enough time has passed\n lastRunRef.current = now;\n callbackRef.current(...args);\n } else if (!timeoutRef.current) {\n // Schedule execution for later\n const timeUntilNext = delay - timeSinceLastRun;\n timeoutRef.current = setTimeout(() => {\n lastRunRef.current = Date.now();\n timeoutRef.current = null;\n callbackRef.current(...args);\n }, timeUntilNext);\n }\n },\n [delay]\n ) as T;\n}\n\nexport default useThrottle;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YACd,UACA,OACG;CACH,MAAM,aAAa,OAAe,EAAE;CACpC,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;EAC1B,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,mBAAmB,MAAM,WAAW;EAE1C,IAAI,oBAAoB,OAAO;GAE7B,WAAW,UAAU;GACrB,YAAY,QAAQ,GAAG,KAAK;SACvB,IAAI,CAAC,WAAW,SAAS;GAE9B,MAAM,gBAAgB,QAAQ;GAC9B,WAAW,UAAU,iBAAiB;IACpC,WAAW,UAAU,KAAK,KAAK;IAC/B,WAAW,UAAU;IACrB,YAAY,QAAQ,GAAG,KAAK;MAC3B,cAAc;;IAGrB,CAAC,MAAM,CACR"}
1
+ {"version":3,"file":"useThrottle.js","names":[],"sources":["../../src/hooks/useThrottle.ts"],"sourcesContent":["/**\n * useThrottle Hook\n * \n * Throttles a function to execute at most once per specified interval.\n * Useful for high-frequency events like scroll, resize, or touch move.\n * \n * Uses a ref pattern to ensure the latest callback is always called\n * without recreating the throttled function on every render.\n * \n * @module hooks/useThrottle\n * @category Performance\n * @korean 쓰로틀 훅\n */\n\nimport { useCallback, useRef, useLayoutEffect, useEffect } from 'react';\n\n/**\n * Hook to throttle a callback function\n * \n * @param callback - Function to throttle\n * @param delay - Minimum delay between executions in milliseconds\n * @returns Throttled function\n * \n * @example\n * ```tsx\n * const handleTouchMove = useThrottle((event: TouchEvent) => {\n * // Handle touch move\n * }, 16); // ~60fps\n * ```\n */\nexport function useThrottle<T extends (...args: never[]) => void>(\n callback: T,\n delay: number\n): T {\n const lastRunRef = useRef<number>(0);\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 pending timeout 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 const now = Date.now();\n const timeSinceLastRun = now - lastRunRef.current;\n\n if (timeSinceLastRun >= delay) {\n // Execute immediately if enough time has passed\n lastRunRef.current = now;\n callbackRef.current(...args);\n } else if (!timeoutRef.current) {\n // Schedule execution for later\n const timeUntilNext = delay - timeSinceLastRun;\n timeoutRef.current = setTimeout(() => {\n lastRunRef.current = Date.now();\n timeoutRef.current = null;\n callbackRef.current(...args);\n }, timeUntilNext);\n }\n },\n [delay]\n ) as T;\n}\n\nexport default useThrottle;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YACd,UACA,OACG;CACH,MAAM,aAAa,OAAe,CAAC;CACnC,MAAM,aAAa,OAA6C,IAAI;CACpE,MAAM,cAAc,OAAO,QAAQ;CAGnC,sBAAsB;EACpB,YAAY,UAAU;CACxB,CAAC;CAGD,gBAAgB;EACd,aAAa;GACX,IAAI,WAAW,SACb,aAAa,WAAW,OAAO;EAEnC;CACF,GAAG,CAAC,CAAC;CAEL,OAAO,aACJ,GAAG,SAAwB;EAC1B,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,mBAAmB,MAAM,WAAW;EAE1C,IAAI,oBAAoB,OAAO;GAE7B,WAAW,UAAU;GACrB,YAAY,QAAQ,GAAG,IAAI;EAC7B,OAAO,IAAI,CAAC,WAAW,SAAS;GAE9B,MAAM,gBAAgB,QAAQ;GAC9B,WAAW,UAAU,iBAAiB;IACpC,WAAW,UAAU,KAAK,IAAI;IAC9B,WAAW,UAAU;IACrB,YAAY,QAAQ,GAAG,IAAI;GAC7B,GAAG,aAAa;EAClB;CACF,GACA,CAAC,KAAK,CACR;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useTouchControls.js","names":[],"sources":["../../src/hooks/useTouchControls.ts"],"sourcesContent":["/**\n * Touch Controls Hook\n * \n * Manages touch event handling and gesture recognition for mobile gameplay\n * Provides swipe detection, multi-touch support, and touch-based movement\n * \n * @module hooks/useTouchControls\n * @category Mobile Controls\n * @korean 터치 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n/**\n * Gesture types supported by the touch control system\n * \n * Added tactical step gestures for Korean martial arts footwork:\n * - tap-{direction}: Quick tap for tactical 30cm step\n * - hold-{direction}: Hold for continuous walk\n * \n * @korean 제스처타입\n */\nexport type GestureType =\n | 'swipe-right'\n | 'swipe-left'\n | 'swipe-up'\n | 'swipe-down'\n | 'two-finger-tap'\n | 'tap'\n | 'tap-forward'\n | 'tap-back'\n | 'tap-left'\n | 'tap-right'\n | 'tap-forward-left'\n | 'tap-forward-right'\n | 'tap-back-left'\n | 'tap-back-right'\n | 'hold-forward'\n | 'hold-back'\n | 'hold-left'\n | 'hold-right';\n\n/**\n * Gesture event data\n */\nexport interface GestureEvent {\n /** Type of gesture detected */\n readonly type: GestureType;\n /** Distance of swipe in pixels (for swipe gestures) */\n readonly distance?: number;\n /** Coordinates of touch start */\n readonly startX?: number;\n readonly startY?: number;\n /** Coordinates of touch end */\n readonly endX?: number;\n readonly endY?: number;\n}\n\n/**\n * Props for useTouchControls hook\n */\nexport interface UseTouchControlsProps {\n /** Callback when gesture is detected */\n readonly onGesture: (gesture: GestureEvent) => void;\n /** Whether touch input is enabled */\n readonly enabled?: boolean;\n /** Minimum swipe distance in pixels (default: 50) */\n readonly minSwipeDistance?: number;\n /** Maximum time for tap in ms (default: 300) */\n readonly maxTapDuration?: number;\n /** Time threshold for hold vs tap in ms (default: 200) */\n readonly holdThreshold?: number;\n /** Enable haptic feedback for steps (default: true) */\n readonly enableHaptics?: boolean;\n}\n\n/**\n * Return type for useTouchControls hook\n */\nexport interface UseTouchControlsReturn {\n /** Whether a touch is currently active */\n readonly isTouching: boolean;\n}\n\n/**\n * Custom hook for handling touch controls and gesture recognition\n * \n * Features:\n * - Swipe detection (horizontal and vertical)\n * - Two-finger tap detection for vital point mode\n * - Single tap detection\n * - Tactical step gestures (tap) vs continuous walk (hold)\n * - Distance calculation for swipe intensity\n * - Configurable thresholds\n * - Haptic feedback for tactical steps\n * \n * Gesture Mapping:\n * - Swipe Right: Advance toward opponent\n * - Swipe Left: Retreat from opponent\n * - Swipe Up: High stance mode\n * - Swipe Down: Low stance mode\n * - Two-Finger Tap: Activate vital point targeting mode\n * - Single Tap (directional): Tactical 30cm step (전술적 발걸음)\n * - Hold (directional): Continuous walk movement\n * \n * @example\n * ```typescript\n * const { isTouching } = useTouchControls({\n * onGesture: (gesture) => {\n * switch (gesture.type) {\n * case 'tap-forward':\n * handleTacticalStep('forward'); // 전진보법\n * break;\n * case 'hold-forward':\n * handleContinuousWalk('forward');\n * break;\n * case 'two-finger-tap':\n * activateVitalPointMode();\n * break;\n * }\n * },\n * enabled: !isPaused,\n * holdThreshold: 200, // 200ms to distinguish tap from hold\n * enableHaptics: true,\n * });\n * ```\n * \n * @public\n * @korean 터치컨트롤사용\n */\nexport function useTouchControls({\n onGesture,\n enabled = true,\n minSwipeDistance = 50,\n maxTapDuration = 300,\n holdThreshold = 200,\n enableHaptics = true,\n}: UseTouchControlsProps): UseTouchControlsReturn {\n const touchStartRef = useRef<Touch | null>(null);\n const touchStartTimeRef = useRef<number>(0);\n const [isTouching, setIsTouching] = useState<boolean>(false);\n const holdTimerRef = useRef<number | null>(null);\n \n /**\n * Trigger haptic feedback for tactical step\n * Light vibration (10ms) to confirm step input\n * \n * @korean 햅틱피드백\n */\n const triggerStepHaptic = useCallback(() => {\n if (!enableHaptics || !navigator.vibrate) return;\n \n try {\n // Short, light vibration for step (10ms)\n navigator.vibrate(10);\n } catch (error) {\n // Haptic feedback not supported or failed\n console.debug('Haptic feedback not available:', error);\n }\n }, [enableHaptics]);\n \n /**\n * Determine directional gesture from touch position\n * Used for D-pad style controls\n * Returns null for ambiguous/stationary taps\n * \n * @korean 방향제스처감지\n */\n const getDirectionalGesture = useCallback((\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n isTap: boolean\n ): GestureType | null => {\n const deltaX = endX - startX;\n const deltaY = endY - startY;\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n \n // If movement is too small, it's not a directional gesture\n const minDirectionalMovement = 15; // pixels\n if (absX < minDirectionalMovement && absY < minDirectionalMovement) {\n return null; // Ambiguous tap, not directional\n }\n \n // Check for diagonal gestures (45-degree threshold)\n const isDiagonal = absX > 20 && absY > 20 && Math.abs(absX - absY) < 30;\n \n const prefix = isTap ? 'tap' : 'hold';\n \n if (isDiagonal) {\n // Diagonal gestures (only for taps/steps)\n if (isTap) {\n if (deltaY < 0 && deltaX < 0) return 'tap-forward-left';\n if (deltaY < 0 && deltaX > 0) return 'tap-forward-right';\n if (deltaY > 0 && deltaX < 0) return 'tap-back-left';\n if (deltaY > 0 && deltaX > 0) return 'tap-back-right';\n }\n return null;\n }\n \n // Cardinal directions\n if (absX > absY) {\n // Horizontal\n return deltaX > 0 ? `${prefix}-right` as GestureType : `${prefix}-left` as GestureType;\n } else {\n // Vertical\n return deltaY < 0 ? `${prefix}-forward` as GestureType : `${prefix}-back` as GestureType;\n }\n }, []);\n\n /**\n * Handle touch start event\n */\n const handleTouchStart = useCallback((e: TouchEvent) => {\n if (!enabled) return;\n\n const touch = e.touches[0];\n touchStartRef.current = touch;\n touchStartTimeRef.current = Date.now();\n setIsTouching(true);\n\n // Check for two-finger tap immediately\n if (e.touches.length === 2) {\n e.preventDefault();\n onGesture({\n type: 'two-finger-tap',\n startX: touch.clientX,\n startY: touch.clientY,\n });\n return;\n }\n \n // Capture screen dimensions at touch start time to prevent incorrect\n // direction calculation if window is resized during hold\n const screenCenterX = window.innerWidth / 2;\n const screenCenterY = window.innerHeight / 2;\n \n // Set up hold detection timer\n // Note: Hold gesture direction is determined from the initial touch position\n // relative to screen center. This supports D-pad style layouts where each\n // region of the screen (or an overlaid control) corresponds to a cardinal direction.\n holdTimerRef.current = window.setTimeout(() => {\n // Touch held for longer than threshold - trigger hold gesture\n // Check touchStartRef to ensure touch hasn't ended before timer fired\n if (touchStartRef.current) {\n const { clientX, clientY } = touchStartRef.current;\n\n // Use captured screen center coordinates (from touch start time)\n const deltaX = clientX - screenCenterX;\n // Invert Y so that a touch higher on the screen is considered \"forward\"\n const deltaY = screenCenterY - clientY;\n\n const holdGesture: GestureType =\n Math.abs(deltaX) >= Math.abs(deltaY)\n ? (deltaX > 0 ? 'hold-right' : 'hold-left')\n : (deltaY > 0 ? 'hold-forward' : 'hold-back');\n\n onGesture({\n type: holdGesture,\n startX: clientX,\n startY: clientY,\n });\n }\n }, holdThreshold);\n }, [enabled, onGesture, holdThreshold]);\n\n /**\n * Handle touch end event\n */\n const handleTouchEnd = useCallback((e: TouchEvent) => {\n if (!enabled || !touchStartRef.current) return;\n\n const touchEnd = e.changedTouches[0];\n const touchStart = touchStartRef.current;\n const touchDuration = Date.now() - touchStartTimeRef.current;\n\n // Clear hold timer\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n holdTimerRef.current = null;\n }\n\n // Calculate deltas\n const deltaX = touchEnd.clientX - touchStart.clientX;\n const deltaY = touchEnd.clientY - touchStart.clientY;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n // Reset touch state\n setIsTouching(false);\n\n // Detect gesture type\n if (distance >= minSwipeDistance) {\n // Swipe gesture (for quick directional inputs)\n e.preventDefault();\n\n // Determine primary direction\n if (Math.abs(deltaX) > Math.abs(deltaY)) {\n // Horizontal swipe\n if (deltaX > 0) {\n onGesture({\n type: 'swipe-right',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n onGesture({\n type: 'swipe-left',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n } else {\n // Vertical swipe\n if (deltaY > 0) {\n onGesture({\n type: 'swipe-down',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n onGesture({\n type: 'swipe-up',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n }\n } else if (touchDuration <= maxTapDuration && touchDuration < holdThreshold) {\n // Quick tap - tactical step gesture\n e.preventDefault();\n \n const tapGesture = getDirectionalGesture(\n touchStart.clientX,\n touchStart.clientY,\n touchEnd.clientX,\n touchEnd.clientY,\n true // Is a tap\n );\n \n if (tapGesture) {\n // Directional step tap\n triggerStepHaptic();\n onGesture({\n type: tapGesture,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n // Generic tap (fallback)\n onGesture({\n type: 'tap',\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n }\n\n // Clear touch start reference\n touchStartRef.current = null;\n }, [enabled, minSwipeDistance, maxTapDuration, holdThreshold, onGesture, getDirectionalGesture, triggerStepHaptic]);\n\n /**\n * Handle touch cancel event\n */\n const handleTouchCancel = useCallback(() => {\n // Clear hold timer\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n holdTimerRef.current = null;\n }\n \n touchStartRef.current = null;\n touchStartTimeRef.current = 0;\n setIsTouching(false);\n }, []);\n\n /**\n * Setup touch event listeners\n */\n useEffect(() => {\n if (!enabled) return;\n\n const options: AddEventListenerOptions = {\n passive: false, // Allow preventDefault for gesture handling\n };\n\n document.addEventListener('touchstart', handleTouchStart, options);\n document.addEventListener('touchend', handleTouchEnd, options);\n document.addEventListener('touchcancel', handleTouchCancel, options);\n\n return () => {\n document.removeEventListener('touchstart', handleTouchStart);\n document.removeEventListener('touchend', handleTouchEnd);\n document.removeEventListener('touchcancel', handleTouchCancel);\n };\n }, [enabled, handleTouchStart, handleTouchEnd, handleTouchCancel]);\n\n return {\n isTouching,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useTouchControls.js","names":[],"sources":["../../src/hooks/useTouchControls.ts"],"sourcesContent":["/**\n * Touch Controls Hook\n * \n * Manages touch event handling and gesture recognition for mobile gameplay\n * Provides swipe detection, multi-touch support, and touch-based movement\n * \n * @module hooks/useTouchControls\n * @category Mobile Controls\n * @korean 터치 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n/**\n * Gesture types supported by the touch control system\n * \n * Added tactical step gestures for Korean martial arts footwork:\n * - tap-{direction}: Quick tap for tactical 30cm step\n * - hold-{direction}: Hold for continuous walk\n * \n * @korean 제스처타입\n */\nexport type GestureType =\n | 'swipe-right'\n | 'swipe-left'\n | 'swipe-up'\n | 'swipe-down'\n | 'two-finger-tap'\n | 'tap'\n | 'tap-forward'\n | 'tap-back'\n | 'tap-left'\n | 'tap-right'\n | 'tap-forward-left'\n | 'tap-forward-right'\n | 'tap-back-left'\n | 'tap-back-right'\n | 'hold-forward'\n | 'hold-back'\n | 'hold-left'\n | 'hold-right';\n\n/**\n * Gesture event data\n */\nexport interface GestureEvent {\n /** Type of gesture detected */\n readonly type: GestureType;\n /** Distance of swipe in pixels (for swipe gestures) */\n readonly distance?: number;\n /** Coordinates of touch start */\n readonly startX?: number;\n readonly startY?: number;\n /** Coordinates of touch end */\n readonly endX?: number;\n readonly endY?: number;\n}\n\n/**\n * Props for useTouchControls hook\n */\nexport interface UseTouchControlsProps {\n /** Callback when gesture is detected */\n readonly onGesture: (gesture: GestureEvent) => void;\n /** Whether touch input is enabled */\n readonly enabled?: boolean;\n /** Minimum swipe distance in pixels (default: 50) */\n readonly minSwipeDistance?: number;\n /** Maximum time for tap in ms (default: 300) */\n readonly maxTapDuration?: number;\n /** Time threshold for hold vs tap in ms (default: 200) */\n readonly holdThreshold?: number;\n /** Enable haptic feedback for steps (default: true) */\n readonly enableHaptics?: boolean;\n}\n\n/**\n * Return type for useTouchControls hook\n */\nexport interface UseTouchControlsReturn {\n /** Whether a touch is currently active */\n readonly isTouching: boolean;\n}\n\n/**\n * Custom hook for handling touch controls and gesture recognition\n * \n * Features:\n * - Swipe detection (horizontal and vertical)\n * - Two-finger tap detection for vital point mode\n * - Single tap detection\n * - Tactical step gestures (tap) vs continuous walk (hold)\n * - Distance calculation for swipe intensity\n * - Configurable thresholds\n * - Haptic feedback for tactical steps\n * \n * Gesture Mapping:\n * - Swipe Right: Advance toward opponent\n * - Swipe Left: Retreat from opponent\n * - Swipe Up: High stance mode\n * - Swipe Down: Low stance mode\n * - Two-Finger Tap: Activate vital point targeting mode\n * - Single Tap (directional): Tactical 30cm step (전술적 발걸음)\n * - Hold (directional): Continuous walk movement\n * \n * @example\n * ```typescript\n * const { isTouching } = useTouchControls({\n * onGesture: (gesture) => {\n * switch (gesture.type) {\n * case 'tap-forward':\n * handleTacticalStep('forward'); // 전진보법\n * break;\n * case 'hold-forward':\n * handleContinuousWalk('forward');\n * break;\n * case 'two-finger-tap':\n * activateVitalPointMode();\n * break;\n * }\n * },\n * enabled: !isPaused,\n * holdThreshold: 200, // 200ms to distinguish tap from hold\n * enableHaptics: true,\n * });\n * ```\n * \n * @public\n * @korean 터치컨트롤사용\n */\nexport function useTouchControls({\n onGesture,\n enabled = true,\n minSwipeDistance = 50,\n maxTapDuration = 300,\n holdThreshold = 200,\n enableHaptics = true,\n}: UseTouchControlsProps): UseTouchControlsReturn {\n const touchStartRef = useRef<Touch | null>(null);\n const touchStartTimeRef = useRef<number>(0);\n const [isTouching, setIsTouching] = useState<boolean>(false);\n const holdTimerRef = useRef<number | null>(null);\n \n /**\n * Trigger haptic feedback for tactical step\n * Light vibration (10ms) to confirm step input\n * \n * @korean 햅틱피드백\n */\n const triggerStepHaptic = useCallback(() => {\n if (!enableHaptics || !navigator.vibrate) return;\n \n try {\n // Short, light vibration for step (10ms)\n navigator.vibrate(10);\n } catch (error) {\n // Haptic feedback not supported or failed\n console.debug('Haptic feedback not available:', error);\n }\n }, [enableHaptics]);\n \n /**\n * Determine directional gesture from touch position\n * Used for D-pad style controls\n * Returns null for ambiguous/stationary taps\n * \n * @korean 방향제스처감지\n */\n const getDirectionalGesture = useCallback((\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n isTap: boolean\n ): GestureType | null => {\n const deltaX = endX - startX;\n const deltaY = endY - startY;\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n \n // If movement is too small, it's not a directional gesture\n const minDirectionalMovement = 15; // pixels\n if (absX < minDirectionalMovement && absY < minDirectionalMovement) {\n return null; // Ambiguous tap, not directional\n }\n \n // Check for diagonal gestures (45-degree threshold)\n const isDiagonal = absX > 20 && absY > 20 && Math.abs(absX - absY) < 30;\n \n const prefix = isTap ? 'tap' : 'hold';\n \n if (isDiagonal) {\n // Diagonal gestures (only for taps/steps)\n if (isTap) {\n if (deltaY < 0 && deltaX < 0) return 'tap-forward-left';\n if (deltaY < 0 && deltaX > 0) return 'tap-forward-right';\n if (deltaY > 0 && deltaX < 0) return 'tap-back-left';\n if (deltaY > 0 && deltaX > 0) return 'tap-back-right';\n }\n return null;\n }\n \n // Cardinal directions\n if (absX > absY) {\n // Horizontal\n return deltaX > 0 ? `${prefix}-right` as GestureType : `${prefix}-left` as GestureType;\n } else {\n // Vertical\n return deltaY < 0 ? `${prefix}-forward` as GestureType : `${prefix}-back` as GestureType;\n }\n }, []);\n\n /**\n * Handle touch start event\n */\n const handleTouchStart = useCallback((e: TouchEvent) => {\n if (!enabled) return;\n\n const touch = e.touches[0];\n touchStartRef.current = touch;\n touchStartTimeRef.current = Date.now();\n setIsTouching(true);\n\n // Check for two-finger tap immediately\n if (e.touches.length === 2) {\n e.preventDefault();\n onGesture({\n type: 'two-finger-tap',\n startX: touch.clientX,\n startY: touch.clientY,\n });\n return;\n }\n \n // Capture screen dimensions at touch start time to prevent incorrect\n // direction calculation if window is resized during hold\n const screenCenterX = window.innerWidth / 2;\n const screenCenterY = window.innerHeight / 2;\n \n // Set up hold detection timer\n // Note: Hold gesture direction is determined from the initial touch position\n // relative to screen center. This supports D-pad style layouts where each\n // region of the screen (or an overlaid control) corresponds to a cardinal direction.\n holdTimerRef.current = window.setTimeout(() => {\n // Touch held for longer than threshold - trigger hold gesture\n // Check touchStartRef to ensure touch hasn't ended before timer fired\n if (touchStartRef.current) {\n const { clientX, clientY } = touchStartRef.current;\n\n // Use captured screen center coordinates (from touch start time)\n const deltaX = clientX - screenCenterX;\n // Invert Y so that a touch higher on the screen is considered \"forward\"\n const deltaY = screenCenterY - clientY;\n\n const holdGesture: GestureType =\n Math.abs(deltaX) >= Math.abs(deltaY)\n ? (deltaX > 0 ? 'hold-right' : 'hold-left')\n : (deltaY > 0 ? 'hold-forward' : 'hold-back');\n\n onGesture({\n type: holdGesture,\n startX: clientX,\n startY: clientY,\n });\n }\n }, holdThreshold);\n }, [enabled, onGesture, holdThreshold]);\n\n /**\n * Handle touch end event\n */\n const handleTouchEnd = useCallback((e: TouchEvent) => {\n if (!enabled || !touchStartRef.current) return;\n\n const touchEnd = e.changedTouches[0];\n const touchStart = touchStartRef.current;\n const touchDuration = Date.now() - touchStartTimeRef.current;\n\n // Clear hold timer\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n holdTimerRef.current = null;\n }\n\n // Calculate deltas\n const deltaX = touchEnd.clientX - touchStart.clientX;\n const deltaY = touchEnd.clientY - touchStart.clientY;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n // Reset touch state\n setIsTouching(false);\n\n // Detect gesture type\n if (distance >= minSwipeDistance) {\n // Swipe gesture (for quick directional inputs)\n e.preventDefault();\n\n // Determine primary direction\n if (Math.abs(deltaX) > Math.abs(deltaY)) {\n // Horizontal swipe\n if (deltaX > 0) {\n onGesture({\n type: 'swipe-right',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n onGesture({\n type: 'swipe-left',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n } else {\n // Vertical swipe\n if (deltaY > 0) {\n onGesture({\n type: 'swipe-down',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n onGesture({\n type: 'swipe-up',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n }\n } else if (touchDuration <= maxTapDuration && touchDuration < holdThreshold) {\n // Quick tap - tactical step gesture\n e.preventDefault();\n \n const tapGesture = getDirectionalGesture(\n touchStart.clientX,\n touchStart.clientY,\n touchEnd.clientX,\n touchEnd.clientY,\n true // Is a tap\n );\n \n if (tapGesture) {\n // Directional step tap\n triggerStepHaptic();\n onGesture({\n type: tapGesture,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n // Generic tap (fallback)\n onGesture({\n type: 'tap',\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n }\n\n // Clear touch start reference\n touchStartRef.current = null;\n }, [enabled, minSwipeDistance, maxTapDuration, holdThreshold, onGesture, getDirectionalGesture, triggerStepHaptic]);\n\n /**\n * Handle touch cancel event\n */\n const handleTouchCancel = useCallback(() => {\n // Clear hold timer\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n holdTimerRef.current = null;\n }\n \n touchStartRef.current = null;\n touchStartTimeRef.current = 0;\n setIsTouching(false);\n }, []);\n\n /**\n * Setup touch event listeners\n */\n useEffect(() => {\n if (!enabled) return;\n\n const options: AddEventListenerOptions = {\n passive: false, // Allow preventDefault for gesture handling\n };\n\n document.addEventListener('touchstart', handleTouchStart, options);\n document.addEventListener('touchend', handleTouchEnd, options);\n document.addEventListener('touchcancel', handleTouchCancel, options);\n\n return () => {\n document.removeEventListener('touchstart', handleTouchStart);\n document.removeEventListener('touchend', handleTouchEnd);\n document.removeEventListener('touchcancel', handleTouchCancel);\n };\n }, [enabled, handleTouchStart, handleTouchEnd, handleTouchCancel]);\n\n return {\n isTouching,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useWebGLContextLossHandler.js","names":[],"sources":["../../src/hooks/useWebGLContextLossHandler.ts"],"sourcesContent":["/**\n * useWebGLContextLossHandler - React hook for handling WebGL context loss\n *\n * This hook sets up event listeners for WebGL context loss and restoration,\n * which can occur due to GPU issues, memory pressure, or browser tab switching.\n *\n * @example\n * ```tsx\n * const Canvas = () => {\n * useWebGLContextLossHandler();\n * return <Canvas>...</Canvas>;\n * };\n * ```\n */\n\nimport type React from \"react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Global WebGL context state tracking\n * Helps coordinate context recovery across multiple Canvas components\n */\nlet globalContextLossCount = 0;\nlet globalIsContextLost = false;\nlet lastContextLossTime = 0;\n\n/**\n * Minimum delay between context loss events to prevent thrashing\n */\nexport const MIN_CONTEXT_RECOVERY_DELAY = 100; // ms\n\n/**\n * Get global WebGL context state\n */\nexport const getWebGLContextState = (): {\n isLost: boolean;\n lossCount: number;\n timeSinceLastLoss: number;\n} => ({\n isLost: globalIsContextLost,\n lossCount: globalContextLossCount,\n timeSinceLastLoss: Date.now() - lastContextLossTime,\n});\n\nexport interface WebGLContextLossOptions {\n /**\n * Callback when context is lost\n */\n readonly onContextLost?: () => void;\n\n /**\n * Callback when context is restored\n */\n readonly onContextRestored?: () => void;\n\n /**\n * Whether to attempt automatic restoration (default: true)\n */\n readonly autoRestore?: boolean;\n\n /**\n * Optional canvas ref to attach to a specific canvas element\n * If not provided, will query for the first canvas in the document\n */\n readonly canvasRef?: React.RefObject<HTMLCanvasElement>;\n\n /**\n * Delay in ms before attempting to query for canvas (default: 50)\n * Helps avoid race conditions during screen transitions\n */\n readonly mountDelay?: number;\n\n /**\n * Maximum attempts to find canvas element (default: 5)\n */\n readonly maxRetries?: number;\n}\n\nexport interface WebGLContextState {\n /** Whether context is currently lost */\n readonly isContextLost: boolean;\n /** Number of times context has been lost */\n readonly lossCount: number;\n /** Whether the canvas element has been found */\n readonly isCanvasMounted: boolean;\n}\n\n/**\n * Hook to handle WebGL context loss and restoration\n * Returns state information about the WebGL context\n */\nexport const useWebGLContextLossHandler = (\n options: WebGLContextLossOptions = {}\n): WebGLContextState => {\n const {\n onContextLost,\n onContextRestored,\n autoRestore = true,\n canvasRef,\n mountDelay = 50,\n maxRetries = 5,\n } = options;\n\n const [contextState, setContextState] = useState<WebGLContextState>({\n isContextLost: globalIsContextLost,\n lossCount: globalContextLossCount,\n isCanvasMounted: false,\n });\n\n // Use refs to store the latest callbacks to avoid re-registering event listeners\n const onContextLostRef = useRef(onContextLost);\n const onContextRestoredRef = useRef(onContextRestored);\n const retryCountRef = useRef(0);\n const mountedRef = useRef(true);\n\n // Update refs when callbacks change\n useEffect(() => {\n onContextLostRef.current = onContextLost;\n onContextRestoredRef.current = onContextRestored;\n });\n\n // Track component mount state\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const handleContextLost = useCallback(\n (event: Event) => {\n const now = Date.now();\n globalIsContextLost = true;\n globalContextLossCount++;\n lastContextLossTime = now;\n\n console.warn(\n `⚠️ WebGL context lost (count: ${globalContextLossCount}, time since mount: ${\n now - lastContextLossTime\n }ms)`\n );\n\n // Prevent default behavior to allow restoration\n if (autoRestore) {\n event.preventDefault();\n }\n\n if (mountedRef.current) {\n setContextState({\n isContextLost: true,\n lossCount: globalContextLossCount,\n isCanvasMounted: true,\n });\n }\n\n onContextLostRef.current?.();\n },\n [autoRestore]\n );\n\n const handleContextRestored = useCallback(() => {\n globalIsContextLost = false;\n console.log(\"✅ WebGL context restored successfully\");\n\n if (mountedRef.current) {\n setContextState((prev) => ({\n ...prev,\n isContextLost: false,\n }));\n }\n\n onContextRestoredRef.current?.();\n }, []);\n\n useEffect(() => {\n let canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n let cleanupFn: (() => void) | undefined;\n let retryTimeout: ReturnType<typeof setTimeout> | undefined;\n let observer: MutationObserver | undefined;\n\n const attachListeners = (canvasEl: HTMLCanvasElement) => {\n canvasEl.addEventListener(\"webglcontextlost\", handleContextLost, false);\n canvasEl.addEventListener(\n \"webglcontextrestored\",\n handleContextRestored,\n false\n );\n\n if (mountedRef.current) {\n setContextState((prev) => ({\n ...prev,\n isCanvasMounted: true,\n }));\n }\n\n return () => {\n canvasEl.removeEventListener(\"webglcontextlost\", handleContextLost);\n canvasEl.removeEventListener(\n \"webglcontextrestored\",\n handleContextRestored\n );\n };\n };\n\n // Retry finding canvas with delay to handle screen transitions\n const tryFindCanvas = () => {\n if (!mountedRef.current) return;\n\n canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n if (canvas) {\n cleanupFn = attachListeners(canvas);\n retryCountRef.current = 0;\n } else if (retryCountRef.current < maxRetries) {\n retryCountRef.current++;\n retryTimeout = setTimeout(tryFindCanvas, mountDelay);\n } else {\n // Fall back to MutationObserver\n observer = new MutationObserver(() => {\n canvas = document.querySelector(\"canvas\");\n if (canvas && mountedRef.current) {\n observer?.disconnect();\n cleanupFn = attachListeners(canvas);\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n };\n\n // Attach immediately if a canvas is already mounted, otherwise retry\n canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n if (canvas) {\n cleanupFn = attachListeners(canvas);\n } else {\n retryTimeout = setTimeout(tryFindCanvas, mountDelay);\n }\n\n // Cleanup\n return () => {\n if (retryTimeout) clearTimeout(retryTimeout);\n observer?.disconnect();\n cleanupFn?.();\n };\n }, [\n autoRestore,\n canvasRef,\n handleContextLost,\n handleContextRestored,\n maxRetries,\n mountDelay,\n ]);\n\n return contextState;\n};\n\n/**\n * Check if WebGL is available in the current browser\n */\nexport const isWebGLAvailable = (): boolean => {\n try {\n const canvas = document.createElement(\"canvas\");\n const gl =\n canvas.getContext(\"webgl\") ?? canvas.getContext(\"experimental-webgl\");\n const available = gl !== null;\n // Help GC by cleaning up WebGL context\n if (gl && \"getExtension\" in gl) {\n const loseContext = gl.getExtension(\"WEBGL_lose_context\");\n loseContext?.loseContext();\n }\n return available;\n } catch {\n return false;\n }\n};\n\n/**\n * Check if WebGL2 is available in the current browser\n */\nexport const isWebGL2Available = (): boolean => {\n try {\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl2\");\n const available = gl !== null;\n // Help GC by cleaning up WebGL context\n if (gl && \"getExtension\" in gl) {\n const loseContext = gl.getExtension(\"WEBGL_lose_context\");\n loseContext?.loseContext();\n }\n return available;\n } catch {\n return false;\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useWebGLContextLossHandler.js","names":[],"sources":["../../src/hooks/useWebGLContextLossHandler.ts"],"sourcesContent":["/**\n * useWebGLContextLossHandler - React hook for handling WebGL context loss\n *\n * This hook sets up event listeners for WebGL context loss and restoration,\n * which can occur due to GPU issues, memory pressure, or browser tab switching.\n *\n * @example\n * ```tsx\n * const Canvas = () => {\n * useWebGLContextLossHandler();\n * return <Canvas>...</Canvas>;\n * };\n * ```\n */\n\nimport type React from \"react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Global WebGL context state tracking\n * Helps coordinate context recovery across multiple Canvas components\n */\nlet globalContextLossCount = 0;\nlet globalIsContextLost = false;\nlet lastContextLossTime = 0;\n\n/**\n * Minimum delay between context loss events to prevent thrashing\n */\nexport const MIN_CONTEXT_RECOVERY_DELAY = 100; // ms\n\n/**\n * Get global WebGL context state\n */\nexport const getWebGLContextState = (): {\n isLost: boolean;\n lossCount: number;\n timeSinceLastLoss: number;\n} => ({\n isLost: globalIsContextLost,\n lossCount: globalContextLossCount,\n timeSinceLastLoss: Date.now() - lastContextLossTime,\n});\n\nexport interface WebGLContextLossOptions {\n /**\n * Callback when context is lost\n */\n readonly onContextLost?: () => void;\n\n /**\n * Callback when context is restored\n */\n readonly onContextRestored?: () => void;\n\n /**\n * Whether to attempt automatic restoration (default: true)\n */\n readonly autoRestore?: boolean;\n\n /**\n * Optional canvas ref to attach to a specific canvas element\n * If not provided, will query for the first canvas in the document\n */\n readonly canvasRef?: React.RefObject<HTMLCanvasElement>;\n\n /**\n * Delay in ms before attempting to query for canvas (default: 50)\n * Helps avoid race conditions during screen transitions\n */\n readonly mountDelay?: number;\n\n /**\n * Maximum attempts to find canvas element (default: 5)\n */\n readonly maxRetries?: number;\n}\n\nexport interface WebGLContextState {\n /** Whether context is currently lost */\n readonly isContextLost: boolean;\n /** Number of times context has been lost */\n readonly lossCount: number;\n /** Whether the canvas element has been found */\n readonly isCanvasMounted: boolean;\n}\n\n/**\n * Hook to handle WebGL context loss and restoration\n * Returns state information about the WebGL context\n */\nexport const useWebGLContextLossHandler = (\n options: WebGLContextLossOptions = {}\n): WebGLContextState => {\n const {\n onContextLost,\n onContextRestored,\n autoRestore = true,\n canvasRef,\n mountDelay = 50,\n maxRetries = 5,\n } = options;\n\n const [contextState, setContextState] = useState<WebGLContextState>({\n isContextLost: globalIsContextLost,\n lossCount: globalContextLossCount,\n isCanvasMounted: false,\n });\n\n // Use refs to store the latest callbacks to avoid re-registering event listeners\n const onContextLostRef = useRef(onContextLost);\n const onContextRestoredRef = useRef(onContextRestored);\n const retryCountRef = useRef(0);\n const mountedRef = useRef(true);\n\n // Update refs when callbacks change\n useEffect(() => {\n onContextLostRef.current = onContextLost;\n onContextRestoredRef.current = onContextRestored;\n });\n\n // Track component mount state\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const handleContextLost = useCallback(\n (event: Event) => {\n const now = Date.now();\n globalIsContextLost = true;\n globalContextLossCount++;\n lastContextLossTime = now;\n\n console.warn(\n `⚠️ WebGL context lost (count: ${globalContextLossCount}, time since mount: ${\n now - lastContextLossTime\n }ms)`\n );\n\n // Prevent default behavior to allow restoration\n if (autoRestore) {\n event.preventDefault();\n }\n\n if (mountedRef.current) {\n setContextState({\n isContextLost: true,\n lossCount: globalContextLossCount,\n isCanvasMounted: true,\n });\n }\n\n onContextLostRef.current?.();\n },\n [autoRestore]\n );\n\n const handleContextRestored = useCallback(() => {\n globalIsContextLost = false;\n console.log(\"✅ WebGL context restored successfully\");\n\n if (mountedRef.current) {\n setContextState((prev) => ({\n ...prev,\n isContextLost: false,\n }));\n }\n\n onContextRestoredRef.current?.();\n }, []);\n\n useEffect(() => {\n let canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n let cleanupFn: (() => void) | undefined;\n let retryTimeout: ReturnType<typeof setTimeout> | undefined;\n let observer: MutationObserver | undefined;\n\n const attachListeners = (canvasEl: HTMLCanvasElement) => {\n canvasEl.addEventListener(\"webglcontextlost\", handleContextLost, false);\n canvasEl.addEventListener(\n \"webglcontextrestored\",\n handleContextRestored,\n false\n );\n\n if (mountedRef.current) {\n setContextState((prev) => ({\n ...prev,\n isCanvasMounted: true,\n }));\n }\n\n return () => {\n canvasEl.removeEventListener(\"webglcontextlost\", handleContextLost);\n canvasEl.removeEventListener(\n \"webglcontextrestored\",\n handleContextRestored\n );\n };\n };\n\n // Retry finding canvas with delay to handle screen transitions\n const tryFindCanvas = () => {\n if (!mountedRef.current) return;\n\n canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n if (canvas) {\n cleanupFn = attachListeners(canvas);\n retryCountRef.current = 0;\n } else if (retryCountRef.current < maxRetries) {\n retryCountRef.current++;\n retryTimeout = setTimeout(tryFindCanvas, mountDelay);\n } else {\n // Fall back to MutationObserver\n observer = new MutationObserver(() => {\n canvas = document.querySelector(\"canvas\");\n if (canvas && mountedRef.current) {\n observer?.disconnect();\n cleanupFn = attachListeners(canvas);\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n };\n\n // Attach immediately if a canvas is already mounted, otherwise retry\n canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n if (canvas) {\n cleanupFn = attachListeners(canvas);\n } else {\n retryTimeout = setTimeout(tryFindCanvas, mountDelay);\n }\n\n // Cleanup\n return () => {\n if (retryTimeout) clearTimeout(retryTimeout);\n observer?.disconnect();\n cleanupFn?.();\n };\n }, [\n autoRestore,\n canvasRef,\n handleContextLost,\n handleContextRestored,\n maxRetries,\n mountDelay,\n ]);\n\n return contextState;\n};\n\n/**\n * Check if WebGL is available in the current browser\n */\nexport const isWebGLAvailable = (): boolean => {\n try {\n const canvas = document.createElement(\"canvas\");\n const gl =\n canvas.getContext(\"webgl\") ?? canvas.getContext(\"experimental-webgl\");\n const available = gl !== null;\n // Help GC by cleaning up WebGL context\n if (gl && \"getExtension\" in gl) {\n const loseContext = gl.getExtension(\"WEBGL_lose_context\");\n loseContext?.loseContext();\n }\n return available;\n } catch {\n return false;\n }\n};\n\n/**\n * Check if WebGL2 is available in the current browser\n */\nexport const isWebGL2Available = (): boolean => {\n try {\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl2\");\n const available = gl !== null;\n // Help GC by cleaning up WebGL context\n if (gl && \"getExtension\" in gl) {\n const loseContext = gl.getExtension(\"WEBGL_lose_context\");\n loseContext?.loseContext();\n }\n return available;\n } catch {\n return false;\n }\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useWindowSize.js","names":[],"sources":["../../src/hooks/useWindowSize.ts"],"sourcesContent":["/**\n * useWindowSize - Shared hook for responsive window dimensions\n *\n * @korean 윈도우크기훅 - 반응형 창 크기를 위한 공유 훅\n *\n * Eliminates duplication across screen components\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\n\nexport interface WindowSize {\n readonly width: number;\n readonly height: number;\n}\n\nexport interface UseWindowSizeOptions {\n /**\n * Initial width if window is not available (SSR)\n * @default 1200\n */\n readonly initialWidth?: number;\n\n /**\n * Initial height if window is not available (SSR)\n * @default 800\n */\n readonly initialHeight?: number;\n\n /**\n * Debounce delay in milliseconds\n * @default 0\n */\n readonly debounceMs?: number;\n}\n\n/**\n * Hook to track window dimensions with optional debouncing\n *\n * @korean 윈도우 크기를 추적하는 훅 (선택적 디바운싱 지원)\n *\n * @param options - Configuration options\n * @returns Current window dimensions\n *\n * @example\n * ```tsx\n * const { width, height } = useWindowSize();\n * const isMobile = width < 768;\n * ```\n */\nexport function useWindowSize(options: UseWindowSizeOptions = {}): WindowSize {\n const { initialWidth = 1200, initialHeight = 800, debounceMs = 0 } = options;\n\n const [size, setSize] = useState<WindowSize>(() => {\n // Check if window is available (SSR safety)\n if (typeof window !== \"undefined\") {\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n };\n }\n return {\n width: initialWidth,\n height: initialHeight,\n };\n });\n\n const handleResize = useCallback(() => {\n setSize({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n }, []);\n\n useEffect(() => {\n // Safety check for SSR\n if (typeof window === \"undefined\") return;\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const debouncedResize = () => {\n if (debounceMs > 0) {\n if (timeoutId) clearTimeout(timeoutId);\n timeoutId = setTimeout(handleResize, debounceMs);\n } else {\n handleResize();\n }\n };\n\n window.addEventListener(\"resize\", debouncedResize);\n\n return () => {\n window.removeEventListener(\"resize\", debouncedResize);\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [handleResize, debounceMs]);\n\n return size;\n}\n\n/**\n * Hook to determine if current viewport is mobile-sized\n *\n * @korean 현재 뷰포트가 모바일 크기인지 확인하는 훅\n *\n * @param breakpoint - Width threshold for mobile (default: 768)\n * @returns True if viewport width is less than breakpoint\n *\n * @example\n * ```tsx\n * const isMobile = useIsMobile();\n * const isMobileCustom = useIsMobile(640);\n * ```\n */\nexport function useIsMobile(breakpoint = 768): boolean {\n const { width } = useWindowSize();\n return width < breakpoint;\n}\n\nexport default useWindowSize;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,cAAc,UAAgC,EAAE,EAAc;CAC5E,MAAM,EAAE,eAAe,MAAM,gBAAgB,KAAK,aAAa,MAAM;CAErE,MAAM,CAAC,MAAM,WAAW,eAA2B;EAEjD,IAAI,OAAO,WAAW,aACpB,OAAO;GACL,OAAO,OAAO;GACd,QAAQ,OAAO;GAChB;EAEH,OAAO;GACL,OAAO;GACP,QAAQ;GACT;GACD;CAEF,MAAM,eAAe,kBAAkB;EACrC,QAAQ;GACN,OAAO,OAAO;GACd,QAAQ,OAAO;GAChB,CAAC;IACD,EAAE,CAAC;CAEN,gBAAgB;EAEd,IAAI,OAAO,WAAW,aAAa;EAEnC,IAAI,YAAkD;EAEtD,MAAM,wBAAwB;GAC5B,IAAI,aAAa,GAAG;IAClB,IAAI,WAAW,aAAa,UAAU;IACtC,YAAY,WAAW,cAAc,WAAW;UAEhD,cAAc;;EAIlB,OAAO,iBAAiB,UAAU,gBAAgB;EAElD,aAAa;GACX,OAAO,oBAAoB,UAAU,gBAAgB;GACrD,IAAI,WAAW,aAAa,UAAU;;IAEvC,CAAC,cAAc,WAAW,CAAC;CAE9B,OAAO;;;;;;;;;;;;;;;;AAiBT,SAAgB,YAAY,aAAa,KAAc;CACrD,MAAM,EAAE,UAAU,eAAe;CACjC,OAAO,QAAQ"}
1
+ {"version":3,"file":"useWindowSize.js","names":[],"sources":["../../src/hooks/useWindowSize.ts"],"sourcesContent":["/**\n * useWindowSize - Shared hook for responsive window dimensions\n *\n * @korean 윈도우크기훅 - 반응형 창 크기를 위한 공유 훅\n *\n * Eliminates duplication across screen components\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\n\nexport interface WindowSize {\n readonly width: number;\n readonly height: number;\n}\n\nexport interface UseWindowSizeOptions {\n /**\n * Initial width if window is not available (SSR)\n * @default 1200\n */\n readonly initialWidth?: number;\n\n /**\n * Initial height if window is not available (SSR)\n * @default 800\n */\n readonly initialHeight?: number;\n\n /**\n * Debounce delay in milliseconds\n * @default 0\n */\n readonly debounceMs?: number;\n}\n\n/**\n * Hook to track window dimensions with optional debouncing\n *\n * @korean 윈도우 크기를 추적하는 훅 (선택적 디바운싱 지원)\n *\n * @param options - Configuration options\n * @returns Current window dimensions\n *\n * @example\n * ```tsx\n * const { width, height } = useWindowSize();\n * const isMobile = width < 768;\n * ```\n */\nexport function useWindowSize(options: UseWindowSizeOptions = {}): WindowSize {\n const { initialWidth = 1200, initialHeight = 800, debounceMs = 0 } = options;\n\n const [size, setSize] = useState<WindowSize>(() => {\n // Check if window is available (SSR safety)\n if (typeof window !== \"undefined\") {\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n };\n }\n return {\n width: initialWidth,\n height: initialHeight,\n };\n });\n\n const handleResize = useCallback(() => {\n setSize({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n }, []);\n\n useEffect(() => {\n // Safety check for SSR\n if (typeof window === \"undefined\") return;\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const debouncedResize = () => {\n if (debounceMs > 0) {\n if (timeoutId) clearTimeout(timeoutId);\n timeoutId = setTimeout(handleResize, debounceMs);\n } else {\n handleResize();\n }\n };\n\n window.addEventListener(\"resize\", debouncedResize);\n\n return () => {\n window.removeEventListener(\"resize\", debouncedResize);\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [handleResize, debounceMs]);\n\n return size;\n}\n\n/**\n * Hook to determine if current viewport is mobile-sized\n *\n * @korean 현재 뷰포트가 모바일 크기인지 확인하는 훅\n *\n * @param breakpoint - Width threshold for mobile (default: 768)\n * @returns True if viewport width is less than breakpoint\n *\n * @example\n * ```tsx\n * const isMobile = useIsMobile();\n * const isMobileCustom = useIsMobile(640);\n * ```\n */\nexport function useIsMobile(breakpoint = 768): boolean {\n const { width } = useWindowSize();\n return width < breakpoint;\n}\n\nexport default useWindowSize;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,cAAc,UAAgC,CAAC,GAAe;CAC5E,MAAM,EAAE,eAAe,MAAM,gBAAgB,KAAK,aAAa,MAAM;CAErE,MAAM,CAAC,MAAM,WAAW,eAA2B;EAEjD,IAAI,OAAO,WAAW,aACpB,OAAO;GACL,OAAO,OAAO;GACd,QAAQ,OAAO;EACjB;EAEF,OAAO;GACL,OAAO;GACP,QAAQ;EACV;CACF,CAAC;CAED,MAAM,eAAe,kBAAkB;EACrC,QAAQ;GACN,OAAO,OAAO;GACd,QAAQ,OAAO;EACjB,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,gBAAgB;EAEd,IAAI,OAAO,WAAW,aAAa;EAEnC,IAAI,YAAkD;EAEtD,MAAM,wBAAwB;GAC5B,IAAI,aAAa,GAAG;IAClB,IAAI,WAAW,aAAa,SAAS;IACrC,YAAY,WAAW,cAAc,UAAU;GACjD,OACE,aAAa;EAEjB;EAEA,OAAO,iBAAiB,UAAU,eAAe;EAEjD,aAAa;GACX,OAAO,oBAAoB,UAAU,eAAe;GACpD,IAAI,WAAW,aAAa,SAAS;EACvC;CACF,GAAG,CAAC,cAAc,UAAU,CAAC;CAE7B,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,SAAgB,YAAY,aAAa,KAAc;CACrD,MAAM,EAAE,UAAU,cAAc;CAChC,OAAO,QAAQ;AACjB"}