blacktrigram 0.7.68 → 0.7.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/assets/index.css +987 -987
  3. package/lib/audio/AudioAssetLoader.js.map +1 -1
  4. package/lib/audio/AudioAssetRegistry.js +2 -2
  5. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  6. package/lib/audio/AudioCache.js.map +1 -1
  7. package/lib/audio/AudioManager.js +1 -1
  8. package/lib/audio/AudioManager.js.map +1 -1
  9. package/lib/audio/AudioMonitor.js.map +1 -1
  10. package/lib/audio/AudioPool.js.map +1 -1
  11. package/lib/audio/AudioProvider.js.map +1 -1
  12. package/lib/audio/AudioUtils.js.map +1 -1
  13. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +3 -3
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  18. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  19. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  20. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  25. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  26. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  27. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  28. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  29. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  30. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  31. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  32. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  34. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  35. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  36. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  38. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  39. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  40. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  41. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  42. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  43. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  44. package/lib/components/screens/training/TrainingScreen3D.js +1 -1
  45. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  46. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  47. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  48. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  49. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  50. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  51. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  52. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  53. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  54. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  55. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  56. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  57. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  58. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  59. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  60. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  61. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  62. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  63. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  64. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  65. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  66. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  67. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  68. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  69. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  70. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  71. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  72. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  73. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  74. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  75. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  76. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  77. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  78. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  79. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  80. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  81. package/lib/components/shared/ui/SplashScreen.js +2 -2
  82. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  83. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  84. package/lib/data/techniques.js.map +1 -1
  85. package/lib/hooks/useCombatTimer.js.map +1 -1
  86. package/lib/hooks/useKeyboardControls.js.map +1 -1
  87. package/lib/hooks/useSkeletalAnimation.js +2 -2
  88. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  89. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  90. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  91. package/lib/index.js +2 -2
  92. package/lib/systems/CombatSystem.js.map +1 -1
  93. package/lib/systems/EffectCalculator.js.map +1 -1
  94. package/lib/systems/ResponsiveScaling.js.map +1 -1
  95. package/lib/systems/TrigramSystem.js.map +1 -1
  96. package/lib/systems/ai/AIPersonality.js.map +1 -1
  97. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  98. package/lib/systems/ai/DecisionTree.js.map +1 -1
  99. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  100. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  101. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  102. package/lib/systems/animation/builders/SkeletonRig.js +1 -1
  103. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  104. package/lib/systems/animation/catalogs/AttackAnimations.js +1 -1
  105. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  106. package/lib/systems/animation/catalogs/BasicAnimations.js +1 -1
  107. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  108. package/lib/systems/animation/catalogs/ComboAnimations.js +1 -1
  109. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  110. package/lib/systems/animation/catalogs/DarkOpsAnimations.js +1 -1
  111. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  112. package/lib/systems/animation/catalogs/DefensiveAnimations.js +2 -11
  113. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  114. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js +1 -1
  115. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  116. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js +1 -1
  117. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  118. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  119. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -1
  120. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  121. package/lib/systems/animation/catalogs/GanStanceAnimations.js +1 -1
  122. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  123. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js +1 -1
  124. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  125. package/lib/systems/animation/catalogs/GeonStanceAnimations.js +1 -1
  126. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  127. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +1 -1
  128. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -1
  129. package/lib/systems/animation/catalogs/GrapplingAnimations.js +1 -1
  130. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  131. package/lib/systems/animation/catalogs/JinStanceAnimations.js +1 -1
  132. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  133. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js +1 -1
  134. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  135. package/lib/systems/animation/catalogs/KickAnimations.js +1 -1
  136. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  137. package/lib/systems/animation/catalogs/LiStanceAnimations.js +1 -1
  138. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  139. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  140. package/lib/systems/animation/catalogs/MovementAnimations.js +1 -1
  141. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  142. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  143. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  144. package/lib/systems/animation/catalogs/SonStanceAnimations.js +1 -1
  145. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  146. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js +1 -1
  147. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  148. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js +1 -1
  149. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  150. package/lib/systems/animation/catalogs/StanceAnimations.js +1 -1
  151. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  152. package/lib/systems/animation/catalogs/StanceAttackAnimations.js +215 -272
  153. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  154. package/lib/systems/animation/catalogs/StanceIdleAnimations.js +2 -11
  155. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  156. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js +2 -2
  157. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  158. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js +1 -1
  159. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  160. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  161. package/lib/systems/animation/catalogs/TaeStanceAnimations.js +1 -1
  162. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  163. package/lib/systems/animation/core/AnimationOptimizations.js +1 -1
  164. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  165. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  166. package/lib/systems/animation/core/AnimationRegistry.js +11 -209
  167. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  168. package/lib/systems/animation/core/AnimationStateMachine.js +2 -2
  169. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  170. package/lib/systems/animation/core/TechniqueAnimationMapper.js +1 -1
  171. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  172. package/lib/systems/animation/core/TechniqueAnimationMapping.js +1 -1
  173. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  174. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  175. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  176. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  177. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  178. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  179. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  180. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  181. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  182. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  183. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  184. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  185. package/lib/systems/combat/typeGuards.js.map +1 -1
  186. package/lib/systems/index.js +2 -2
  187. package/lib/systems/movement/integration.js.map +1 -1
  188. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  189. package/lib/systems/physics/CollisionDetection.js +1 -1
  190. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  191. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  192. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  193. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  194. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  195. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  196. package/lib/systems/trigram/StanceManager.js.map +1 -1
  197. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  198. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  199. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  200. package/lib/systems/trigram/techniques/index.js.map +1 -1
  201. package/lib/systems/trigram/types.js.map +1 -1
  202. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  203. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  204. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  205. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  206. package/lib/types/constants/designSystem.js.map +1 -1
  207. package/lib/utils/colorHelpers.js.map +1 -1
  208. package/lib/utils/colorUtils.js.map +1 -1
  209. package/lib/utils/controlMapping.js.map +1 -1
  210. package/lib/utils/deviceDetection.js.map +1 -1
  211. package/lib/utils/effectUtils.js.map +1 -1
  212. package/lib/utils/fabricTextures.js.map +1 -1
  213. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  214. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  215. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  216. package/lib/utils/safeAreaUtils.js.map +1 -1
  217. package/lib/utils/stanceHelpers.js.map +1 -1
  218. package/lib/utils/visualEffects.js.map +1 -1
  219. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"techniques.js","names":[],"sources":["../../src/data/techniques.ts"],"sourcesContent":["/**\n * Technique definitions for all player archetypes.\n *\n * **Korean**: 기술 정의 (Technique Definitions)\n *\n * Each archetype has 3-5 unique techniques that reflect their combat philosophy\n * and specialization. Techniques are mapped to keyboard shortcuts Q-E-R-T-Y-F-G-Z-X-C.\n *\n * @module data/techniques\n * @category Combat System\n * @korean 기술데이터\n */\n\nimport { KoreanTechniquesSystem } from \"../systems/trigram/KoreanTechniques\";\nimport { KoreanTechnique } from \"../systems/vitalpoint/types\";\nimport {\n DamageType,\n PlayerArchetype,\n Technique,\n TechniqueKey,\n TrigramStance,\n} from \"../types\";\nimport { AttackAnimationType } from \"../types/skeletal\";\n\n/**\n * Technique definitions for 무사 (Musa) - Traditional Warrior.\n *\n * Philosophy: Honor through disciplined strength and overwhelming force.\n * Favored Stance: ☰ 건 (Geon) - Heaven\n */\nexport const MUSA_TECHNIQUES: readonly Technique[] = [\n {\n id: \"musa_thunder_strike\",\n name: {\n korean: \"천둥벽력\",\n english: \"Thunder Strike\",\n },\n description: {\n korean: \"강력한 하늘의 힘으로 적을 강타합니다\",\n english: \"Strike with the power of heaven\",\n },\n staminaCost: 15,\n kiCost: 10,\n damage: { min: 25, max: 35 },\n damageType: DamageType.BLUNT,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n requiredStance: TrigramStance.GEON,\n keyboardShortcut: \"Q\",\n criticalChance: 0.25,\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 0.9, // Powerful strike, slightly slower\n },\n },\n {\n id: \"musa_iron_defense\",\n name: {\n korean: \"철벽방어\",\n english: \"Iron Defense\",\n },\n description: {\n korean: \"산처럼 굳건한 방어 자세를 취합니다\",\n english: \"Adopt an immovable defensive stance\",\n },\n staminaCost: 10,\n kiCost: 8,\n damage: { min: 0, max: 5 },\n damageType: DamageType.IMPACT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.GAN,\n keyboardShortcut: \"T\",\n specialEffect: \"defense_boost\",\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.0, // Normal defensive speed\n },\n },\n {\n id: \"musa_dragon_fist\",\n name: {\n korean: \"용권\",\n english: \"Dragon Fist\",\n },\n description: {\n korean: \"용의 기세로 적을 관통합니다\",\n english: \"Pierce through with dragon's might\",\n },\n staminaCost: 18,\n kiCost: 12,\n damage: { min: 30, max: 40 },\n damageType: DamageType.PIERCING,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n keyboardShortcut: \"E\",\n targetsVitalPoint: true,\n criticalChance: 0.3,\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 0.8, // Heavy piercing strike\n },\n },\n {\n id: \"musa_mountain_breaker\",\n name: {\n korean: \"파산격\",\n english: \"Mountain Breaker\",\n },\n description: {\n korean: \"산을 깨뜨리는 강력한 일격\",\n english: \"A devastating blow that shatters mountains\",\n },\n staminaCost: 20,\n kiCost: 15,\n damage: { min: 35, max: 50 },\n damageType: DamageType.CRUSHING,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.GEON,\n keyboardShortcut: \"R\",\n criticalChance: 0.2,\n specialEffect: \"armor_break\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 0.8, // Powerful, slower strike\n },\n },\n];\n\n/**\n * Technique definitions for 암살자 (Amsalja) - Shadow Assassin.\n *\n * Philosophy: Precision through stealth and vital point mastery.\n * Favored Stance: ☲ 리 (Li) - Fire\n */\nexport const AMSALJA_TECHNIQUES: readonly Technique[] = [\n {\n id: \"amsalja_shadow_strike\",\n name: {\n korean: \"암영격\",\n english: \"Shadow Strike\",\n },\n description: {\n korean: \"그림자처럼 빠르게 급소를 노립니다\",\n english: \"Strike vital points with shadow speed\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 20, max: 35 },\n damageType: DamageType.NERVE,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.LI,\n keyboardShortcut: \"Q\",\n targetsVitalPoint: true,\n criticalChance: 0.4,\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.2, // Very fast, precise strike\n },\n },\n {\n id: \"amsalja_nerve_strike\",\n name: {\n korean: \"신경타\",\n english: \"Nerve Strike\",\n },\n description: {\n korean: \"정확한 신경 타격으로 적을 마비시킵니다\",\n english: \"Paralyze the enemy with precise nerve strikes\",\n },\n staminaCost: 15,\n kiCost: 12,\n damage: { min: 15, max: 25 },\n damageType: DamageType.NERVE,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n keyboardShortcut: \"T\",\n targetsVitalPoint: true,\n specialEffect: \"paralysis\",\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Fast precision\n },\n },\n {\n id: \"amsalja_deadly_precision\",\n name: {\n korean: \"치명정밀\",\n english: \"Deadly Precision\",\n },\n description: {\n korean: \"완벽한 정밀도로 치명적인 급소를 공격합니다\",\n english: \"Attack critical vital points with perfect accuracy\",\n },\n staminaCost: 18,\n kiCost: 15,\n damage: { min: 25, max: 45 },\n damageType: DamageType.PRESSURE,\n cooldown: 1400, // animationDuration (900) + recovery (500)\n requiredStance: TrigramStance.LI,\n keyboardShortcut: \"E\",\n targetsVitalPoint: true,\n criticalChance: 0.5,\n animationDuration: 900,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Precise, deliberate\n },\n },\n {\n id: \"amsalja_silent_death\",\n name: {\n korean: \"무음살\",\n english: \"Silent Death\",\n },\n description: {\n korean: \"소리 없이 치명적인 일격을 가합니다\",\n english: \"Deliver a silent, lethal strike\",\n },\n staminaCost: 22,\n kiCost: 18,\n damage: { min: 40, max: 60 },\n damageType: DamageType.NERVE,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n criticalChance: 0.6,\n specialEffect: \"instant_kill_chance\",\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.9, // Powerful lethal strike\n },\n },\n];\n\n/**\n * Technique definitions for 해커 (Hacker) - Cyber Warrior.\n *\n * Philosophy: Technology-enhanced combat with data-driven precision.\n * Favored Stance: ☳ 진 (Jin) - Thunder\n */\nexport const HACKER_TECHNIQUES: readonly Technique[] = [\n {\n id: \"hacker_electric_shock\",\n name: {\n korean: \"전격\",\n english: \"Electric Shock\",\n },\n description: {\n korean: \"사이버 임플란트로 전기 충격을 가합니다\",\n english: \"Deliver electric shock via cyber implants\",\n },\n staminaCost: 10,\n kiCost: 12,\n damage: { min: 18, max: 28 },\n damageType: DamageType.ELECTRIC,\n cooldown: 1000, // animationDuration (500) + recovery (500)\n requiredStance: TrigramStance.JIN,\n keyboardShortcut: \"Q\",\n specialEffect: \"stun\",\n animationDuration: 500,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.2, // Fast electric strike\n },\n },\n {\n id: \"hacker_data_strike\",\n name: {\n korean: \"데이터 타격\",\n english: \"Data Strike\",\n },\n description: {\n korean: \"전투 데이터를 분석하여 최적의 공격을 수행합니다\",\n english: \"Analyze combat data for optimal attack\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 22, max: 32 },\n damageType: DamageType.PIERCING,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n keyboardShortcut: \"T\",\n targetsVitalPoint: true,\n criticalChance: 0.35,\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Data-enhanced precision\n },\n },\n {\n id: \"hacker_cyber_overdrive\",\n name: {\n korean: \"사이버 가속\",\n english: \"Cyber Overdrive\",\n },\n description: {\n korean: \"임플란트를 과부하시켜 초고속 공격을 수행합니다\",\n english: \"Overload implants for lightning-fast attacks\",\n },\n staminaCost: 18,\n kiCost: 15,\n damage: { min: 15, max: 25 },\n damageType: DamageType.ELECTRIC,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.JIN,\n keyboardShortcut: \"E\",\n specialEffect: \"multi_hit\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT_RAPID,\n speedModifier: 1.3, // Ultra-fast combo\n },\n },\n {\n id: \"hacker_system_crash\",\n name: {\n korean: \"시스템 크래시\",\n english: \"System Crash\",\n },\n description: {\n korean: \"적의 신경 시스템을 해킹하여 무력화합니다\",\n english: \"Hack the enemy's nervous system\",\n },\n staminaCost: 20,\n kiCost: 18,\n damage: { min: 30, max: 45 },\n damageType: DamageType.PSYCHIC,\n cooldown: 1600, // animationDuration (1100) + recovery (500)\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n specialEffect: \"system_shutdown\",\n animationDuration: 1100,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Calculated precision\n },\n },\n];\n\n/**\n * Technique definitions for 정보요원 (Jeongbo Yowon) - Intelligence Operative.\n *\n * Philosophy: Strategic analysis and exploiting weaknesses.\n * Favored Stance: ☵ 감 (Gam) - Water\n */\nexport const JEONGBO_YOWON_TECHNIQUES: readonly Technique[] = [\n {\n id: \"jeongbo_tactical_strike\",\n name: {\n korean: \"전술타격\",\n english: \"Tactical Strike\",\n },\n description: {\n korean: \"적의 약점을 분석하여 전술적으로 공격합니다\",\n english: \"Analyze and strike enemy weaknesses tactically\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 20, max: 30 },\n damageType: DamageType.PIERCING,\n cooldown: 1150, // animationDuration (650) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"Q\",\n targetsVitalPoint: true,\n criticalChance: 0.3,\n animationDuration: 650,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Strategic precision\n },\n },\n {\n id: \"jeongbo_counter_intelligence\",\n name: {\n korean: \"역정보공작\",\n english: \"Counter Intelligence\",\n },\n description: {\n korean: \"적의 움직임을 읽고 반격합니다\",\n english: \"Read and counter enemy movements\",\n },\n staminaCost: 10,\n kiCost: 12,\n damage: { min: 15, max: 30 },\n damageType: DamageType.IMPACT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n keyboardShortcut: \"T\",\n specialEffect: \"counter_stance\",\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.2, // Fast counter\n },\n },\n {\n id: \"jeongbo_psychological_warfare\",\n name: {\n korean: \"심리전\",\n english: \"Psychological Warfare\",\n },\n description: {\n korean: \"적의 정신을 교란하여 약화시킵니다\",\n english: \"Disrupt enemy's mental state\",\n },\n staminaCost: 15,\n kiCost: 15,\n damage: { min: 10, max: 20 },\n damageType: DamageType.PSYCHIC,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n keyboardShortcut: \"E\",\n specialEffect: \"confusion\",\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Deliberate mental attack\n },\n },\n {\n id: \"jeongbo_intelligence_strike\",\n name: {\n korean: \"정보타격\",\n english: \"Intelligence Strike\",\n },\n description: {\n korean: \"수집한 정보를 바탕으로 완벽한 공격을 수행합니다\",\n english: \"Execute perfect attack based on gathered intelligence\",\n },\n staminaCost: 20,\n kiCost: 18,\n damage: { min: 35, max: 50 },\n damageType: DamageType.PIERCING,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n criticalChance: 0.45,\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.9, // Powerful decisive strike\n },\n },\n {\n id: \"jeongbo_precision_takedown\",\n name: {\n korean: \"정밀제압\",\n english: \"Precision Takedown\",\n },\n description: {\n korean: \"무력한 적을 정밀하게 제압합니다 - 서명 기술\",\n english: \"Precisely takedown helpless opponent - Signature Move\",\n },\n staminaCost: 25,\n kiCost: 20,\n damage: { min: 45, max: 65 },\n damageType: DamageType.PIERCING,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"Y\",\n targetsVitalPoint: true,\n criticalChance: 0.6,\n specialEffect: \"signature_move\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.8, // Deliberate, precise execution\n },\n },\n];\n\n/**\n * Technique definitions for 조직폭력배 (Jojik Pokryeokbae) - Organized Crime.\n *\n * Philosophy: Ruthless pragmatism and brutal efficiency.\n * Favored Stance: ☷ 곤 (Gon) - Earth\n */\nexport const JOJIK_POKRYEOKBAE_TECHNIQUES: readonly Technique[] = [\n {\n id: \"jojik_street_brawl\",\n name: {\n korean: \"거리싸움\",\n english: \"Street Brawl\",\n },\n description: {\n korean: \"더러운 싸움으로 적을 제압합니다\",\n english: \"Overwhelm with dirty fighting\",\n },\n staminaCost: 10,\n kiCost: 8,\n damage: { min: 18, max: 28 },\n damageType: DamageType.BLUNT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.GON,\n keyboardShortcut: \"Q\",\n criticalChance: 0.25,\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.1, // Fast brawl strike\n },\n },\n {\n id: \"jojik_brutal_takedown\",\n name: {\n korean: \"잔혹제압\",\n english: \"Brutal Takedown\",\n },\n description: {\n korean: \"무자비하게 적을 쓰러뜨립니다\",\n english: \"Mercilessly takedown the enemy\",\n },\n staminaCost: 15,\n kiCost: 10,\n damage: { min: 25, max: 35 },\n damageType: DamageType.CRUSHING,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n keyboardShortcut: \"T\",\n specialEffect: \"knockdown\",\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.ELBOW_STRIKE,\n speedModifier: 0.9, // Heavy takedown\n },\n },\n {\n id: \"jojik_improvised_weapon\",\n name: {\n korean: \"즉석무기\",\n english: \"Improvised Weapon\",\n },\n description: {\n korean: \"주변 물건을 이용하여 공격합니다\",\n english: \"Attack using improvised objects\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 22, max: 38 },\n damageType: DamageType.SHARP,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n requiredStance: TrigramStance.GON,\n keyboardShortcut: \"E\",\n criticalChance: 0.3,\n specialEffect: \"bleed\",\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 1.0, // Unpredictable weapon strike\n },\n },\n {\n id: \"jojik_ruthless_assault\",\n name: {\n korean: \"무자비공격\",\n english: \"Ruthless Assault\",\n },\n description: {\n korean: \"자비 없이 적을 집중 공격합니다\",\n english: \"Relentlessly assault without mercy\",\n },\n staminaCost: 22,\n kiCost: 12,\n damage: { min: 30, max: 55 },\n damageType: DamageType.CRUSHING,\n cooldown: 1800, // animationDuration (1300) + recovery (500)\n keyboardShortcut: \"R\",\n criticalChance: 0.35,\n specialEffect: \"rage\",\n animationDuration: 1300,\n animation: {\n type: AttackAnimationType.PUNCH_LOW,\n speedModifier: 0.8, // Heavy brutal assault\n },\n },\n];\n\n/**\n * Get techniques for a specific player archetype.\n *\n * @param archetype - Player archetype\n * @returns Array of techniques for the archetype\n *\n * @public\n */\nexport function getTechniquesForArchetype(\n archetype: PlayerArchetype,\n): readonly Technique[] {\n switch (archetype) {\n case PlayerArchetype.MUSA:\n return MUSA_TECHNIQUES;\n case PlayerArchetype.AMSALJA:\n return AMSALJA_TECHNIQUES;\n case PlayerArchetype.HACKER:\n return HACKER_TECHNIQUES;\n case PlayerArchetype.JEONGBO_YOWON:\n return JEONGBO_YOWON_TECHNIQUES;\n case PlayerArchetype.JOJIK_POKRYEOKBAE:\n return JOJIK_POKRYEOKBAE_TECHNIQUES;\n default:\n // Exhaustive check: if a new archetype is added, TypeScript will error here\n const _exhaustiveCheck: never = archetype;\n throw new Error(`Unknown archetype: ${_exhaustiveCheck}`);\n }\n}\n\n/**\n * Convert KoreanTechnique to Technique format for UI compatibility\n */\nfunction convertKoreanToTechnique(koreanTech: KoreanTechnique): Technique {\n // Convert string damageType to DamageType enum\n const getDamageType = (type: string): DamageType => {\n // Map common string values to DamageType enum\n const typeMap: Record<string, DamageType> = {\n blunt: DamageType.BLUNT,\n physical: DamageType.BLUNT,\n piercing: DamageType.PIERCING,\n slashing: DamageType.SLASHING,\n crushing: DamageType.CRUSHING,\n impact: DamageType.IMPACT,\n joint: DamageType.JOINT,\n electric: DamageType.ELECTRIC,\n psychic: DamageType.PSYCHIC,\n };\n return typeMap[type.toLowerCase()] ?? DamageType.BLUNT;\n };\n\n return {\n id: koreanTech.id,\n name: {\n korean: koreanTech.koreanName || koreanTech.name.korean,\n english: koreanTech.englishName || koreanTech.name.english,\n romanized: koreanTech.romanized || koreanTech.name.romanized,\n },\n description: koreanTech.description,\n staminaCost: koreanTech.staminaCost,\n kiCost: koreanTech.kiCost,\n damage: {\n min: Math.floor(koreanTech.damage * 0.8),\n max: Math.ceil(koreanTech.damage * 1.2),\n },\n damageType: getDamageType(koreanTech.damageType),\n cooldown: koreanTech.recoveryTime + koreanTech.executionTime,\n requiredStance: koreanTech.stance,\n keyboardShortcut: \"Q\", // Placeholder; immediately overridden by getTechniquesForStanceAndArchetype()\n criticalChance: koreanTech.critChance,\n animationDuration: koreanTech.executionTime,\n };\n}\n\n/**\n * Get techniques for a player based on their current stance and archetype.\n * Combines trigram stance techniques with archetype-specific bonuses.\n *\n * @param stance - Current player stance\n * @param archetype - Player archetype\n * @returns Array of available techniques with proper keyboard shortcuts assigned\n *\n * @public\n */\nexport function getTechniquesForStanceAndArchetype(\n stance: TrigramStance,\n archetype: PlayerArchetype,\n): readonly Technique[] {\n // Get stance-based techniques from Korean martial arts system\n const koreanTechniques = KoreanTechniquesSystem.getAllAvailableTechniques(\n stance,\n archetype,\n );\n\n // Convert to Technique format\n const convertedTechniques = koreanTechniques.map(convertKoreanToTechnique);\n\n // Also include archetype-specific special techniques\n const archetypeTechniques = getTechniquesForArchetype(archetype);\n\n // Filter archetype techniques to only include those matching current stance or no stance requirement\n const filteredArchetypeTechniques = archetypeTechniques.filter(\n (tech) => !tech.requiredStance || tech.requiredStance === stance,\n );\n\n // Combine both sets, prioritizing stance techniques\n const allTechniques = [\n ...convertedTechniques,\n ...filteredArchetypeTechniques,\n ];\n\n // Limit to maximum 10 techniques (keyboard shortcuts Q-E-R-T-Y-F-G-Z-X-C)\n const limitedTechniques = allTechniques.slice(0, 10);\n\n // Assign keyboard shortcuts using conflict-free keys around WASD\n const keyboardShortcuts = [\n \"Q\",\n \"E\",\n \"R\",\n \"T\",\n \"Y\",\n \"F\",\n \"G\",\n \"Z\",\n \"X\",\n \"C\",\n ] as const;\n return limitedTechniques.map((tech, index) => ({\n ...tech,\n keyboardShortcut: keyboardShortcuts[index] as TechniqueKey,\n }));\n}\n\n/**\n * Get a specific technique by ID.\n *\n * @param techniqueId - Unique technique identifier\n * @returns Technique if found, undefined otherwise\n *\n * @public\n */\nexport function getTechniqueById(techniqueId: string): Technique | undefined {\n const allTechniques = [\n ...MUSA_TECHNIQUES,\n ...AMSALJA_TECHNIQUES,\n ...HACKER_TECHNIQUES,\n ...JEONGBO_YOWON_TECHNIQUES,\n ...JOJIK_POKRYEOKBAE_TECHNIQUES,\n ];\n\n return allTechniques.find((tech) => tech.id === techniqueId);\n}\n\nexport default {\n getTechniquesForArchetype,\n getTechniquesForStanceAndArchetype,\n getTechniqueById,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,kBAAwC;CACnD;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,gBAAgB;EAChB,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAG,KAAK;EAAE;EACzB,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,gBAAgB;EAChB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;AACF;;;;;;;AAQA,IAAa,qBAA2C;CACtD;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,kBAAkB;EAClB,mBAAmB;EACnB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;AACF;;;;;;;AAQA,IAAa,oBAA0C;CACrD;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,kBAAkB;EAClB,mBAAmB;EACnB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;AACF;;;;;;;AAQA,IAAa,2BAAiD;CAC5D;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,kBAAkB;EAClB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,kBAAkB;EAClB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;AACF;;;;;;;AAQA,IAAa,+BAAqD;CAChE;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,gBAAgB;EAChB,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,kBAAkB;EAClB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,gBAAgB,cAAc;EAC9B,kBAAkB;EAClB,gBAAgB;EAChB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;CACA;EACE,IAAI;EACJ,MAAM;GACJ,QAAQ;GACR,SAAS;EACX;EACA,aAAa;GACX,QAAQ;GACR,SAAS;EACX;EACA,aAAa;EACb,QAAQ;EACR,QAAQ;GAAE,KAAK;GAAI,KAAK;EAAG;EAC3B,YAAY,WAAW;EACvB,UAAU;EACV,kBAAkB;EAClB,gBAAgB;EAChB,eAAe;EACf,mBAAmB;EACnB,WAAW;GACT,MAAM,oBAAoB;GAC1B,eAAe;EACjB;CACF;AACF;;;;;;;;;AAUA,SAAgB,0BACd,WACsB;CACtB,QAAQ,WAAR;EACE,KAAK,gBAAgB,MACnB,OAAO;EACT,KAAK,gBAAgB,SACnB,OAAO;EACT,KAAK,gBAAgB,QACnB,OAAO;EACT,KAAK,gBAAgB,eACnB,OAAO;EACT,KAAK,gBAAgB,mBACnB,OAAO;EACT,SAGE,MAAM,IAAI,MAAM,sBAAsB,WAAkB;CAC5D;AACF;;;;AAKA,SAAS,yBAAyB,YAAwC;CAExE,MAAM,iBAAiB,SAA6B;EAalD,OAAO;GAVL,OAAO,WAAW;GAClB,UAAU,WAAW;GACrB,UAAU,WAAW;GACrB,UAAU,WAAW;GACrB,UAAU,WAAW;GACrB,QAAQ,WAAW;GACnB,OAAO,WAAW;GAClB,UAAU,WAAW;GACrB,SAAS,WAAW;EAEf,EAAQ,KAAK,YAAY,MAAM,WAAW;CACnD;CAEA,OAAO;EACL,IAAI,WAAW;EACf,MAAM;GACJ,QAAQ,WAAW,cAAc,WAAW,KAAK;GACjD,SAAS,WAAW,eAAe,WAAW,KAAK;GACnD,WAAW,WAAW,aAAa,WAAW,KAAK;EACrD;EACA,aAAa,WAAW;EACxB,aAAa,WAAW;EACxB,QAAQ,WAAW;EACnB,QAAQ;GACN,KAAK,KAAK,MAAM,WAAW,SAAS,EAAG;GACvC,KAAK,KAAK,KAAK,WAAW,SAAS,GAAG;EACxC;EACA,YAAY,cAAc,WAAW,UAAU;EAC/C,UAAU,WAAW,eAAe,WAAW;EAC/C,gBAAgB,WAAW;EAC3B,kBAAkB;EAClB,gBAAgB,WAAW;EAC3B,mBAAmB,WAAW;CAChC;AACF;;;;;;;;;;;AAYA,SAAgB,mCACd,QACA,WACsB;CAQtB,MAAM,sBANmB,uBAAuB,0BAC9C,QACA,SAI0B,EAAiB,IAAI,wBAAwB;CAMzE,MAAM,8BAHsB,0BAA0B,SAGlB,EAAoB,QACrD,SAAS,CAAC,KAAK,kBAAkB,KAAK,mBAAmB,MAC5D;CASA,MAAM,oBAAoB,CALxB,GAAG,qBACH,GAAG,2BAIqB,EAAc,MAAM,GAAG,EAAE;CAGnD,MAAM,oBAAoB;EACxB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,OAAO,kBAAkB,KAAK,MAAM,WAAW;EAC7C,GAAG;EACH,kBAAkB,kBAAkB;CACtC,EAAE;AACJ;;;;;;;;;AAUA,SAAgB,iBAAiB,aAA4C;CAS3E,OAAO;EAPL,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;CAGE,EAAc,MAAM,SAAS,KAAK,OAAO,WAAW;AAC7D"}
1
+ {"version":3,"file":"techniques.js","names":[],"sources":["../../src/data/techniques.ts"],"sourcesContent":["/**\n * Technique definitions for all player archetypes.\n *\n * **Korean**: 기술 정의 (Technique Definitions)\n *\n * Each archetype has 3-5 unique techniques that reflect their combat philosophy\n * and specialization. Techniques are mapped to keyboard shortcuts Q-E-R-T-Y-F-G-Z-X-C.\n *\n * @module data/techniques\n * @category Combat System\n * @korean 기술데이터\n */\n\nimport { KoreanTechniquesSystem } from \"../systems/trigram/KoreanTechniques\";\nimport { KoreanTechnique } from \"../systems/vitalpoint/types\";\nimport {\n DamageType,\n PlayerArchetype,\n Technique,\n TechniqueKey,\n TrigramStance,\n} from \"../types\";\nimport { AttackAnimationType } from \"../types/skeletal\";\n\n/**\n * Technique definitions for 무사 (Musa) - Traditional Warrior.\n *\n * Philosophy: Honor through disciplined strength and overwhelming force.\n * Favored Stance: ☰ 건 (Geon) - Heaven\n */\nexport const MUSA_TECHNIQUES: readonly Technique[] = [\n {\n id: \"musa_thunder_strike\",\n name: {\n korean: \"천둥벽력\",\n english: \"Thunder Strike\",\n },\n description: {\n korean: \"강력한 하늘의 힘으로 적을 강타합니다\",\n english: \"Strike with the power of heaven\",\n },\n staminaCost: 15,\n kiCost: 10,\n damage: { min: 25, max: 35 },\n damageType: DamageType.BLUNT,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n requiredStance: TrigramStance.GEON,\n keyboardShortcut: \"Q\",\n criticalChance: 0.25,\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 0.9, // Powerful strike, slightly slower\n },\n },\n {\n id: \"musa_iron_defense\",\n name: {\n korean: \"철벽방어\",\n english: \"Iron Defense\",\n },\n description: {\n korean: \"산처럼 굳건한 방어 자세를 취합니다\",\n english: \"Adopt an immovable defensive stance\",\n },\n staminaCost: 10,\n kiCost: 8,\n damage: { min: 0, max: 5 },\n damageType: DamageType.IMPACT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.GAN,\n keyboardShortcut: \"T\",\n specialEffect: \"defense_boost\",\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.0, // Normal defensive speed\n },\n },\n {\n id: \"musa_dragon_fist\",\n name: {\n korean: \"용권\",\n english: \"Dragon Fist\",\n },\n description: {\n korean: \"용의 기세로 적을 관통합니다\",\n english: \"Pierce through with dragon's might\",\n },\n staminaCost: 18,\n kiCost: 12,\n damage: { min: 30, max: 40 },\n damageType: DamageType.PIERCING,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n keyboardShortcut: \"E\",\n targetsVitalPoint: true,\n criticalChance: 0.3,\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 0.8, // Heavy piercing strike\n },\n },\n {\n id: \"musa_mountain_breaker\",\n name: {\n korean: \"파산격\",\n english: \"Mountain Breaker\",\n },\n description: {\n korean: \"산을 깨뜨리는 강력한 일격\",\n english: \"A devastating blow that shatters mountains\",\n },\n staminaCost: 20,\n kiCost: 15,\n damage: { min: 35, max: 50 },\n damageType: DamageType.CRUSHING,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.GEON,\n keyboardShortcut: \"R\",\n criticalChance: 0.2,\n specialEffect: \"armor_break\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 0.8, // Powerful, slower strike\n },\n },\n];\n\n/**\n * Technique definitions for 암살자 (Amsalja) - Shadow Assassin.\n *\n * Philosophy: Precision through stealth and vital point mastery.\n * Favored Stance: ☲ 리 (Li) - Fire\n */\nexport const AMSALJA_TECHNIQUES: readonly Technique[] = [\n {\n id: \"amsalja_shadow_strike\",\n name: {\n korean: \"암영격\",\n english: \"Shadow Strike\",\n },\n description: {\n korean: \"그림자처럼 빠르게 급소를 노립니다\",\n english: \"Strike vital points with shadow speed\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 20, max: 35 },\n damageType: DamageType.NERVE,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.LI,\n keyboardShortcut: \"Q\",\n targetsVitalPoint: true,\n criticalChance: 0.4,\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.2, // Very fast, precise strike\n },\n },\n {\n id: \"amsalja_nerve_strike\",\n name: {\n korean: \"신경타\",\n english: \"Nerve Strike\",\n },\n description: {\n korean: \"정확한 신경 타격으로 적을 마비시킵니다\",\n english: \"Paralyze the enemy with precise nerve strikes\",\n },\n staminaCost: 15,\n kiCost: 12,\n damage: { min: 15, max: 25 },\n damageType: DamageType.NERVE,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n keyboardShortcut: \"T\",\n targetsVitalPoint: true,\n specialEffect: \"paralysis\",\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Fast precision\n },\n },\n {\n id: \"amsalja_deadly_precision\",\n name: {\n korean: \"치명정밀\",\n english: \"Deadly Precision\",\n },\n description: {\n korean: \"완벽한 정밀도로 치명적인 급소를 공격합니다\",\n english: \"Attack critical vital points with perfect accuracy\",\n },\n staminaCost: 18,\n kiCost: 15,\n damage: { min: 25, max: 45 },\n damageType: DamageType.PRESSURE,\n cooldown: 1400, // animationDuration (900) + recovery (500)\n requiredStance: TrigramStance.LI,\n keyboardShortcut: \"E\",\n targetsVitalPoint: true,\n criticalChance: 0.5,\n animationDuration: 900,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Precise, deliberate\n },\n },\n {\n id: \"amsalja_silent_death\",\n name: {\n korean: \"무음살\",\n english: \"Silent Death\",\n },\n description: {\n korean: \"소리 없이 치명적인 일격을 가합니다\",\n english: \"Deliver a silent, lethal strike\",\n },\n staminaCost: 22,\n kiCost: 18,\n damage: { min: 40, max: 60 },\n damageType: DamageType.NERVE,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n criticalChance: 0.6,\n specialEffect: \"instant_kill_chance\",\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.9, // Powerful lethal strike\n },\n },\n];\n\n/**\n * Technique definitions for 해커 (Hacker) - Cyber Warrior.\n *\n * Philosophy: Technology-enhanced combat with data-driven precision.\n * Favored Stance: ☳ 진 (Jin) - Thunder\n */\nexport const HACKER_TECHNIQUES: readonly Technique[] = [\n {\n id: \"hacker_electric_shock\",\n name: {\n korean: \"전격\",\n english: \"Electric Shock\",\n },\n description: {\n korean: \"사이버 임플란트로 전기 충격을 가합니다\",\n english: \"Deliver electric shock via cyber implants\",\n },\n staminaCost: 10,\n kiCost: 12,\n damage: { min: 18, max: 28 },\n damageType: DamageType.ELECTRIC,\n cooldown: 1000, // animationDuration (500) + recovery (500)\n requiredStance: TrigramStance.JIN,\n keyboardShortcut: \"Q\",\n specialEffect: \"stun\",\n animationDuration: 500,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.2, // Fast electric strike\n },\n },\n {\n id: \"hacker_data_strike\",\n name: {\n korean: \"데이터 타격\",\n english: \"Data Strike\",\n },\n description: {\n korean: \"전투 데이터를 분석하여 최적의 공격을 수행합니다\",\n english: \"Analyze combat data for optimal attack\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 22, max: 32 },\n damageType: DamageType.PIERCING,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n keyboardShortcut: \"T\",\n targetsVitalPoint: true,\n criticalChance: 0.35,\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Data-enhanced precision\n },\n },\n {\n id: \"hacker_cyber_overdrive\",\n name: {\n korean: \"사이버 가속\",\n english: \"Cyber Overdrive\",\n },\n description: {\n korean: \"임플란트를 과부하시켜 초고속 공격을 수행합니다\",\n english: \"Overload implants for lightning-fast attacks\",\n },\n staminaCost: 18,\n kiCost: 15,\n damage: { min: 15, max: 25 },\n damageType: DamageType.ELECTRIC,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.JIN,\n keyboardShortcut: \"E\",\n specialEffect: \"multi_hit\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT_RAPID,\n speedModifier: 1.3, // Ultra-fast combo\n },\n },\n {\n id: \"hacker_system_crash\",\n name: {\n korean: \"시스템 크래시\",\n english: \"System Crash\",\n },\n description: {\n korean: \"적의 신경 시스템을 해킹하여 무력화합니다\",\n english: \"Hack the enemy's nervous system\",\n },\n staminaCost: 20,\n kiCost: 18,\n damage: { min: 30, max: 45 },\n damageType: DamageType.PSYCHIC,\n cooldown: 1600, // animationDuration (1100) + recovery (500)\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n specialEffect: \"system_shutdown\",\n animationDuration: 1100,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Calculated precision\n },\n },\n];\n\n/**\n * Technique definitions for 정보요원 (Jeongbo Yowon) - Intelligence Operative.\n *\n * Philosophy: Strategic analysis and exploiting weaknesses.\n * Favored Stance: ☵ 감 (Gam) - Water\n */\nexport const JEONGBO_YOWON_TECHNIQUES: readonly Technique[] = [\n {\n id: \"jeongbo_tactical_strike\",\n name: {\n korean: \"전술타격\",\n english: \"Tactical Strike\",\n },\n description: {\n korean: \"적의 약점을 분석하여 전술적으로 공격합니다\",\n english: \"Analyze and strike enemy weaknesses tactically\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 20, max: 30 },\n damageType: DamageType.PIERCING,\n cooldown: 1150, // animationDuration (650) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"Q\",\n targetsVitalPoint: true,\n criticalChance: 0.3,\n animationDuration: 650,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Strategic precision\n },\n },\n {\n id: \"jeongbo_counter_intelligence\",\n name: {\n korean: \"역정보공작\",\n english: \"Counter Intelligence\",\n },\n description: {\n korean: \"적의 움직임을 읽고 반격합니다\",\n english: \"Read and counter enemy movements\",\n },\n staminaCost: 10,\n kiCost: 12,\n damage: { min: 15, max: 30 },\n damageType: DamageType.IMPACT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n keyboardShortcut: \"T\",\n specialEffect: \"counter_stance\",\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.2, // Fast counter\n },\n },\n {\n id: \"jeongbo_psychological_warfare\",\n name: {\n korean: \"심리전\",\n english: \"Psychological Warfare\",\n },\n description: {\n korean: \"적의 정신을 교란하여 약화시킵니다\",\n english: \"Disrupt enemy's mental state\",\n },\n staminaCost: 15,\n kiCost: 15,\n damage: { min: 10, max: 20 },\n damageType: DamageType.PSYCHIC,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n keyboardShortcut: \"E\",\n specialEffect: \"confusion\",\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Deliberate mental attack\n },\n },\n {\n id: \"jeongbo_intelligence_strike\",\n name: {\n korean: \"정보타격\",\n english: \"Intelligence Strike\",\n },\n description: {\n korean: \"수집한 정보를 바탕으로 완벽한 공격을 수행합니다\",\n english: \"Execute perfect attack based on gathered intelligence\",\n },\n staminaCost: 20,\n kiCost: 18,\n damage: { min: 35, max: 50 },\n damageType: DamageType.PIERCING,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n criticalChance: 0.45,\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.9, // Powerful decisive strike\n },\n },\n {\n id: \"jeongbo_precision_takedown\",\n name: {\n korean: \"정밀제압\",\n english: \"Precision Takedown\",\n },\n description: {\n korean: \"무력한 적을 정밀하게 제압합니다 - 서명 기술\",\n english: \"Precisely takedown helpless opponent - Signature Move\",\n },\n staminaCost: 25,\n kiCost: 20,\n damage: { min: 45, max: 65 },\n damageType: DamageType.PIERCING,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"Y\",\n targetsVitalPoint: true,\n criticalChance: 0.6,\n specialEffect: \"signature_move\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.8, // Deliberate, precise execution\n },\n },\n];\n\n/**\n * Technique definitions for 조직폭력배 (Jojik Pokryeokbae) - Organized Crime.\n *\n * Philosophy: Ruthless pragmatism and brutal efficiency.\n * Favored Stance: ☷ 곤 (Gon) - Earth\n */\nexport const JOJIK_POKRYEOKBAE_TECHNIQUES: readonly Technique[] = [\n {\n id: \"jojik_street_brawl\",\n name: {\n korean: \"거리싸움\",\n english: \"Street Brawl\",\n },\n description: {\n korean: \"더러운 싸움으로 적을 제압합니다\",\n english: \"Overwhelm with dirty fighting\",\n },\n staminaCost: 10,\n kiCost: 8,\n damage: { min: 18, max: 28 },\n damageType: DamageType.BLUNT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.GON,\n keyboardShortcut: \"Q\",\n criticalChance: 0.25,\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.1, // Fast brawl strike\n },\n },\n {\n id: \"jojik_brutal_takedown\",\n name: {\n korean: \"잔혹제압\",\n english: \"Brutal Takedown\",\n },\n description: {\n korean: \"무자비하게 적을 쓰러뜨립니다\",\n english: \"Mercilessly takedown the enemy\",\n },\n staminaCost: 15,\n kiCost: 10,\n damage: { min: 25, max: 35 },\n damageType: DamageType.CRUSHING,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n keyboardShortcut: \"T\",\n specialEffect: \"knockdown\",\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.ELBOW_STRIKE,\n speedModifier: 0.9, // Heavy takedown\n },\n },\n {\n id: \"jojik_improvised_weapon\",\n name: {\n korean: \"즉석무기\",\n english: \"Improvised Weapon\",\n },\n description: {\n korean: \"주변 물건을 이용하여 공격합니다\",\n english: \"Attack using improvised objects\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 22, max: 38 },\n damageType: DamageType.SHARP,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n requiredStance: TrigramStance.GON,\n keyboardShortcut: \"E\",\n criticalChance: 0.3,\n specialEffect: \"bleed\",\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 1.0, // Unpredictable weapon strike\n },\n },\n {\n id: \"jojik_ruthless_assault\",\n name: {\n korean: \"무자비공격\",\n english: \"Ruthless Assault\",\n },\n description: {\n korean: \"자비 없이 적을 집중 공격합니다\",\n english: \"Relentlessly assault without mercy\",\n },\n staminaCost: 22,\n kiCost: 12,\n damage: { min: 30, max: 55 },\n damageType: DamageType.CRUSHING,\n cooldown: 1800, // animationDuration (1300) + recovery (500)\n keyboardShortcut: \"R\",\n criticalChance: 0.35,\n specialEffect: \"rage\",\n animationDuration: 1300,\n animation: {\n type: AttackAnimationType.PUNCH_LOW,\n speedModifier: 0.8, // Heavy brutal assault\n },\n },\n];\n\n/**\n * Get techniques for a specific player archetype.\n *\n * @param archetype - Player archetype\n * @returns Array of techniques for the archetype\n *\n * @public\n */\nexport function getTechniquesForArchetype(\n archetype: PlayerArchetype,\n): readonly Technique[] {\n switch (archetype) {\n case PlayerArchetype.MUSA:\n return MUSA_TECHNIQUES;\n case PlayerArchetype.AMSALJA:\n return AMSALJA_TECHNIQUES;\n case PlayerArchetype.HACKER:\n return HACKER_TECHNIQUES;\n case PlayerArchetype.JEONGBO_YOWON:\n return JEONGBO_YOWON_TECHNIQUES;\n case PlayerArchetype.JOJIK_POKRYEOKBAE:\n return JOJIK_POKRYEOKBAE_TECHNIQUES;\n default:\n // Exhaustive check: if a new archetype is added, TypeScript will error here\n const _exhaustiveCheck: never = archetype;\n throw new Error(`Unknown archetype: ${_exhaustiveCheck}`);\n }\n}\n\n/**\n * Convert KoreanTechnique to Technique format for UI compatibility\n */\nfunction convertKoreanToTechnique(koreanTech: KoreanTechnique): Technique {\n // Convert string damageType to DamageType enum\n const getDamageType = (type: string): DamageType => {\n // Map common string values to DamageType enum\n const typeMap: Record<string, DamageType> = {\n blunt: DamageType.BLUNT,\n physical: DamageType.BLUNT,\n piercing: DamageType.PIERCING,\n slashing: DamageType.SLASHING,\n crushing: DamageType.CRUSHING,\n impact: DamageType.IMPACT,\n joint: DamageType.JOINT,\n electric: DamageType.ELECTRIC,\n psychic: DamageType.PSYCHIC,\n };\n return typeMap[type.toLowerCase()] ?? DamageType.BLUNT;\n };\n\n return {\n id: koreanTech.id,\n name: {\n korean: koreanTech.koreanName || koreanTech.name.korean,\n english: koreanTech.englishName || koreanTech.name.english,\n romanized: koreanTech.romanized || koreanTech.name.romanized,\n },\n description: koreanTech.description,\n staminaCost: koreanTech.staminaCost,\n kiCost: koreanTech.kiCost,\n damage: {\n min: Math.floor(koreanTech.damage * 0.8),\n max: Math.ceil(koreanTech.damage * 1.2),\n },\n damageType: getDamageType(koreanTech.damageType),\n cooldown: koreanTech.recoveryTime + koreanTech.executionTime,\n requiredStance: koreanTech.stance,\n keyboardShortcut: \"Q\", // Placeholder; immediately overridden by getTechniquesForStanceAndArchetype()\n criticalChance: koreanTech.critChance,\n animationDuration: koreanTech.executionTime,\n };\n}\n\n/**\n * Get techniques for a player based on their current stance and archetype.\n * Combines trigram stance techniques with archetype-specific bonuses.\n *\n * @param stance - Current player stance\n * @param archetype - Player archetype\n * @returns Array of available techniques with proper keyboard shortcuts assigned\n *\n * @public\n */\nexport function getTechniquesForStanceAndArchetype(\n stance: TrigramStance,\n archetype: PlayerArchetype,\n): readonly Technique[] {\n // Get stance-based techniques from Korean martial arts system\n const koreanTechniques = KoreanTechniquesSystem.getAllAvailableTechniques(\n stance,\n archetype,\n );\n\n // Convert to Technique format\n const convertedTechniques = koreanTechniques.map(convertKoreanToTechnique);\n\n // Also include archetype-specific special techniques\n const archetypeTechniques = getTechniquesForArchetype(archetype);\n\n // Filter archetype techniques to only include those matching current stance or no stance requirement\n const filteredArchetypeTechniques = archetypeTechniques.filter(\n (tech) => !tech.requiredStance || tech.requiredStance === stance,\n );\n\n // Combine both sets, prioritizing stance techniques\n const allTechniques = [\n ...convertedTechniques,\n ...filteredArchetypeTechniques,\n ];\n\n // Limit to maximum 10 techniques (keyboard shortcuts Q-E-R-T-Y-F-G-Z-X-C)\n const limitedTechniques = allTechniques.slice(0, 10);\n\n // Assign keyboard shortcuts using conflict-free keys around WASD\n const keyboardShortcuts = [\n \"Q\",\n \"E\",\n \"R\",\n \"T\",\n \"Y\",\n \"F\",\n \"G\",\n \"Z\",\n \"X\",\n \"C\",\n ] as const;\n return limitedTechniques.map((tech, index) => ({\n ...tech,\n keyboardShortcut: keyboardShortcuts[index] as TechniqueKey,\n }));\n}\n\n/**\n * Get a specific technique by ID.\n *\n * @param techniqueId - Unique technique identifier\n * @returns Technique if found, undefined otherwise\n *\n * @public\n */\nexport function getTechniqueById(techniqueId: string): Technique | undefined {\n const allTechniques = [\n ...MUSA_TECHNIQUES,\n ...AMSALJA_TECHNIQUES,\n ...HACKER_TECHNIQUES,\n ...JEONGBO_YOWON_TECHNIQUES,\n ...JOJIK_POKRYEOKBAE_TECHNIQUES,\n ];\n\n return allTechniques.find((tech) => tech.id === techniqueId);\n}\n\nexport default {\n getTechniquesForArchetype,\n getTechniquesForStanceAndArchetype,\n getTechniqueById,\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useCombatTimer.js","names":[],"sources":["../../src/hooks/useCombatTimer.ts"],"sourcesContent":["/**\n * useCombatTimer - Hook for managing combat round timer\n *\n * Korean: 전투 라운드 타이머 훅 (Combat Round Timer Hook)\n *\n * Manages countdown timer for combat rounds with:\n * - Pause/resume support\n * - Warning thresholds at 10s and 5s\n * - Audio alerts for warnings\n * - Time's up callback\n *\n * @module hooks/useCombatTimer\n * @category Combat Hooks\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useAudio } from \"../audio/AudioProvider\";\n\n/**\n * Timer warning level indicating urgency\n */\nexport type TimerWarningLevel = \"none\" | \"warning\" | \"urgent\";\n\n/**\n * Configuration for the combat timer hook\n */\nexport interface UseCombatTimerConfig {\n /** Initial time in seconds */\n readonly initialTime: number;\n /** Whether the timer is paused */\n readonly isPaused: boolean;\n /** Callback when timer reaches 0 */\n readonly onTimeUp: () => void;\n /** Warning threshold in seconds (default: 10) */\n readonly warningThreshold?: number;\n /** Urgent warning threshold in seconds (default: 5) */\n readonly urgentThreshold?: number;\n /** Optional key to force timer reset (e.g., round number) */\n readonly resetKey?: string;\n}\n\n/**\n * Return value from useCombatTimer hook\n */\nexport interface UseCombatTimerReturn {\n /** Current time remaining in seconds */\n readonly timeRemaining: number;\n /** Current warning level */\n readonly warningLevel: TimerWarningLevel;\n /** Whether timer has reached 0 */\n readonly isTimeUp: boolean;\n /** Formatted time string (MM:SS) */\n readonly formattedTime: string;\n}\n\n/**\n * Format seconds into MM:SS format\n * @param seconds - Time in seconds\n * @returns Formatted string (e.g., \"03:45\", \"00:05\")\n */\nfunction formatTime(seconds: number): string {\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = Math.floor(seconds % 60);\n return `${minutes.toString().padStart(2, \"0\")}:${remainingSeconds\n .toString()\n .padStart(2, \"0\")}`;\n}\n\n/**\n * Get warning level based on time remaining\n */\nfunction getWarningLevel(\n timeRemaining: number,\n warningThreshold: number,\n urgentThreshold: number,\n): TimerWarningLevel {\n if (timeRemaining <= urgentThreshold) {\n return \"urgent\";\n }\n if (timeRemaining <= warningThreshold) {\n return \"warning\";\n }\n return \"none\";\n}\n\n/**\n * useCombatTimer Hook\n *\n * Manages combat round countdown timer with pause support and audio warnings.\n *\n * Features:\n * - Counts down from initial time to 0\n * - Pauses/resumes based on isPaused prop\n * - Plays audio warning at warning threshold (default 10s)\n * - Plays urgent audio warning at urgent threshold (default 5s)\n * - Calls onTimeUp when timer reaches 0\n * - Provides formatted time string (MM:SS)\n * - Returns current warning level for UI styling\n *\n * Korean: 전투 라운드 타이머 관리 훅\n *\n * @example\n * ```tsx\n * const { timeRemaining, warningLevel, formattedTime } = useCombatTimer({\n * initialTime: 180, // 3 minutes\n * isPaused: false,\n * onTimeUp: () => handleRoundEnd(),\n * warningThreshold: 10,\n * urgentThreshold: 5,\n * });\n * ```\n */\nexport function useCombatTimer(\n config: UseCombatTimerConfig,\n): UseCombatTimerReturn {\n const {\n initialTime,\n isPaused,\n onTimeUp,\n warningThreshold = 10,\n urgentThreshold = 5,\n resetKey,\n } = config;\n\n const audio = useAudio();\n const [timeRemaining, setTimeRemaining] = useState(initialTime);\n const [isTimeUp, setIsTimeUp] = useState(false);\n const lastWarningRef = useRef<TimerWarningLevel>(\"none\");\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n // Reset timer when initialTime or resetKey changes (new round)\n useEffect(() => {\n setTimeRemaining(initialTime);\n setIsTimeUp(false);\n lastWarningRef.current = \"none\";\n }, [initialTime, resetKey]);\n\n // Timer countdown logic\n useEffect(() => {\n // Don't run if paused or time is up\n if (isPaused || isTimeUp) {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n return;\n }\n\n // Track start time for precise elapsed time calculation\n const startTimeRef = Date.now();\n const startingTimeRemaining = timeRemaining;\n\n // Start interval\n intervalRef.current = setInterval(() => {\n const elapsed = (Date.now() - startTimeRef) / 1000;\n const next = Math.max(0, startingTimeRemaining - elapsed);\n\n setTimeRemaining(next);\n\n // Check if time just reached 0\n if (next <= 0 && !isTimeUp) {\n setIsTimeUp(true);\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n onTimeUp();\n }\n }, 100);\n\n return () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n };\n // timeRemaining is intentionally excluded - we capture the starting value once\n // and count down from there, not restart the interval on every tick\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isPaused, isTimeUp, onTimeUp]);\n\n // Warning level calculation\n const warningLevel = getWarningLevel(\n timeRemaining,\n warningThreshold,\n urgentThreshold,\n );\n\n // Audio warnings - timeRemaining checked via warningLevel which captures threshold transitions\n useEffect(() => {\n if (!audio.isAudioReady) return;\n if (isPaused) return;\n\n // Play warning sound at threshold\n if (\n warningLevel === \"warning\" &&\n lastWarningRef.current === \"none\" &&\n timeRemaining <= warningThreshold &&\n timeRemaining > urgentThreshold\n ) {\n audio.playSFX(\"attack_light\"); // Placeholder - will be timer_warning_10s\n lastWarningRef.current = \"warning\";\n }\n\n // Play urgent warning sound at urgent threshold\n if (\n warningLevel === \"urgent\" &&\n lastWarningRef.current !== \"urgent\" &&\n timeRemaining <= urgentThreshold &&\n timeRemaining > 0\n ) {\n audio.playSFX(\"attack_heavy\"); // Placeholder - will be timer_warning_5s\n lastWarningRef.current = \"urgent\";\n }\n }, [\n warningLevel,\n timeRemaining,\n audio,\n isPaused,\n warningThreshold,\n urgentThreshold,\n ]);\n\n // Format time for display\n const formattedTime = formatTime(timeRemaining);\n\n return {\n timeRemaining,\n warningLevel,\n isTimeUp,\n formattedTime,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAS,WAAW,SAAyB;CAC3C,MAAM,UAAU,KAAK,MAAM,UAAU,EAAE;CACvC,MAAM,mBAAmB,KAAK,MAAM,UAAU,EAAE;CAChD,OAAO,GAAG,QAAQ,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE,GAAG,iBAC9C,SAAS,EACT,SAAS,GAAG,GAAG;AACpB;;;;AAKA,SAAS,gBACP,eACA,kBACA,iBACmB;CACnB,IAAI,iBAAiB,iBACnB,OAAO;CAET,IAAI,iBAAiB,kBACnB,OAAO;CAET,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,eACd,QACsB;CACtB,MAAM,EACJ,aACA,UACA,UACA,mBAAmB,IACnB,kBAAkB,GAClB,aACE;CAEJ,MAAM,QAAQ,SAAS;CACvB,MAAM,CAAC,eAAe,oBAAoB,SAAS,WAAW;CAC9D,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,iBAAiB,OAA0B,MAAM;CACvD,MAAM,cAAc,OAA8C,IAAI;CAGtE,gBAAgB;EACd,iBAAiB,WAAW;EAC5B,YAAY,KAAK;EACjB,eAAe,UAAU;CAC3B,GAAG,CAAC,aAAa,QAAQ,CAAC;CAG1B,gBAAgB;EAEd,IAAI,YAAY,UAAU;GACxB,IAAI,YAAY,SAAS;IACvB,cAAc,YAAY,OAAO;IACjC,YAAY,UAAU;GACxB;GACA;EACF;EAGA,MAAM,eAAe,KAAK,IAAI;EAC9B,MAAM,wBAAwB;EAG9B,YAAY,UAAU,kBAAkB;GACtC,MAAM,WAAW,KAAK,IAAI,IAAI,gBAAgB;GAC9C,MAAM,OAAO,KAAK,IAAI,GAAG,wBAAwB,OAAO;GAExD,iBAAiB,IAAI;GAGrB,IAAI,QAAQ,KAAK,CAAC,UAAU;IAC1B,YAAY,IAAI;IAChB,IAAI,YAAY,SAAS;KACvB,cAAc,YAAY,OAAO;KACjC,YAAY,UAAU;IACxB;IACA,SAAS;GACX;EACF,GAAG,GAAG;EAEN,aAAa;GACX,IAAI,YAAY,SAAS;IACvB,cAAc,YAAY,OAAO;IACjC,YAAY,UAAU;GACxB;EACF;CAIF,GAAG;EAAC;EAAU;EAAU;CAAQ,CAAC;CAGjC,MAAM,eAAe,gBACnB,eACA,kBACA,eACF;CAGA,gBAAgB;EACd,IAAI,CAAC,MAAM,cAAc;EACzB,IAAI,UAAU;EAGd,IACE,iBAAiB,aACjB,eAAe,YAAY,UAC3B,iBAAiB,oBACjB,gBAAgB,iBAChB;GACA,MAAM,QAAQ,cAAc;GAC5B,eAAe,UAAU;EAC3B;EAGA,IACE,iBAAiB,YACjB,eAAe,YAAY,YAC3B,iBAAiB,mBACjB,gBAAgB,GAChB;GACA,MAAM,QAAQ,cAAc;GAC5B,eAAe,UAAU;EAC3B;CACF,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAKD,OAAO;EACL;EACA;EACA;EACA,eANoB,WAAW,aAM/B;CACF;AACF"}
1
+ {"version":3,"file":"useCombatTimer.js","names":[],"sources":["../../src/hooks/useCombatTimer.ts"],"sourcesContent":["/**\n * useCombatTimer - Hook for managing combat round timer\n *\n * Korean: 전투 라운드 타이머 훅 (Combat Round Timer Hook)\n *\n * Manages countdown timer for combat rounds with:\n * - Pause/resume support\n * - Warning thresholds at 10s and 5s\n * - Audio alerts for warnings\n * - Time's up callback\n *\n * @module hooks/useCombatTimer\n * @category Combat Hooks\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useAudio } from \"../audio/AudioProvider\";\n\n/**\n * Timer warning level indicating urgency\n */\nexport type TimerWarningLevel = \"none\" | \"warning\" | \"urgent\";\n\n/**\n * Configuration for the combat timer hook\n */\nexport interface UseCombatTimerConfig {\n /** Initial time in seconds */\n readonly initialTime: number;\n /** Whether the timer is paused */\n readonly isPaused: boolean;\n /** Callback when timer reaches 0 */\n readonly onTimeUp: () => void;\n /** Warning threshold in seconds (default: 10) */\n readonly warningThreshold?: number;\n /** Urgent warning threshold in seconds (default: 5) */\n readonly urgentThreshold?: number;\n /** Optional key to force timer reset (e.g., round number) */\n readonly resetKey?: string;\n}\n\n/**\n * Return value from useCombatTimer hook\n */\nexport interface UseCombatTimerReturn {\n /** Current time remaining in seconds */\n readonly timeRemaining: number;\n /** Current warning level */\n readonly warningLevel: TimerWarningLevel;\n /** Whether timer has reached 0 */\n readonly isTimeUp: boolean;\n /** Formatted time string (MM:SS) */\n readonly formattedTime: string;\n}\n\n/**\n * Format seconds into MM:SS format\n * @param seconds - Time in seconds\n * @returns Formatted string (e.g., \"03:45\", \"00:05\")\n */\nfunction formatTime(seconds: number): string {\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = Math.floor(seconds % 60);\n return `${minutes.toString().padStart(2, \"0\")}:${remainingSeconds\n .toString()\n .padStart(2, \"0\")}`;\n}\n\n/**\n * Get warning level based on time remaining\n */\nfunction getWarningLevel(\n timeRemaining: number,\n warningThreshold: number,\n urgentThreshold: number,\n): TimerWarningLevel {\n if (timeRemaining <= urgentThreshold) {\n return \"urgent\";\n }\n if (timeRemaining <= warningThreshold) {\n return \"warning\";\n }\n return \"none\";\n}\n\n/**\n * useCombatTimer Hook\n *\n * Manages combat round countdown timer with pause support and audio warnings.\n *\n * Features:\n * - Counts down from initial time to 0\n * - Pauses/resumes based on isPaused prop\n * - Plays audio warning at warning threshold (default 10s)\n * - Plays urgent audio warning at urgent threshold (default 5s)\n * - Calls onTimeUp when timer reaches 0\n * - Provides formatted time string (MM:SS)\n * - Returns current warning level for UI styling\n *\n * Korean: 전투 라운드 타이머 관리 훅\n *\n * @example\n * ```tsx\n * const { timeRemaining, warningLevel, formattedTime } = useCombatTimer({\n * initialTime: 180, // 3 minutes\n * isPaused: false,\n * onTimeUp: () => handleRoundEnd(),\n * warningThreshold: 10,\n * urgentThreshold: 5,\n * });\n * ```\n */\nexport function useCombatTimer(\n config: UseCombatTimerConfig,\n): UseCombatTimerReturn {\n const {\n initialTime,\n isPaused,\n onTimeUp,\n warningThreshold = 10,\n urgentThreshold = 5,\n resetKey,\n } = config;\n\n const audio = useAudio();\n const [timeRemaining, setTimeRemaining] = useState(initialTime);\n const [isTimeUp, setIsTimeUp] = useState(false);\n const lastWarningRef = useRef<TimerWarningLevel>(\"none\");\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n // Reset timer when initialTime or resetKey changes (new round)\n useEffect(() => {\n setTimeRemaining(initialTime);\n setIsTimeUp(false);\n lastWarningRef.current = \"none\";\n }, [initialTime, resetKey]);\n\n // Timer countdown logic\n useEffect(() => {\n // Don't run if paused or time is up\n if (isPaused || isTimeUp) {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n return;\n }\n\n // Track start time for precise elapsed time calculation\n const startTimeRef = Date.now();\n const startingTimeRemaining = timeRemaining;\n\n // Start interval\n intervalRef.current = setInterval(() => {\n const elapsed = (Date.now() - startTimeRef) / 1000;\n const next = Math.max(0, startingTimeRemaining - elapsed);\n\n setTimeRemaining(next);\n\n // Check if time just reached 0\n if (next <= 0 && !isTimeUp) {\n setIsTimeUp(true);\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n onTimeUp();\n }\n }, 100);\n\n return () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n };\n // timeRemaining is intentionally excluded - we capture the starting value once\n // and count down from there, not restart the interval on every tick\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isPaused, isTimeUp, onTimeUp]);\n\n // Warning level calculation\n const warningLevel = getWarningLevel(\n timeRemaining,\n warningThreshold,\n urgentThreshold,\n );\n\n // Audio warnings - timeRemaining checked via warningLevel which captures threshold transitions\n useEffect(() => {\n if (!audio.isAudioReady) return;\n if (isPaused) return;\n\n // Play warning sound at threshold\n if (\n warningLevel === \"warning\" &&\n lastWarningRef.current === \"none\" &&\n timeRemaining <= warningThreshold &&\n timeRemaining > urgentThreshold\n ) {\n audio.playSFX(\"attack_light\"); // Placeholder - will be timer_warning_10s\n lastWarningRef.current = \"warning\";\n }\n\n // Play urgent warning sound at urgent threshold\n if (\n warningLevel === \"urgent\" &&\n lastWarningRef.current !== \"urgent\" &&\n timeRemaining <= urgentThreshold &&\n timeRemaining > 0\n ) {\n audio.playSFX(\"attack_heavy\"); // Placeholder - will be timer_warning_5s\n lastWarningRef.current = \"urgent\";\n }\n }, [\n warningLevel,\n timeRemaining,\n audio,\n isPaused,\n warningThreshold,\n urgentThreshold,\n ]);\n\n // Format time for display\n const formattedTime = formatTime(timeRemaining);\n\n return {\n timeRemaining,\n warningLevel,\n isTimeUp,\n formattedTime,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAS,WAAW,SAAyB;CAC3C,MAAM,UAAU,KAAK,MAAM,UAAU,EAAE;CACvC,MAAM,mBAAmB,KAAK,MAAM,UAAU,EAAE;CAChD,OAAO,GAAG,QAAQ,SAAS,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,iBAC9C,SAAS,CAAC,CACV,SAAS,GAAG,GAAG;AACpB;;;;AAKA,SAAS,gBACP,eACA,kBACA,iBACmB;CACnB,IAAI,iBAAiB,iBACnB,OAAO;CAET,IAAI,iBAAiB,kBACnB,OAAO;CAET,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,eACd,QACsB;CACtB,MAAM,EACJ,aACA,UACA,UACA,mBAAmB,IACnB,kBAAkB,GAClB,aACE;CAEJ,MAAM,QAAQ,SAAS;CACvB,MAAM,CAAC,eAAe,oBAAoB,SAAS,WAAW;CAC9D,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,iBAAiB,OAA0B,MAAM;CACvD,MAAM,cAAc,OAA8C,IAAI;CAGtE,gBAAgB;EACd,iBAAiB,WAAW;EAC5B,YAAY,KAAK;EACjB,eAAe,UAAU;CAC3B,GAAG,CAAC,aAAa,QAAQ,CAAC;CAG1B,gBAAgB;EAEd,IAAI,YAAY,UAAU;GACxB,IAAI,YAAY,SAAS;IACvB,cAAc,YAAY,OAAO;IACjC,YAAY,UAAU;GACxB;GACA;EACF;EAGA,MAAM,eAAe,KAAK,IAAI;EAC9B,MAAM,wBAAwB;EAG9B,YAAY,UAAU,kBAAkB;GACtC,MAAM,WAAW,KAAK,IAAI,IAAI,gBAAgB;GAC9C,MAAM,OAAO,KAAK,IAAI,GAAG,wBAAwB,OAAO;GAExD,iBAAiB,IAAI;GAGrB,IAAI,QAAQ,KAAK,CAAC,UAAU;IAC1B,YAAY,IAAI;IAChB,IAAI,YAAY,SAAS;KACvB,cAAc,YAAY,OAAO;KACjC,YAAY,UAAU;IACxB;IACA,SAAS;GACX;EACF,GAAG,GAAG;EAEN,aAAa;GACX,IAAI,YAAY,SAAS;IACvB,cAAc,YAAY,OAAO;IACjC,YAAY,UAAU;GACxB;EACF;CAIF,GAAG;EAAC;EAAU;EAAU;CAAQ,CAAC;CAGjC,MAAM,eAAe,gBACnB,eACA,kBACA,eACF;CAGA,gBAAgB;EACd,IAAI,CAAC,MAAM,cAAc;EACzB,IAAI,UAAU;EAGd,IACE,iBAAiB,aACjB,eAAe,YAAY,UAC3B,iBAAiB,oBACjB,gBAAgB,iBAChB;GACA,MAAM,QAAQ,cAAc;GAC5B,eAAe,UAAU;EAC3B;EAGA,IACE,iBAAiB,YACjB,eAAe,YAAY,YAC3B,iBAAiB,mBACjB,gBAAgB,GAChB;GACA,MAAM,QAAQ,cAAc;GAC5B,eAAe,UAAU;EAC3B;CACF,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAKD,OAAO;EACL;EACA;EACA;EACA,eANoB,WAAW,aAM/B;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardControls.js","names":[],"sources":["../../src/hooks/useKeyboardControls.ts"],"sourcesContent":["/**\n * Custom hook for keyboard controls with visual feedback\n * Manages keyboard input for trigram stance switching and combat actions\n *\n * @module hooks/useKeyboardControls\n * @category Input System\n * @korean 키보드 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FOOTWORK_KOREAN_TERMS } from \"../systems/animation/core/types\";\nimport { ControlMapper } from \"../utils/controlMapping\";\n\n// Korean terminology constants (module-level to avoid recreation on every keystroke)\nconst DIAGONAL_KOREAN_TERMS: Record<string, string> = {\n step_forward_left: \"전좌측보법 (Forward-Left)\",\n step_forward_right: \"전우측보법 (Forward-Right)\",\n step_back_left: \"후좌측보법 (Back-Left)\",\n step_back_right: \"후우측보법 (Back-Right)\",\n};\n\nconst STEP_KOREAN_TERMS: Record<string, string> = {\n step_forward: \"전진보법 (Forward Step)\",\n step_back: \"후퇴보법 (Retreat Step)\",\n step_left: \"좌측면보법 (Left Step)\",\n step_right: \"우측면보법 (Right Step)\",\n};\n\n// Footwork pattern Korean terminology - maps animation states to display names\n// Reuses FOOTWORK_KOREAN_TERMS from types.ts but adds specific animation state labels\nconst FOOTWORK_DISPLAY_TERMS: Record<string, string> = {\n footwork_circular_left: `${FOOTWORK_KOREAN_TERMS.circular.korean} 좌 (Circular Left)`,\n footwork_circular_right: `${FOOTWORK_KOREAN_TERMS.circular.korean} 우 (Circular Right)`,\n footwork_pivot_left: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 좌 (Pivot Left)`,\n footwork_pivot_right: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 우 (Pivot Right)`,\n footwork_slide_forward: `${FOOTWORK_KOREAN_TERMS.slide.korean} 전 (Slide Forward)`,\n footwork_slide_back: `${FOOTWORK_KOREAN_TERMS.slide.korean} 후 (Slide Back)`,\n footwork_slide_left: `${FOOTWORK_KOREAN_TERMS.slide.korean} 좌 (Slide Left)`,\n footwork_slide_right: `${FOOTWORK_KOREAN_TERMS.slide.korean} 우 (Slide Right)`,\n footwork_shuffle: `${FOOTWORK_KOREAN_TERMS.shuffle.korean} (Shuffle)`,\n};\n\n// Step direction mapping (all move_* actions covered explicitly)\nconst STEP_DIRECTION_MAP: Record<string, string> = {\n move_up: \"step_forward\",\n move_down: \"step_back\",\n move_left: \"step_left\",\n move_right: \"step_right\",\n};\n\n/**\n * Queued input for input buffer display\n */\nexport interface QueuedInput {\n readonly action: string;\n readonly key: string;\n readonly timestamp: number;\n}\n\n/**\n * Props for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsProps {\n /** Callback when stance changes (receives stance index 0-7) */\n readonly onStanceChange: (stance: number) => void;\n /** Callback for combat actions (attack, block, etc.) */\n readonly onAction: (action: string) => void;\n /** Whether keyboard input is enabled */\n readonly enabled?: boolean;\n /** Current stance index (0-7) for validation */\n readonly currentStance?: number;\n /** Callback when hints toggle is requested */\n readonly onToggleHints?: () => void;\n /** Play sound effect function */\n readonly playSFX?: (soundId: string) => void;\n /** Current animation state for grounded detection */\n readonly currentAnimationState?: string;\n}\n\n/**\n * Return type for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsReturn {\n /** Queued inputs for display */\n readonly queuedInputs: readonly QueuedInput[];\n /** Whether hints are visible */\n readonly showHints: boolean;\n /** Toggle hints visibility */\n readonly toggleHints: () => void;\n}\n\n/**\n * Maximum number of inputs to keep in queue\n */\nconst MAX_QUEUE_SIZE = 3;\n\n/**\n * Time to keep inputs in queue (milliseconds)\n */\nconst QUEUE_RETENTION_TIME = 2000;\n\n/**\n * Custom hook for handling keyboard controls with visual feedback\n *\n * Features:\n * - Trigram stance switching (1-8 keys or custom bindings)\n * - Combat action handling (attack, block, movement)\n * - Input queue visualization\n * - Keyboard hints toggle (F1)\n * - Invalid input prevention\n * - Audio feedback integration\n *\n * @example\n * ```typescript\n * const { queuedInputs, showHints, toggleHints } = useKeyboardControls({\n * onStanceChange: (stance) => handleStanceChange(stance),\n * onAction: (action) => handleAction(action),\n * enabled: !isPaused,\n * currentStance: player.stance,\n * playSFX: audio.playSFX,\n * });\n * ```\n *\n * @public\n * @korean 키보드컨트롤사용\n */\nexport function useKeyboardControls({\n onStanceChange,\n onAction,\n enabled = true,\n currentStance = 0,\n onToggleHints,\n playSFX,\n currentAnimationState,\n}: UseKeyboardControlsProps): UseKeyboardControlsReturn {\n const [queuedInputs, setQueuedInputs] = useState<readonly QueuedInput[]>([]);\n const [showHints, setShowHints] = useState(false);\n const controlMapperRef = useRef<ControlMapper>(new ControlMapper());\n const lastStanceChangeRef = useRef<number>(0);\n\n // Track currently pressed keys for diagonal step detection\n const pressedKeysRef = useRef<Set<string>>(new Set());\n\n /**\n * Toggle hints visibility\n */\n const toggleHints = useCallback(() => {\n setShowHints((prev) => {\n const newState = !prev;\n if (onToggleHints) {\n onToggleHints();\n }\n // Play UI sound\n if (playSFX) {\n playSFX(\"menu_select\");\n }\n return newState;\n });\n }, [onToggleHints, playSFX]);\n\n /**\n * Detect diagonal step direction from pressed keys\n * Returns step action for diagonal or null if not diagonal\n *\n * @korean 대각선발걸음감지\n */\n const getDiagonalStepAction = useCallback((): string | null => {\n const keys = pressedKeysRef.current;\n const mapper = controlMapperRef.current;\n\n // Check for forward diagonals\n if (keys.has(mapper.getBindings().movement.up.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_forward_left\"; // 전좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_forward_right\"; // 전우측보법\n }\n }\n\n // Check for backward diagonals\n if (keys.has(mapper.getBindings().movement.down.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_back_left\"; // 후좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_back_right\"; // 후우측보법\n }\n }\n\n return null;\n }, []);\n\n /**\n * Add input to queue\n */\n const addToQueue = useCallback((action: string, key: string) => {\n const newInput: QueuedInput = {\n action,\n key,\n timestamp: Date.now(),\n };\n\n setQueuedInputs((prev) => {\n const updated = [newInput, ...prev].slice(0, MAX_QUEUE_SIZE);\n return updated;\n });\n }, []);\n\n /**\n * Clean up old queued inputs\n * Single interval for component lifetime to avoid recreating on every input\n */\n useEffect(() => {\n const interval = setInterval(() => {\n const now = Date.now();\n setQueuedInputs((prev) => {\n if (prev.length === 0) return prev;\n return prev.filter(\n (input) => now - input.timestamp < QUEUE_RETENTION_TIME\n );\n });\n }, 500);\n\n return () => clearInterval(interval);\n }, []); // Empty deps - single interval for component lifetime\n\n /**\n * Handle keyboard input\n */\n useEffect(() => {\n if (!enabled) return;\n\n // Copy ref value for cleanup\n const pressedKeys = pressedKeysRef.current;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const mapper = controlMapperRef.current;\n\n // Track pressed keys for diagonal detection\n pressedKeysRef.current.add(e.key.toLowerCase());\n\n // F1: Toggle hints (prevent default browser help)\n if (e.key === \"F1\") {\n e.preventDefault();\n toggleHints();\n return;\n }\n\n // Escape: Close hints if open\n if (e.key === \"Escape\" && showHints) {\n e.preventDefault();\n setShowHints(false);\n return;\n }\n\n // Recovery input detection (기상 입력 감지)\n // When player is in ground state, any button triggers recovery options\n const isGrounded = currentAnimationState?.startsWith(\"ground_\");\n\n if (isGrounded) {\n // Space: Quick/default recovery\n if (e.key === \" \") {\n e.preventDefault();\n onAction(\"recovery_quick\");\n addToQueue(\"기상 (Quick Recovery)\", \"Space\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // R or Enter: Roll recovery (회전기상) - fast but costs stamina\n if (e.key.toLowerCase() === \"r\" || e.key === \"Enter\") {\n e.preventDefault();\n onAction(\"recovery_roll\");\n addToQueue(\"회전기상 (Roll Recovery)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // Shift: Defensive getup (방어기상) - slow but protected\n if (e.shiftKey) {\n e.preventDefault();\n onAction(\"recovery_defensive\");\n addToQueue(\"방어기상 (Defensive Getup)\", \"Shift\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // If grounded, don't process other inputs (can't attack/move while down)\n return;\n }\n\n // Check for stance change (1-8 or custom bindings)\n const stanceIndex = mapper.getStanceForKey(e.key);\n if (stanceIndex !== null) {\n e.preventDefault();\n\n // Prevent switching to the same stance\n if (stanceIndex === currentStance) {\n // Invalid input feedback (shake animation trigger)\n addToQueue(`Stance ${stanceIndex + 1} (already active)`, e.key);\n\n // Play error sound\n if (playSFX) {\n playSFX(\"menu_error\");\n }\n return;\n }\n\n // Throttle stance changes (minimum 8 frames at 60fps = 133ms)\n const now = Date.now();\n if (now - lastStanceChangeRef.current < 133) {\n return;\n }\n lastStanceChangeRef.current = now;\n\n // Execute stance change\n onStanceChange(stanceIndex);\n addToQueue(`Stance ${stanceIndex + 1}`, e.key);\n\n // Play stance change sound\n if (playSFX) {\n playSFX(\"stance_change\");\n }\n return;\n }\n\n // Handle other actions\n const action = mapper.getActionForKey(e.key);\n if (action) {\n e.preventDefault();\n\n switch (action) {\n case \"attack\":\n onAction(\"attack\");\n addToQueue(\"Attack\", e.key);\n if (playSFX) playSFX(\"attack_light\");\n break;\n\n case \"block\":\n onAction(\"block\");\n addToQueue(\"Block\", e.key);\n if (playSFX) playSFX(\"block\");\n break;\n\n case \"move_up\":\n case \"move_down\":\n case \"move_left\":\n case \"move_right\":\n // Check for Alt+A/D modifier for slide left/right (MUST be checked first)\n if (e.altKey) {\n // Alt modifier for slide left/right footwork patterns\n let slideFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Alt+A: Slide left (미끄럼보 좌)\n slideFootwork = \"footwork_slide_left\";\n } else if (action === \"move_right\") {\n // Alt+D: Slide right (미끄럼보 우)\n slideFootwork = \"footwork_slide_right\";\n }\n\n if (slideFootwork) {\n onAction(slideFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[slideFootwork] ?? \"Slide Footwork\",\n `Alt+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey && e.ctrlKey) {\n // Shift+Ctrl modifier for advanced footwork patterns\n let advancedFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Shift+Ctrl+A: Pivot left (축족회전 좌)\n advancedFootwork = \"footwork_pivot_left\";\n } else if (action === \"move_right\") {\n // Shift+Ctrl+D: Pivot right (축족회전 우)\n advancedFootwork = \"footwork_pivot_right\";\n } else if (action === \"move_up\" || action === \"move_down\") {\n // Shift+Ctrl+W or Shift+Ctrl+S: Shuffle step (섞음보)\n advancedFootwork = \"footwork_shuffle\";\n }\n\n if (advancedFootwork) {\n onAction(advancedFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[advancedFootwork] ??\n \"Advanced Footwork\",\n `Shift+Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.ctrlKey) {\n // Footwork patterns with Ctrl+WASD\n let footworkAction: string | null = null;\n\n if (action === \"move_left\") {\n // Ctrl+A: Circular step left (원형보)\n footworkAction = \"footwork_circular_left\";\n } else if (action === \"move_right\") {\n // Ctrl+D: Circular step right (원형보)\n footworkAction = \"footwork_circular_right\";\n } else if (action === \"move_up\") {\n // Ctrl+W: Slide forward (미끄럼보)\n footworkAction = \"footwork_slide_forward\";\n } else if (action === \"move_down\") {\n // Ctrl+S: Slide back (미끄럼보)\n footworkAction = \"footwork_slide_back\";\n }\n\n if (footworkAction) {\n onAction(footworkAction);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[footworkAction] ?? \"Footwork\",\n `Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey) {\n // Check if Shift is held for tactical step instead of walk\n // Check for diagonal step first\n const diagonalStep = getDiagonalStepAction();\n\n if (diagonalStep) {\n // Diagonal tactical step\n onAction(diagonalStep);\n\n addToQueue(\n DIAGONAL_KOREAN_TERMS[diagonalStep] ?? \"Diagonal Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n } else {\n // Cardinal direction tactical step\n // Map move_up/move_down to step_forward/step_back to match AnimationState\n const stepDirection = STEP_DIRECTION_MAP[action];\n onAction(stepDirection);\n\n addToQueue(\n STEP_KOREAN_TERMS[stepDirection] ?? \"Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n }\n } else {\n // Regular movement\n onAction(action);\n addToQueue(action, e.key);\n }\n break;\n\n case \"stance_side_switch\":\n // H key: Switch front foot (stance side switch)\n onAction(\"stance_side_switch\");\n addToQueue(\"발 바꿈 (Switch Stance Side)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n break;\n\n case \"vital_points_overlay\":\n // Vital points overlay toggle\n onAction(\"vital_points_overlay\");\n addToQueue(\"Vital Points\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n\n case \"pause\":\n // Pause menu toggle\n onAction(\"pause\");\n addToQueue(\"Pause\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n }\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n // Remove key from pressed keys set\n pressedKeysRef.current.delete(e.key.toLowerCase());\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n window.addEventListener(\"keyup\", handleKeyUp);\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n window.removeEventListener(\"keyup\", handleKeyUp);\n // Clear pressed keys on cleanup using copied ref value\n pressedKeys.clear();\n };\n }, [\n enabled,\n currentStance,\n onStanceChange,\n onAction,\n addToQueue,\n toggleHints,\n getDiagonalStepAction,\n showHints,\n playSFX,\n currentAnimationState,\n ]);\n\n return {\n queuedInputs,\n showHints,\n toggleHints,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useKeyboardControls.js","names":[],"sources":["../../src/hooks/useKeyboardControls.ts"],"sourcesContent":["/**\n * Custom hook for keyboard controls with visual feedback\n * Manages keyboard input for trigram stance switching and combat actions\n *\n * @module hooks/useKeyboardControls\n * @category Input System\n * @korean 키보드 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FOOTWORK_KOREAN_TERMS } from \"../systems/animation/core/types\";\nimport { ControlMapper } from \"../utils/controlMapping\";\n\n// Korean terminology constants (module-level to avoid recreation on every keystroke)\nconst DIAGONAL_KOREAN_TERMS: Record<string, string> = {\n step_forward_left: \"전좌측보법 (Forward-Left)\",\n step_forward_right: \"전우측보법 (Forward-Right)\",\n step_back_left: \"후좌측보법 (Back-Left)\",\n step_back_right: \"후우측보법 (Back-Right)\",\n};\n\nconst STEP_KOREAN_TERMS: Record<string, string> = {\n step_forward: \"전진보법 (Forward Step)\",\n step_back: \"후퇴보법 (Retreat Step)\",\n step_left: \"좌측면보법 (Left Step)\",\n step_right: \"우측면보법 (Right Step)\",\n};\n\n// Footwork pattern Korean terminology - maps animation states to display names\n// Reuses FOOTWORK_KOREAN_TERMS from types.ts but adds specific animation state labels\nconst FOOTWORK_DISPLAY_TERMS: Record<string, string> = {\n footwork_circular_left: `${FOOTWORK_KOREAN_TERMS.circular.korean} 좌 (Circular Left)`,\n footwork_circular_right: `${FOOTWORK_KOREAN_TERMS.circular.korean} 우 (Circular Right)`,\n footwork_pivot_left: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 좌 (Pivot Left)`,\n footwork_pivot_right: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 우 (Pivot Right)`,\n footwork_slide_forward: `${FOOTWORK_KOREAN_TERMS.slide.korean} 전 (Slide Forward)`,\n footwork_slide_back: `${FOOTWORK_KOREAN_TERMS.slide.korean} 후 (Slide Back)`,\n footwork_slide_left: `${FOOTWORK_KOREAN_TERMS.slide.korean} 좌 (Slide Left)`,\n footwork_slide_right: `${FOOTWORK_KOREAN_TERMS.slide.korean} 우 (Slide Right)`,\n footwork_shuffle: `${FOOTWORK_KOREAN_TERMS.shuffle.korean} (Shuffle)`,\n};\n\n// Step direction mapping (all move_* actions covered explicitly)\nconst STEP_DIRECTION_MAP: Record<string, string> = {\n move_up: \"step_forward\",\n move_down: \"step_back\",\n move_left: \"step_left\",\n move_right: \"step_right\",\n};\n\n/**\n * Queued input for input buffer display\n */\nexport interface QueuedInput {\n readonly action: string;\n readonly key: string;\n readonly timestamp: number;\n}\n\n/**\n * Props for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsProps {\n /** Callback when stance changes (receives stance index 0-7) */\n readonly onStanceChange: (stance: number) => void;\n /** Callback for combat actions (attack, block, etc.) */\n readonly onAction: (action: string) => void;\n /** Whether keyboard input is enabled */\n readonly enabled?: boolean;\n /** Current stance index (0-7) for validation */\n readonly currentStance?: number;\n /** Callback when hints toggle is requested */\n readonly onToggleHints?: () => void;\n /** Play sound effect function */\n readonly playSFX?: (soundId: string) => void;\n /** Current animation state for grounded detection */\n readonly currentAnimationState?: string;\n}\n\n/**\n * Return type for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsReturn {\n /** Queued inputs for display */\n readonly queuedInputs: readonly QueuedInput[];\n /** Whether hints are visible */\n readonly showHints: boolean;\n /** Toggle hints visibility */\n readonly toggleHints: () => void;\n}\n\n/**\n * Maximum number of inputs to keep in queue\n */\nconst MAX_QUEUE_SIZE = 3;\n\n/**\n * Time to keep inputs in queue (milliseconds)\n */\nconst QUEUE_RETENTION_TIME = 2000;\n\n/**\n * Custom hook for handling keyboard controls with visual feedback\n *\n * Features:\n * - Trigram stance switching (1-8 keys or custom bindings)\n * - Combat action handling (attack, block, movement)\n * - Input queue visualization\n * - Keyboard hints toggle (F1)\n * - Invalid input prevention\n * - Audio feedback integration\n *\n * @example\n * ```typescript\n * const { queuedInputs, showHints, toggleHints } = useKeyboardControls({\n * onStanceChange: (stance) => handleStanceChange(stance),\n * onAction: (action) => handleAction(action),\n * enabled: !isPaused,\n * currentStance: player.stance,\n * playSFX: audio.playSFX,\n * });\n * ```\n *\n * @public\n * @korean 키보드컨트롤사용\n */\nexport function useKeyboardControls({\n onStanceChange,\n onAction,\n enabled = true,\n currentStance = 0,\n onToggleHints,\n playSFX,\n currentAnimationState,\n}: UseKeyboardControlsProps): UseKeyboardControlsReturn {\n const [queuedInputs, setQueuedInputs] = useState<readonly QueuedInput[]>([]);\n const [showHints, setShowHints] = useState(false);\n const controlMapperRef = useRef<ControlMapper>(new ControlMapper());\n const lastStanceChangeRef = useRef<number>(0);\n\n // Track currently pressed keys for diagonal step detection\n const pressedKeysRef = useRef<Set<string>>(new Set());\n\n /**\n * Toggle hints visibility\n */\n const toggleHints = useCallback(() => {\n setShowHints((prev) => {\n const newState = !prev;\n if (onToggleHints) {\n onToggleHints();\n }\n // Play UI sound\n if (playSFX) {\n playSFX(\"menu_select\");\n }\n return newState;\n });\n }, [onToggleHints, playSFX]);\n\n /**\n * Detect diagonal step direction from pressed keys\n * Returns step action for diagonal or null if not diagonal\n *\n * @korean 대각선발걸음감지\n */\n const getDiagonalStepAction = useCallback((): string | null => {\n const keys = pressedKeysRef.current;\n const mapper = controlMapperRef.current;\n\n // Check for forward diagonals\n if (keys.has(mapper.getBindings().movement.up.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_forward_left\"; // 전좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_forward_right\"; // 전우측보법\n }\n }\n\n // Check for backward diagonals\n if (keys.has(mapper.getBindings().movement.down.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_back_left\"; // 후좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_back_right\"; // 후우측보법\n }\n }\n\n return null;\n }, []);\n\n /**\n * Add input to queue\n */\n const addToQueue = useCallback((action: string, key: string) => {\n const newInput: QueuedInput = {\n action,\n key,\n timestamp: Date.now(),\n };\n\n setQueuedInputs((prev) => {\n const updated = [newInput, ...prev].slice(0, MAX_QUEUE_SIZE);\n return updated;\n });\n }, []);\n\n /**\n * Clean up old queued inputs\n * Single interval for component lifetime to avoid recreating on every input\n */\n useEffect(() => {\n const interval = setInterval(() => {\n const now = Date.now();\n setQueuedInputs((prev) => {\n if (prev.length === 0) return prev;\n return prev.filter(\n (input) => now - input.timestamp < QUEUE_RETENTION_TIME\n );\n });\n }, 500);\n\n return () => clearInterval(interval);\n }, []); // Empty deps - single interval for component lifetime\n\n /**\n * Handle keyboard input\n */\n useEffect(() => {\n if (!enabled) return;\n\n // Copy ref value for cleanup\n const pressedKeys = pressedKeysRef.current;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const mapper = controlMapperRef.current;\n\n // Track pressed keys for diagonal detection\n pressedKeysRef.current.add(e.key.toLowerCase());\n\n // F1: Toggle hints (prevent default browser help)\n if (e.key === \"F1\") {\n e.preventDefault();\n toggleHints();\n return;\n }\n\n // Escape: Close hints if open\n if (e.key === \"Escape\" && showHints) {\n e.preventDefault();\n setShowHints(false);\n return;\n }\n\n // Recovery input detection (기상 입력 감지)\n // When player is in ground state, any button triggers recovery options\n const isGrounded = currentAnimationState?.startsWith(\"ground_\");\n\n if (isGrounded) {\n // Space: Quick/default recovery\n if (e.key === \" \") {\n e.preventDefault();\n onAction(\"recovery_quick\");\n addToQueue(\"기상 (Quick Recovery)\", \"Space\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // R or Enter: Roll recovery (회전기상) - fast but costs stamina\n if (e.key.toLowerCase() === \"r\" || e.key === \"Enter\") {\n e.preventDefault();\n onAction(\"recovery_roll\");\n addToQueue(\"회전기상 (Roll Recovery)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // Shift: Defensive getup (방어기상) - slow but protected\n if (e.shiftKey) {\n e.preventDefault();\n onAction(\"recovery_defensive\");\n addToQueue(\"방어기상 (Defensive Getup)\", \"Shift\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // If grounded, don't process other inputs (can't attack/move while down)\n return;\n }\n\n // Check for stance change (1-8 or custom bindings)\n const stanceIndex = mapper.getStanceForKey(e.key);\n if (stanceIndex !== null) {\n e.preventDefault();\n\n // Prevent switching to the same stance\n if (stanceIndex === currentStance) {\n // Invalid input feedback (shake animation trigger)\n addToQueue(`Stance ${stanceIndex + 1} (already active)`, e.key);\n\n // Play error sound\n if (playSFX) {\n playSFX(\"menu_error\");\n }\n return;\n }\n\n // Throttle stance changes (minimum 8 frames at 60fps = 133ms)\n const now = Date.now();\n if (now - lastStanceChangeRef.current < 133) {\n return;\n }\n lastStanceChangeRef.current = now;\n\n // Execute stance change\n onStanceChange(stanceIndex);\n addToQueue(`Stance ${stanceIndex + 1}`, e.key);\n\n // Play stance change sound\n if (playSFX) {\n playSFX(\"stance_change\");\n }\n return;\n }\n\n // Handle other actions\n const action = mapper.getActionForKey(e.key);\n if (action) {\n e.preventDefault();\n\n switch (action) {\n case \"attack\":\n onAction(\"attack\");\n addToQueue(\"Attack\", e.key);\n if (playSFX) playSFX(\"attack_light\");\n break;\n\n case \"block\":\n onAction(\"block\");\n addToQueue(\"Block\", e.key);\n if (playSFX) playSFX(\"block\");\n break;\n\n case \"move_up\":\n case \"move_down\":\n case \"move_left\":\n case \"move_right\":\n // Check for Alt+A/D modifier for slide left/right (MUST be checked first)\n if (e.altKey) {\n // Alt modifier for slide left/right footwork patterns\n let slideFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Alt+A: Slide left (미끄럼보 좌)\n slideFootwork = \"footwork_slide_left\";\n } else if (action === \"move_right\") {\n // Alt+D: Slide right (미끄럼보 우)\n slideFootwork = \"footwork_slide_right\";\n }\n\n if (slideFootwork) {\n onAction(slideFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[slideFootwork] ?? \"Slide Footwork\",\n `Alt+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey && e.ctrlKey) {\n // Shift+Ctrl modifier for advanced footwork patterns\n let advancedFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Shift+Ctrl+A: Pivot left (축족회전 좌)\n advancedFootwork = \"footwork_pivot_left\";\n } else if (action === \"move_right\") {\n // Shift+Ctrl+D: Pivot right (축족회전 우)\n advancedFootwork = \"footwork_pivot_right\";\n } else if (action === \"move_up\" || action === \"move_down\") {\n // Shift+Ctrl+W or Shift+Ctrl+S: Shuffle step (섞음보)\n advancedFootwork = \"footwork_shuffle\";\n }\n\n if (advancedFootwork) {\n onAction(advancedFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[advancedFootwork] ??\n \"Advanced Footwork\",\n `Shift+Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.ctrlKey) {\n // Footwork patterns with Ctrl+WASD\n let footworkAction: string | null = null;\n\n if (action === \"move_left\") {\n // Ctrl+A: Circular step left (원형보)\n footworkAction = \"footwork_circular_left\";\n } else if (action === \"move_right\") {\n // Ctrl+D: Circular step right (원형보)\n footworkAction = \"footwork_circular_right\";\n } else if (action === \"move_up\") {\n // Ctrl+W: Slide forward (미끄럼보)\n footworkAction = \"footwork_slide_forward\";\n } else if (action === \"move_down\") {\n // Ctrl+S: Slide back (미끄럼보)\n footworkAction = \"footwork_slide_back\";\n }\n\n if (footworkAction) {\n onAction(footworkAction);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[footworkAction] ?? \"Footwork\",\n `Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey) {\n // Check if Shift is held for tactical step instead of walk\n // Check for diagonal step first\n const diagonalStep = getDiagonalStepAction();\n\n if (diagonalStep) {\n // Diagonal tactical step\n onAction(diagonalStep);\n\n addToQueue(\n DIAGONAL_KOREAN_TERMS[diagonalStep] ?? \"Diagonal Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n } else {\n // Cardinal direction tactical step\n // Map move_up/move_down to step_forward/step_back to match AnimationState\n const stepDirection = STEP_DIRECTION_MAP[action];\n onAction(stepDirection);\n\n addToQueue(\n STEP_KOREAN_TERMS[stepDirection] ?? \"Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n }\n } else {\n // Regular movement\n onAction(action);\n addToQueue(action, e.key);\n }\n break;\n\n case \"stance_side_switch\":\n // H key: Switch front foot (stance side switch)\n onAction(\"stance_side_switch\");\n addToQueue(\"발 바꿈 (Switch Stance Side)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n break;\n\n case \"vital_points_overlay\":\n // Vital points overlay toggle\n onAction(\"vital_points_overlay\");\n addToQueue(\"Vital Points\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n\n case \"pause\":\n // Pause menu toggle\n onAction(\"pause\");\n addToQueue(\"Pause\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n }\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n // Remove key from pressed keys set\n pressedKeysRef.current.delete(e.key.toLowerCase());\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n window.addEventListener(\"keyup\", handleKeyUp);\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n window.removeEventListener(\"keyup\", handleKeyUp);\n // Clear pressed keys on cleanup using copied ref value\n pressedKeys.clear();\n };\n }, [\n enabled,\n currentStance,\n onStanceChange,\n onAction,\n addToQueue,\n toggleHints,\n getDiagonalStepAction,\n showHints,\n playSFX,\n currentAnimationState,\n ]);\n\n return {\n queuedInputs,\n showHints,\n toggleHints,\n };\n}\n"],"mappings":"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"}
@@ -2,9 +2,9 @@ import { getDefensiveAnimation } from "../systems/animation/catalogs/DefensiveAn
2
2
  import { getAttackAnimation } from "../systems/animation/catalogs/StanceAttackAnimations.js";
3
3
  import { getAnimation, getAnimationByName } from "../systems/animation/core/AnimationRegistry.js";
4
4
  import { batchUpdateBones, interpolateKeyframeCached, performanceMonitor } from "../systems/animation/core/AnimationOptimizations.js";
5
- import { applyLaterality } from "../systems/animation/core/LateralityTransform.js";
6
5
  import { getStepAnimation } from "../systems/animation/catalogs/StepSkeletalAnimations.js";
7
6
  import { getFootworkAnimation } from "../systems/animation/catalogs/FootworkSkeletalAnimations.js";
7
+ import { applyLaterality } from "../systems/animation/core/LateralityTransform.js";
8
8
  import { useCallback, useEffect, useRef, useState } from "react";
9
9
  //#region src/hooks/useSkeletalAnimation.ts
10
10
  /**
@@ -24,7 +24,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
24
24
  * Set of diagonal step animations for O(1) lookup
25
25
  * @korean 대각선스텝애니메이션집합
26
26
  */
27
- var DIAGONAL_STEP_ANIMATIONS = new Set([
27
+ var DIAGONAL_STEP_ANIMATIONS = /* @__PURE__ */ new Set([
28
28
  "step_forward_left",
29
29
  "step_forward_right",
30
30
  "step_back_left",
@@ -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 =\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
+ {"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":"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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"}
package/lib/index.js CHANGED
@@ -42,9 +42,7 @@ import { DARK_OPS_ARCHETYPE_BONUSES, DARK_OPS_NIGHT_BONUS, DARK_OPS_SPECIAL_EFFE
42
42
  import { DAMAGE_TYPE_EFFECTIVENESS, KOREAN_TECHNIQUE_CATEGORIES, TECHNIQUE_DIFFICULTY_LEVELS, TECHNIQUE_MODIFIERS, TECHNIQUE_NAMING, TECHNIQUE_PROPERTIES, TRIGRAM_TECHNIQUE_PROPERTIES, calculateDamageEffectiveness, getTrigramKey, getTrigramProperties } from "./systems/trigram/techniques/TechniqueConfig.js";
43
43
  import { TRIGRAM_TECHNIQUES, getAllTechniques, getTechniqueById, getTechniqueCountByStance, getTotalTechniqueCount } from "./systems/trigram/techniques/index.js";
44
44
  import { KoreanTechniquesSystem, TECHNIQUE_EFFECTIVENESS_MATRIX, getTechniquesByStance } from "./systems/trigram/KoreanTechniques.js";
45
- import { STANCE_SPEED_MODIFIERS } from "./systems/physics/MovementPhysics.js";
46
45
  import { EffectIntensity, HitEffectEnum, HitEffectType } from "./systems/effects.js";
47
- import { DEFAULT_INJURY_MOVEMENT_CONFIG, InjuryMovementModifier, injuryMovementModifier } from "./systems/movement/InjuryMovementModifier.js";
48
46
  import { BodyPartHealthSystem, bodyPartHealthSystem } from "./systems/bodypart/BodyPartHealthSystem.js";
49
47
  import { applyDamageToBodyParts, applyVitalPointDamageToBodyParts, getBodyPartCombatEffects, getBodyRegionFromVitalPoint, healBodyPartsProportionally, initializeBodyPartHealthForPlayer, isPlayerIncapacitatedByBodyDamage } from "./systems/bodypart/BodyPartDamageIntegration.js";
50
48
  import { MovementPenaltySystem, movementPenaltySystem } from "./systems/bodypart/MovementPenaltySystem.js";
@@ -78,6 +76,8 @@ import { AIActionType } from "./systems/ai/types.js";
78
76
  import { ARCHETYPE_ENFORCEMENT, enforceArchetypeBehavior, getActionFrequency, getSignatureMove, isActionProhibited, shouldExecuteSignatureMove } from "./systems/ai/ArchetypeEnforcer.js";
79
77
  import { AIDecisionTree } from "./systems/ai/DecisionTree.js";
80
78
  import TrainingAI from "./systems/ai/TrainingAI.js";
79
+ import { STANCE_SPEED_MODIFIERS } from "./systems/physics/MovementPhysics.js";
80
+ import { DEFAULT_INJURY_MOVEMENT_CONFIG, InjuryMovementModifier, injuryMovementModifier } from "./systems/movement/InjuryMovementModifier.js";
81
81
  import { getColorRGB, mixColors, toHexColor } from "./utils/colorHelpers.js";
82
82
  import TrainingCombatSystem from "./systems/combat/TrainingCombatSystem.js";
83
83
  import { isValidArchetype, isVitalPoint, isVitalPointCategory } from "./systems/combat/typeGuards.js";