blacktrigram 0.7.39 → 0.7.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/audio/AudioAssetLoader.js.map +1 -1
  3. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  4. package/lib/audio/AudioCache.js.map +1 -1
  5. package/lib/audio/AudioManager.js.map +1 -1
  6. package/lib/audio/AudioMonitor.js.map +1 -1
  7. package/lib/audio/AudioPool.js.map +1 -1
  8. package/lib/audio/AudioProvider.js.map +1 -1
  9. package/lib/audio/AudioUtils.js.map +1 -1
  10. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  11. package/lib/audio/VariantSelector.js.map +1 -1
  12. package/lib/audio/types.js.map +1 -1
  13. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  14. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  15. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  31. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  32. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  36. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  39. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  42. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  44. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  45. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  46. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  47. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  48. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  49. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  50. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  51. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  52. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  53. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  54. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  55. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  56. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  57. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  58. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  59. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  60. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  61. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  62. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  63. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  64. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  65. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  66. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  67. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  68. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  69. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  70. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  71. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  72. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  74. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  75. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  76. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  77. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  78. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  79. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  84. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  85. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  86. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  87. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  88. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  89. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  90. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  91. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  93. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  94. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  96. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  97. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  98. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  99. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  100. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  101. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  102. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  103. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  104. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  105. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  106. package/lib/components/shared/base/BaseButton.js.map +1 -1
  107. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  108. package/lib/components/shared/base/BasePanel.js.map +1 -1
  109. package/lib/components/shared/base/BaseText.js.map +1 -1
  110. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  111. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  112. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  113. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  114. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  115. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  116. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  117. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  118. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  119. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  120. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  121. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  122. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  123. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  124. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  125. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  126. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  127. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  128. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  129. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  130. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  131. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  132. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  133. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  134. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  135. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  136. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  137. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  138. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  139. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  140. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  141. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  142. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  143. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  144. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  145. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  146. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  147. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  148. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  149. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  150. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  151. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  152. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  153. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  154. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  155. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  156. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  157. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  158. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  159. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  160. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  161. package/lib/components/shared/ui/BackButton.js.map +1 -1
  162. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  163. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  164. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  165. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  166. package/lib/components/shared/ui/SplashScreen.js +2 -2
  167. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  168. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  169. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  170. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  171. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  172. package/lib/constants/bodyDimensions.js.map +1 -1
  173. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  174. package/lib/data/archetypeClothing.js.map +1 -1
  175. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  176. package/lib/data/techniqueMappings.js.map +1 -1
  177. package/lib/data/techniques.js.map +1 -1
  178. package/lib/hooks/useActionFeedback.js.map +1 -1
  179. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  180. package/lib/hooks/useCombatTimer.js.map +1 -1
  181. package/lib/hooks/useDebounce.js.map +1 -1
  182. package/lib/hooks/useHUDLayout.js.map +1 -1
  183. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  184. package/lib/hooks/useKeyboardControls.js.map +1 -1
  185. package/lib/hooks/useMatchCountdown.js.map +1 -1
  186. package/lib/hooks/useMuscleActivation.js.map +1 -1
  187. package/lib/hooks/usePauseMenu.js.map +1 -1
  188. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  189. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  190. package/lib/hooks/useRoundTransition.js.map +1 -1
  191. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  192. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  193. package/lib/hooks/useThrottle.js.map +1 -1
  194. package/lib/hooks/useTouchControls.js.map +1 -1
  195. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  196. package/lib/hooks/useWindowSize.js.map +1 -1
  197. package/lib/systems/CombatSystem.js.map +1 -1
  198. package/lib/systems/EffectCalculator.js.map +1 -1
  199. package/lib/systems/LayoutSystem.js.map +1 -1
  200. package/lib/systems/PlayerEffectManager.js.map +1 -1
  201. package/lib/systems/ResponsiveScaling.js.map +1 -1
  202. package/lib/systems/TrigramSystem.js.map +1 -1
  203. package/lib/systems/VitalPointSystem.js.map +1 -1
  204. package/lib/systems/ai/AIPersonality.js.map +1 -1
  205. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  206. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  207. package/lib/systems/ai/ComboSystem.js.map +1 -1
  208. package/lib/systems/ai/DecisionTree.js.map +1 -1
  209. package/lib/systems/ai/TrainingAI.js.map +1 -1
  210. package/lib/systems/ai/types.js.map +1 -1
  211. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  212. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  213. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  214. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  215. package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
  216. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  217. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  218. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  219. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  220. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  221. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  222. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  223. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  224. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  225. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  226. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  227. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  228. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  229. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  230. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  231. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  232. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  233. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  234. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  235. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  236. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  237. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  238. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  239. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  240. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  241. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  242. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  243. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  244. package/lib/systems/animation/core/types.js.map +1 -1
  245. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  246. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  247. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  248. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  249. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  250. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  251. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  252. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  253. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  254. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  255. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  256. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  257. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  258. package/lib/systems/bodypart/types.js.map +1 -1
  259. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  260. package/lib/systems/breathing/feedback.js.map +1 -1
  261. package/lib/systems/breathing/integration.js.map +1 -1
  262. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  263. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  264. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  265. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  266. package/lib/systems/combat/FallIntegration.js.map +1 -1
  267. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  268. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  269. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  270. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  271. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  272. package/lib/systems/combat/typeGuards.js.map +1 -1
  273. package/lib/systems/effects.js.map +1 -1
  274. package/lib/systems/game.js.map +1 -1
  275. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  276. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  277. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  278. package/lib/systems/movement/integration.js.map +1 -1
  279. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  280. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  281. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  282. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  283. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  284. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  285. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  286. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  287. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  288. package/lib/systems/trigram/StanceManager.js.map +1 -1
  289. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  290. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  291. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  292. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  293. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  294. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  295. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  296. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  297. package/lib/systems/trigram/techniques/index.js.map +1 -1
  298. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  299. package/lib/systems/trigram/types.js.map +1 -1
  300. package/lib/systems/types.js.map +1 -1
  301. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  302. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  303. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  304. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  305. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  306. package/lib/types/AccessibilityTypes.js.map +1 -1
  307. package/lib/types/PhysicsTypes.js.map +1 -1
  308. package/lib/types/common.js.map +1 -1
  309. package/lib/types/constants/colors.js.map +1 -1
  310. package/lib/types/constants/designSystem.js.map +1 -1
  311. package/lib/types/constants/layout.js.map +1 -1
  312. package/lib/types/constants/performance.js.map +1 -1
  313. package/lib/types/constants/typography.js.map +1 -1
  314. package/lib/types/facial.js.map +1 -1
  315. package/lib/types/hand-animation.js.map +1 -1
  316. package/lib/types/injury.js.map +1 -1
  317. package/lib/types/physics.js.map +1 -1
  318. package/lib/types/skeletal.js.map +1 -1
  319. package/lib/types/techniqueId.js.map +1 -1
  320. package/lib/utils/accessibility.js.map +1 -1
  321. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  322. package/lib/utils/assetConfig.js.map +1 -1
  323. package/lib/utils/characterScaling.js.map +1 -1
  324. package/lib/utils/colorHelpers.js.map +1 -1
  325. package/lib/utils/colorUtils.js.map +1 -1
  326. package/lib/utils/combatReadiness.js.map +1 -1
  327. package/lib/utils/controlMapping.js.map +1 -1
  328. package/lib/utils/deviceDetection.js.map +1 -1
  329. package/lib/utils/effectUtils.js.map +1 -1
  330. package/lib/utils/fabricTextures.js.map +1 -1
  331. package/lib/utils/hapticFeedback.js.map +1 -1
  332. package/lib/utils/haptics.js.map +1 -1
  333. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  334. package/lib/utils/inputSystem.js.map +1 -1
  335. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  336. package/lib/utils/math.js.map +1 -1
  337. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  338. package/lib/utils/mobileUIUtils.js.map +1 -1
  339. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  340. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  341. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  342. package/lib/utils/performanceOptimization.js.map +1 -1
  343. package/lib/utils/player3DHelpers.js.map +1 -1
  344. package/lib/utils/playerUtils.js.map +1 -1
  345. package/lib/utils/responsiveLayout.js.map +1 -1
  346. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  347. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  348. package/lib/utils/safeAreaUtils.js.map +1 -1
  349. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  350. package/lib/utils/skeletonScaling.js.map +1 -1
  351. package/lib/utils/stanceHelpers.js.map +1 -1
  352. package/lib/utils/threeObjectPool.js.map +1 -1
  353. package/lib/utils/visualEffects.js.map +1 -1
  354. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"painConsciousnessUtils.js","names":[],"sources":["../../../src/systems/combat/painConsciousnessUtils.ts"],"sourcesContent":["/**\n * Utility functions for Pain and Consciousness management in combat.\n * \n * **Korean**: 고통 의식 관리 유틸리티\n * \n * Provides helper functions for managing pain and consciousness effects\n * in combat scenarios, including status checking, effect application,\n * and recovery management.\n * \n * @module systems/combat/painConsciousnessUtils\n * @category Combat System\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointCategory } from \"@/types\";\nimport { CombatResult } from \"./types\";\nimport PainResponseSystem, { PainLevel, ShockPainEffect } from \"./PainResponseSystem\";\nimport ConsciousnessSystem, { ConsciousnessLevel } from \"./ConsciousnessSystem\";\n\n/**\n * Pain and consciousness status information.\n */\nexport interface PainConsciousnessStatus {\n /** Current pain level */\n readonly painLevel: PainLevel;\n /** Current consciousness level */\n readonly consciousnessLevel: ConsciousnessLevel;\n /** Whether player is in pain overload */\n readonly isInPainOverload: boolean;\n /** Whether player is at incapacitation threshold */\n readonly isIncapacitated: boolean;\n /** Whether player is combat effective */\n readonly isCombatEffective: boolean;\n /** Overall combat effectiveness percentage (0-1) */\n readonly combatEffectiveness: number;\n /** Bilingual status description */\n readonly statusDescription: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\n/**\n * Gets comprehensive pain and consciousness status for a player.\n * \n * @param player - Player to check status for\n * @param painSystem - Pain response system instance\n * @param consciousnessSystem - Consciousness system instance\n * @returns Comprehensive status information\n * \n * @example\n * ```typescript\n * const status = getPainConsciousnessStatus(player, painSystem, consciousnessSystem);\n * console.log(`Combat effectiveness: ${status.combatEffectiveness * 100}%`);\n * console.log(`Status: ${status.statusDescription.english}`);\n * ```\n * \n * @public\n * @korean 상태확인\n */\nexport function getPainConsciousnessStatus(\n player: PlayerState,\n painSystem: PainResponseSystem,\n consciousnessSystem: ConsciousnessSystem\n): PainConsciousnessStatus {\n const painLevel = painSystem.getPainLevel(player.pain);\n const consciousnessLevel = consciousnessSystem.getLevel(player.consciousness);\n \n const isInPainOverload = painSystem.isInPainOverload(player);\n const isIncapacitated = consciousnessSystem.isAtIncapacitationThreshold(player);\n \n // Calculate overall combat effectiveness\n const painPenalty = painSystem.getEffects(painLevel).performancePenalty;\n const consciousnessPenalty = consciousnessSystem.getEffects(consciousnessLevel).accuracyPenalty;\n const combatEffectiveness = Math.max(0, 1 - Math.max(painPenalty, consciousnessPenalty));\n \n const isCombatEffective = combatEffectiveness > 0.5;\n \n // Generate status description\n const painName = painSystem.getLevelName(painLevel);\n const consciousnessName = consciousnessSystem.getLevelName(consciousnessLevel);\n \n let statusKorean = `고통: ${painName.korean}, 의식: ${consciousnessName.korean}`;\n let statusEnglish = `Pain: ${painName.english}, Consciousness: ${consciousnessName.english}`;\n \n if (isIncapacitated) {\n statusKorean = \"무력화 상태\";\n statusEnglish = \"Incapacitated\";\n } else if (isInPainOverload) {\n statusKorean = \"고통 과부하\";\n statusEnglish = \"Pain Overload\";\n }\n \n return {\n painLevel,\n consciousnessLevel,\n isInPainOverload,\n isIncapacitated,\n isCombatEffective,\n combatEffectiveness,\n statusDescription: {\n korean: statusKorean,\n english: statusEnglish,\n },\n };\n}\n\n/**\n * Determines if a combat result should trigger head trauma.\n * \n * Head trauma occurs for:\n * - Neurological vital point hits\n * - Vascular hits to head region\n * - High damage hits (>25) that could cause concussion\n * - Critical hits to upper body\n * \n * @param result - Combat result to check\n * @param category - Vital point category if known\n * @returns True if result should cause head trauma\n * \n * @public\n * @korean 두부외상확인\n */\nexport function isHeadTraumaHit(\n result: CombatResult,\n category?: VitalPointCategory\n): boolean {\n // Neurological hits often target head\n if (category === VitalPointCategory.NEUROLOGICAL) {\n return true;\n }\n \n // Vascular hits to head region\n if (category === VitalPointCategory.VASCULAR && result.damage > 15) {\n return true;\n }\n \n // High damage hits that could concuss\n if (result.damage > 25) {\n return true;\n }\n \n // Critical hits to vital points\n if (result.isCritical && result.vitalPointHit) {\n return true;\n }\n \n return false;\n}\n\n/**\n * Extracts vital point category from combat result.\n * \n * Uses heuristics to determine category from effect sources and hit data.\n * \n * @param result - Combat result\n * @returns Vital point category if determinable\n * \n * @public\n * @korean 급소분류추출\n */\nexport function extractVitalPointCategory(result: CombatResult): VitalPointCategory | undefined {\n if (!result.vitalPointHit || !result.effects || result.effects.length === 0) {\n return undefined;\n }\n \n const effect = result.effects[0];\n if (!effect.source) {\n return undefined;\n }\n \n const source = effect.source.toLowerCase();\n \n // Map source strings to categories\n if (source.includes('neuro') || source.includes('nerve')) {\n return VitalPointCategory.NEUROLOGICAL;\n }\n if (source.includes('vascular') || source.includes('blood') || source.includes('artery')) {\n return VitalPointCategory.VASCULAR;\n }\n if (source.includes('respiratory') || source.includes('breath') || source.includes('lung')) {\n return VitalPointCategory.RESPIRATORY;\n }\n if (source.includes('skeletal') || source.includes('bone')) {\n return VitalPointCategory.SKELETAL;\n }\n if (source.includes('muscular') || source.includes('muscle')) {\n return VitalPointCategory.MUSCULAR;\n }\n if (source.includes('organ') || source.includes('visceral')) {\n return VitalPointCategory.ORGAN;\n }\n if (source.includes('joint')) {\n return VitalPointCategory.JOINT;\n }\n \n return undefined;\n}\n\n/**\n * Calculates recommended recovery time based on player condition.\n * \n * @param player - Player state\n * @param consciousnessSystem - Consciousness system\n * @returns Estimated recovery time in seconds\n * \n * @example\n * ```typescript\n * const recoveryTime = getRecommendedRecoveryTime(player, consciousnessSystem);\n * console.log(`Recovery needed: ${recoveryTime}s`);\n * ```\n * \n * @public\n * @korean 회복시간계산\n */\nexport function getRecommendedRecoveryTime(\n player: PlayerState,\n consciousnessSystem: ConsciousnessSystem\n): number {\n let painRecoveryTime = 0;\n let consciousnessRecoveryTime = 0;\n \n // Pain recovery time (-5 pain/second)\n if (player.pain > 0) {\n painRecoveryTime = player.pain / 5;\n }\n \n // Consciousness recovery time (5 points/second after 5s delay)\n if (player.consciousness < 100) {\n const consciousnessToRecover = 100 - player.consciousness;\n const consciousnessLevel = consciousnessSystem.getLevel(player.consciousness);\n \n // Account for slower recovery at low consciousness\n let recoveryRate = 5; // Base rate\n if (consciousnessLevel === ConsciousnessLevel.STUNNED) {\n recoveryRate = 2.5; // 50% slower\n } else if (consciousnessLevel === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryRate = 1; // 20% of base\n }\n \n consciousnessRecoveryTime = 5 + (consciousnessToRecover / recoveryRate);\n }\n \n // Since pain and consciousness recover in parallel (concurrently in game loop),\n // return the maximum of the two recovery times, not the sum\n return Math.ceil(Math.max(painRecoveryTime, consciousnessRecoveryTime));\n}\n\n/**\n * Checks if shock pain effect is still active.\n * \n * @param shockEffect - Shock pain effect to check\n * @returns True if effect is still active\n * \n * @public\n * @korean 충격통활성확인\n */\nexport function isShockPainActive(shockEffect: ShockPainEffect): boolean {\n const elapsed = Date.now() - shockEffect.startTime;\n return elapsed < shockEffect.duration;\n}\n\n/**\n * Gets remaining shock pain duration.\n * \n * @param shockEffect - Shock pain effect\n * @returns Remaining duration in milliseconds, or 0 if expired\n * \n * @public\n * @korean 충격통잔여시간\n */\nexport function getShockPainRemainingDuration(shockEffect: ShockPainEffect): number {\n const elapsed = Date.now() - shockEffect.startTime;\n return Math.max(0, shockEffect.duration - elapsed);\n}\n\n/**\n * Formats pain and consciousness values for display.\n * \n * @param player - Player state\n * @returns Formatted display strings\n * \n * @example\n * ```typescript\n * const display = formatPainConsciousnessDisplay(player);\n * console.log(display.pain); // \"Pain: 45/100\"\n * console.log(display.consciousness); // \"Consciousness: 85/100\"\n * ```\n * \n * @public\n * @korean 표시형식\n */\nexport function formatPainConsciousnessDisplay(player: PlayerState): {\n readonly pain: string;\n readonly consciousness: string;\n readonly painKorean: string;\n readonly consciousnessKorean: string;\n} {\n return {\n pain: `Pain: ${Math.floor(player.pain)}/100`,\n consciousness: `Consciousness: ${Math.floor(player.consciousness)}/100`,\n painKorean: `고통: ${Math.floor(player.pain)}/100`,\n consciousnessKorean: `의식: ${Math.floor(player.consciousness)}/100`,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"painConsciousnessUtils.js","names":[],"sources":["../../../src/systems/combat/painConsciousnessUtils.ts"],"sourcesContent":["/**\n * Utility functions for Pain and Consciousness management in combat.\n * \n * **Korean**: 고통 의식 관리 유틸리티\n * \n * Provides helper functions for managing pain and consciousness effects\n * in combat scenarios, including status checking, effect application,\n * and recovery management.\n * \n * @module systems/combat/painConsciousnessUtils\n * @category Combat System\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointCategory } from \"@/types\";\nimport { CombatResult } from \"./types\";\nimport PainResponseSystem, { PainLevel, ShockPainEffect } from \"./PainResponseSystem\";\nimport ConsciousnessSystem, { ConsciousnessLevel } from \"./ConsciousnessSystem\";\n\n/**\n * Pain and consciousness status information.\n */\nexport interface PainConsciousnessStatus {\n /** Current pain level */\n readonly painLevel: PainLevel;\n /** Current consciousness level */\n readonly consciousnessLevel: ConsciousnessLevel;\n /** Whether player is in pain overload */\n readonly isInPainOverload: boolean;\n /** Whether player is at incapacitation threshold */\n readonly isIncapacitated: boolean;\n /** Whether player is combat effective */\n readonly isCombatEffective: boolean;\n /** Overall combat effectiveness percentage (0-1) */\n readonly combatEffectiveness: number;\n /** Bilingual status description */\n readonly statusDescription: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\n/**\n * Gets comprehensive pain and consciousness status for a player.\n * \n * @param player - Player to check status for\n * @param painSystem - Pain response system instance\n * @param consciousnessSystem - Consciousness system instance\n * @returns Comprehensive status information\n * \n * @example\n * ```typescript\n * const status = getPainConsciousnessStatus(player, painSystem, consciousnessSystem);\n * console.log(`Combat effectiveness: ${status.combatEffectiveness * 100}%`);\n * console.log(`Status: ${status.statusDescription.english}`);\n * ```\n * \n * @public\n * @korean 상태확인\n */\nexport function getPainConsciousnessStatus(\n player: PlayerState,\n painSystem: PainResponseSystem,\n consciousnessSystem: ConsciousnessSystem\n): PainConsciousnessStatus {\n const painLevel = painSystem.getPainLevel(player.pain);\n const consciousnessLevel = consciousnessSystem.getLevel(player.consciousness);\n \n const isInPainOverload = painSystem.isInPainOverload(player);\n const isIncapacitated = consciousnessSystem.isAtIncapacitationThreshold(player);\n \n // Calculate overall combat effectiveness\n const painPenalty = painSystem.getEffects(painLevel).performancePenalty;\n const consciousnessPenalty = consciousnessSystem.getEffects(consciousnessLevel).accuracyPenalty;\n const combatEffectiveness = Math.max(0, 1 - Math.max(painPenalty, consciousnessPenalty));\n \n const isCombatEffective = combatEffectiveness > 0.5;\n \n // Generate status description\n const painName = painSystem.getLevelName(painLevel);\n const consciousnessName = consciousnessSystem.getLevelName(consciousnessLevel);\n \n let statusKorean = `고통: ${painName.korean}, 의식: ${consciousnessName.korean}`;\n let statusEnglish = `Pain: ${painName.english}, Consciousness: ${consciousnessName.english}`;\n \n if (isIncapacitated) {\n statusKorean = \"무력화 상태\";\n statusEnglish = \"Incapacitated\";\n } else if (isInPainOverload) {\n statusKorean = \"고통 과부하\";\n statusEnglish = \"Pain Overload\";\n }\n \n return {\n painLevel,\n consciousnessLevel,\n isInPainOverload,\n isIncapacitated,\n isCombatEffective,\n combatEffectiveness,\n statusDescription: {\n korean: statusKorean,\n english: statusEnglish,\n },\n };\n}\n\n/**\n * Determines if a combat result should trigger head trauma.\n * \n * Head trauma occurs for:\n * - Neurological vital point hits\n * - Vascular hits to head region\n * - High damage hits (>25) that could cause concussion\n * - Critical hits to upper body\n * \n * @param result - Combat result to check\n * @param category - Vital point category if known\n * @returns True if result should cause head trauma\n * \n * @public\n * @korean 두부외상확인\n */\nexport function isHeadTraumaHit(\n result: CombatResult,\n category?: VitalPointCategory\n): boolean {\n // Neurological hits often target head\n if (category === VitalPointCategory.NEUROLOGICAL) {\n return true;\n }\n \n // Vascular hits to head region\n if (category === VitalPointCategory.VASCULAR && result.damage > 15) {\n return true;\n }\n \n // High damage hits that could concuss\n if (result.damage > 25) {\n return true;\n }\n \n // Critical hits to vital points\n if (result.isCritical && result.vitalPointHit) {\n return true;\n }\n \n return false;\n}\n\n/**\n * Extracts vital point category from combat result.\n * \n * Uses heuristics to determine category from effect sources and hit data.\n * \n * @param result - Combat result\n * @returns Vital point category if determinable\n * \n * @public\n * @korean 급소분류추출\n */\nexport function extractVitalPointCategory(result: CombatResult): VitalPointCategory | undefined {\n if (!result.vitalPointHit || !result.effects || result.effects.length === 0) {\n return undefined;\n }\n \n const effect = result.effects[0];\n if (!effect.source) {\n return undefined;\n }\n \n const source = effect.source.toLowerCase();\n \n // Map source strings to categories\n if (source.includes('neuro') || source.includes('nerve')) {\n return VitalPointCategory.NEUROLOGICAL;\n }\n if (source.includes('vascular') || source.includes('blood') || source.includes('artery')) {\n return VitalPointCategory.VASCULAR;\n }\n if (source.includes('respiratory') || source.includes('breath') || source.includes('lung')) {\n return VitalPointCategory.RESPIRATORY;\n }\n if (source.includes('skeletal') || source.includes('bone')) {\n return VitalPointCategory.SKELETAL;\n }\n if (source.includes('muscular') || source.includes('muscle')) {\n return VitalPointCategory.MUSCULAR;\n }\n if (source.includes('organ') || source.includes('visceral')) {\n return VitalPointCategory.ORGAN;\n }\n if (source.includes('joint')) {\n return VitalPointCategory.JOINT;\n }\n \n return undefined;\n}\n\n/**\n * Calculates recommended recovery time based on player condition.\n * \n * @param player - Player state\n * @param consciousnessSystem - Consciousness system\n * @returns Estimated recovery time in seconds\n * \n * @example\n * ```typescript\n * const recoveryTime = getRecommendedRecoveryTime(player, consciousnessSystem);\n * console.log(`Recovery needed: ${recoveryTime}s`);\n * ```\n * \n * @public\n * @korean 회복시간계산\n */\nexport function getRecommendedRecoveryTime(\n player: PlayerState,\n consciousnessSystem: ConsciousnessSystem\n): number {\n let painRecoveryTime = 0;\n let consciousnessRecoveryTime = 0;\n \n // Pain recovery time (-5 pain/second)\n if (player.pain > 0) {\n painRecoveryTime = player.pain / 5;\n }\n \n // Consciousness recovery time (5 points/second after 5s delay)\n if (player.consciousness < 100) {\n const consciousnessToRecover = 100 - player.consciousness;\n const consciousnessLevel = consciousnessSystem.getLevel(player.consciousness);\n \n // Account for slower recovery at low consciousness\n let recoveryRate = 5; // Base rate\n if (consciousnessLevel === ConsciousnessLevel.STUNNED) {\n recoveryRate = 2.5; // 50% slower\n } else if (consciousnessLevel === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryRate = 1; // 20% of base\n }\n \n consciousnessRecoveryTime = 5 + (consciousnessToRecover / recoveryRate);\n }\n \n // Since pain and consciousness recover in parallel (concurrently in game loop),\n // return the maximum of the two recovery times, not the sum\n return Math.ceil(Math.max(painRecoveryTime, consciousnessRecoveryTime));\n}\n\n/**\n * Checks if shock pain effect is still active.\n * \n * @param shockEffect - Shock pain effect to check\n * @returns True if effect is still active\n * \n * @public\n * @korean 충격통활성확인\n */\nexport function isShockPainActive(shockEffect: ShockPainEffect): boolean {\n const elapsed = Date.now() - shockEffect.startTime;\n return elapsed < shockEffect.duration;\n}\n\n/**\n * Gets remaining shock pain duration.\n * \n * @param shockEffect - Shock pain effect\n * @returns Remaining duration in milliseconds, or 0 if expired\n * \n * @public\n * @korean 충격통잔여시간\n */\nexport function getShockPainRemainingDuration(shockEffect: ShockPainEffect): number {\n const elapsed = Date.now() - shockEffect.startTime;\n return Math.max(0, shockEffect.duration - elapsed);\n}\n\n/**\n * Formats pain and consciousness values for display.\n * \n * @param player - Player state\n * @returns Formatted display strings\n * \n * @example\n * ```typescript\n * const display = formatPainConsciousnessDisplay(player);\n * console.log(display.pain); // \"Pain: 45/100\"\n * console.log(display.consciousness); // \"Consciousness: 85/100\"\n * ```\n * \n * @public\n * @korean 표시형식\n */\nexport function formatPainConsciousnessDisplay(player: PlayerState): {\n readonly pain: string;\n readonly consciousness: string;\n readonly painKorean: string;\n readonly consciousnessKorean: string;\n} {\n return {\n pain: `Pain: ${Math.floor(player.pain)}/100`,\n consciousness: `Consciousness: ${Math.floor(player.consciousness)}/100`,\n painKorean: `고통: ${Math.floor(player.pain)}/100`,\n consciousnessKorean: `의식: ${Math.floor(player.consciousness)}/100`,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"typeGuards.js","names":[],"sources":["../../../src/systems/combat/typeGuards.ts"],"sourcesContent":["/**\n * Type guard functions for combat system\n * Provides runtime type checking for better type safety\n */\n\nimport { PlayerArchetype, VitalPointCategory, VitalPointSeverity } from \"../../types/common\";\nimport { VitalPoint } from \"../vitalpoint/types\";\n\n/**\n * Type guard to check if a value is a valid PlayerArchetype\n * @param value - Value to check\n * @returns True if value is a valid PlayerArchetype\n */\nexport function isValidArchetype(value: unknown): value is PlayerArchetype {\n if (typeof value !== \"string\") {\n return false;\n }\n \n return Object.values(PlayerArchetype).includes(value as PlayerArchetype);\n}\n\n/**\n * Type guard to check if a value is a valid VitalPoint\n * @param value - Value to check\n * @returns True if value is a valid VitalPoint\n */\nexport function isVitalPoint(value: unknown): value is VitalPoint {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n \n const obj = value as Record<string, unknown>;\n \n // Validate position structure\n const hasValidPosition = \n obj.position !== undefined &&\n typeof obj.position === \"object\" &&\n obj.position !== null &&\n typeof (obj.position as Record<string, unknown>).x === \"number\" &&\n typeof (obj.position as Record<string, unknown>).y === \"number\";\n \n return (\n typeof obj.id === \"string\" &&\n typeof obj.category === \"string\" &&\n Object.values(VitalPointCategory).includes(obj.category as VitalPointCategory) &&\n typeof obj.severity === \"string\" &&\n Object.values(VitalPointSeverity).includes(obj.severity as VitalPointSeverity) &&\n hasValidPosition &&\n Array.isArray(obj.effects) &&\n typeof obj.names === \"object\" &&\n obj.names !== null &&\n typeof obj.description === \"object\" &&\n obj.description !== null &&\n typeof obj.targetingDifficulty === \"number\" &&\n Array.isArray(obj.effectiveStances)\n );\n}\n\n/**\n * Type guard to check if a value is a valid VitalPointCategory\n * @param value - Value to check\n * @returns True if value is a valid VitalPointCategory\n */\nexport function isVitalPointCategory(value: unknown): value is VitalPointCategory {\n if (typeof value !== \"string\") {\n return false;\n }\n \n return Object.values(VitalPointCategory).includes(value as VitalPointCategory);\n}\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,iBAAiB,OAA0C;AACzE,KAAI,OAAO,UAAU,SACnB,QAAO;AAGT,QAAO,OAAO,OAAO,gBAAgB,CAAC,SAAS,MAAyB;;;;;;;AAQ1E,SAAgB,aAAa,OAAqC;AAChE,KAAI,CAAC,SAAS,OAAO,UAAU,SAC7B,QAAO;CAGT,MAAM,MAAM;CAGZ,MAAM,mBACJ,IAAI,aAAa,KAAA,KACjB,OAAO,IAAI,aAAa,YACxB,IAAI,aAAa,QACjB,OAAQ,IAAI,SAAqC,MAAM,YACvD,OAAQ,IAAI,SAAqC,MAAM;AAEzD,QACE,OAAO,IAAI,OAAO,YAClB,OAAO,IAAI,aAAa,YACxB,OAAO,OAAO,mBAAmB,CAAC,SAAS,IAAI,SAA+B,IAC9E,OAAO,IAAI,aAAa,YACxB,OAAO,OAAO,mBAAmB,CAAC,SAAS,IAAI,SAA+B,IAC9E,oBACA,MAAM,QAAQ,IAAI,QAAQ,IAC1B,OAAO,IAAI,UAAU,YACrB,IAAI,UAAU,QACd,OAAO,IAAI,gBAAgB,YAC3B,IAAI,gBAAgB,QACpB,OAAO,IAAI,wBAAwB,YACnC,MAAM,QAAQ,IAAI,iBAAiB;;;;;;;AASvC,SAAgB,qBAAqB,OAA6C;AAChF,KAAI,OAAO,UAAU,SACnB,QAAO;AAGT,QAAO,OAAO,OAAO,mBAAmB,CAAC,SAAS,MAA4B"}
1
+ {"version":3,"file":"typeGuards.js","names":[],"sources":["../../../src/systems/combat/typeGuards.ts"],"sourcesContent":["/**\n * Type guard functions for combat system\n * Provides runtime type checking for better type safety\n */\n\nimport { PlayerArchetype, VitalPointCategory, VitalPointSeverity } from \"../../types/common\";\nimport { VitalPoint } from \"../vitalpoint/types\";\n\n/**\n * Type guard to check if a value is a valid PlayerArchetype\n * @param value - Value to check\n * @returns True if value is a valid PlayerArchetype\n */\nexport function isValidArchetype(value: unknown): value is PlayerArchetype {\n if (typeof value !== \"string\") {\n return false;\n }\n \n return Object.values(PlayerArchetype).includes(value as PlayerArchetype);\n}\n\n/**\n * Type guard to check if a value is a valid VitalPoint\n * @param value - Value to check\n * @returns True if value is a valid VitalPoint\n */\nexport function isVitalPoint(value: unknown): value is VitalPoint {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n \n const obj = value as Record<string, unknown>;\n \n // Validate position structure\n const hasValidPosition = \n obj.position !== undefined &&\n typeof obj.position === \"object\" &&\n obj.position !== null &&\n typeof (obj.position as Record<string, unknown>).x === \"number\" &&\n typeof (obj.position as Record<string, unknown>).y === \"number\";\n \n return (\n typeof obj.id === \"string\" &&\n typeof obj.category === \"string\" &&\n Object.values(VitalPointCategory).includes(obj.category as VitalPointCategory) &&\n typeof obj.severity === \"string\" &&\n Object.values(VitalPointSeverity).includes(obj.severity as VitalPointSeverity) &&\n hasValidPosition &&\n Array.isArray(obj.effects) &&\n typeof obj.names === \"object\" &&\n obj.names !== null &&\n typeof obj.description === \"object\" &&\n obj.description !== null &&\n typeof obj.targetingDifficulty === \"number\" &&\n Array.isArray(obj.effectiveStances)\n );\n}\n\n/**\n * Type guard to check if a value is a valid VitalPointCategory\n * @param value - Value to check\n * @returns True if value is a valid VitalPointCategory\n */\nexport function isVitalPointCategory(value: unknown): value is VitalPointCategory {\n if (typeof value !== \"string\") {\n return false;\n }\n \n return Object.values(VitalPointCategory).includes(value as VitalPointCategory);\n}\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,iBAAiB,OAA0C;CACzE,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,OAAO,OAAO,OAAO,gBAAgB,CAAC,SAAS,MAAyB;;;;;;;AAQ1E,SAAgB,aAAa,OAAqC;CAChE,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;CAGT,MAAM,MAAM;CAGZ,MAAM,mBACJ,IAAI,aAAa,KAAA,KACjB,OAAO,IAAI,aAAa,YACxB,IAAI,aAAa,QACjB,OAAQ,IAAI,SAAqC,MAAM,YACvD,OAAQ,IAAI,SAAqC,MAAM;CAEzD,OACE,OAAO,IAAI,OAAO,YAClB,OAAO,IAAI,aAAa,YACxB,OAAO,OAAO,mBAAmB,CAAC,SAAS,IAAI,SAA+B,IAC9E,OAAO,IAAI,aAAa,YACxB,OAAO,OAAO,mBAAmB,CAAC,SAAS,IAAI,SAA+B,IAC9E,oBACA,MAAM,QAAQ,IAAI,QAAQ,IAC1B,OAAO,IAAI,UAAU,YACrB,IAAI,UAAU,QACd,OAAO,IAAI,gBAAgB,YAC3B,IAAI,gBAAgB,QACpB,OAAO,IAAI,wBAAwB,YACnC,MAAM,QAAQ,IAAI,iBAAiB;;;;;;;AASvC,SAAgB,qBAAqB,OAA6C;CAChF,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,OAAO,OAAO,OAAO,mBAAmB,CAAC,SAAS,MAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"effects.js","names":[],"sources":["../../src/systems/effects.ts"],"sourcesContent":["// Import enums from enums.ts\n\n// Combat effects and status system for Korean martial arts\n\n// Hit effect for visual feedback\n// Hit effect types\n\nexport enum HitEffectType {\n GENERAL_DAMAGE = \"general_damage\",\n CRITICAL_HIT = \"critical_hit\",\n VITAL_POINT_STRIKE = \"vital_point_strike\",\n STATUS_EFFECT = \"status_effect\",\n MISS = \"miss\",\n BLOCK = \"block\",\n PARRY = \"parry\",\n COUNTER = \"counter\",\n HIT = \"hit\",\n}\n\n// Effect types for status effects\nexport enum HitEffectEnum {\n STUN = \"stun\",\n WEAKNESS = \"weakness\",\n STAMINA_DRAIN = \"stamina_drain\",\n VULNERABILITY = \"vulnerability\",\n BLEEDING = \"bleeding\",\n BUFF = \"buff\",\n DEBUFF = \"debuff\",\n PARALYSIS = \"paralysis\",\n POISON = \"poison\",\n BURN = \"burn\",\n FREEZE = \"freeze\",\n CONFUSION = \"confusion\",\n}\n\n// Effect intensity levels\nexport enum EffectIntensity {\n WEAK = \"weak\",\n MINOR = \"minor\",\n LOW = \"low\",\n MEDIUM = \"medium\",\n MODERATE = \"moderate\",\n HIGH = \"high\",\n SEVERE = \"severe\",\n CRITICAL = \"critical\",\n EXTREME = \"extreme\",\n}\n\n// Status effects that can be applied to players\n// Effect types\nexport type EffectType =\n | \"stun\"\n | \"poison\"\n | \"burn\"\n | \"bleed\"\n | \"exhausted\"\n | \"focused\"\n | \"rage\"\n | \"defensive\"\n | \"weakened\"\n | \"strengthened\"\n | \"paralysis\" // Add missing paralysis\n | \"confusion\" // Add missing confusion\n | \"vulnerability\" // Add missing vulnerability\n | \"stamina_drain\"; // Add missing stamina_drain\n\n// Particle effect for visual feedback\n// Particle effect types\nexport type ParticleType =\n | \"spark\"\n | \"blood\"\n | \"energy\"\n | \"dust\"\n | \"flash\"\n | \"smoke\"\n | \"lightning\"\n | \"wind\";\n\n// Environmental effect\n// Environmental effect types\nexport type EnvironmentalEffectType =\n | \"smoke\"\n | \"fire\"\n | \"ice\"\n | \"wind\"\n | \"lightning\"\n | \"darkness\"\n | \"light\"\n | \"pressure\";\n"],"mappings":";AAOA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,oBAAiB;AACjB,eAAA,kBAAe;AACf,eAAA,wBAAqB;AACrB,eAAA,mBAAgB;AAChB,eAAA,UAAO;AACP,eAAA,WAAQ;AACR,eAAA,WAAQ;AACR,eAAA,aAAU;AACV,eAAA,SAAM;;KACP;AAGD,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,UAAO;AACP,eAAA,cAAW;AACX,eAAA,mBAAgB;AAChB,eAAA,mBAAgB;AAChB,eAAA,cAAW;AACX,eAAA,UAAO;AACP,eAAA,YAAS;AACT,eAAA,eAAY;AACZ,eAAA,YAAS;AACT,eAAA,UAAO;AACP,eAAA,YAAS;AACT,eAAA,eAAY;;KACb;AAGD,IAAY,kBAAL,yBAAA,iBAAA;AACL,iBAAA,UAAO;AACP,iBAAA,WAAQ;AACR,iBAAA,SAAM;AACN,iBAAA,YAAS;AACT,iBAAA,cAAW;AACX,iBAAA,UAAO;AACP,iBAAA,YAAS;AACT,iBAAA,cAAW;AACX,iBAAA,aAAU;;KACX"}
1
+ {"version":3,"file":"effects.js","names":[],"sources":["../../src/systems/effects.ts"],"sourcesContent":["// Import enums from enums.ts\n\n// Combat effects and status system for Korean martial arts\n\n// Hit effect for visual feedback\n// Hit effect types\n\nexport enum HitEffectType {\n GENERAL_DAMAGE = \"general_damage\",\n CRITICAL_HIT = \"critical_hit\",\n VITAL_POINT_STRIKE = \"vital_point_strike\",\n STATUS_EFFECT = \"status_effect\",\n MISS = \"miss\",\n BLOCK = \"block\",\n PARRY = \"parry\",\n COUNTER = \"counter\",\n HIT = \"hit\",\n}\n\n// Effect types for status effects\nexport enum HitEffectEnum {\n STUN = \"stun\",\n WEAKNESS = \"weakness\",\n STAMINA_DRAIN = \"stamina_drain\",\n VULNERABILITY = \"vulnerability\",\n BLEEDING = \"bleeding\",\n BUFF = \"buff\",\n DEBUFF = \"debuff\",\n PARALYSIS = \"paralysis\",\n POISON = \"poison\",\n BURN = \"burn\",\n FREEZE = \"freeze\",\n CONFUSION = \"confusion\",\n}\n\n// Effect intensity levels\nexport enum EffectIntensity {\n WEAK = \"weak\",\n MINOR = \"minor\",\n LOW = \"low\",\n MEDIUM = \"medium\",\n MODERATE = \"moderate\",\n HIGH = \"high\",\n SEVERE = \"severe\",\n CRITICAL = \"critical\",\n EXTREME = \"extreme\",\n}\n\n// Status effects that can be applied to players\n// Effect types\nexport type EffectType =\n | \"stun\"\n | \"poison\"\n | \"burn\"\n | \"bleed\"\n | \"exhausted\"\n | \"focused\"\n | \"rage\"\n | \"defensive\"\n | \"weakened\"\n | \"strengthened\"\n | \"paralysis\" // Add missing paralysis\n | \"confusion\" // Add missing confusion\n | \"vulnerability\" // Add missing vulnerability\n | \"stamina_drain\"; // Add missing stamina_drain\n\n// Particle effect for visual feedback\n// Particle effect types\nexport type ParticleType =\n | \"spark\"\n | \"blood\"\n | \"energy\"\n | \"dust\"\n | \"flash\"\n | \"smoke\"\n | \"lightning\"\n | \"wind\";\n\n// Environmental effect\n// Environmental effect types\nexport type EnvironmentalEffectType =\n | \"smoke\"\n | \"fire\"\n | \"ice\"\n | \"wind\"\n | \"lightning\"\n | \"darkness\"\n | \"light\"\n | \"pressure\";\n"],"mappings":";AAOA,IAAY,gBAAL,yBAAA,eAAA;CACL,cAAA,oBAAiB;CACjB,cAAA,kBAAe;CACf,cAAA,wBAAqB;CACrB,cAAA,mBAAgB;CAChB,cAAA,UAAO;CACP,cAAA,WAAQ;CACR,cAAA,WAAQ;CACR,cAAA,aAAU;CACV,cAAA,SAAM;;KACP;AAGD,IAAY,gBAAL,yBAAA,eAAA;CACL,cAAA,UAAO;CACP,cAAA,cAAW;CACX,cAAA,mBAAgB;CAChB,cAAA,mBAAgB;CAChB,cAAA,cAAW;CACX,cAAA,UAAO;CACP,cAAA,YAAS;CACT,cAAA,eAAY;CACZ,cAAA,YAAS;CACT,cAAA,UAAO;CACP,cAAA,YAAS;CACT,cAAA,eAAY;;KACb;AAGD,IAAY,kBAAL,yBAAA,iBAAA;CACL,gBAAA,UAAO;CACP,gBAAA,WAAQ;CACR,gBAAA,SAAM;CACN,gBAAA,YAAS;CACT,gBAAA,cAAW;CACX,gBAAA,UAAO;CACP,gBAAA,YAAS;CACT,gBAAA,cAAW;CACX,gBAAA,aAAU;;KACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"game.js","names":[],"sources":["../../src/systems/game.ts"],"sourcesContent":["// Core game state and flow management\n\nimport { GameMode, KoreanText } from \"@/types\";\nimport type { PlayerState } from \"./player\";\n\n// Match configuration\nexport interface MatchConfig {\n readonly mode: GameMode;\n readonly rounds: number;\n readonly roundDuration: number; // seconds\n readonly player1Archetype: string;\n readonly player2Archetype: string;\n readonly stage: string;\n readonly difficulty?: \"easy\" | \"medium\" | \"hard\" | \"expert\";\n}\n\n// Game event system\nexport interface GameEvent {\n readonly id: string;\n readonly type: GameEventType;\n readonly timestamp: number;\n readonly playerId?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Event data can contain various types depending on event type\n readonly data: Record<string, any>;\n readonly message?: KoreanText;\n}\n\n// Game event types\nexport enum GameEventType {\n GAME_START = \"game_start\",\n ROUND_START = \"round_start\",\n ROUND_END = \"round_end\",\n MATCH_END = \"match_end\",\n PLAYER_ATTACK = \"player_attack\",\n PLAYER_HIT = \"player_hit\",\n STANCE_CHANGE = \"stance_change\",\n TECHNIQUE_EXECUTE = \"technique_execute\",\n VITAL_POINT_HIT = \"vital_point_hit\",\n STATUS_EFFECT = \"status_effect\",\n PAUSE_TOGGLE = \"pause_toggle\",\n ERROR = \"error\",\n}\n\n// Game session interface\nexport interface GameSession {\n readonly id: string;\n readonly gameMode: GameMode;\n readonly players: readonly [PlayerState, PlayerState];\n readonly currentRound: number;\n readonly maxRounds: number;\n readonly roundTimeLimit: number;\n readonly timeRemaining: number;\n readonly isPaused: boolean;\n readonly isGameOver: boolean;\n readonly winner: PlayerState | null;\n}\n\n// Game configuration\nexport interface GameConfig {\n readonly maxHealth: number;\n readonly maxKi: number;\n readonly maxStamina: number;\n readonly roundDuration: number;\n readonly maxRounds: number;\n readonly difficulty: \"beginner\" | \"intermediate\" | \"expert\" | \"master\";\n readonly enableVitalPoints: boolean;\n readonly enableStatusEffects: boolean;\n readonly allowArchetypeSwitching: boolean;\n}\n\n// Game save data\nexport interface GameSaveData {\n readonly version: string;\n readonly playerId: string;\n readonly playerProgress: {\n readonly archetypeExperience: Record<string, number>;\n readonly unlockedTechniques: readonly string[];\n readonly achievements: readonly string[];\n };\n readonly settings: {\n readonly volume: number;\n readonly difficulty: string;\n readonly controls: Record<string, string>;\n };\n readonly statistics: {\n readonly totalMatches: number;\n readonly wins: number;\n readonly losses: number;\n readonly favoriteArchetype: string;\n };\n}\n"],"mappings":";AA4BA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,gBAAa;AACb,eAAA,iBAAc;AACd,eAAA,eAAY;AACZ,eAAA,eAAY;AACZ,eAAA,mBAAgB;AAChB,eAAA,gBAAa;AACb,eAAA,mBAAgB;AAChB,eAAA,uBAAoB;AACpB,eAAA,qBAAkB;AAClB,eAAA,mBAAgB;AAChB,eAAA,kBAAe;AACf,eAAA,WAAQ;;KACT"}
1
+ {"version":3,"file":"game.js","names":[],"sources":["../../src/systems/game.ts"],"sourcesContent":["// Core game state and flow management\n\nimport { GameMode, KoreanText } from \"@/types\";\nimport type { PlayerState } from \"./player\";\n\n// Match configuration\nexport interface MatchConfig {\n readonly mode: GameMode;\n readonly rounds: number;\n readonly roundDuration: number; // seconds\n readonly player1Archetype: string;\n readonly player2Archetype: string;\n readonly stage: string;\n readonly difficulty?: \"easy\" | \"medium\" | \"hard\" | \"expert\";\n}\n\n// Game event system\nexport interface GameEvent {\n readonly id: string;\n readonly type: GameEventType;\n readonly timestamp: number;\n readonly playerId?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Event data can contain various types depending on event type\n readonly data: Record<string, any>;\n readonly message?: KoreanText;\n}\n\n// Game event types\nexport enum GameEventType {\n GAME_START = \"game_start\",\n ROUND_START = \"round_start\",\n ROUND_END = \"round_end\",\n MATCH_END = \"match_end\",\n PLAYER_ATTACK = \"player_attack\",\n PLAYER_HIT = \"player_hit\",\n STANCE_CHANGE = \"stance_change\",\n TECHNIQUE_EXECUTE = \"technique_execute\",\n VITAL_POINT_HIT = \"vital_point_hit\",\n STATUS_EFFECT = \"status_effect\",\n PAUSE_TOGGLE = \"pause_toggle\",\n ERROR = \"error\",\n}\n\n// Game session interface\nexport interface GameSession {\n readonly id: string;\n readonly gameMode: GameMode;\n readonly players: readonly [PlayerState, PlayerState];\n readonly currentRound: number;\n readonly maxRounds: number;\n readonly roundTimeLimit: number;\n readonly timeRemaining: number;\n readonly isPaused: boolean;\n readonly isGameOver: boolean;\n readonly winner: PlayerState | null;\n}\n\n// Game configuration\nexport interface GameConfig {\n readonly maxHealth: number;\n readonly maxKi: number;\n readonly maxStamina: number;\n readonly roundDuration: number;\n readonly maxRounds: number;\n readonly difficulty: \"beginner\" | \"intermediate\" | \"expert\" | \"master\";\n readonly enableVitalPoints: boolean;\n readonly enableStatusEffects: boolean;\n readonly allowArchetypeSwitching: boolean;\n}\n\n// Game save data\nexport interface GameSaveData {\n readonly version: string;\n readonly playerId: string;\n readonly playerProgress: {\n readonly archetypeExperience: Record<string, number>;\n readonly unlockedTechniques: readonly string[];\n readonly achievements: readonly string[];\n };\n readonly settings: {\n readonly volume: number;\n readonly difficulty: string;\n readonly controls: Record<string, string>;\n };\n readonly statistics: {\n readonly totalMatches: number;\n readonly wins: number;\n readonly losses: number;\n readonly favoriteArchetype: string;\n };\n}\n"],"mappings":";AA4BA,IAAY,gBAAL,yBAAA,eAAA;CACL,cAAA,gBAAa;CACb,cAAA,iBAAc;CACd,cAAA,eAAY;CACZ,cAAA,eAAY;CACZ,cAAA,mBAAgB;CAChB,cAAA,gBAAa;CACb,cAAA,mBAAgB;CAChB,cAAA,uBAAoB;CACpB,cAAA,qBAAkB;CAClB,cAAA,mBAAgB;CAChB,cAAA,kBAAe;CACf,cAAA,WAAQ;;KACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"InjuryMovementModifier.js","names":[],"sources":["../../../src/systems/movement/InjuryMovementModifier.ts"],"sourcesContent":["/**\n * Injury-Based Movement Modifier System\n * \n * **Korean**: 손상 기반 이동 시스템 (Injury-Based Movement System)\n * \n * Dynamically calculates movement speed modifiers based on leg injuries, torso\n * damage, stance configuration, and pain levels. Part of the 12 combat realism\n * systems for authentic Korean martial arts gameplay.\n * \n * ## Movement Speed Calculation\n * \n * Base speed is modified by multiple factors:\n * - **Leg Injuries**: 0-100% penalty based on health\n * - **Torso Injuries**: 0-30% minor penalty\n * - **Both Legs Injured**: Additional 20% cumulative penalty\n * - **Stance Modifiers**: -20% (defensive) to +25% (offensive)\n * - **Pain Overload**: -15% when pain ≥ 80\n * \n * ## Injury Severity Thresholds\n * \n * | Leg Health | State | Speed Penalty |\n * |-----------|-------|---------------|\n * | 70-100% | Normal | 0% |\n * | 30-69% | Limping | 0-40% |\n * | 10-29% | Severe Limp | 40-80% |\n * | 0-9% | Critical | 80-100% |\n * \n * @module systems/movement/InjuryMovementModifier\n * @category Movement System\n * @korean 손상기반이동\n */\n\nimport type { TrigramStance } from \"@/types/common\";\nimport type { BodyPartHealth } from \"../bodypart/types\";\nimport { STANCE_SPEED_MODIFIERS } from \"../physics/MovementPhysics\";\n\n/**\n * Configuration for injury-based movement calculations.\n * \n * **Korean**: 손상 이동 설정\n * \n * @public\n * @category Movement System\n */\nexport interface InjuryMovementConfig {\n /** Leg health thresholds for injury states */\n readonly legThresholds: {\n readonly normal: number; // 70% - no penalty\n readonly limping: number; // 30% - start of severe penalty\n readonly critical: number; // 10% - near-maximum penalty\n };\n /** Maximum torso injury penalty (0.3 = 30%) */\n readonly maxTorsoPenalty: number;\n /** Both legs injured cumulative penalty multiplier (0.8 = -20%) */\n readonly bothLegsInjuredMultiplier: number;\n /** Pain overload threshold (80) */\n readonly painOverloadThreshold: number;\n /** Pain overload penalty multiplier (0.85 = -15%) */\n readonly painOverloadMultiplier: number;\n /** Minimum speed multiplier (0.1 = 10% of base speed) */\n readonly minSpeedMultiplier: number;\n}\n\n/**\n * Deep partial type for InjuryMovementConfig to support partial overrides.\n * Allows callers to override individual threshold values without providing all.\n * \n * @public\n * @category Movement System\n */\nexport type PartialInjuryMovementConfig = {\n readonly legThresholds?: Partial<InjuryMovementConfig[\"legThresholds\"]>;\n readonly maxTorsoPenalty?: number;\n readonly bothLegsInjuredMultiplier?: number;\n readonly painOverloadThreshold?: number;\n readonly painOverloadMultiplier?: number;\n readonly minSpeedMultiplier?: number;\n};\n\n/**\n * Default configuration matching acceptance criteria.\n */\nexport const DEFAULT_INJURY_MOVEMENT_CONFIG: InjuryMovementConfig = {\n legThresholds: {\n normal: 70,\n limping: 30,\n critical: 10,\n },\n maxTorsoPenalty: 0.3,\n bothLegsInjuredMultiplier: 0.8,\n painOverloadThreshold: 80,\n painOverloadMultiplier: 0.85,\n minSpeedMultiplier: 0.1,\n} as const;\n\n/**\n * Result of injury-based movement calculation.\n * \n * **Korean**: 손상 이동 결과\n * \n * @public\n * @category Movement System\n */\nexport interface InjuryMovementResult {\n /** Final speed in units/second */\n readonly finalSpeed: number;\n /** Combined speed multiplier (0.1-1.0+) */\n readonly speedMultiplier: number;\n /** Breakdown of penalty sources */\n readonly penalties: {\n readonly leftLegPenalty: number;\n readonly rightLegPenalty: number;\n readonly torsoPenalty: number;\n readonly bothLegsInjured: boolean;\n readonly stanceModifier: number;\n readonly painOverload: boolean;\n };\n /** Whether player is limping */\n readonly isLimping: boolean;\n /** Whether player has severe limp */\n readonly isSevereLimp: boolean;\n /** Bilingual status text */\n readonly statusText: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\n/**\n * Injury-Based Movement Modifier System.\n * \n * **Korean**: 손상 기반 이동 수정자 시스템\n * \n * Calculates dynamic movement speed based on injuries, stance, and pain.\n * Integrates with MovementPhysics to apply realistic combat trauma effects.\n * \n * @example\n * ```typescript\n * const modifier = new InjuryMovementModifier();\n * \n * const result = modifier.calculateMovementSpeed(\n * 5.0, // base speed\n * bodyPartHealth, // current health\n * TrigramStance.GEON, // current stance\n * 65 // pain level\n * );\n * \n * console.log(`Speed: ${result.finalSpeed} m/s`);\n * console.log(`Status: ${result.statusText.korean} | ${result.statusText.english}`);\n * ```\n * \n * @public\n * @category Movement System\n * @korean 손상기반이동수정자\n */\nexport class InjuryMovementModifier {\n private readonly config: InjuryMovementConfig;\n\n /**\n * Threshold for determining if both legs are significantly injured.\n * Uses epsilon to avoid floating-point rounding issues at boundary.\n * \n * @private\n */\n private static readonly BOTH_LEGS_THRESHOLD = 0.3 + 1e-10;\n\n /**\n * Creates a new InjuryMovementModifier with optional configuration.\n * \n * @param config - Optional configuration overrides (supports partial threshold overrides)\n */\n constructor(config?: PartialInjuryMovementConfig) {\n // Deep merge leg thresholds to prevent partial overrides from breaking the config.\n // Use ?? to avoid undefined values overwriting defaults when exactOptionalPropertyTypes is disabled.\n const mergedLegThresholds = {\n normal:\n config?.legThresholds?.normal ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.legThresholds.normal,\n limping:\n config?.legThresholds?.limping ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.legThresholds.limping,\n critical:\n config?.legThresholds?.critical ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.legThresholds.critical,\n };\n\n const mergedConfig: InjuryMovementConfig = {\n legThresholds: mergedLegThresholds,\n maxTorsoPenalty:\n config?.maxTorsoPenalty ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.maxTorsoPenalty,\n bothLegsInjuredMultiplier:\n config?.bothLegsInjuredMultiplier ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.bothLegsInjuredMultiplier,\n painOverloadThreshold:\n config?.painOverloadThreshold ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.painOverloadThreshold,\n painOverloadMultiplier:\n config?.painOverloadMultiplier ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.painOverloadMultiplier,\n minSpeedMultiplier:\n config?.minSpeedMultiplier ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.minSpeedMultiplier,\n };\n\n // Development-mode sanity check: ensure thresholds are finite and ordered\n if (process.env.NODE_ENV !== \"production\") {\n const { normal, limping, critical } = mergedConfig.legThresholds;\n const allFinite =\n Number.isFinite(normal) &&\n Number.isFinite(limping) &&\n Number.isFinite(critical);\n const ordered = normal >= limping && limping >= critical;\n\n if (!allFinite || !ordered) {\n // Non-throwing warning to avoid breaking existing behavior\n console.warn(\n \"[InjuryMovementModifier] Invalid legThresholds configuration detected:\",\n mergedConfig.legThresholds\n );\n }\n }\n\n this.config = mergedConfig;\n }\n\n /**\n * Calculate modified movement speed based on all injury factors.\n * \n * **Korean**: 이동 속도 계산\n * \n * Applies penalties from:\n * 1. Leg injuries (primary factor)\n * 2. Torso injuries (minor factor)\n * 3. Both legs injured (cumulative penalty)\n * 4. Stance modifiers\n * 5. Pain overload\n * \n * @param baseSpeed - Base movement speed (m/s)\n * @param bodyPartHealth - Current body part health values\n * @param stance - Current trigram stance\n * @param painLevel - Current pain level (0-100)\n * @returns Complete movement calculation result\n * \n * @public\n * @korean 이동속도계산\n */\n public calculateMovementSpeed(\n baseSpeed: number,\n bodyPartHealth: BodyPartHealth,\n stance: TrigramStance,\n painLevel: number\n ): InjuryMovementResult {\n // Calculate individual leg penalties\n const leftLegPenalty = this.calculateLegPenalty(bodyPartHealth.legLeft);\n const rightLegPenalty = this.calculateLegPenalty(bodyPartHealth.legRight);\n\n // Use worst leg penalty as base\n const baseLegPenalty = Math.max(leftLegPenalty, rightLegPenalty);\n let speedMultiplier = 1.0 - baseLegPenalty;\n\n // Check if both legs are significantly injured using centralized method\n const bothLegsInjured = this.areBothLegsInjured(leftLegPenalty, rightLegPenalty);\n if (bothLegsInjured) {\n // Additional 20% penalty when both legs injured\n speedMultiplier *= this.config.bothLegsInjuredMultiplier;\n }\n\n // Calculate torso penalty (minor effect, 0-30% max)\n const avgTorsoHealth = (bodyPartHealth.torsoUpper + bodyPartHealth.torsoLower) / 2;\n // Clamp torso health to prevent negative penalties or exceeding max penalty\n const clampedAvgTorsoHealth = Math.min(100, Math.max(0, avgTorsoHealth));\n const torsoPenalty = ((100 - clampedAvgTorsoHealth) / 100) * this.config.maxTorsoPenalty;\n speedMultiplier *= (1 - torsoPenalty);\n\n // Apply stance modifier\n const stanceModifier = this.getStanceSpeedModifier(stance);\n speedMultiplier *= stanceModifier;\n\n // Apply pain overload penalty if applicable (>= threshold, not just >)\n const painOverload = painLevel >= this.config.painOverloadThreshold;\n if (painOverload) {\n speedMultiplier *= this.config.painOverloadMultiplier; // -15%\n }\n\n // Clamp to minimum speed (10% of base)\n speedMultiplier = Math.max(speedMultiplier, this.config.minSpeedMultiplier);\n\n // Calculate final speed\n const finalSpeed = baseSpeed * speedMultiplier;\n\n // Determine injury state for status text based on worst leg health\n // This ensures status matches the actual movement penalty (which uses worst leg)\n // Note: isLimping = true when leg health is in range [30-70) (limping range with 0-40% penalty)\n // isSevereLimp = true when leg health is < 30 (severe/critical with 40-100% penalty)\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n const isLimping =\n worstLegHealth < this.config.legThresholds.normal &&\n worstLegHealth >= this.config.legThresholds.limping;\n const isSevereLimp = worstLegHealth < this.config.legThresholds.limping;\n\n // Generate status text\n const statusText = this.generateStatusText(worstLegHealth, bothLegsInjured, painOverload);\n\n return {\n finalSpeed,\n speedMultiplier,\n penalties: {\n leftLegPenalty,\n rightLegPenalty,\n torsoPenalty,\n bothLegsInjured,\n stanceModifier,\n painOverload,\n },\n isLimping,\n isSevereLimp,\n statusText,\n };\n }\n\n /**\n * Calculate movement penalty from leg injury severity.\n * \n * **Korean**: 다리 부상 페널티 계산\n * \n * Progressive penalty scaling:\n * - 100-70%: No penalty (0%)\n * - 70-30%: Linear scaling (0-40%)\n * - 30-10%: Accelerated scaling (40-80%)\n * - 10-0%: Critical scaling (80-100%)\n * \n * @param legHealth - Leg health (0-100)\n * @returns Penalty factor (0.0-1.0), clamped to valid range\n * \n * @private\n */\n private calculateLegPenalty(legHealth: number): number {\n const { normal, limping, critical } = this.config.legThresholds;\n\n // Clamp input health to valid range to prevent runaway penalties\n const clampedHealth = Math.min(Math.max(legHealth, 0), 100);\n\n let penalty: number;\n\n if (clampedHealth >= normal) {\n // 70-100%: No penalty\n penalty = 0;\n } else if (clampedHealth >= limping) {\n // 30-70%: 0-40% penalty (linear)\n const healthRange = normal - limping;\n // Guard against division by zero if thresholds are misconfigured\n const healthFactor = healthRange > 0 \n ? (normal - clampedHealth) / healthRange \n : 0;\n penalty = healthFactor * 0.4;\n } else if (clampedHealth >= critical) {\n // 10-30%: 40-80% penalty (accelerated)\n const healthRange = limping - critical;\n // Guard against division by zero if thresholds are misconfigured\n const healthFactor = healthRange > 0 \n ? (limping - clampedHealth) / healthRange \n : 0;\n penalty = 0.4 + (healthFactor * 0.4);\n } else {\n // 0-10%: 80-100% penalty (critical)\n // Guard against division by zero if critical is 0\n const healthFactor = critical > 0 \n ? (critical - clampedHealth) / critical \n : 1;\n penalty = 0.8 + (healthFactor * 0.2);\n }\n\n // Ensure returned penalty is always within [0, 1]\n return Math.min(Math.max(penalty, 0), 1);\n }\n\n /**\n * Checks if both legs are significantly injured (>30% penalty each).\n * Uses epsilon-based comparison for floating-point robustness.\n * \n * @param leftLegPenalty - Penalty for left leg (0-1)\n * @param rightLegPenalty - Penalty for right leg (0-1)\n * @returns True if both legs are significantly injured\n * @private\n */\n private areBothLegsInjured(leftLegPenalty: number, rightLegPenalty: number): boolean {\n return leftLegPenalty >= InjuryMovementModifier.BOTH_LEGS_THRESHOLD && \n rightLegPenalty >= InjuryMovementModifier.BOTH_LEGS_THRESHOLD;\n }\n\n /**\n * Get stance-based speed modifier.\n * \n * **Korean**: 자세 속도 배수 가져오기\n * \n * @param stance - Current trigram stance\n * @returns Speed multiplier (0.8-1.25), defaults to 1.0 for unknown stances\n * \n * @public\n */\n public getStanceSpeedModifier(stance: TrigramStance): number {\n const modifier = STANCE_SPEED_MODIFIERS[stance];\n\n // Defensive fallback for unknown stances (e.g., test/invalid values)\n if (modifier == null) {\n // Runtime-safe environment check (works in both Node and browser/Vite environments)\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"development\"\n ) {\n console.warn(\n `[InjuryMovementModifier] Unknown stance \"${String(\n stance\n )}\" received in getStanceSpeedModifier; defaulting to 1.0.`\n );\n }\n return 1.0;\n }\n\n return modifier;\n }\n\n /**\n * Generate bilingual status text based on injury state.\n * \n * **Korean**: 상태 텍스트 생성\n * \n * @param legHealthForStatus - Leg health percentage used for status determination (typically worst leg)\n * @param bothLegsInjured - Whether both legs are significantly injured\n * @param painOverload - Whether pain is over threshold\n * @returns Bilingual status text\n * \n * @private\n */\n private generateStatusText(\n legHealthForStatus: number,\n bothLegsInjured: boolean,\n painOverload: boolean\n ): { korean: string; english: string } {\n const { normal, limping, critical } = this.config.legThresholds;\n\n if (legHealthForStatus < critical) {\n const painText = painOverload ? \" | 고통 과부하\" : \"\";\n const painTextEn = painOverload ? \" | Pain Overload\" : \"\";\n const baseKorean = bothLegsInjured ? \"심각한 부상 | 양 다리\" : \"심각한 부상\";\n const baseEnglish = bothLegsInjured ? \"Critical Injury | Both Legs\" : \"Critical Injury\";\n return {\n korean: `${baseKorean}${painText}`,\n english: `${baseEnglish}${painTextEn}`,\n };\n } else if (legHealthForStatus < limping) {\n const painText = painOverload ? \" | 고통 과부하\" : \"\";\n const painTextEn = painOverload ? \" | Pain Overload\" : \"\";\n const baseKorean = bothLegsInjured ? \"중증 절름거림 | 양 다리\" : \"중증 절름거림\";\n const baseEnglish = bothLegsInjured ? \"Severe Limping | Both Legs\" : \"Severe Limping\";\n return {\n korean: `${baseKorean}${painText}`,\n english: `${baseEnglish}${painTextEn}`,\n };\n } else if (legHealthForStatus < normal) {\n const statusKrParts = [\"절름거림\"];\n const statusEnParts = [\"Limping\"];\n\n if (bothLegsInjured) {\n statusKrParts.push(\"양 다리\");\n statusEnParts.push(\"Both Legs\");\n }\n\n if (painOverload) {\n statusKrParts.push(\"고통 과부하\");\n statusEnParts.push(\"Pain Overload\");\n }\n\n return {\n korean: statusKrParts.join(\" | \"),\n english: statusEnParts.join(\" | \"),\n };\n } else if (painOverload) {\n return {\n korean: \"고통 과부하\",\n english: \"Pain Overload\",\n };\n } else {\n return {\n korean: \"정상\",\n english: \"Normal\",\n };\n }\n }\n\n /**\n * Check if player should display limping animation.\n * \n * **Korean**: 절름거림 확인\n * \n * Uses worst leg health to match movement penalty behavior.\n * \n * @param bodyPartHealth - Current body part health\n * @returns True if limping animation should play\n * \n * @public\n */\n public shouldLimp(bodyPartHealth: BodyPartHealth): boolean {\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n return worstLegHealth < this.config.legThresholds.normal;\n }\n\n /**\n * Check if player has severe limp.\n * \n * **Korean**: 중증 절름거림 확인\n * \n * Uses worst leg health to match movement penalty behavior.\n * \n * @param bodyPartHealth - Current body part health\n * @returns True if severe limp (leg health < 30%)\n * \n * @public\n */\n public hasSevereLimp(bodyPartHealth: BodyPartHealth): boolean {\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n return worstLegHealth < this.config.legThresholds.limping;\n }\n\n /**\n * Get current injury state description.\n * \n * **Korean**: 부상 상태 설명\n * \n * Uses worst leg health to match movement penalty behavior.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Bilingual injury description\n * \n * @public\n */\n public getInjuryDescription(bodyPartHealth: BodyPartHealth): {\n korean: string;\n english: string;\n } {\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n const leftLegPenalty = this.calculateLegPenalty(bodyPartHealth.legLeft);\n const rightLegPenalty = this.calculateLegPenalty(bodyPartHealth.legRight);\n const bothLegsInjured = this.areBothLegsInjured(leftLegPenalty, rightLegPenalty);\n\n return this.generateStatusText(worstLegHealth, bothLegsInjured, false);\n }\n}\n\n/**\n * Singleton instance for global access.\n * \n * @public\n */\nexport const injuryMovementModifier = new InjuryMovementModifier();\n"],"mappings":"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"}
1
+ {"version":3,"file":"InjuryMovementModifier.js","names":[],"sources":["../../../src/systems/movement/InjuryMovementModifier.ts"],"sourcesContent":["/**\n * Injury-Based Movement Modifier System\n * \n * **Korean**: 손상 기반 이동 시스템 (Injury-Based Movement System)\n * \n * Dynamically calculates movement speed modifiers based on leg injuries, torso\n * damage, stance configuration, and pain levels. Part of the 12 combat realism\n * systems for authentic Korean martial arts gameplay.\n * \n * ## Movement Speed Calculation\n * \n * Base speed is modified by multiple factors:\n * - **Leg Injuries**: 0-100% penalty based on health\n * - **Torso Injuries**: 0-30% minor penalty\n * - **Both Legs Injured**: Additional 20% cumulative penalty\n * - **Stance Modifiers**: -20% (defensive) to +25% (offensive)\n * - **Pain Overload**: -15% when pain ≥ 80\n * \n * ## Injury Severity Thresholds\n * \n * | Leg Health | State | Speed Penalty |\n * |-----------|-------|---------------|\n * | 70-100% | Normal | 0% |\n * | 30-69% | Limping | 0-40% |\n * | 10-29% | Severe Limp | 40-80% |\n * | 0-9% | Critical | 80-100% |\n * \n * @module systems/movement/InjuryMovementModifier\n * @category Movement System\n * @korean 손상기반이동\n */\n\nimport type { TrigramStance } from \"@/types/common\";\nimport type { BodyPartHealth } from \"../bodypart/types\";\nimport { STANCE_SPEED_MODIFIERS } from \"../physics/MovementPhysics\";\n\n/**\n * Configuration for injury-based movement calculations.\n * \n * **Korean**: 손상 이동 설정\n * \n * @public\n * @category Movement System\n */\nexport interface InjuryMovementConfig {\n /** Leg health thresholds for injury states */\n readonly legThresholds: {\n readonly normal: number; // 70% - no penalty\n readonly limping: number; // 30% - start of severe penalty\n readonly critical: number; // 10% - near-maximum penalty\n };\n /** Maximum torso injury penalty (0.3 = 30%) */\n readonly maxTorsoPenalty: number;\n /** Both legs injured cumulative penalty multiplier (0.8 = -20%) */\n readonly bothLegsInjuredMultiplier: number;\n /** Pain overload threshold (80) */\n readonly painOverloadThreshold: number;\n /** Pain overload penalty multiplier (0.85 = -15%) */\n readonly painOverloadMultiplier: number;\n /** Minimum speed multiplier (0.1 = 10% of base speed) */\n readonly minSpeedMultiplier: number;\n}\n\n/**\n * Deep partial type for InjuryMovementConfig to support partial overrides.\n * Allows callers to override individual threshold values without providing all.\n * \n * @public\n * @category Movement System\n */\nexport type PartialInjuryMovementConfig = {\n readonly legThresholds?: Partial<InjuryMovementConfig[\"legThresholds\"]>;\n readonly maxTorsoPenalty?: number;\n readonly bothLegsInjuredMultiplier?: number;\n readonly painOverloadThreshold?: number;\n readonly painOverloadMultiplier?: number;\n readonly minSpeedMultiplier?: number;\n};\n\n/**\n * Default configuration matching acceptance criteria.\n */\nexport const DEFAULT_INJURY_MOVEMENT_CONFIG: InjuryMovementConfig = {\n legThresholds: {\n normal: 70,\n limping: 30,\n critical: 10,\n },\n maxTorsoPenalty: 0.3,\n bothLegsInjuredMultiplier: 0.8,\n painOverloadThreshold: 80,\n painOverloadMultiplier: 0.85,\n minSpeedMultiplier: 0.1,\n} as const;\n\n/**\n * Result of injury-based movement calculation.\n * \n * **Korean**: 손상 이동 결과\n * \n * @public\n * @category Movement System\n */\nexport interface InjuryMovementResult {\n /** Final speed in units/second */\n readonly finalSpeed: number;\n /** Combined speed multiplier (0.1-1.0+) */\n readonly speedMultiplier: number;\n /** Breakdown of penalty sources */\n readonly penalties: {\n readonly leftLegPenalty: number;\n readonly rightLegPenalty: number;\n readonly torsoPenalty: number;\n readonly bothLegsInjured: boolean;\n readonly stanceModifier: number;\n readonly painOverload: boolean;\n };\n /** Whether player is limping */\n readonly isLimping: boolean;\n /** Whether player has severe limp */\n readonly isSevereLimp: boolean;\n /** Bilingual status text */\n readonly statusText: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\n/**\n * Injury-Based Movement Modifier System.\n * \n * **Korean**: 손상 기반 이동 수정자 시스템\n * \n * Calculates dynamic movement speed based on injuries, stance, and pain.\n * Integrates with MovementPhysics to apply realistic combat trauma effects.\n * \n * @example\n * ```typescript\n * const modifier = new InjuryMovementModifier();\n * \n * const result = modifier.calculateMovementSpeed(\n * 5.0, // base speed\n * bodyPartHealth, // current health\n * TrigramStance.GEON, // current stance\n * 65 // pain level\n * );\n * \n * console.log(`Speed: ${result.finalSpeed} m/s`);\n * console.log(`Status: ${result.statusText.korean} | ${result.statusText.english}`);\n * ```\n * \n * @public\n * @category Movement System\n * @korean 손상기반이동수정자\n */\nexport class InjuryMovementModifier {\n private readonly config: InjuryMovementConfig;\n\n /**\n * Threshold for determining if both legs are significantly injured.\n * Uses epsilon to avoid floating-point rounding issues at boundary.\n * \n * @private\n */\n private static readonly BOTH_LEGS_THRESHOLD = 0.3 + 1e-10;\n\n /**\n * Creates a new InjuryMovementModifier with optional configuration.\n * \n * @param config - Optional configuration overrides (supports partial threshold overrides)\n */\n constructor(config?: PartialInjuryMovementConfig) {\n // Deep merge leg thresholds to prevent partial overrides from breaking the config.\n // Use ?? to avoid undefined values overwriting defaults when exactOptionalPropertyTypes is disabled.\n const mergedLegThresholds = {\n normal:\n config?.legThresholds?.normal ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.legThresholds.normal,\n limping:\n config?.legThresholds?.limping ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.legThresholds.limping,\n critical:\n config?.legThresholds?.critical ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.legThresholds.critical,\n };\n\n const mergedConfig: InjuryMovementConfig = {\n legThresholds: mergedLegThresholds,\n maxTorsoPenalty:\n config?.maxTorsoPenalty ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.maxTorsoPenalty,\n bothLegsInjuredMultiplier:\n config?.bothLegsInjuredMultiplier ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.bothLegsInjuredMultiplier,\n painOverloadThreshold:\n config?.painOverloadThreshold ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.painOverloadThreshold,\n painOverloadMultiplier:\n config?.painOverloadMultiplier ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.painOverloadMultiplier,\n minSpeedMultiplier:\n config?.minSpeedMultiplier ??\n DEFAULT_INJURY_MOVEMENT_CONFIG.minSpeedMultiplier,\n };\n\n // Development-mode sanity check: ensure thresholds are finite and ordered\n if (process.env.NODE_ENV !== \"production\") {\n const { normal, limping, critical } = mergedConfig.legThresholds;\n const allFinite =\n Number.isFinite(normal) &&\n Number.isFinite(limping) &&\n Number.isFinite(critical);\n const ordered = normal >= limping && limping >= critical;\n\n if (!allFinite || !ordered) {\n // Non-throwing warning to avoid breaking existing behavior\n console.warn(\n \"[InjuryMovementModifier] Invalid legThresholds configuration detected:\",\n mergedConfig.legThresholds\n );\n }\n }\n\n this.config = mergedConfig;\n }\n\n /**\n * Calculate modified movement speed based on all injury factors.\n * \n * **Korean**: 이동 속도 계산\n * \n * Applies penalties from:\n * 1. Leg injuries (primary factor)\n * 2. Torso injuries (minor factor)\n * 3. Both legs injured (cumulative penalty)\n * 4. Stance modifiers\n * 5. Pain overload\n * \n * @param baseSpeed - Base movement speed (m/s)\n * @param bodyPartHealth - Current body part health values\n * @param stance - Current trigram stance\n * @param painLevel - Current pain level (0-100)\n * @returns Complete movement calculation result\n * \n * @public\n * @korean 이동속도계산\n */\n public calculateMovementSpeed(\n baseSpeed: number,\n bodyPartHealth: BodyPartHealth,\n stance: TrigramStance,\n painLevel: number\n ): InjuryMovementResult {\n // Calculate individual leg penalties\n const leftLegPenalty = this.calculateLegPenalty(bodyPartHealth.legLeft);\n const rightLegPenalty = this.calculateLegPenalty(bodyPartHealth.legRight);\n\n // Use worst leg penalty as base\n const baseLegPenalty = Math.max(leftLegPenalty, rightLegPenalty);\n let speedMultiplier = 1.0 - baseLegPenalty;\n\n // Check if both legs are significantly injured using centralized method\n const bothLegsInjured = this.areBothLegsInjured(leftLegPenalty, rightLegPenalty);\n if (bothLegsInjured) {\n // Additional 20% penalty when both legs injured\n speedMultiplier *= this.config.bothLegsInjuredMultiplier;\n }\n\n // Calculate torso penalty (minor effect, 0-30% max)\n const avgTorsoHealth = (bodyPartHealth.torsoUpper + bodyPartHealth.torsoLower) / 2;\n // Clamp torso health to prevent negative penalties or exceeding max penalty\n const clampedAvgTorsoHealth = Math.min(100, Math.max(0, avgTorsoHealth));\n const torsoPenalty = ((100 - clampedAvgTorsoHealth) / 100) * this.config.maxTorsoPenalty;\n speedMultiplier *= (1 - torsoPenalty);\n\n // Apply stance modifier\n const stanceModifier = this.getStanceSpeedModifier(stance);\n speedMultiplier *= stanceModifier;\n\n // Apply pain overload penalty if applicable (>= threshold, not just >)\n const painOverload = painLevel >= this.config.painOverloadThreshold;\n if (painOverload) {\n speedMultiplier *= this.config.painOverloadMultiplier; // -15%\n }\n\n // Clamp to minimum speed (10% of base)\n speedMultiplier = Math.max(speedMultiplier, this.config.minSpeedMultiplier);\n\n // Calculate final speed\n const finalSpeed = baseSpeed * speedMultiplier;\n\n // Determine injury state for status text based on worst leg health\n // This ensures status matches the actual movement penalty (which uses worst leg)\n // Note: isLimping = true when leg health is in range [30-70) (limping range with 0-40% penalty)\n // isSevereLimp = true when leg health is < 30 (severe/critical with 40-100% penalty)\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n const isLimping =\n worstLegHealth < this.config.legThresholds.normal &&\n worstLegHealth >= this.config.legThresholds.limping;\n const isSevereLimp = worstLegHealth < this.config.legThresholds.limping;\n\n // Generate status text\n const statusText = this.generateStatusText(worstLegHealth, bothLegsInjured, painOverload);\n\n return {\n finalSpeed,\n speedMultiplier,\n penalties: {\n leftLegPenalty,\n rightLegPenalty,\n torsoPenalty,\n bothLegsInjured,\n stanceModifier,\n painOverload,\n },\n isLimping,\n isSevereLimp,\n statusText,\n };\n }\n\n /**\n * Calculate movement penalty from leg injury severity.\n * \n * **Korean**: 다리 부상 페널티 계산\n * \n * Progressive penalty scaling:\n * - 100-70%: No penalty (0%)\n * - 70-30%: Linear scaling (0-40%)\n * - 30-10%: Accelerated scaling (40-80%)\n * - 10-0%: Critical scaling (80-100%)\n * \n * @param legHealth - Leg health (0-100)\n * @returns Penalty factor (0.0-1.0), clamped to valid range\n * \n * @private\n */\n private calculateLegPenalty(legHealth: number): number {\n const { normal, limping, critical } = this.config.legThresholds;\n\n // Clamp input health to valid range to prevent runaway penalties\n const clampedHealth = Math.min(Math.max(legHealth, 0), 100);\n\n let penalty: number;\n\n if (clampedHealth >= normal) {\n // 70-100%: No penalty\n penalty = 0;\n } else if (clampedHealth >= limping) {\n // 30-70%: 0-40% penalty (linear)\n const healthRange = normal - limping;\n // Guard against division by zero if thresholds are misconfigured\n const healthFactor = healthRange > 0 \n ? (normal - clampedHealth) / healthRange \n : 0;\n penalty = healthFactor * 0.4;\n } else if (clampedHealth >= critical) {\n // 10-30%: 40-80% penalty (accelerated)\n const healthRange = limping - critical;\n // Guard against division by zero if thresholds are misconfigured\n const healthFactor = healthRange > 0 \n ? (limping - clampedHealth) / healthRange \n : 0;\n penalty = 0.4 + (healthFactor * 0.4);\n } else {\n // 0-10%: 80-100% penalty (critical)\n // Guard against division by zero if critical is 0\n const healthFactor = critical > 0 \n ? (critical - clampedHealth) / critical \n : 1;\n penalty = 0.8 + (healthFactor * 0.2);\n }\n\n // Ensure returned penalty is always within [0, 1]\n return Math.min(Math.max(penalty, 0), 1);\n }\n\n /**\n * Checks if both legs are significantly injured (>30% penalty each).\n * Uses epsilon-based comparison for floating-point robustness.\n * \n * @param leftLegPenalty - Penalty for left leg (0-1)\n * @param rightLegPenalty - Penalty for right leg (0-1)\n * @returns True if both legs are significantly injured\n * @private\n */\n private areBothLegsInjured(leftLegPenalty: number, rightLegPenalty: number): boolean {\n return leftLegPenalty >= InjuryMovementModifier.BOTH_LEGS_THRESHOLD && \n rightLegPenalty >= InjuryMovementModifier.BOTH_LEGS_THRESHOLD;\n }\n\n /**\n * Get stance-based speed modifier.\n * \n * **Korean**: 자세 속도 배수 가져오기\n * \n * @param stance - Current trigram stance\n * @returns Speed multiplier (0.8-1.25), defaults to 1.0 for unknown stances\n * \n * @public\n */\n public getStanceSpeedModifier(stance: TrigramStance): number {\n const modifier = STANCE_SPEED_MODIFIERS[stance];\n\n // Defensive fallback for unknown stances (e.g., test/invalid values)\n if (modifier == null) {\n // Runtime-safe environment check (works in both Node and browser/Vite environments)\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"development\"\n ) {\n console.warn(\n `[InjuryMovementModifier] Unknown stance \"${String(\n stance\n )}\" received in getStanceSpeedModifier; defaulting to 1.0.`\n );\n }\n return 1.0;\n }\n\n return modifier;\n }\n\n /**\n * Generate bilingual status text based on injury state.\n * \n * **Korean**: 상태 텍스트 생성\n * \n * @param legHealthForStatus - Leg health percentage used for status determination (typically worst leg)\n * @param bothLegsInjured - Whether both legs are significantly injured\n * @param painOverload - Whether pain is over threshold\n * @returns Bilingual status text\n * \n * @private\n */\n private generateStatusText(\n legHealthForStatus: number,\n bothLegsInjured: boolean,\n painOverload: boolean\n ): { korean: string; english: string } {\n const { normal, limping, critical } = this.config.legThresholds;\n\n if (legHealthForStatus < critical) {\n const painText = painOverload ? \" | 고통 과부하\" : \"\";\n const painTextEn = painOverload ? \" | Pain Overload\" : \"\";\n const baseKorean = bothLegsInjured ? \"심각한 부상 | 양 다리\" : \"심각한 부상\";\n const baseEnglish = bothLegsInjured ? \"Critical Injury | Both Legs\" : \"Critical Injury\";\n return {\n korean: `${baseKorean}${painText}`,\n english: `${baseEnglish}${painTextEn}`,\n };\n } else if (legHealthForStatus < limping) {\n const painText = painOverload ? \" | 고통 과부하\" : \"\";\n const painTextEn = painOverload ? \" | Pain Overload\" : \"\";\n const baseKorean = bothLegsInjured ? \"중증 절름거림 | 양 다리\" : \"중증 절름거림\";\n const baseEnglish = bothLegsInjured ? \"Severe Limping | Both Legs\" : \"Severe Limping\";\n return {\n korean: `${baseKorean}${painText}`,\n english: `${baseEnglish}${painTextEn}`,\n };\n } else if (legHealthForStatus < normal) {\n const statusKrParts = [\"절름거림\"];\n const statusEnParts = [\"Limping\"];\n\n if (bothLegsInjured) {\n statusKrParts.push(\"양 다리\");\n statusEnParts.push(\"Both Legs\");\n }\n\n if (painOverload) {\n statusKrParts.push(\"고통 과부하\");\n statusEnParts.push(\"Pain Overload\");\n }\n\n return {\n korean: statusKrParts.join(\" | \"),\n english: statusEnParts.join(\" | \"),\n };\n } else if (painOverload) {\n return {\n korean: \"고통 과부하\",\n english: \"Pain Overload\",\n };\n } else {\n return {\n korean: \"정상\",\n english: \"Normal\",\n };\n }\n }\n\n /**\n * Check if player should display limping animation.\n * \n * **Korean**: 절름거림 확인\n * \n * Uses worst leg health to match movement penalty behavior.\n * \n * @param bodyPartHealth - Current body part health\n * @returns True if limping animation should play\n * \n * @public\n */\n public shouldLimp(bodyPartHealth: BodyPartHealth): boolean {\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n return worstLegHealth < this.config.legThresholds.normal;\n }\n\n /**\n * Check if player has severe limp.\n * \n * **Korean**: 중증 절름거림 확인\n * \n * Uses worst leg health to match movement penalty behavior.\n * \n * @param bodyPartHealth - Current body part health\n * @returns True if severe limp (leg health < 30%)\n * \n * @public\n */\n public hasSevereLimp(bodyPartHealth: BodyPartHealth): boolean {\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n return worstLegHealth < this.config.legThresholds.limping;\n }\n\n /**\n * Get current injury state description.\n * \n * **Korean**: 부상 상태 설명\n * \n * Uses worst leg health to match movement penalty behavior.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Bilingual injury description\n * \n * @public\n */\n public getInjuryDescription(bodyPartHealth: BodyPartHealth): {\n korean: string;\n english: string;\n } {\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n const leftLegPenalty = this.calculateLegPenalty(bodyPartHealth.legLeft);\n const rightLegPenalty = this.calculateLegPenalty(bodyPartHealth.legRight);\n const bothLegsInjured = this.areBothLegsInjured(leftLegPenalty, rightLegPenalty);\n\n return this.generateStatusText(worstLegHealth, bothLegsInjured, false);\n }\n}\n\n/**\n * Singleton instance for global access.\n * \n * @public\n */\nexport const injuryMovementModifier = new InjuryMovementModifier();\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AccelerationUpdater.js","names":[],"sources":["../../../../src/systems/movement/helpers/AccelerationUpdater.tsx"],"sourcesContent":["/**\n * AccelerationUpdater - Component that updates movement acceleration at 60fps\n *\n * Uses useFrame to track continuous movement time and calculate acceleration-based speed.\n * Throttles state updates to only call onSpeedUpdate when speed changes meaningfully.\n * This component only updates movement state and renders no visual elements.\n *\n * @module systems/movement/helpers/AccelerationUpdater\n * @category Movement\n * @korean 가속업데이터\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useRef } from \"react\";\nimport {\n ACCELERATION_CONSTANTS,\n calculateAcceleratedSpeed,\n isDirectionConsistent,\n isSpeedChangeMeaningful,\n} from \"./accelerationUtils\";\n\n/**\n * Props for AccelerationUpdater component\n */\nexport interface AccelerationUpdaterProps {\n /** Whether player is currently moving */\n readonly isMoving: boolean;\n /** Current velocity vector */\n readonly velocity: { x: number; y: number } | undefined;\n /** Ref to track accumulated movement time */\n readonly movementTimeRef: React.MutableRefObject<number>;\n /** Ref to track last movement direction */\n readonly lastDirectionRef: React.MutableRefObject<{ x: number; y: number }>;\n /** Callback to update calculated speed - only called on meaningful changes */\n readonly onSpeedUpdate: (speed: number) => void;\n /** Walking speed in m/s (from archetype or default) */\n readonly walkSpeed?: number;\n /** Running speed in m/s (from archetype or default) */\n readonly runSpeed?: number;\n}\n\n/**\n * AccelerationUpdater Component\n *\n * Updates movement acceleration at 60fps using Three.js useFrame hook.\n * Tracks continuous movement time and calculates speed based on direction consistency.\n * Only calls onSpeedUpdate when speed changes by more than epsilon AND sufficient time\n * has passed (100ms throttle), preventing excessive React re-renders at frame rate.\n *\n * @example\n * ```tsx\n * <AccelerationUpdater\n * isMoving={isMoving}\n * velocity={velocity}\n * movementTimeRef={movementTimeRef}\n * lastDirectionRef={lastDirectionRef}\n * onSpeedUpdate={setAccelerationBasedSpeed}\n * walkSpeed={physicalAttributes.walkSpeed}\n * runSpeed={physicalAttributes.runSpeed}\n * />\n * ```\n */\nexport const AccelerationUpdater: React.FC<AccelerationUpdaterProps> = ({\n isMoving,\n velocity,\n movementTimeRef,\n lastDirectionRef,\n onSpeedUpdate,\n walkSpeed = ACCELERATION_CONSTANTS.DEFAULT_WALK_SPEED,\n runSpeed = ACCELERATION_CONSTANTS.DEFAULT_RUN_SPEED,\n}) => {\n // Track last reported speed and time to throttle updates\n // Initialize with walk speed (archetype-specific or default)\n const lastReportedSpeedRef = useRef<number>(walkSpeed);\n const lastUpdateTimeRef = useRef<number>(0);\n // Throttle interval: update at most every ~100ms (10Hz) instead of 60fps\n const UPDATE_THROTTLE_MS = 100;\n\n useFrame((_state, delta) => {\n // If not moving, reset timers and direction\n if (!isMoving || !velocity || (velocity.x === 0 && velocity.y === 0)) {\n movementTimeRef.current = 0;\n lastDirectionRef.current = { x: 0, y: 0 };\n \n // Only update if changed meaningfully\n if (isSpeedChangeMeaningful(lastReportedSpeedRef.current, walkSpeed)) {\n lastReportedSpeedRef.current = walkSpeed;\n onSpeedUpdate(walkSpeed);\n lastUpdateTimeRef.current = performance.now();\n }\n return;\n }\n\n // Check direction consistency (within 45 degrees)\n const currentDir = { x: velocity.x, y: velocity.y };\n const isSameDirection = isDirectionConsistent(currentDir, lastDirectionRef.current);\n\n // Reset accumulated movement time if direction changed too much\n if (!isSameDirection) {\n movementTimeRef.current = 0;\n } else {\n // Accumulate movement time while moving in a consistent direction\n movementTimeRef.current += delta;\n }\n\n // Update last direction for the next frame\n lastDirectionRef.current = currentDir;\n\n // Calculate new speed with archetype-specific walk/run speeds\n const newSpeed = calculateAcceleratedSpeed(movementTimeRef.current, walkSpeed, runSpeed);\n\n // Throttle updates by both time and epsilon\n // Only call onSpeedUpdate if enough time has passed AND speed changed meaningfully\n const now = performance.now();\n const timeSinceLastUpdate = now - lastUpdateTimeRef.current;\n \n if (\n timeSinceLastUpdate >= UPDATE_THROTTLE_MS &&\n isSpeedChangeMeaningful(lastReportedSpeedRef.current, newSpeed)\n ) {\n lastReportedSpeedRef.current = newSpeed;\n onSpeedUpdate(newSpeed);\n lastUpdateTimeRef.current = now;\n }\n });\n\n return null; // Component only updates movement state, renders no visual elements\n};\n\nexport default AccelerationUpdater;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA,IAAa,uBAA2D,EACtE,UACA,UACA,iBACA,kBACA,eACA,YAAY,uBAAuB,oBACnC,WAAW,uBAAuB,wBAC9B;CAGJ,MAAM,uBAAuB,OAAe,UAAU;CACtD,MAAM,oBAAoB,OAAe,EAAE;CAE3C,MAAM,qBAAqB;AAE3B,WAAU,QAAQ,UAAU;AAE1B,MAAI,CAAC,YAAY,CAAC,YAAa,SAAS,MAAM,KAAK,SAAS,MAAM,GAAI;AACpE,mBAAgB,UAAU;AAC1B,oBAAiB,UAAU;IAAE,GAAG;IAAG,GAAG;IAAG;AAGzC,OAAI,wBAAwB,qBAAqB,SAAS,UAAU,EAAE;AACpE,yBAAqB,UAAU;AAC/B,kBAAc,UAAU;AACxB,sBAAkB,UAAU,YAAY,KAAK;;AAE/C;;EAIF,MAAM,aAAa;GAAE,GAAG,SAAS;GAAG,GAAG,SAAS;GAAG;AAInD,MAAI,CAHoB,sBAAsB,YAAY,iBAAiB,QAGtE,CACH,iBAAgB,UAAU;MAG1B,iBAAgB,WAAW;AAI7B,mBAAiB,UAAU;EAG3B,MAAM,WAAW,0BAA0B,gBAAgB,SAAS,WAAW,SAAS;EAIxF,MAAM,MAAM,YAAY,KAAK;AAG7B,MAF4B,MAAM,kBAAkB,WAG3B,sBACvB,wBAAwB,qBAAqB,SAAS,SAAS,EAC/D;AACA,wBAAqB,UAAU;AAC/B,iBAAc,SAAS;AACvB,qBAAkB,UAAU;;GAE9B;AAEF,QAAO"}
1
+ {"version":3,"file":"AccelerationUpdater.js","names":[],"sources":["../../../../src/systems/movement/helpers/AccelerationUpdater.tsx"],"sourcesContent":["/**\n * AccelerationUpdater - Component that updates movement acceleration at 60fps\n *\n * Uses useFrame to track continuous movement time and calculate acceleration-based speed.\n * Throttles state updates to only call onSpeedUpdate when speed changes meaningfully.\n * This component only updates movement state and renders no visual elements.\n *\n * @module systems/movement/helpers/AccelerationUpdater\n * @category Movement\n * @korean 가속업데이터\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useRef } from \"react\";\nimport {\n ACCELERATION_CONSTANTS,\n calculateAcceleratedSpeed,\n isDirectionConsistent,\n isSpeedChangeMeaningful,\n} from \"./accelerationUtils\";\n\n/**\n * Props for AccelerationUpdater component\n */\nexport interface AccelerationUpdaterProps {\n /** Whether player is currently moving */\n readonly isMoving: boolean;\n /** Current velocity vector */\n readonly velocity: { x: number; y: number } | undefined;\n /** Ref to track accumulated movement time */\n readonly movementTimeRef: React.MutableRefObject<number>;\n /** Ref to track last movement direction */\n readonly lastDirectionRef: React.MutableRefObject<{ x: number; y: number }>;\n /** Callback to update calculated speed - only called on meaningful changes */\n readonly onSpeedUpdate: (speed: number) => void;\n /** Walking speed in m/s (from archetype or default) */\n readonly walkSpeed?: number;\n /** Running speed in m/s (from archetype or default) */\n readonly runSpeed?: number;\n}\n\n/**\n * AccelerationUpdater Component\n *\n * Updates movement acceleration at 60fps using Three.js useFrame hook.\n * Tracks continuous movement time and calculates speed based on direction consistency.\n * Only calls onSpeedUpdate when speed changes by more than epsilon AND sufficient time\n * has passed (100ms throttle), preventing excessive React re-renders at frame rate.\n *\n * @example\n * ```tsx\n * <AccelerationUpdater\n * isMoving={isMoving}\n * velocity={velocity}\n * movementTimeRef={movementTimeRef}\n * lastDirectionRef={lastDirectionRef}\n * onSpeedUpdate={setAccelerationBasedSpeed}\n * walkSpeed={physicalAttributes.walkSpeed}\n * runSpeed={physicalAttributes.runSpeed}\n * />\n * ```\n */\nexport const AccelerationUpdater: React.FC<AccelerationUpdaterProps> = ({\n isMoving,\n velocity,\n movementTimeRef,\n lastDirectionRef,\n onSpeedUpdate,\n walkSpeed = ACCELERATION_CONSTANTS.DEFAULT_WALK_SPEED,\n runSpeed = ACCELERATION_CONSTANTS.DEFAULT_RUN_SPEED,\n}) => {\n // Track last reported speed and time to throttle updates\n // Initialize with walk speed (archetype-specific or default)\n const lastReportedSpeedRef = useRef<number>(walkSpeed);\n const lastUpdateTimeRef = useRef<number>(0);\n // Throttle interval: update at most every ~100ms (10Hz) instead of 60fps\n const UPDATE_THROTTLE_MS = 100;\n\n useFrame((_state, delta) => {\n // If not moving, reset timers and direction\n if (!isMoving || !velocity || (velocity.x === 0 && velocity.y === 0)) {\n movementTimeRef.current = 0;\n lastDirectionRef.current = { x: 0, y: 0 };\n \n // Only update if changed meaningfully\n if (isSpeedChangeMeaningful(lastReportedSpeedRef.current, walkSpeed)) {\n lastReportedSpeedRef.current = walkSpeed;\n onSpeedUpdate(walkSpeed);\n lastUpdateTimeRef.current = performance.now();\n }\n return;\n }\n\n // Check direction consistency (within 45 degrees)\n const currentDir = { x: velocity.x, y: velocity.y };\n const isSameDirection = isDirectionConsistent(currentDir, lastDirectionRef.current);\n\n // Reset accumulated movement time if direction changed too much\n if (!isSameDirection) {\n movementTimeRef.current = 0;\n } else {\n // Accumulate movement time while moving in a consistent direction\n movementTimeRef.current += delta;\n }\n\n // Update last direction for the next frame\n lastDirectionRef.current = currentDir;\n\n // Calculate new speed with archetype-specific walk/run speeds\n const newSpeed = calculateAcceleratedSpeed(movementTimeRef.current, walkSpeed, runSpeed);\n\n // Throttle updates by both time and epsilon\n // Only call onSpeedUpdate if enough time has passed AND speed changed meaningfully\n const now = performance.now();\n const timeSinceLastUpdate = now - lastUpdateTimeRef.current;\n \n if (\n timeSinceLastUpdate >= UPDATE_THROTTLE_MS &&\n isSpeedChangeMeaningful(lastReportedSpeedRef.current, newSpeed)\n ) {\n lastReportedSpeedRef.current = newSpeed;\n onSpeedUpdate(newSpeed);\n lastUpdateTimeRef.current = now;\n }\n });\n\n return null; // Component only updates movement state, renders no visual elements\n};\n\nexport default AccelerationUpdater;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA,IAAa,uBAA2D,EACtE,UACA,UACA,iBACA,kBACA,eACA,YAAY,uBAAuB,oBACnC,WAAW,uBAAuB,wBAC9B;CAGJ,MAAM,uBAAuB,OAAe,UAAU;CACtD,MAAM,oBAAoB,OAAe,EAAE;CAE3C,MAAM,qBAAqB;CAE3B,UAAU,QAAQ,UAAU;EAE1B,IAAI,CAAC,YAAY,CAAC,YAAa,SAAS,MAAM,KAAK,SAAS,MAAM,GAAI;GACpE,gBAAgB,UAAU;GAC1B,iBAAiB,UAAU;IAAE,GAAG;IAAG,GAAG;IAAG;GAGzC,IAAI,wBAAwB,qBAAqB,SAAS,UAAU,EAAE;IACpE,qBAAqB,UAAU;IAC/B,cAAc,UAAU;IACxB,kBAAkB,UAAU,YAAY,KAAK;;GAE/C;;EAIF,MAAM,aAAa;GAAE,GAAG,SAAS;GAAG,GAAG,SAAS;GAAG;EAInD,IAAI,CAHoB,sBAAsB,YAAY,iBAAiB,QAGtE,EACH,gBAAgB,UAAU;OAG1B,gBAAgB,WAAW;EAI7B,iBAAiB,UAAU;EAG3B,MAAM,WAAW,0BAA0B,gBAAgB,SAAS,WAAW,SAAS;EAIxF,MAAM,MAAM,YAAY,KAAK;EAG7B,IAF4B,MAAM,kBAAkB,WAG3B,sBACvB,wBAAwB,qBAAqB,SAAS,SAAS,EAC/D;GACA,qBAAqB,UAAU;GAC/B,cAAc,SAAS;GACvB,kBAAkB,UAAU;;GAE9B;CAEF,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"accelerationUtils.js","names":[],"sources":["../../../../src/systems/movement/helpers/accelerationUtils.ts"],"sourcesContent":["/**\n * Acceleration Utilities for Movement System\n *\n * Pure functions for calculating acceleration-based running speeds.\n * Extracted for testability and reusability across training and combat screens.\n *\n * @module systems/movement/helpers/accelerationUtils\n * @category Movement\n * @korean 가속 유틸리티\n */\n\n/**\n * Step distance thresholds for foot laterality alternation\n * 발 측면성 교대를 위한 걸음 거리 임계값\n */\nexport const STEP_DISTANCE_THRESHOLDS = {\n /**\n * Average step length when walking (meters)\n * 걷기 시 평균 걸음 길이\n */\n WALK: 0.7,\n\n /**\n * Average step length when running (meters)\n * 달리기 시 평균 걸음 길이\n */\n RUN: 1.0,\n} as const;\n\n/**\n * Constants for acceleration-based running (defaults for non-archetype usage)\n */\nexport const ACCELERATION_CONSTANTS = {\n /** Default walking speed in m/s (when no archetype speed provided) */\n DEFAULT_WALK_SPEED: 6.0,\n /** Default running speed in m/s (when no archetype speed provided) */\n DEFAULT_RUN_SPEED: 10.0,\n /** Time to reach running speed in seconds */\n TIME_TO_RUN: 1.5,\n /** Threshold for considering direction \"same\" (cos(45°) = √2/2) */\n DIRECTION_THRESHOLD: Math.cos(Math.PI / 4),\n /** Running threshold as percentage of max speed (0-1) */\n RUN_THRESHOLD_PERCENT: 0.9,\n /** Epsilon for speed change detection (m/s) */\n SPEED_CHANGE_EPSILON: 0.05,\n} as const;\n\n/**\n * Calculate running threshold speed\n * @param runSpeed - Maximum running speed (from archetype or default)\n * @returns Speed at which movement is considered running (m/s)\n */\nexport function calculateRunThreshold(\n runSpeed: number = ACCELERATION_CONSTANTS.DEFAULT_RUN_SPEED\n): number {\n return runSpeed * ACCELERATION_CONSTANTS.RUN_THRESHOLD_PERCENT;\n}\n\n/**\n * Check if two directions are consistent (within threshold angle)\n * @param currentDir Current direction vector\n * @param lastDir Previous direction vector\n * @returns True if directions are within 45° of each other\n */\nexport function isDirectionConsistent(\n currentDir: { x: number; y: number },\n lastDir: { x: number; y: number }\n): boolean {\n // If last direction is zero, consider any movement as consistent\n if (lastDir.x === 0 && lastDir.y === 0) {\n return true;\n }\n\n // Calculate dot product\n const dot = currentDir.x * lastDir.x + currentDir.y * lastDir.y;\n const magCurrent = Math.sqrt(currentDir.x ** 2 + currentDir.y ** 2);\n const magLast = Math.sqrt(lastDir.x ** 2 + lastDir.y ** 2);\n\n if (magCurrent === 0 || magLast === 0) {\n return false;\n }\n\n // Clamp cosAngle to [-1, 1] to handle floating-point edge cases\n const cosAngleRaw = dot / (magCurrent * magLast);\n const cosAngle = Math.max(-1, Math.min(1, cosAngleRaw));\n \n // Use >= to include exactly 45° as consistent (not trigger reset)\n return cosAngle >= ACCELERATION_CONSTANTS.DIRECTION_THRESHOLD;\n}\n\n/**\n * Calculate acceleration-based speed\n * @param movementTime Accumulated movement time in same direction (seconds)\n * @param walkSpeed - Walking speed in m/s (from archetype or default)\n * @param runSpeed - Running speed in m/s (from archetype or default)\n * @returns Interpolated speed between walk and run (m/s)\n */\nexport function calculateAcceleratedSpeed(\n movementTime: number,\n walkSpeed: number = ACCELERATION_CONSTANTS.DEFAULT_WALK_SPEED,\n runSpeed: number = ACCELERATION_CONSTANTS.DEFAULT_RUN_SPEED\n): number {\n const progress = Math.min(movementTime / ACCELERATION_CONSTANTS.TIME_TO_RUN, 1.0);\n return walkSpeed + (runSpeed - walkSpeed) * progress;\n}\n\n/**\n * Check if speed change is meaningful (exceeds epsilon)\n * @param oldSpeed Previous speed (m/s)\n * @param newSpeed New speed (m/s)\n * @returns True if change exceeds threshold\n */\nexport function isSpeedChangeMeaningful(oldSpeed: number, newSpeed: number): boolean {\n return Math.abs(newSpeed - oldSpeed) >= ACCELERATION_CONSTANTS.SPEED_CHANGE_EPSILON;\n}\n\n/**\n * Determine if player is running based on current speed\n * @param speed Current speed (m/s)\n * @param runSpeed - Maximum running speed (from archetype or default)\n * @returns True if speed exceeds running threshold\n */\nexport function isRunningSpeed(\n speed: number,\n runSpeed: number = ACCELERATION_CONSTANTS.DEFAULT_RUN_SPEED\n): boolean {\n return speed >= calculateRunThreshold(runSpeed);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,IAAa,2BAA2B;;;;;CAKtC,MAAM;;;;;CAMN,KAAK;CACN;;;;AAKD,IAAa,yBAAyB;;CAEpC,oBAAoB;;CAEpB,mBAAmB;;CAEnB,aAAa;;CAEb,qBAAqB,KAAK,IAAI,KAAK,KAAK,EAAE;;CAE1C,uBAAuB;;CAEvB,sBAAsB;CACvB;;;;;;AAOD,SAAgB,sBACd,WAAmB,uBAAuB,mBAClC;AACR,QAAO,WAAW,uBAAuB;;;;;;;;AAS3C,SAAgB,sBACd,YACA,SACS;AAET,KAAI,QAAQ,MAAM,KAAK,QAAQ,MAAM,EACnC,QAAO;CAIT,MAAM,MAAM,WAAW,IAAI,QAAQ,IAAI,WAAW,IAAI,QAAQ;CAC9D,MAAM,aAAa,KAAK,KAAK,WAAW,KAAK,IAAI,WAAW,KAAK,EAAE;CACnE,MAAM,UAAU,KAAK,KAAK,QAAQ,KAAK,IAAI,QAAQ,KAAK,EAAE;AAE1D,KAAI,eAAe,KAAK,YAAY,EAClC,QAAO;CAIT,MAAM,cAAc,OAAO,aAAa;AAIxC,QAHiB,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,YAAY,CAG/C,IAAY,uBAAuB;;;;;;;;;AAU5C,SAAgB,0BACd,cACA,YAAoB,uBAAuB,oBAC3C,WAAmB,uBAAuB,mBAClC;CACR,MAAM,WAAW,KAAK,IAAI,eAAe,uBAAuB,aAAa,EAAI;AACjF,QAAO,aAAa,WAAW,aAAa;;;;;;;;AAS9C,SAAgB,wBAAwB,UAAkB,UAA2B;AACnF,QAAO,KAAK,IAAI,WAAW,SAAS,IAAI,uBAAuB;;;;;;;;AASjE,SAAgB,eACd,OACA,WAAmB,uBAAuB,mBACjC;AACT,QAAO,SAAS,sBAAsB,SAAS"}
1
+ {"version":3,"file":"accelerationUtils.js","names":[],"sources":["../../../../src/systems/movement/helpers/accelerationUtils.ts"],"sourcesContent":["/**\n * Acceleration Utilities for Movement System\n *\n * Pure functions for calculating acceleration-based running speeds.\n * Extracted for testability and reusability across training and combat screens.\n *\n * @module systems/movement/helpers/accelerationUtils\n * @category Movement\n * @korean 가속 유틸리티\n */\n\n/**\n * Step distance thresholds for foot laterality alternation\n * 발 측면성 교대를 위한 걸음 거리 임계값\n */\nexport const STEP_DISTANCE_THRESHOLDS = {\n /**\n * Average step length when walking (meters)\n * 걷기 시 평균 걸음 길이\n */\n WALK: 0.7,\n\n /**\n * Average step length when running (meters)\n * 달리기 시 평균 걸음 길이\n */\n RUN: 1.0,\n} as const;\n\n/**\n * Constants for acceleration-based running (defaults for non-archetype usage)\n */\nexport const ACCELERATION_CONSTANTS = {\n /** Default walking speed in m/s (when no archetype speed provided) */\n DEFAULT_WALK_SPEED: 6.0,\n /** Default running speed in m/s (when no archetype speed provided) */\n DEFAULT_RUN_SPEED: 10.0,\n /** Time to reach running speed in seconds */\n TIME_TO_RUN: 1.5,\n /** Threshold for considering direction \"same\" (cos(45°) = √2/2) */\n DIRECTION_THRESHOLD: Math.cos(Math.PI / 4),\n /** Running threshold as percentage of max speed (0-1) */\n RUN_THRESHOLD_PERCENT: 0.9,\n /** Epsilon for speed change detection (m/s) */\n SPEED_CHANGE_EPSILON: 0.05,\n} as const;\n\n/**\n * Calculate running threshold speed\n * @param runSpeed - Maximum running speed (from archetype or default)\n * @returns Speed at which movement is considered running (m/s)\n */\nexport function calculateRunThreshold(\n runSpeed: number = ACCELERATION_CONSTANTS.DEFAULT_RUN_SPEED\n): number {\n return runSpeed * ACCELERATION_CONSTANTS.RUN_THRESHOLD_PERCENT;\n}\n\n/**\n * Check if two directions are consistent (within threshold angle)\n * @param currentDir Current direction vector\n * @param lastDir Previous direction vector\n * @returns True if directions are within 45° of each other\n */\nexport function isDirectionConsistent(\n currentDir: { x: number; y: number },\n lastDir: { x: number; y: number }\n): boolean {\n // If last direction is zero, consider any movement as consistent\n if (lastDir.x === 0 && lastDir.y === 0) {\n return true;\n }\n\n // Calculate dot product\n const dot = currentDir.x * lastDir.x + currentDir.y * lastDir.y;\n const magCurrent = Math.sqrt(currentDir.x ** 2 + currentDir.y ** 2);\n const magLast = Math.sqrt(lastDir.x ** 2 + lastDir.y ** 2);\n\n if (magCurrent === 0 || magLast === 0) {\n return false;\n }\n\n // Clamp cosAngle to [-1, 1] to handle floating-point edge cases\n const cosAngleRaw = dot / (magCurrent * magLast);\n const cosAngle = Math.max(-1, Math.min(1, cosAngleRaw));\n \n // Use >= to include exactly 45° as consistent (not trigger reset)\n return cosAngle >= ACCELERATION_CONSTANTS.DIRECTION_THRESHOLD;\n}\n\n/**\n * Calculate acceleration-based speed\n * @param movementTime Accumulated movement time in same direction (seconds)\n * @param walkSpeed - Walking speed in m/s (from archetype or default)\n * @param runSpeed - Running speed in m/s (from archetype or default)\n * @returns Interpolated speed between walk and run (m/s)\n */\nexport function calculateAcceleratedSpeed(\n movementTime: number,\n walkSpeed: number = ACCELERATION_CONSTANTS.DEFAULT_WALK_SPEED,\n runSpeed: number = ACCELERATION_CONSTANTS.DEFAULT_RUN_SPEED\n): number {\n const progress = Math.min(movementTime / ACCELERATION_CONSTANTS.TIME_TO_RUN, 1.0);\n return walkSpeed + (runSpeed - walkSpeed) * progress;\n}\n\n/**\n * Check if speed change is meaningful (exceeds epsilon)\n * @param oldSpeed Previous speed (m/s)\n * @param newSpeed New speed (m/s)\n * @returns True if change exceeds threshold\n */\nexport function isSpeedChangeMeaningful(oldSpeed: number, newSpeed: number): boolean {\n return Math.abs(newSpeed - oldSpeed) >= ACCELERATION_CONSTANTS.SPEED_CHANGE_EPSILON;\n}\n\n/**\n * Determine if player is running based on current speed\n * @param speed Current speed (m/s)\n * @param runSpeed - Maximum running speed (from archetype or default)\n * @returns True if speed exceeds running threshold\n */\nexport function isRunningSpeed(\n speed: number,\n runSpeed: number = ACCELERATION_CONSTANTS.DEFAULT_RUN_SPEED\n): boolean {\n return speed >= calculateRunThreshold(runSpeed);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,IAAa,2BAA2B;;;;;CAKtC,MAAM;;;;;CAMN,KAAK;CACN;;;;AAKD,IAAa,yBAAyB;;CAEpC,oBAAoB;;CAEpB,mBAAmB;;CAEnB,aAAa;;CAEb,qBAAqB,KAAK,IAAI,KAAK,KAAK,EAAE;;CAE1C,uBAAuB;;CAEvB,sBAAsB;CACvB;;;;;;AAOD,SAAgB,sBACd,WAAmB,uBAAuB,mBAClC;CACR,OAAO,WAAW,uBAAuB;;;;;;;;AAS3C,SAAgB,sBACd,YACA,SACS;CAET,IAAI,QAAQ,MAAM,KAAK,QAAQ,MAAM,GACnC,OAAO;CAIT,MAAM,MAAM,WAAW,IAAI,QAAQ,IAAI,WAAW,IAAI,QAAQ;CAC9D,MAAM,aAAa,KAAK,KAAK,WAAW,KAAK,IAAI,WAAW,KAAK,EAAE;CACnE,MAAM,UAAU,KAAK,KAAK,QAAQ,KAAK,IAAI,QAAQ,KAAK,EAAE;CAE1D,IAAI,eAAe,KAAK,YAAY,GAClC,OAAO;CAIT,MAAM,cAAc,OAAO,aAAa;CAIxC,OAHiB,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,YAAY,CAG/C,IAAY,uBAAuB;;;;;;;;;AAU5C,SAAgB,0BACd,cACA,YAAoB,uBAAuB,oBAC3C,WAAmB,uBAAuB,mBAClC;CACR,MAAM,WAAW,KAAK,IAAI,eAAe,uBAAuB,aAAa,EAAI;CACjF,OAAO,aAAa,WAAW,aAAa;;;;;;;;AAS9C,SAAgB,wBAAwB,UAAkB,UAA2B;CACnF,OAAO,KAAK,IAAI,WAAW,SAAS,IAAI,uBAAuB;;;;;;;;AASjE,SAAgB,eACd,OACA,WAAmB,uBAAuB,mBACjC;CACT,OAAO,SAAS,sBAAsB,SAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"integration.js","names":[],"sources":["../../../src/systems/movement/integration.ts"],"sourcesContent":["/**\n * Integration module for InjuryMovementModifier with MovementPhysics\n * \n * **Korean**: 손상 이동 통합 (Injury Movement Integration)\n * \n * Provides helper functions to integrate the InjuryMovementModifier system\n * with the existing MovementPhysics system.\n * \n * @module systems/movement/integration\n * @category Movement System\n * @korean 손상이동통합\n */\n\nimport { injuryMovementModifier } from \"./InjuryMovementModifier\";\nimport type { BodyPartHealth } from \"../bodypart/types\";\nimport { TrigramStance } from \"@/types/common\";\n\n/**\n * Calculate leg injury factor for MovementPhysics from body part health.\n * \n * **Korean**: 다리 손상 요소 계산\n * \n * Converts detailed body part health into a simple 0-1 injury factor\n * that MovementPhysics.MovementState expects. This allows gradual\n * migration from the old simple system to the new detailed system.\n * \n * Uses the **worst** (minimum) leg health to match the main injury system's behavior,\n * ensuring asymmetric injuries (e.g., one leg at 0%, other at 100%) are properly represented.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Leg injury factor (0 = healthy, 1 = fully injured), clamped to 0-1 range\n * \n * @example\n * ```typescript\n * const legInjuryFactor = calculateLegInjuryFactor(bodyPartHealth);\n * movementState.legInjuryFactor = legInjuryFactor;\n * ```\n * \n * @public\n * @korean 다리손상요소계산\n */\nexport function calculateLegInjuryFactor(\n bodyPartHealth: BodyPartHealth\n): number {\n // Use worst (minimum) leg health to match main injury system behavior\n // This ensures asymmetric injuries are properly represented\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n \n // Clamp worst leg health to valid 0-100 range to avoid invalid injury factors\n const clampedLegHealth = Math.min(100, Math.max(0, worstLegHealth));\n\n // Normalize to 0-1 health ratio (1 = fully healthy, 0 = no health)\n const healthRatio = clampedLegHealth / 100;\n\n // Convert to 0-1 injury factor (0 = healthy, 1 = fully injured)\n // Use inverse of health ratio and clamp defensively to 0-1\n const injuryFactor = 1.0 - healthRatio;\n return Math.min(1, Math.max(0, injuryFactor));\n}\n\n/**\n * Calculate comprehensive movement speed with all injury modifiers.\n * \n * **Korean**: 종합 이동 속도 계산\n * \n * This is the recommended function for full integration with the new\n * injury system. It applies all modifiers including leg injuries,\n * torso damage, stance bonuses, and pain penalties.\n * \n * Uses the shared singleton instance to avoid allocations in per-frame loops.\n * \n * @param baseSpeed - Base movement speed (m/s)\n * @param bodyPartHealth - Current body part health\n * @param stance - Current trigram stance\n * @param painLevel - Current pain level (0-100)\n * @returns Final calculated speed in m/s\n * \n * @example\n * ```typescript\n * const finalSpeed = calculateMovementSpeed(\n * 5.0,\n * playerBodyHealth,\n * TrigramStance.GEON,\n * 65\n * );\n * \n * // Use this speed to override MovementPhysics\n * movementPhysics.setMaxSpeed(finalSpeed);\n * ```\n * \n * @public\n * @korean 종합이동속도계산\n */\nexport function calculateMovementSpeed(\n baseSpeed: number,\n bodyPartHealth: BodyPartHealth,\n stance: TrigramStance,\n painLevel: number\n): number {\n const result = injuryMovementModifier.calculateMovementSpeed(\n baseSpeed,\n bodyPartHealth,\n stance,\n painLevel\n );\n \n return result.finalSpeed;\n}\n\n/**\n * Calculate speed multiplier from injuries (without stance or pain).\n * \n * **Korean**: 손상 속도 배수 계산\n * \n * Useful when you want to apply injury penalties separately from\n * stance and pain modifiers.\n * \n * Uses the shared singleton instance to avoid allocations in per-frame loops.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Speed multiplier (0.1-1.0)\n * \n * @public\n * @korean 손상속도배수계산\n */\nexport function calculateInjuryMultiplier(\n bodyPartHealth: BodyPartHealth\n): number {\n // Calculate without stance or pain modifiers\n const result = injuryMovementModifier.calculateMovementSpeed(\n 1.0, // Base speed of 1.0 to get pure multiplier\n bodyPartHealth,\n TrigramStance.GEON, // Neutral stance (1.0x)\n 0 // No pain\n );\n \n // The result will be the pure injury multiplier\n return result.speedMultiplier;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,yBACd,gBACQ;CAGR,MAAM,iBAAiB,KAAK,IAAI,eAAe,SAAS,eAAe,SAAS;CAUhF,MAAM,eAAe,IAPI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,eAAe,CAG9C,GAAmB;AAKvC,QAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,aAAa,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoC/C,SAAgB,uBACd,WACA,gBACA,QACA,WACQ;AAQR,QAPe,uBAAuB,uBACpC,WACA,gBACA,QACA,UAGK,CAAO;;;;;;;;;;;;;;;;;;AAmBhB,SAAgB,0BACd,gBACQ;AAUR,QARe,uBAAuB,uBACpC,GACA,gBACA,cAAc,MACd,EAIK,CAAO"}
1
+ {"version":3,"file":"integration.js","names":[],"sources":["../../../src/systems/movement/integration.ts"],"sourcesContent":["/**\n * Integration module for InjuryMovementModifier with MovementPhysics\n * \n * **Korean**: 손상 이동 통합 (Injury Movement Integration)\n * \n * Provides helper functions to integrate the InjuryMovementModifier system\n * with the existing MovementPhysics system.\n * \n * @module systems/movement/integration\n * @category Movement System\n * @korean 손상이동통합\n */\n\nimport { injuryMovementModifier } from \"./InjuryMovementModifier\";\nimport type { BodyPartHealth } from \"../bodypart/types\";\nimport { TrigramStance } from \"@/types/common\";\n\n/**\n * Calculate leg injury factor for MovementPhysics from body part health.\n * \n * **Korean**: 다리 손상 요소 계산\n * \n * Converts detailed body part health into a simple 0-1 injury factor\n * that MovementPhysics.MovementState expects. This allows gradual\n * migration from the old simple system to the new detailed system.\n * \n * Uses the **worst** (minimum) leg health to match the main injury system's behavior,\n * ensuring asymmetric injuries (e.g., one leg at 0%, other at 100%) are properly represented.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Leg injury factor (0 = healthy, 1 = fully injured), clamped to 0-1 range\n * \n * @example\n * ```typescript\n * const legInjuryFactor = calculateLegInjuryFactor(bodyPartHealth);\n * movementState.legInjuryFactor = legInjuryFactor;\n * ```\n * \n * @public\n * @korean 다리손상요소계산\n */\nexport function calculateLegInjuryFactor(\n bodyPartHealth: BodyPartHealth\n): number {\n // Use worst (minimum) leg health to match main injury system behavior\n // This ensures asymmetric injuries are properly represented\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n \n // Clamp worst leg health to valid 0-100 range to avoid invalid injury factors\n const clampedLegHealth = Math.min(100, Math.max(0, worstLegHealth));\n\n // Normalize to 0-1 health ratio (1 = fully healthy, 0 = no health)\n const healthRatio = clampedLegHealth / 100;\n\n // Convert to 0-1 injury factor (0 = healthy, 1 = fully injured)\n // Use inverse of health ratio and clamp defensively to 0-1\n const injuryFactor = 1.0 - healthRatio;\n return Math.min(1, Math.max(0, injuryFactor));\n}\n\n/**\n * Calculate comprehensive movement speed with all injury modifiers.\n * \n * **Korean**: 종합 이동 속도 계산\n * \n * This is the recommended function for full integration with the new\n * injury system. It applies all modifiers including leg injuries,\n * torso damage, stance bonuses, and pain penalties.\n * \n * Uses the shared singleton instance to avoid allocations in per-frame loops.\n * \n * @param baseSpeed - Base movement speed (m/s)\n * @param bodyPartHealth - Current body part health\n * @param stance - Current trigram stance\n * @param painLevel - Current pain level (0-100)\n * @returns Final calculated speed in m/s\n * \n * @example\n * ```typescript\n * const finalSpeed = calculateMovementSpeed(\n * 5.0,\n * playerBodyHealth,\n * TrigramStance.GEON,\n * 65\n * );\n * \n * // Use this speed to override MovementPhysics\n * movementPhysics.setMaxSpeed(finalSpeed);\n * ```\n * \n * @public\n * @korean 종합이동속도계산\n */\nexport function calculateMovementSpeed(\n baseSpeed: number,\n bodyPartHealth: BodyPartHealth,\n stance: TrigramStance,\n painLevel: number\n): number {\n const result = injuryMovementModifier.calculateMovementSpeed(\n baseSpeed,\n bodyPartHealth,\n stance,\n painLevel\n );\n \n return result.finalSpeed;\n}\n\n/**\n * Calculate speed multiplier from injuries (without stance or pain).\n * \n * **Korean**: 손상 속도 배수 계산\n * \n * Useful when you want to apply injury penalties separately from\n * stance and pain modifiers.\n * \n * Uses the shared singleton instance to avoid allocations in per-frame loops.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Speed multiplier (0.1-1.0)\n * \n * @public\n * @korean 손상속도배수계산\n */\nexport function calculateInjuryMultiplier(\n bodyPartHealth: BodyPartHealth\n): number {\n // Calculate without stance or pain modifiers\n const result = injuryMovementModifier.calculateMovementSpeed(\n 1.0, // Base speed of 1.0 to get pure multiplier\n bodyPartHealth,\n TrigramStance.GEON, // Neutral stance (1.0x)\n 0 // No pain\n );\n \n // The result will be the pure injury multiplier\n return result.speedMultiplier;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,yBACd,gBACQ;CAGR,MAAM,iBAAiB,KAAK,IAAI,eAAe,SAAS,eAAe,SAAS;CAUhF,MAAM,eAAe,IAPI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,eAAe,CAG9C,GAAmB;CAKvC,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,aAAa,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoC/C,SAAgB,uBACd,WACA,gBACA,QACA,WACQ;CAQR,OAPe,uBAAuB,uBACpC,WACA,gBACA,QACA,UAGK,CAAO;;;;;;;;;;;;;;;;;;AAmBhB,SAAgB,0BACd,gBACQ;CAUR,OARe,uBAAuB,uBACpC,GACA,gBACA,cAAc,MACd,EAIK,CAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"AttackMovementPhysics.js","names":[],"sources":["../../../src/systems/physics/AttackMovementPhysics.ts"],"sourcesContent":["/**\n * Attack Movement Physics System for realistic forward momentum during attacks.\n *\n * **Korean**: 공격 이동 물리 시스템 (Attack Movement Physics System)\n *\n * Implements realistic forward movement physics when fighters execute attacks.\n * Kicks naturally extend the body forward, punches lunge with body weight,\n * and spinning techniques carry rotational momentum.\n *\n * ## Attack Movement Mechanics\n *\n * Forward movement distance is determined by:\n * - Animation type (kicks > punches > elbows/knees)\n * - Stance modifiers (8 trigram stances affect aggression)\n * - Animation phase timing (extension → peak → recovery)\n *\n * ## Movement Integration\n *\n * - Extension phase (frames 3-6): Forward lunge with ease-out\n * - Peak phase (frame 6): Maximum extension reached\n * - Recovery phase (frames 7-10): Return to original stance\n * - Arena bounds: Automatically clamp movement to valid zone\n *\n * ## Performance\n *\n * Optimized for 60fps with efficient vector calculations and\n * smooth easing curves for realistic attack momentum feel.\n *\n * @module systems/physics/AttackMovementPhysics\n * @category Physics System\n * @korean 공격이동물리\n */\n\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"@/types/common\";\nimport { AnimationType } from \"@/systems/animation\";\n\n/**\n * Configuration for attack movement calculation.\n *\n * **Korean**: 공격 이동 설정 (Attack Movement Configuration)\n *\n * @public\n * @korean 공격이동설정\n */\nexport interface AttackMovementConfig {\n /** Animation type being executed */\n readonly animationType: AnimationType;\n /** Current trigram stance */\n readonly currentStance: TrigramStance;\n /** Normalized attack direction vector (attacker → target) */\n readonly direction: THREE.Vector3;\n /** Total attack animation duration in seconds */\n readonly animationDuration: number;\n}\n\n/**\n * Result of attack movement calculation.\n *\n * **Korean**: 공격 이동 결과 (Attack Movement Result)\n *\n * Contains displacement vector and timing for attack lunge.\n *\n * @public\n * @korean 공격이동결과\n */\nexport interface AttackMovementResult {\n /** Total forward displacement vector in world space */\n readonly displacement: THREE.Vector3;\n /** Duration of forward lunge phase in seconds */\n readonly lungeDuration: number;\n /** Duration of recovery return phase in seconds */\n readonly recoveryDuration: number;\n /** Total movement cycle duration in seconds */\n readonly totalDuration: number;\n}\n\n/**\n * Attack Movement Physics Engine.\n *\n * **Korean**: 공격 이동 물리 엔진\n *\n * Calculates realistic forward movement during attack animations\n * based on technique type, stance modifiers, and animation timing.\n * Provides smooth lunge and recovery phases for authentic martial arts feel.\n *\n * @example\n * ```typescript\n * const physics = new AttackMovementPhysics();\n *\n * // Calculate movement for roundhouse kick\n * const config: AttackMovementConfig = {\n * animationType: AnimationType.ROUNDHOUSE_KICK,\n * currentStance: TrigramStance.LI, // Fire stance (aggressive)\n * direction: new THREE.Vector3(1, 0, 0).normalize(),\n * animationDuration: 0.48, // 480ms kick animation\n * };\n *\n * const result = physics.calculateAttackMovement(config);\n * // Result: 1.0m base * 1.3 (Fire bonus) = ~1.3m forward lunge\n * // lungeDuration: 0.24s (50% of animation)\n * // recoveryDuration: 0.24s (50% of animation)\n * ```\n *\n * @public\n * @korean 공격이동물리\n */\nexport class AttackMovementPhysics {\n /**\n * Calculates attack movement displacement and timing.\n *\n * **Korean**: 공격 이동 계산 (Calculate Attack Movement)\n *\n * Determines forward lunge distance and recovery timing based on:\n * 1. Base movement from animation type\n * 2. Stance movement modifier (8 trigram effects)\n * 3. Animation phase durations (lunge vs recovery)\n *\n * @param config - Attack movement configuration\n * @returns Attack movement result with displacement and timing\n *\n * @example\n * ```typescript\n * // Front kick with Heaven stance\n * const kick = physics.calculateAttackMovement({\n * animationType: AnimationType.FRONT_KICK,\n * currentStance: TrigramStance.GEON,\n * direction: attackVector,\n * animationDuration: 0.4,\n * });\n * // Result: ~0.88m forward (0.8m * 1.1 Geon modifier)\n *\n * // Jab with Mountain stance\n * const jab = physics.calculateAttackMovement({\n * animationType: AnimationType.JAB,\n * currentStance: TrigramStance.GAN,\n * direction: attackVector,\n * animationDuration: 0.25,\n * });\n * // Result: ~0.24m forward (0.3m * 0.8 Gan modifier)\n * ```\n *\n * @public\n * @korean 공격이동계산\n */\n calculateAttackMovement(\n config: AttackMovementConfig\n ): AttackMovementResult {\n // 1. Get base movement distance for animation type\n const baseDistance = this.getBaseMovementDistance(config.animationType);\n\n // 2. Apply stance movement modifier\n const stanceModifier = this.getStanceMovementModifier(config.currentStance);\n const finalDistance = baseDistance * stanceModifier;\n\n // 3. Calculate displacement vector\n const displacement = config.direction.clone().multiplyScalar(finalDistance);\n\n // 4. Calculate phase durations (lunge + recovery)\n const lungeDuration = config.animationDuration * 0.5; // First 50% = forward\n const recoveryDuration = config.animationDuration * 0.5; // Last 50% = return\n\n return {\n displacement,\n lungeDuration,\n recoveryDuration,\n totalDuration: config.animationDuration,\n };\n }\n\n /**\n * Gets base forward movement distance for animation type.\n *\n * **Korean**: 기본 이동 거리 (Base Movement Distance)\n *\n * Movement distances by technique category:\n * - Kicks: 0.8-1.2m (leg extension, longest reach)\n * - Punches: 0.3-0.5m (arm extension, body lunge)\n * - Elbows/Knees: 0.2-0.3m (close range techniques)\n * - Spinning: 0.5-0.8m (rotation carries momentum)\n *\n * @param animationType - Type of attack animation\n * @returns Base movement distance in meters\n *\n * @private\n * @korean 기본이동거리\n */\n private getBaseMovementDistance(animationType: AnimationType): number {\n // Kicks - highest forward movement (leg extension)\n if (\n animationType === AnimationType.ROUNDHOUSE_KICK ||\n animationType === AnimationType.SIDE_KICK ||\n animationType === AnimationType.BACK_KICK\n ) {\n return 1.0; // 1.0m forward lunge\n }\n\n if (\n animationType === AnimationType.FRONT_KICK ||\n animationType === AnimationType.PUSH_KICK\n ) {\n return 0.8; // 0.8m forward lunge\n }\n\n if (\n animationType === AnimationType.AXE_KICK ||\n animationType === AnimationType.CRESCENT_KICK ||\n animationType === AnimationType.LOW_KICK\n ) {\n return 0.9; // 0.9m forward lunge\n }\n\n // Spinning kicks - momentum carries body forward\n if (\n animationType === AnimationType.SPINNING_HOOK ||\n animationType === AnimationType.SPINNING_HEEL_KICK ||\n animationType === AnimationType.TORNADO_KICK\n ) {\n return 0.8; // 0.8m with rotation\n }\n\n // Jumping/flying kicks - aerial momentum\n if (\n animationType === AnimationType.FLYING_KICK ||\n animationType === AnimationType.JUMPING_KICK\n ) {\n return 1.2; // 1.2m maximum extension\n }\n\n // Punches - moderate forward movement (arm + body lunge)\n if (\n animationType === AnimationType.CROSS ||\n animationType === AnimationType.OVERHAND ||\n animationType === AnimationType.HOOK\n ) {\n return 0.5; // 0.5m forward lunge\n }\n\n if (\n animationType === AnimationType.JAB ||\n animationType === AnimationType.BACKFIST\n ) {\n return 0.3; // 0.3m forward lunge\n }\n\n if (\n animationType === AnimationType.UPPERCUT ||\n animationType === AnimationType.HAMMER_FIST ||\n animationType === AnimationType.PALM_STRIKE\n ) {\n return 0.4; // 0.4m forward lunge\n }\n\n // Elbow/Knee - minimal movement (close range)\n if (\n animationType === AnimationType.ELBOW_STRIKE ||\n animationType === AnimationType.KNEE_STRIKE ||\n animationType === AnimationType.KNEE_KICK ||\n animationType === AnimationType.ELBOW_UPPERCUT ||\n animationType === AnimationType.FLYING_KNEE ||\n animationType === AnimationType.CLINCH_KNEE ||\n animationType === AnimationType.SPINNING_ELBOW ||\n animationType === AnimationType.TEMPLE_ELBOW ||\n animationType === AnimationType.SPINNING_BACK_ELBOW ||\n animationType === AnimationType.SPINAL_ELBOW ||\n animationType === AnimationType.BRACHIAL_ELBOW ||\n animationType === AnimationType.KIDNEY_KNEE ||\n animationType === AnimationType.FEMORAL_KNEE\n ) {\n return 0.2; // 0.2m forward lunge\n }\n\n // Specialized jab variants - similar to jab (quick extension)\n if (\n animationType === AnimationType.SPEAR_HAND_STRIKE ||\n animationType === AnimationType.NERVE_STRIKE ||\n animationType === AnimationType.PRESSURE_POINT_STRIKE ||\n animationType === AnimationType.LIGHTNING_STRIKE ||\n animationType === AnimationType.RAPID_BARRAGE ||\n animationType === AnimationType.RHYTHMIC_STRIKES ||\n animationType === AnimationType.NERVE_PARALYSIS ||\n animationType === AnimationType.THROAT_STRIKE ||\n animationType === AnimationType.EYE_GOUGE\n ) {\n return 0.3; // 0.3m forward lunge (jab-like)\n }\n\n // Specialized cross variants - similar to cross (body weight transfer)\n if (\n animationType === AnimationType.HEAVEN_STRIKE ||\n animationType === AnimationType.FLOWING_CROSS\n ) {\n return 0.5; // 0.5m forward lunge (cross-like)\n }\n\n // Specialized palm strikes - similar to palm strike\n if (\n animationType === AnimationType.SOLAR_PLEXUS_STRIKE ||\n animationType === AnimationType.FLOWING_PUSH ||\n animationType === AnimationType.LIVER_DISRUPTION ||\n animationType === AnimationType.EAR_STRIKE\n ) {\n return 0.4; // 0.4m forward lunge (palm-like)\n }\n\n // Grappling - minimal forward movement (rotation/off-balancing in place)\n if (\n animationType === AnimationType.THROW ||\n animationType === AnimationType.JOINT_LOCK ||\n animationType === AnimationType.TAKEDOWN ||\n animationType === AnimationType.SWEEP ||\n animationType === AnimationType.CLINCH ||\n animationType === AnimationType.GRAPPLE ||\n animationType === AnimationType.SLAM ||\n animationType === AnimationType.WRIST_LOCK ||\n animationType === AnimationType.ARM_BAR ||\n animationType === AnimationType.SHOULDER_LOCK ||\n animationType === AnimationType.HIP_THROW ||\n animationType === AnimationType.LEG_REAP ||\n animationType === AnimationType.SMALL_CIRCLE_LOCK ||\n animationType === AnimationType.FINGER_LOCK ||\n animationType === AnimationType.ELBOW_LOCK ||\n animationType === AnimationType.SHOULDER_MANIPULATION ||\n animationType === AnimationType.MOUNTAIN_LOCK ||\n animationType === AnimationType.EARTH_EMBRACE ||\n animationType === AnimationType.CAROTID_CHOKE\n ) {\n return 0.05; // 0.05m forward movement for grappling entries\n }\n\n // Default - conservative minimal movement for any unmapped types\n // Additional AnimationType values can be grouped with the closest category above\n // to fine-tune their forward displacement without changing overall behavior\n return 0.2;\n }\n\n /**\n * Gets stance movement modifier for attack lunge.\n *\n * **Korean**: 자세 이동 배율 (Stance Movement Modifier)\n *\n * Trigram stance movement modifiers based on combat philosophy:\n * - ☰ 건 (Geon/Heaven): +30% movement (aggressive, drives forward with pure yang)\n * - ☳ 진 (Jin/Thunder): +20% movement (explosive power, shocking force)\n * - ☴ 손 (Son/Wind): +15% movement (continuous flow, mobile)\n * - ☲ 리 (Li/Fire): +10% movement (precision strikes, controlled aggression)\n * - ☱ 태 (Tae/Lake): 0% (neutral, fluid and adaptive)\n * - ☵ 감 (Gam/Water): 0% (neutral, flowing and flexible)\n * - ☷ 곤 (Gon/Earth): -10% movement (grounded, stable techniques)\n * - ☶ 간 (Gan/Mountain): -20% movement (defensive mastery, minimal advance)\n *\n * @param stance - Current trigram stance\n * @returns Movement multiplier (0.8 to 1.3)\n *\n * @private\n * @korean 자세이동배율\n */\n private getStanceMovementModifier(stance: TrigramStance): number {\n const modifiers: Record<TrigramStance, number> = {\n [TrigramStance.GEON]: 1.3, // Heaven: +30% aggressive forward pressure (pure yang drives forward)\n [TrigramStance.JIN]: 1.2, // Thunder: +20% explosive movement\n [TrigramStance.SON]: 1.15, // Wind: +15% flowing movement\n [TrigramStance.LI]: 1.1, // Fire: +10% precision strikes with controlled forward movement\n [TrigramStance.TAE]: 1.0, // Lake: neutral movement\n [TrigramStance.GAM]: 1.0, // Water: neutral movement\n [TrigramStance.GON]: 0.9, // Earth: -10% grounded movement\n [TrigramStance.GAN]: 0.8, // Mountain: -20% defensive movement\n };\n return modifiers[stance];\n }\n\n /**\n * Applies attack movement force to attacker position over time.\n *\n * **Korean**: 공격 이동 적용 (Apply Attack Movement)\n *\n * Uses smooth ease-out cubic curve for realistic lunge feel:\n * - Fast initial forward movement (attack commitment)\n * - Gradual deceleration at peak extension\n * - Smooth return during recovery phase\n *\n * @param basePosition - Original stance position (does not change during attack)\n * @param result - Attack movement result with displacement\n * @param elapsedTime - Time elapsed since attack started\n * @param isRecoveryPhase - Whether in recovery (return) phase\n * @returns New position with attack displacement applied\n *\n * @example\n * ```typescript\n * // In animation update loop (60fps)\n * const basePos = new THREE.Vector3(0, 0, 0); // Original stance position\n * \n * if (elapsedTime < result.lungeDuration) {\n * // Lunge forward phase\n * const newPosition = physics.applyAttackMovement(\n * basePos,\n * result,\n * elapsedTime,\n * false\n * );\n * } else if (elapsedTime < result.totalDuration) {\n * // Recovery return phase\n * const newPosition = physics.applyAttackMovement(\n * basePos,\n * result,\n * elapsedTime,\n * true\n * );\n * }\n * ```\n *\n * @public\n * @korean 공격이동적용\n */\n applyAttackMovement(\n basePosition: THREE.Vector3,\n result: AttackMovementResult,\n elapsedTime: number,\n isRecoveryPhase: boolean\n ): THREE.Vector3 {\n let progress: number;\n\n if (!isRecoveryPhase) {\n // Lunge forward phase\n progress = Math.min(1.0, elapsedTime / result.lungeDuration);\n\n // Smooth forward lunge curve (ease-out cubic)\n // Fast initial commitment, gradual deceleration at peak\n const easedProgress = 1 - Math.pow(1 - progress, 3);\n\n // Calculate position along forward path\n const currentDisplacement = result.displacement\n .clone()\n .multiplyScalar(easedProgress);\n\n return basePosition.clone().add(currentDisplacement);\n } else {\n // Recovery return phase\n const recoveryTime = elapsedTime - result.lungeDuration;\n progress = Math.min(1.0, recoveryTime / result.recoveryDuration);\n\n // Smooth return curve (ease-in cubic)\n // Gradual start, faster finish back to stance\n const easedProgress = Math.pow(progress, 3);\n\n // Calculate return position (from peak back to origin)\n const returnDisplacement = result.displacement\n .clone()\n .multiplyScalar(1.0 - easedProgress);\n\n return basePosition.clone().add(returnDisplacement);\n }\n }\n\n /**\n * Checks if attacker is currently in lunge phase.\n *\n * **Korean**: 돌진 상태 확인 (Check Lunge State)\n *\n * @param elapsedTime - Time elapsed since attack started\n * @param lungeDuration - Total lunge phase duration\n * @returns True if in forward lunge phase\n *\n * @public\n * @korean 돌진상태확인\n */\n isInLungePhase(elapsedTime: number, lungeDuration: number): boolean {\n return elapsedTime < lungeDuration;\n }\n\n /**\n * Checks if attacker is in recovery return phase.\n *\n * **Korean**: 회복 상태 확인 (Check Recovery State)\n *\n * @param elapsedTime - Time elapsed since attack started\n * @param result - Attack movement result with timing\n * @returns True if in recovery return phase\n *\n * @public\n * @korean 회복상태확인\n */\n isInRecoveryPhase(\n elapsedTime: number,\n result: AttackMovementResult\n ): boolean {\n return (\n elapsedTime >= result.lungeDuration &&\n elapsedTime < result.totalDuration\n );\n }\n\n /**\n * Gets bilingual Korean-English name for lunge phase.\n *\n * **Korean**: 돌진 단계 이름 (Lunge Phase Name)\n *\n * @returns Korean and English phase names\n *\n * @public\n * @korean 돌진단계이름\n */\n getLungePhaseName(): { korean: string; english: string } {\n return {\n korean: \"돌진\",\n english: \"Lunge\",\n };\n }\n\n /**\n * Gets bilingual Korean-English name for recovery phase.\n *\n * **Korean**: 복귀 단계 이름 (Recovery Phase Name)\n *\n * @returns Korean and English phase names\n *\n * @public\n * @korean 복귀단계이름\n */\n getRecoveryPhaseName(): { korean: string; english: string } {\n return {\n korean: \"복귀\",\n english: \"Recovery\",\n };\n }\n}\n\nexport default AttackMovementPhysics;\n"],"mappings":"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"}
1
+ {"version":3,"file":"AttackMovementPhysics.js","names":[],"sources":["../../../src/systems/physics/AttackMovementPhysics.ts"],"sourcesContent":["/**\n * Attack Movement Physics System for realistic forward momentum during attacks.\n *\n * **Korean**: 공격 이동 물리 시스템 (Attack Movement Physics System)\n *\n * Implements realistic forward movement physics when fighters execute attacks.\n * Kicks naturally extend the body forward, punches lunge with body weight,\n * and spinning techniques carry rotational momentum.\n *\n * ## Attack Movement Mechanics\n *\n * Forward movement distance is determined by:\n * - Animation type (kicks > punches > elbows/knees)\n * - Stance modifiers (8 trigram stances affect aggression)\n * - Animation phase timing (extension → peak → recovery)\n *\n * ## Movement Integration\n *\n * - Extension phase (frames 3-6): Forward lunge with ease-out\n * - Peak phase (frame 6): Maximum extension reached\n * - Recovery phase (frames 7-10): Return to original stance\n * - Arena bounds: Automatically clamp movement to valid zone\n *\n * ## Performance\n *\n * Optimized for 60fps with efficient vector calculations and\n * smooth easing curves for realistic attack momentum feel.\n *\n * @module systems/physics/AttackMovementPhysics\n * @category Physics System\n * @korean 공격이동물리\n */\n\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"@/types/common\";\nimport { AnimationType } from \"@/systems/animation\";\n\n/**\n * Configuration for attack movement calculation.\n *\n * **Korean**: 공격 이동 설정 (Attack Movement Configuration)\n *\n * @public\n * @korean 공격이동설정\n */\nexport interface AttackMovementConfig {\n /** Animation type being executed */\n readonly animationType: AnimationType;\n /** Current trigram stance */\n readonly currentStance: TrigramStance;\n /** Normalized attack direction vector (attacker → target) */\n readonly direction: THREE.Vector3;\n /** Total attack animation duration in seconds */\n readonly animationDuration: number;\n}\n\n/**\n * Result of attack movement calculation.\n *\n * **Korean**: 공격 이동 결과 (Attack Movement Result)\n *\n * Contains displacement vector and timing for attack lunge.\n *\n * @public\n * @korean 공격이동결과\n */\nexport interface AttackMovementResult {\n /** Total forward displacement vector in world space */\n readonly displacement: THREE.Vector3;\n /** Duration of forward lunge phase in seconds */\n readonly lungeDuration: number;\n /** Duration of recovery return phase in seconds */\n readonly recoveryDuration: number;\n /** Total movement cycle duration in seconds */\n readonly totalDuration: number;\n}\n\n/**\n * Attack Movement Physics Engine.\n *\n * **Korean**: 공격 이동 물리 엔진\n *\n * Calculates realistic forward movement during attack animations\n * based on technique type, stance modifiers, and animation timing.\n * Provides smooth lunge and recovery phases for authentic martial arts feel.\n *\n * @example\n * ```typescript\n * const physics = new AttackMovementPhysics();\n *\n * // Calculate movement for roundhouse kick\n * const config: AttackMovementConfig = {\n * animationType: AnimationType.ROUNDHOUSE_KICK,\n * currentStance: TrigramStance.LI, // Fire stance (aggressive)\n * direction: new THREE.Vector3(1, 0, 0).normalize(),\n * animationDuration: 0.48, // 480ms kick animation\n * };\n *\n * const result = physics.calculateAttackMovement(config);\n * // Result: 1.0m base * 1.3 (Fire bonus) = ~1.3m forward lunge\n * // lungeDuration: 0.24s (50% of animation)\n * // recoveryDuration: 0.24s (50% of animation)\n * ```\n *\n * @public\n * @korean 공격이동물리\n */\nexport class AttackMovementPhysics {\n /**\n * Calculates attack movement displacement and timing.\n *\n * **Korean**: 공격 이동 계산 (Calculate Attack Movement)\n *\n * Determines forward lunge distance and recovery timing based on:\n * 1. Base movement from animation type\n * 2. Stance movement modifier (8 trigram effects)\n * 3. Animation phase durations (lunge vs recovery)\n *\n * @param config - Attack movement configuration\n * @returns Attack movement result with displacement and timing\n *\n * @example\n * ```typescript\n * // Front kick with Heaven stance\n * const kick = physics.calculateAttackMovement({\n * animationType: AnimationType.FRONT_KICK,\n * currentStance: TrigramStance.GEON,\n * direction: attackVector,\n * animationDuration: 0.4,\n * });\n * // Result: ~0.88m forward (0.8m * 1.1 Geon modifier)\n *\n * // Jab with Mountain stance\n * const jab = physics.calculateAttackMovement({\n * animationType: AnimationType.JAB,\n * currentStance: TrigramStance.GAN,\n * direction: attackVector,\n * animationDuration: 0.25,\n * });\n * // Result: ~0.24m forward (0.3m * 0.8 Gan modifier)\n * ```\n *\n * @public\n * @korean 공격이동계산\n */\n calculateAttackMovement(\n config: AttackMovementConfig\n ): AttackMovementResult {\n // 1. Get base movement distance for animation type\n const baseDistance = this.getBaseMovementDistance(config.animationType);\n\n // 2. Apply stance movement modifier\n const stanceModifier = this.getStanceMovementModifier(config.currentStance);\n const finalDistance = baseDistance * stanceModifier;\n\n // 3. Calculate displacement vector\n const displacement = config.direction.clone().multiplyScalar(finalDistance);\n\n // 4. Calculate phase durations (lunge + recovery)\n const lungeDuration = config.animationDuration * 0.5; // First 50% = forward\n const recoveryDuration = config.animationDuration * 0.5; // Last 50% = return\n\n return {\n displacement,\n lungeDuration,\n recoveryDuration,\n totalDuration: config.animationDuration,\n };\n }\n\n /**\n * Gets base forward movement distance for animation type.\n *\n * **Korean**: 기본 이동 거리 (Base Movement Distance)\n *\n * Movement distances by technique category:\n * - Kicks: 0.8-1.2m (leg extension, longest reach)\n * - Punches: 0.3-0.5m (arm extension, body lunge)\n * - Elbows/Knees: 0.2-0.3m (close range techniques)\n * - Spinning: 0.5-0.8m (rotation carries momentum)\n *\n * @param animationType - Type of attack animation\n * @returns Base movement distance in meters\n *\n * @private\n * @korean 기본이동거리\n */\n private getBaseMovementDistance(animationType: AnimationType): number {\n // Kicks - highest forward movement (leg extension)\n if (\n animationType === AnimationType.ROUNDHOUSE_KICK ||\n animationType === AnimationType.SIDE_KICK ||\n animationType === AnimationType.BACK_KICK\n ) {\n return 1.0; // 1.0m forward lunge\n }\n\n if (\n animationType === AnimationType.FRONT_KICK ||\n animationType === AnimationType.PUSH_KICK\n ) {\n return 0.8; // 0.8m forward lunge\n }\n\n if (\n animationType === AnimationType.AXE_KICK ||\n animationType === AnimationType.CRESCENT_KICK ||\n animationType === AnimationType.LOW_KICK\n ) {\n return 0.9; // 0.9m forward lunge\n }\n\n // Spinning kicks - momentum carries body forward\n if (\n animationType === AnimationType.SPINNING_HOOK ||\n animationType === AnimationType.SPINNING_HEEL_KICK ||\n animationType === AnimationType.TORNADO_KICK\n ) {\n return 0.8; // 0.8m with rotation\n }\n\n // Jumping/flying kicks - aerial momentum\n if (\n animationType === AnimationType.FLYING_KICK ||\n animationType === AnimationType.JUMPING_KICK\n ) {\n return 1.2; // 1.2m maximum extension\n }\n\n // Punches - moderate forward movement (arm + body lunge)\n if (\n animationType === AnimationType.CROSS ||\n animationType === AnimationType.OVERHAND ||\n animationType === AnimationType.HOOK\n ) {\n return 0.5; // 0.5m forward lunge\n }\n\n if (\n animationType === AnimationType.JAB ||\n animationType === AnimationType.BACKFIST\n ) {\n return 0.3; // 0.3m forward lunge\n }\n\n if (\n animationType === AnimationType.UPPERCUT ||\n animationType === AnimationType.HAMMER_FIST ||\n animationType === AnimationType.PALM_STRIKE\n ) {\n return 0.4; // 0.4m forward lunge\n }\n\n // Elbow/Knee - minimal movement (close range)\n if (\n animationType === AnimationType.ELBOW_STRIKE ||\n animationType === AnimationType.KNEE_STRIKE ||\n animationType === AnimationType.KNEE_KICK ||\n animationType === AnimationType.ELBOW_UPPERCUT ||\n animationType === AnimationType.FLYING_KNEE ||\n animationType === AnimationType.CLINCH_KNEE ||\n animationType === AnimationType.SPINNING_ELBOW ||\n animationType === AnimationType.TEMPLE_ELBOW ||\n animationType === AnimationType.SPINNING_BACK_ELBOW ||\n animationType === AnimationType.SPINAL_ELBOW ||\n animationType === AnimationType.BRACHIAL_ELBOW ||\n animationType === AnimationType.KIDNEY_KNEE ||\n animationType === AnimationType.FEMORAL_KNEE\n ) {\n return 0.2; // 0.2m forward lunge\n }\n\n // Specialized jab variants - similar to jab (quick extension)\n if (\n animationType === AnimationType.SPEAR_HAND_STRIKE ||\n animationType === AnimationType.NERVE_STRIKE ||\n animationType === AnimationType.PRESSURE_POINT_STRIKE ||\n animationType === AnimationType.LIGHTNING_STRIKE ||\n animationType === AnimationType.RAPID_BARRAGE ||\n animationType === AnimationType.RHYTHMIC_STRIKES ||\n animationType === AnimationType.NERVE_PARALYSIS ||\n animationType === AnimationType.THROAT_STRIKE ||\n animationType === AnimationType.EYE_GOUGE\n ) {\n return 0.3; // 0.3m forward lunge (jab-like)\n }\n\n // Specialized cross variants - similar to cross (body weight transfer)\n if (\n animationType === AnimationType.HEAVEN_STRIKE ||\n animationType === AnimationType.FLOWING_CROSS\n ) {\n return 0.5; // 0.5m forward lunge (cross-like)\n }\n\n // Specialized palm strikes - similar to palm strike\n if (\n animationType === AnimationType.SOLAR_PLEXUS_STRIKE ||\n animationType === AnimationType.FLOWING_PUSH ||\n animationType === AnimationType.LIVER_DISRUPTION ||\n animationType === AnimationType.EAR_STRIKE\n ) {\n return 0.4; // 0.4m forward lunge (palm-like)\n }\n\n // Grappling - minimal forward movement (rotation/off-balancing in place)\n if (\n animationType === AnimationType.THROW ||\n animationType === AnimationType.JOINT_LOCK ||\n animationType === AnimationType.TAKEDOWN ||\n animationType === AnimationType.SWEEP ||\n animationType === AnimationType.CLINCH ||\n animationType === AnimationType.GRAPPLE ||\n animationType === AnimationType.SLAM ||\n animationType === AnimationType.WRIST_LOCK ||\n animationType === AnimationType.ARM_BAR ||\n animationType === AnimationType.SHOULDER_LOCK ||\n animationType === AnimationType.HIP_THROW ||\n animationType === AnimationType.LEG_REAP ||\n animationType === AnimationType.SMALL_CIRCLE_LOCK ||\n animationType === AnimationType.FINGER_LOCK ||\n animationType === AnimationType.ELBOW_LOCK ||\n animationType === AnimationType.SHOULDER_MANIPULATION ||\n animationType === AnimationType.MOUNTAIN_LOCK ||\n animationType === AnimationType.EARTH_EMBRACE ||\n animationType === AnimationType.CAROTID_CHOKE\n ) {\n return 0.05; // 0.05m forward movement for grappling entries\n }\n\n // Default - conservative minimal movement for any unmapped types\n // Additional AnimationType values can be grouped with the closest category above\n // to fine-tune their forward displacement without changing overall behavior\n return 0.2;\n }\n\n /**\n * Gets stance movement modifier for attack lunge.\n *\n * **Korean**: 자세 이동 배율 (Stance Movement Modifier)\n *\n * Trigram stance movement modifiers based on combat philosophy:\n * - ☰ 건 (Geon/Heaven): +30% movement (aggressive, drives forward with pure yang)\n * - ☳ 진 (Jin/Thunder): +20% movement (explosive power, shocking force)\n * - ☴ 손 (Son/Wind): +15% movement (continuous flow, mobile)\n * - ☲ 리 (Li/Fire): +10% movement (precision strikes, controlled aggression)\n * - ☱ 태 (Tae/Lake): 0% (neutral, fluid and adaptive)\n * - ☵ 감 (Gam/Water): 0% (neutral, flowing and flexible)\n * - ☷ 곤 (Gon/Earth): -10% movement (grounded, stable techniques)\n * - ☶ 간 (Gan/Mountain): -20% movement (defensive mastery, minimal advance)\n *\n * @param stance - Current trigram stance\n * @returns Movement multiplier (0.8 to 1.3)\n *\n * @private\n * @korean 자세이동배율\n */\n private getStanceMovementModifier(stance: TrigramStance): number {\n const modifiers: Record<TrigramStance, number> = {\n [TrigramStance.GEON]: 1.3, // Heaven: +30% aggressive forward pressure (pure yang drives forward)\n [TrigramStance.JIN]: 1.2, // Thunder: +20% explosive movement\n [TrigramStance.SON]: 1.15, // Wind: +15% flowing movement\n [TrigramStance.LI]: 1.1, // Fire: +10% precision strikes with controlled forward movement\n [TrigramStance.TAE]: 1.0, // Lake: neutral movement\n [TrigramStance.GAM]: 1.0, // Water: neutral movement\n [TrigramStance.GON]: 0.9, // Earth: -10% grounded movement\n [TrigramStance.GAN]: 0.8, // Mountain: -20% defensive movement\n };\n return modifiers[stance];\n }\n\n /**\n * Applies attack movement force to attacker position over time.\n *\n * **Korean**: 공격 이동 적용 (Apply Attack Movement)\n *\n * Uses smooth ease-out cubic curve for realistic lunge feel:\n * - Fast initial forward movement (attack commitment)\n * - Gradual deceleration at peak extension\n * - Smooth return during recovery phase\n *\n * @param basePosition - Original stance position (does not change during attack)\n * @param result - Attack movement result with displacement\n * @param elapsedTime - Time elapsed since attack started\n * @param isRecoveryPhase - Whether in recovery (return) phase\n * @returns New position with attack displacement applied\n *\n * @example\n * ```typescript\n * // In animation update loop (60fps)\n * const basePos = new THREE.Vector3(0, 0, 0); // Original stance position\n * \n * if (elapsedTime < result.lungeDuration) {\n * // Lunge forward phase\n * const newPosition = physics.applyAttackMovement(\n * basePos,\n * result,\n * elapsedTime,\n * false\n * );\n * } else if (elapsedTime < result.totalDuration) {\n * // Recovery return phase\n * const newPosition = physics.applyAttackMovement(\n * basePos,\n * result,\n * elapsedTime,\n * true\n * );\n * }\n * ```\n *\n * @public\n * @korean 공격이동적용\n */\n applyAttackMovement(\n basePosition: THREE.Vector3,\n result: AttackMovementResult,\n elapsedTime: number,\n isRecoveryPhase: boolean\n ): THREE.Vector3 {\n let progress: number;\n\n if (!isRecoveryPhase) {\n // Lunge forward phase\n progress = Math.min(1.0, elapsedTime / result.lungeDuration);\n\n // Smooth forward lunge curve (ease-out cubic)\n // Fast initial commitment, gradual deceleration at peak\n const easedProgress = 1 - Math.pow(1 - progress, 3);\n\n // Calculate position along forward path\n const currentDisplacement = result.displacement\n .clone()\n .multiplyScalar(easedProgress);\n\n return basePosition.clone().add(currentDisplacement);\n } else {\n // Recovery return phase\n const recoveryTime = elapsedTime - result.lungeDuration;\n progress = Math.min(1.0, recoveryTime / result.recoveryDuration);\n\n // Smooth return curve (ease-in cubic)\n // Gradual start, faster finish back to stance\n const easedProgress = Math.pow(progress, 3);\n\n // Calculate return position (from peak back to origin)\n const returnDisplacement = result.displacement\n .clone()\n .multiplyScalar(1.0 - easedProgress);\n\n return basePosition.clone().add(returnDisplacement);\n }\n }\n\n /**\n * Checks if attacker is currently in lunge phase.\n *\n * **Korean**: 돌진 상태 확인 (Check Lunge State)\n *\n * @param elapsedTime - Time elapsed since attack started\n * @param lungeDuration - Total lunge phase duration\n * @returns True if in forward lunge phase\n *\n * @public\n * @korean 돌진상태확인\n */\n isInLungePhase(elapsedTime: number, lungeDuration: number): boolean {\n return elapsedTime < lungeDuration;\n }\n\n /**\n * Checks if attacker is in recovery return phase.\n *\n * **Korean**: 회복 상태 확인 (Check Recovery State)\n *\n * @param elapsedTime - Time elapsed since attack started\n * @param result - Attack movement result with timing\n * @returns True if in recovery return phase\n *\n * @public\n * @korean 회복상태확인\n */\n isInRecoveryPhase(\n elapsedTime: number,\n result: AttackMovementResult\n ): boolean {\n return (\n elapsedTime >= result.lungeDuration &&\n elapsedTime < result.totalDuration\n );\n }\n\n /**\n * Gets bilingual Korean-English name for lunge phase.\n *\n * **Korean**: 돌진 단계 이름 (Lunge Phase Name)\n *\n * @returns Korean and English phase names\n *\n * @public\n * @korean 돌진단계이름\n */\n getLungePhaseName(): { korean: string; english: string } {\n return {\n korean: \"돌진\",\n english: \"Lunge\",\n };\n }\n\n /**\n * Gets bilingual Korean-English name for recovery phase.\n *\n * **Korean**: 복귀 단계 이름 (Recovery Phase Name)\n *\n * @returns Korean and English phase names\n *\n * @public\n * @korean 복귀단계이름\n */\n getRecoveryPhaseName(): { korean: string; english: string } {\n return {\n korean: \"복귀\",\n english: \"Recovery\",\n };\n }\n}\n\nexport default AttackMovementPhysics;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CollisionDetection.js","names":[],"sources":["../../../src/systems/physics/CollisionDetection.ts"],"sourcesContent":["/**\n * Collision Detection System for Black Trigram combat physics.\n *\n * **Korean**: 충돌 감지 시스템\n *\n * Implements precise collision detection for the 70 vital points combat system using:\n * - Broad-phase AABB (Axis-Aligned Bounding Box) checks for performance\n * - Narrow-phase raycasting for precise vital point detection\n * - Attack reach validation based on technique and stance\n * - Bounding boxes for 5 anatomical regions\n *\n * ## Architecture\n *\n * The system uses a two-phase collision detection approach:\n *\n * 1. **Broad-phase**: Fast AABB checks to eliminate impossible collisions\n * 2. **Narrow-phase**: Precise raycasting to identify specific vital points\n *\n * This ensures 60fps performance even with complex collision queries.\n *\n * ## Performance\n *\n * - Broad-phase: O(1) per check (simple distance comparison)\n * - Narrow-phase: O(n) where n = vital points in region (typically 8-20)\n * - Target: <100 collision checks per frame for 60fps\n *\n * @example\n * ```typescript\n * const collision = new CollisionDetection();\n *\n * const result = collision.checkAttackHit(\n * { x: 0, y: 0, z: 5 }, // Attacker position\n * { x: 0, y: 0, z: 6.5 }, // Defender position\n * { id: \"kick\", type: \"kick\" }, // Technique\n * TrigramStance.LI, // Attacker stance\n * \"torso\" // Target region\n * );\n *\n * if (result.hit) {\n * console.log(`Hit ${result.vitalPoint?.names.english}!`);\n * console.log(`Accuracy: ${(result.accuracy * 100).toFixed(1)}%`);\n * }\n * ```\n *\n * @module systems/physics/CollisionDetection\n * @category Combat Systems\n * @korean 충돌감지시스템\n */\n\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../types/common\";\nimport type {\n AnatomicalRegionPhysics,\n BoundingBox,\n CollisionResult,\n Position3D,\n RaycastQuery,\n TechniqueType,\n} from \"../../types/physics\";\nimport {\n ANATOMICAL_DIMENSIONS,\n BASE_REACH,\n STANCE_REACH_MODIFIERS,\n} from \"../../types/physics\";\nimport type { VitalPoint } from \"../vitalpoint/types\";\nimport { VITAL_POINTS_DATA } from \"../vitalpoint/VitalPointsData\";\nimport { CoordinateMapper } from \"./CoordinateMapper\";\nimport { ThreeObjectPools } from \"../../utils/threeObjectPool\";\n\n/**\n * Collision Detection Engine for combat physics.\n *\n * **Korean**: 충돌 감지 엔진\n *\n * Provides efficient collision detection for combat using bounding boxes and raycasting.\n * Optimized for 60fps performance with multiple simultaneous collision checks.\n *\n * @public\n * @category Combat Systems\n * @korean 충돌감지엔진\n */\nexport class CollisionDetection {\n private readonly boundingBoxes: Map<AnatomicalRegionPhysics, BoundingBox> =\n new Map();\n private readonly raycaster: THREE.Raycaster = new THREE.Raycaster();\n private vitalPointsByRegion: Map<AnatomicalRegionPhysics, VitalPoint[]> =\n new Map();\n\n // Geometry cache for object pooling to avoid repeated allocations during combat\n private readonly geometryCache: Map<string, THREE.BufferGeometry> = new Map();\n\n // Coordinate mapper for 2D→3D conversion\n private readonly coordinateMapper: CoordinateMapper;\n\n /**\n * Creates a new CollisionDetection instance.\n *\n * Initializes bounding boxes for all anatomical regions and organizes\n * vital points by region for efficient lookup.\n */\n constructor() {\n this.coordinateMapper = new CoordinateMapper();\n this.initializeBoundingBoxes();\n this.organizeVitalPointsByRegion();\n this.initializeGeometryCache();\n }\n\n /**\n * Cleans up Three.js resources.\n *\n * **Korean**: 자원 정리\n *\n * Disposes of cached geometries and releases memory to prevent leaks.\n * Should be called when the CollisionDetection instance is no longer needed.\n *\n * @public\n * @korean 자원정리\n */\n public dispose(): void {\n // Dispose all cached geometries\n for (const geometry of this.geometryCache.values()) {\n geometry.dispose();\n }\n this.geometryCache.clear();\n }\n\n /**\n * Checks if an attack hits the defender.\n *\n * **Korean**: 공격 타격 확인\n *\n * Performs two-phase collision detection:\n * 1. Calculate effective attack reach based on technique and stance\n * 2. Broad-phase: Check if defender is within reach\n * 3. Broad-phase: Check if target region's AABB is within reach\n * 4. Narrow-phase: Raycast to find precise vital point hit\n *\n * @param attackerPosition - 3D position of the attacker\n * @param defenderPosition - 3D position of the defender\n * @param technique - Technique being used with type information\n * @param attackerStance - Attacker's current trigram stance\n * @param targetRegion - Anatomical region being targeted\n * @returns Collision result with hit status, vital point, distance, and accuracy\n *\n * @example\n * ```typescript\n * const result = collision.checkAttackHit(\n * { x: 0, y: 0, z: 5 },\n * { x: 0, y: 0, z: 6 },\n * { type: \"punch\" },\n * TrigramStance.GEON,\n * \"head\"\n * );\n * ```\n *\n * @public\n * @korean 공격타격확인\n */\n checkAttackHit(\n attackerPosition: Position3D,\n defenderPosition: Position3D,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Technique object has dynamic properties from combat system\n technique: { type?: string; [key: string]: any },\n attackerStance: TrigramStance,\n targetRegion: AnatomicalRegionPhysics,\n ): CollisionResult {\n // Calculate effective attack reach\n const techniqueType = this.parseTechniqueType(technique.type);\n const attackReach = this.calculateAttackReach(\n techniqueType,\n attackerStance,\n );\n\n // Calculate distance between attacker and defender\n const distance = this.calculateDistance3D(\n attackerPosition,\n defenderPosition,\n );\n\n // Broad-phase: Check if defender is within reach\n if (distance > attackReach.effectiveReach) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Get target region bounding box\n const targetBox = this.boundingBoxes.get(targetRegion);\n if (!targetBox) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Narrow-phase: Raycast from attacker to target region\n const attackDirection = this.normalizeVector3D(\n this.subtractVectors3D(defenderPosition, attackerPosition),\n );\n\n const raycastQuery: RaycastQuery = {\n origin: attackerPosition,\n direction: attackDirection,\n maxDistance: attackReach.effectiveReach,\n targetRegion,\n };\n\n // Check intersection with target bounding box\n const intersection = this.raycastBoundingBox(\n raycastQuery,\n targetBox,\n defenderPosition,\n );\n\n if (!intersection) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Determine specific vital point within region\n const vitalPoint = this.identifyVitalPoint(\n targetRegion,\n intersection.point,\n defenderPosition,\n );\n\n if (!vitalPoint) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Calculate accuracy (how close to vital point center)\n const accuracy = this.calculateHitAccuracy(\n intersection.point,\n vitalPoint,\n defenderPosition,\n );\n\n return {\n hit: true,\n region: targetRegion,\n vitalPoint,\n distance,\n accuracy,\n hitPoint: intersection.point,\n };\n }\n\n /**\n * Calculates effective attack reach for a technique.\n *\n * **Korean**: 공격 범위 계산\n *\n * Applies stance modifiers to base technique reach.\n *\n * @param techniqueType - Type of technique\n * @param stance - Current trigram stance\n * @returns Attack reach with all modifiers applied\n *\n * @private\n * @korean 공격범위계산\n */\n private calculateAttackReach(\n techniqueType: TechniqueType,\n stance: TrigramStance,\n ): {\n technique: TechniqueType;\n baseReach: number;\n stance: TrigramStance;\n stanceModifier: number;\n effectiveReach: number;\n } {\n // Use BASE_REACH constant from physics types\n const baseReach = BASE_REACH[techniqueType];\n\n // Use STANCE_REACH_MODIFIERS constant from physics types\n const stanceModifier = STANCE_REACH_MODIFIERS[stance];\n const effectiveReach = baseReach * stanceModifier;\n\n return {\n technique: techniqueType,\n baseReach,\n stance,\n stanceModifier,\n effectiveReach,\n };\n }\n\n /**\n * Performs raycasting against a bounding box.\n *\n * **Korean**: 경계 상자 광선 투사\n *\n * Uses cached geometries from object pool to avoid repeated allocations\n * during combat. Creates a Three.js mesh for the bounding box and performs\n * raycasting to detect intersection points.\n *\n * Uses object pooling for Vector3 allocations to reduce GC pressure.\n *\n * @param query - Raycast query parameters\n * @param box - Bounding box to test\n * @param defenderPosition - Position of the defender\n * @returns Intersection point or null if no hit\n *\n * @private\n * @korean 경계상자광선투사\n */\n private raycastBoundingBox(\n query: RaycastQuery,\n box: BoundingBox,\n defenderPosition: Position3D,\n ): { point: Position3D } | null {\n // Get cached geometry from pool to avoid repeated allocations\n const cacheKey = `${box.type}-${box.region}`;\n let geometry = this.geometryCache.get(cacheKey);\n\n // If not cached (shouldn't happen after initialization), create it\n if (!geometry) {\n geometry = this.createGeometryForBox(box);\n this.geometryCache.set(cacheKey, geometry);\n }\n\n // Create temporary mesh for raycasting (mesh is lightweight, geometry is cached)\n const mesh = new THREE.Mesh(geometry);\n mesh.position.set(\n defenderPosition.x + box.center.x,\n defenderPosition.y + box.center.y,\n defenderPosition.z + box.center.z,\n );\n\n // Setup raycaster using pooled Vector3 objects to reduce GC pressure\n // Wrap in try-finally to ensure pooled objects are always released\n const origin = ThreeObjectPools.vector3.acquire();\n const direction = ThreeObjectPools.vector3.acquire();\n\n try {\n origin.set(query.origin.x, query.origin.y, query.origin.z);\n direction.set(query.direction.x, query.direction.y, query.direction.z);\n\n this.raycaster.set(origin, direction);\n this.raycaster.far = query.maxDistance;\n\n // Perform raycast\n const intersections = this.raycaster.intersectObject(mesh);\n\n // Clean up temporary mesh (geometry remains cached)\n // Note: mesh.material is undefined, no need to dispose\n\n if (intersections.length > 0) {\n const point = intersections[0].point;\n return {\n point: { x: point.x, y: point.y, z: point.z },\n };\n }\n\n return null;\n } finally {\n // Release pooled objects back to pool (guaranteed even if exception occurs)\n ThreeObjectPools.vector3.release(origin);\n ThreeObjectPools.vector3.release(direction);\n }\n }\n\n /**\n * Creates Three.js geometry for a bounding box.\n *\n * Helper method for geometry cache initialization.\n *\n * @param box - Bounding box specification\n * @returns Three.js geometry\n *\n * @private\n * @korean 경계상자지오메트리생성\n */\n private createGeometryForBox(box: BoundingBox): THREE.BufferGeometry {\n switch (box.type) {\n case \"sphere\":\n return new THREE.SphereGeometry(box.dimensions.x, 8, 8);\n case \"box\":\n return new THREE.BoxGeometry(\n box.dimensions.x,\n box.dimensions.y,\n box.dimensions.z,\n );\n case \"capsule\":\n return new THREE.CapsuleGeometry(\n box.dimensions.x,\n box.dimensions.y,\n 4,\n 8,\n );\n }\n }\n\n /**\n * Identifies the specific vital point hit within a region.\n *\n * **Korean**: 급소 식별\n *\n * Finds the closest vital point to the hit location within the targeted region.\n *\n * @param region - Anatomical region hit\n * @param hitPoint - 3D point where attack intersected\n * @param defenderPosition - Position of the defender\n * @returns Closest vital point or null if none found\n *\n * @private\n * @korean 급소식별\n */\n private identifyVitalPoint(\n region: AnatomicalRegionPhysics,\n hitPoint: Position3D,\n _defenderPosition: Position3D, // Prefixed with _ to indicate intentionally unused\n ): VitalPoint | null {\n // Get all vital points for this region\n const vitalPoints = this.vitalPointsByRegion.get(region);\n if (!vitalPoints || vitalPoints.length === 0) {\n return null;\n }\n\n // Use CoordinateMapper to find the closest vital point in 3D space\n const result = this.coordinateMapper.findClosestVitalPoint(\n hitPoint,\n vitalPoints,\n region,\n );\n\n // For collision detection, we accept any vital point in the region\n // even if it's not very close, as long as the region was hit\n // This provides more forgiving collision detection while still\n // using accurate 3D positioning for scoring/accuracy\n return result\n ? result.vitalPoint\n : vitalPoints.length > 0\n ? vitalPoints[0]\n : null;\n }\n\n /**\n * Calculates hit accuracy based on distance to vital point center.\n *\n * **Korean**: 타격 정확도 계산\n *\n * Accuracy decreases linearly with distance from vital point center.\n * Perfect accuracy (1.0) at center, decreasing to 0 at 5cm radius.\n *\n * @param hitPoint - Point where attack landed\n * @param vitalPoint - Target vital point\n * @param defenderPosition - Position of the defender\n * @returns Accuracy value from 0 to 1\n *\n * @private\n * @korean 타격정확도계산\n */\n private calculateHitAccuracy(\n hitPoint: Position3D,\n vitalPoint: VitalPoint,\n _defenderPosition: Position3D, // Prefixed with _ to indicate intentionally unused\n ): number {\n // Use CoordinateMapper to convert vital point to 3D and calculate distance\n const distance = this.coordinateMapper.distanceToVitalPoint(\n hitPoint,\n vitalPoint,\n );\n\n // Accuracy calculation: Perfect (1.0) at center, decreasing to 0 at 5cm radius\n const maxDistance = 0.05; // 5cm radius for vital points\n const accuracy = Math.max(0, 1 - distance / maxDistance);\n\n return accuracy;\n }\n\n /**\n * Calculates Euclidean distance between two 3D points.\n *\n * @param pos1 - First position\n * @param pos2 - Second position\n * @returns Distance in meters\n *\n * @private\n * @korean 3D거리계산\n */\n private calculateDistance3D(pos1: Position3D, pos2: Position3D): number {\n const dx = pos1.x - pos2.x;\n const dy = pos1.y - pos2.y;\n const dz = pos1.z - pos2.z;\n return Math.sqrt(dx * dx + dy * dy + dz * dz);\n }\n\n /**\n * Subtracts one 3D vector from another.\n *\n * @param v1 - First vector\n * @param v2 - Second vector\n * @returns Resulting vector\n *\n * @private\n * @korean 벡터빼기\n */\n private subtractVectors3D(v1: Position3D, v2: Position3D): Position3D {\n return {\n x: v1.x - v2.x,\n y: v1.y - v2.y,\n z: v1.z - v2.z,\n };\n }\n\n /**\n * Normalizes a 3D vector to unit length.\n *\n * @param vec - Vector to normalize\n * @returns Normalized vector\n *\n * @private\n * @korean 벡터정규화\n */\n private normalizeVector3D(vec: Position3D): Position3D {\n const length = Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);\n if (length === 0) {\n return { x: 0, y: 0, z: 1 }; // Default forward direction\n }\n return {\n x: vec.x / length,\n y: vec.y / length,\n z: vec.z / length,\n };\n }\n\n /**\n * Parses technique type from string.\n *\n * @param techniqueTypeStr - Technique type as string\n * @returns Parsed technique type\n *\n * @private\n * @korean 기술유형파싱\n */\n private parseTechniqueType(techniqueTypeStr?: string): TechniqueType {\n const typeMap: Record<string, TechniqueType> = {\n punch: \"punch\",\n kick: \"kick\",\n elbow: \"elbow\",\n knee: \"knee\",\n pressure_point: \"pressure_point\",\n strike: \"punch\", // Default strike to punch\n };\n\n return typeMap[techniqueTypeStr ?? \"punch\"] ?? \"punch\";\n }\n\n /**\n * Initializes bounding boxes for all anatomical regions.\n *\n * Creates collision volumes for the 5 anatomical regions using the\n * ANATOMICAL_DIMENSIONS constants from the physics types module.\n *\n * @private\n * @korean 경계상자초기화\n */\n private initializeBoundingBoxes(): void {\n // Head: Sphere (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"head\", {\n type: ANATOMICAL_DIMENSIONS.head.type,\n center: ANATOMICAL_DIMENSIONS.head.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.head.radius,\n y: 0,\n z: 0,\n }, // radius only\n region: \"head\",\n });\n\n // Neck: Capsule (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"neck\", {\n type: ANATOMICAL_DIMENSIONS.neck.type,\n center: ANATOMICAL_DIMENSIONS.neck.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.neck.radius,\n y: ANATOMICAL_DIMENSIONS.neck.height,\n z: 0,\n }, // radius and height\n region: \"neck\",\n });\n\n // Torso: Box (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"torso\", {\n type: ANATOMICAL_DIMENSIONS.torso.type,\n center: ANATOMICAL_DIMENSIONS.torso.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.torso.width,\n y: ANATOMICAL_DIMENSIONS.torso.height,\n z: ANATOMICAL_DIMENSIONS.torso.depth,\n }, // width, height, depth\n region: \"torso\",\n });\n\n // Arms: Capsules (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"arms\", {\n type: ANATOMICAL_DIMENSIONS.arms.type,\n center: ANATOMICAL_DIMENSIONS.arms.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.arms.radius,\n y: ANATOMICAL_DIMENSIONS.arms.height,\n z: 0,\n }, // radius and length\n region: \"arms\",\n });\n\n // Legs: Capsules (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"legs\", {\n type: ANATOMICAL_DIMENSIONS.legs.type,\n center: ANATOMICAL_DIMENSIONS.legs.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.legs.radius,\n y: ANATOMICAL_DIMENSIONS.legs.height,\n z: 0,\n }, // radius and length\n region: \"legs\",\n });\n }\n\n /**\n * Initializes geometry cache for object pooling.\n *\n * Pre-creates all geometries needed for raycasting to avoid repeated\n * allocations during combat. Critical for maintaining 60fps with up to\n * 100 collision checks per frame.\n *\n * @private\n * @korean 지오메트리캐시초기화\n */\n private initializeGeometryCache(): void {\n // Pre-create and cache geometries for all bounding boxes\n for (const box of this.boundingBoxes.values()) {\n const cacheKey = `${box.type}-${box.region}`;\n const geometry = this.createGeometryForBox(box);\n this.geometryCache.set(cacheKey, geometry);\n }\n }\n\n /**\n * Organizes vital points by anatomical region for efficient lookup.\n *\n * NOTE: This categorization currently uses y-coordinate thresholds that assume\n * positions are in meters. However, VITAL_POINTS_DATA uses pixel coordinates\n * (e.g., y: 50), which will cause incorrect categorization. Most vital points\n * will end up in the \"legs\" region since pixel y-coordinates are typically less\n * than 0.8.\n *\n * TODO: After implementing 2D→3D coordinate mapping, update this method to use\n * the converted 3D positions, or use the anatomical region data that may already\n * exist in the vital points data structure.\n *\n * Categorizes the 70 vital points into their respective regions:\n * - Head: 10 vital points\n * - Neck: 8 vital points\n * - Torso: 20 vital points\n * - Arms: 16 vital points\n * - Legs: 16 vital points\n *\n * @private\n * @korean 급소영역별정리\n */\n private organizeVitalPointsByRegion(): void {\n const regionMap: Map<AnatomicalRegionPhysics, VitalPoint[]> = new Map([\n [\"head\", []],\n [\"neck\", []],\n [\"torso\", []],\n [\"arms\", []],\n [\"legs\", []],\n ]);\n\n // Use existing VitalPointsData categorization by ID prefix\n // This is more reliable than coordinate-based heuristics\n for (const vp of VITAL_POINTS_DATA) {\n let region: AnatomicalRegionPhysics;\n\n // Categorize by ID prefix (existing VitalPointsData convention)\n // Check neck-related patterns first (more specific) before head (broader)\n if (\n vp.id.includes(\"_neck\") ||\n vp.id.includes(\"_throat\") ||\n vp.id === \"head_side_neck\" ||\n vp.id === \"head_throat\"\n ) {\n region = \"neck\";\n } else if (vp.id.startsWith(\"head_\")) {\n region = \"head\";\n } else if (vp.id.startsWith(\"torso_\")) {\n region = \"torso\";\n } else if (\n vp.id.startsWith(\"arm_left_\") ||\n vp.id.startsWith(\"arm_right_\")\n ) {\n region = \"arms\";\n } else if (\n vp.id.startsWith(\"leg_left_\") ||\n vp.id.startsWith(\"leg_right_\")\n ) {\n region = \"legs\";\n } else {\n // Default to torso for uncategorized points (core region)\n region = \"torso\";\n }\n\n const list = regionMap.get(region);\n if (list) {\n list.push(vp);\n }\n }\n\n this.vitalPointsByRegion = regionMap;\n }\n\n /**\n * Gets the bounding box for an anatomical region.\n *\n * @param region - Anatomical region\n * @returns Bounding box or undefined if not found\n *\n * @public\n * @korean 경계상자조회\n */\n getBoundingBox(region: AnatomicalRegionPhysics): BoundingBox | undefined {\n return this.boundingBoxes.get(region);\n }\n\n /**\n * Gets all bounding boxes.\n *\n * @returns Map of all bounding boxes by region\n *\n * @public\n * @korean 모든경계상자조회\n */\n getAllBoundingBoxes(): ReadonlyMap<AnatomicalRegionPhysics, BoundingBox> {\n return this.boundingBoxes;\n }\n\n /**\n * Gets vital points for a specific region.\n *\n * @param region - Anatomical region\n * @returns Array of vital points in that region\n *\n * @public\n * @korean 영역별급소조회\n */\n getVitalPointsInRegion(\n region: AnatomicalRegionPhysics,\n ): readonly VitalPoint[] {\n return this.vitalPointsByRegion.get(region) ?? [];\n }\n}\n\nexport default CollisionDetection;\n"],"mappings":"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"}
1
+ {"version":3,"file":"CollisionDetection.js","names":[],"sources":["../../../src/systems/physics/CollisionDetection.ts"],"sourcesContent":["/**\n * Collision Detection System for Black Trigram combat physics.\n *\n * **Korean**: 충돌 감지 시스템\n *\n * Implements precise collision detection for the 70 vital points combat system using:\n * - Broad-phase AABB (Axis-Aligned Bounding Box) checks for performance\n * - Narrow-phase raycasting for precise vital point detection\n * - Attack reach validation based on technique and stance\n * - Bounding boxes for 5 anatomical regions\n *\n * ## Architecture\n *\n * The system uses a two-phase collision detection approach:\n *\n * 1. **Broad-phase**: Fast AABB checks to eliminate impossible collisions\n * 2. **Narrow-phase**: Precise raycasting to identify specific vital points\n *\n * This ensures 60fps performance even with complex collision queries.\n *\n * ## Performance\n *\n * - Broad-phase: O(1) per check (simple distance comparison)\n * - Narrow-phase: O(n) where n = vital points in region (typically 8-20)\n * - Target: <100 collision checks per frame for 60fps\n *\n * @example\n * ```typescript\n * const collision = new CollisionDetection();\n *\n * const result = collision.checkAttackHit(\n * { x: 0, y: 0, z: 5 }, // Attacker position\n * { x: 0, y: 0, z: 6.5 }, // Defender position\n * { id: \"kick\", type: \"kick\" }, // Technique\n * TrigramStance.LI, // Attacker stance\n * \"torso\" // Target region\n * );\n *\n * if (result.hit) {\n * console.log(`Hit ${result.vitalPoint?.names.english}!`);\n * console.log(`Accuracy: ${(result.accuracy * 100).toFixed(1)}%`);\n * }\n * ```\n *\n * @module systems/physics/CollisionDetection\n * @category Combat Systems\n * @korean 충돌감지시스템\n */\n\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../types/common\";\nimport type {\n AnatomicalRegionPhysics,\n BoundingBox,\n CollisionResult,\n Position3D,\n RaycastQuery,\n TechniqueType,\n} from \"../../types/physics\";\nimport {\n ANATOMICAL_DIMENSIONS,\n BASE_REACH,\n STANCE_REACH_MODIFIERS,\n} from \"../../types/physics\";\nimport type { VitalPoint } from \"../vitalpoint/types\";\nimport { VITAL_POINTS_DATA } from \"../vitalpoint/VitalPointsData\";\nimport { CoordinateMapper } from \"./CoordinateMapper\";\nimport { ThreeObjectPools } from \"../../utils/threeObjectPool\";\n\n/**\n * Collision Detection Engine for combat physics.\n *\n * **Korean**: 충돌 감지 엔진\n *\n * Provides efficient collision detection for combat using bounding boxes and raycasting.\n * Optimized for 60fps performance with multiple simultaneous collision checks.\n *\n * @public\n * @category Combat Systems\n * @korean 충돌감지엔진\n */\nexport class CollisionDetection {\n private readonly boundingBoxes: Map<AnatomicalRegionPhysics, BoundingBox> =\n new Map();\n private readonly raycaster: THREE.Raycaster = new THREE.Raycaster();\n private vitalPointsByRegion: Map<AnatomicalRegionPhysics, VitalPoint[]> =\n new Map();\n\n // Geometry cache for object pooling to avoid repeated allocations during combat\n private readonly geometryCache: Map<string, THREE.BufferGeometry> = new Map();\n\n // Coordinate mapper for 2D→3D conversion\n private readonly coordinateMapper: CoordinateMapper;\n\n /**\n * Creates a new CollisionDetection instance.\n *\n * Initializes bounding boxes for all anatomical regions and organizes\n * vital points by region for efficient lookup.\n */\n constructor() {\n this.coordinateMapper = new CoordinateMapper();\n this.initializeBoundingBoxes();\n this.organizeVitalPointsByRegion();\n this.initializeGeometryCache();\n }\n\n /**\n * Cleans up Three.js resources.\n *\n * **Korean**: 자원 정리\n *\n * Disposes of cached geometries and releases memory to prevent leaks.\n * Should be called when the CollisionDetection instance is no longer needed.\n *\n * @public\n * @korean 자원정리\n */\n public dispose(): void {\n // Dispose all cached geometries\n for (const geometry of this.geometryCache.values()) {\n geometry.dispose();\n }\n this.geometryCache.clear();\n }\n\n /**\n * Checks if an attack hits the defender.\n *\n * **Korean**: 공격 타격 확인\n *\n * Performs two-phase collision detection:\n * 1. Calculate effective attack reach based on technique and stance\n * 2. Broad-phase: Check if defender is within reach\n * 3. Broad-phase: Check if target region's AABB is within reach\n * 4. Narrow-phase: Raycast to find precise vital point hit\n *\n * @param attackerPosition - 3D position of the attacker\n * @param defenderPosition - 3D position of the defender\n * @param technique - Technique being used with type information\n * @param attackerStance - Attacker's current trigram stance\n * @param targetRegion - Anatomical region being targeted\n * @returns Collision result with hit status, vital point, distance, and accuracy\n *\n * @example\n * ```typescript\n * const result = collision.checkAttackHit(\n * { x: 0, y: 0, z: 5 },\n * { x: 0, y: 0, z: 6 },\n * { type: \"punch\" },\n * TrigramStance.GEON,\n * \"head\"\n * );\n * ```\n *\n * @public\n * @korean 공격타격확인\n */\n checkAttackHit(\n attackerPosition: Position3D,\n defenderPosition: Position3D,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Technique object has dynamic properties from combat system\n technique: { type?: string; [key: string]: any },\n attackerStance: TrigramStance,\n targetRegion: AnatomicalRegionPhysics,\n ): CollisionResult {\n // Calculate effective attack reach\n const techniqueType = this.parseTechniqueType(technique.type);\n const attackReach = this.calculateAttackReach(\n techniqueType,\n attackerStance,\n );\n\n // Calculate distance between attacker and defender\n const distance = this.calculateDistance3D(\n attackerPosition,\n defenderPosition,\n );\n\n // Broad-phase: Check if defender is within reach\n if (distance > attackReach.effectiveReach) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Get target region bounding box\n const targetBox = this.boundingBoxes.get(targetRegion);\n if (!targetBox) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Narrow-phase: Raycast from attacker to target region\n const attackDirection = this.normalizeVector3D(\n this.subtractVectors3D(defenderPosition, attackerPosition),\n );\n\n const raycastQuery: RaycastQuery = {\n origin: attackerPosition,\n direction: attackDirection,\n maxDistance: attackReach.effectiveReach,\n targetRegion,\n };\n\n // Check intersection with target bounding box\n const intersection = this.raycastBoundingBox(\n raycastQuery,\n targetBox,\n defenderPosition,\n );\n\n if (!intersection) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Determine specific vital point within region\n const vitalPoint = this.identifyVitalPoint(\n targetRegion,\n intersection.point,\n defenderPosition,\n );\n\n if (!vitalPoint) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Calculate accuracy (how close to vital point center)\n const accuracy = this.calculateHitAccuracy(\n intersection.point,\n vitalPoint,\n defenderPosition,\n );\n\n return {\n hit: true,\n region: targetRegion,\n vitalPoint,\n distance,\n accuracy,\n hitPoint: intersection.point,\n };\n }\n\n /**\n * Calculates effective attack reach for a technique.\n *\n * **Korean**: 공격 범위 계산\n *\n * Applies stance modifiers to base technique reach.\n *\n * @param techniqueType - Type of technique\n * @param stance - Current trigram stance\n * @returns Attack reach with all modifiers applied\n *\n * @private\n * @korean 공격범위계산\n */\n private calculateAttackReach(\n techniqueType: TechniqueType,\n stance: TrigramStance,\n ): {\n technique: TechniqueType;\n baseReach: number;\n stance: TrigramStance;\n stanceModifier: number;\n effectiveReach: number;\n } {\n // Use BASE_REACH constant from physics types\n const baseReach = BASE_REACH[techniqueType];\n\n // Use STANCE_REACH_MODIFIERS constant from physics types\n const stanceModifier = STANCE_REACH_MODIFIERS[stance];\n const effectiveReach = baseReach * stanceModifier;\n\n return {\n technique: techniqueType,\n baseReach,\n stance,\n stanceModifier,\n effectiveReach,\n };\n }\n\n /**\n * Performs raycasting against a bounding box.\n *\n * **Korean**: 경계 상자 광선 투사\n *\n * Uses cached geometries from object pool to avoid repeated allocations\n * during combat. Creates a Three.js mesh for the bounding box and performs\n * raycasting to detect intersection points.\n *\n * Uses object pooling for Vector3 allocations to reduce GC pressure.\n *\n * @param query - Raycast query parameters\n * @param box - Bounding box to test\n * @param defenderPosition - Position of the defender\n * @returns Intersection point or null if no hit\n *\n * @private\n * @korean 경계상자광선투사\n */\n private raycastBoundingBox(\n query: RaycastQuery,\n box: BoundingBox,\n defenderPosition: Position3D,\n ): { point: Position3D } | null {\n // Get cached geometry from pool to avoid repeated allocations\n const cacheKey = `${box.type}-${box.region}`;\n let geometry = this.geometryCache.get(cacheKey);\n\n // If not cached (shouldn't happen after initialization), create it\n if (!geometry) {\n geometry = this.createGeometryForBox(box);\n this.geometryCache.set(cacheKey, geometry);\n }\n\n // Create temporary mesh for raycasting (mesh is lightweight, geometry is cached)\n const mesh = new THREE.Mesh(geometry);\n mesh.position.set(\n defenderPosition.x + box.center.x,\n defenderPosition.y + box.center.y,\n defenderPosition.z + box.center.z,\n );\n\n // Setup raycaster using pooled Vector3 objects to reduce GC pressure\n // Wrap in try-finally to ensure pooled objects are always released\n const origin = ThreeObjectPools.vector3.acquire();\n const direction = ThreeObjectPools.vector3.acquire();\n\n try {\n origin.set(query.origin.x, query.origin.y, query.origin.z);\n direction.set(query.direction.x, query.direction.y, query.direction.z);\n\n this.raycaster.set(origin, direction);\n this.raycaster.far = query.maxDistance;\n\n // Perform raycast\n const intersections = this.raycaster.intersectObject(mesh);\n\n // Clean up temporary mesh (geometry remains cached)\n // Note: mesh.material is undefined, no need to dispose\n\n if (intersections.length > 0) {\n const point = intersections[0].point;\n return {\n point: { x: point.x, y: point.y, z: point.z },\n };\n }\n\n return null;\n } finally {\n // Release pooled objects back to pool (guaranteed even if exception occurs)\n ThreeObjectPools.vector3.release(origin);\n ThreeObjectPools.vector3.release(direction);\n }\n }\n\n /**\n * Creates Three.js geometry for a bounding box.\n *\n * Helper method for geometry cache initialization.\n *\n * @param box - Bounding box specification\n * @returns Three.js geometry\n *\n * @private\n * @korean 경계상자지오메트리생성\n */\n private createGeometryForBox(box: BoundingBox): THREE.BufferGeometry {\n switch (box.type) {\n case \"sphere\":\n return new THREE.SphereGeometry(box.dimensions.x, 8, 8);\n case \"box\":\n return new THREE.BoxGeometry(\n box.dimensions.x,\n box.dimensions.y,\n box.dimensions.z,\n );\n case \"capsule\":\n return new THREE.CapsuleGeometry(\n box.dimensions.x,\n box.dimensions.y,\n 4,\n 8,\n );\n }\n }\n\n /**\n * Identifies the specific vital point hit within a region.\n *\n * **Korean**: 급소 식별\n *\n * Finds the closest vital point to the hit location within the targeted region.\n *\n * @param region - Anatomical region hit\n * @param hitPoint - 3D point where attack intersected\n * @param defenderPosition - Position of the defender\n * @returns Closest vital point or null if none found\n *\n * @private\n * @korean 급소식별\n */\n private identifyVitalPoint(\n region: AnatomicalRegionPhysics,\n hitPoint: Position3D,\n _defenderPosition: Position3D, // Prefixed with _ to indicate intentionally unused\n ): VitalPoint | null {\n // Get all vital points for this region\n const vitalPoints = this.vitalPointsByRegion.get(region);\n if (!vitalPoints || vitalPoints.length === 0) {\n return null;\n }\n\n // Use CoordinateMapper to find the closest vital point in 3D space\n const result = this.coordinateMapper.findClosestVitalPoint(\n hitPoint,\n vitalPoints,\n region,\n );\n\n // For collision detection, we accept any vital point in the region\n // even if it's not very close, as long as the region was hit\n // This provides more forgiving collision detection while still\n // using accurate 3D positioning for scoring/accuracy\n return result\n ? result.vitalPoint\n : vitalPoints.length > 0\n ? vitalPoints[0]\n : null;\n }\n\n /**\n * Calculates hit accuracy based on distance to vital point center.\n *\n * **Korean**: 타격 정확도 계산\n *\n * Accuracy decreases linearly with distance from vital point center.\n * Perfect accuracy (1.0) at center, decreasing to 0 at 5cm radius.\n *\n * @param hitPoint - Point where attack landed\n * @param vitalPoint - Target vital point\n * @param defenderPosition - Position of the defender\n * @returns Accuracy value from 0 to 1\n *\n * @private\n * @korean 타격정확도계산\n */\n private calculateHitAccuracy(\n hitPoint: Position3D,\n vitalPoint: VitalPoint,\n _defenderPosition: Position3D, // Prefixed with _ to indicate intentionally unused\n ): number {\n // Use CoordinateMapper to convert vital point to 3D and calculate distance\n const distance = this.coordinateMapper.distanceToVitalPoint(\n hitPoint,\n vitalPoint,\n );\n\n // Accuracy calculation: Perfect (1.0) at center, decreasing to 0 at 5cm radius\n const maxDistance = 0.05; // 5cm radius for vital points\n const accuracy = Math.max(0, 1 - distance / maxDistance);\n\n return accuracy;\n }\n\n /**\n * Calculates Euclidean distance between two 3D points.\n *\n * @param pos1 - First position\n * @param pos2 - Second position\n * @returns Distance in meters\n *\n * @private\n * @korean 3D거리계산\n */\n private calculateDistance3D(pos1: Position3D, pos2: Position3D): number {\n const dx = pos1.x - pos2.x;\n const dy = pos1.y - pos2.y;\n const dz = pos1.z - pos2.z;\n return Math.sqrt(dx * dx + dy * dy + dz * dz);\n }\n\n /**\n * Subtracts one 3D vector from another.\n *\n * @param v1 - First vector\n * @param v2 - Second vector\n * @returns Resulting vector\n *\n * @private\n * @korean 벡터빼기\n */\n private subtractVectors3D(v1: Position3D, v2: Position3D): Position3D {\n return {\n x: v1.x - v2.x,\n y: v1.y - v2.y,\n z: v1.z - v2.z,\n };\n }\n\n /**\n * Normalizes a 3D vector to unit length.\n *\n * @param vec - Vector to normalize\n * @returns Normalized vector\n *\n * @private\n * @korean 벡터정규화\n */\n private normalizeVector3D(vec: Position3D): Position3D {\n const length = Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);\n if (length === 0) {\n return { x: 0, y: 0, z: 1 }; // Default forward direction\n }\n return {\n x: vec.x / length,\n y: vec.y / length,\n z: vec.z / length,\n };\n }\n\n /**\n * Parses technique type from string.\n *\n * @param techniqueTypeStr - Technique type as string\n * @returns Parsed technique type\n *\n * @private\n * @korean 기술유형파싱\n */\n private parseTechniqueType(techniqueTypeStr?: string): TechniqueType {\n const typeMap: Record<string, TechniqueType> = {\n punch: \"punch\",\n kick: \"kick\",\n elbow: \"elbow\",\n knee: \"knee\",\n pressure_point: \"pressure_point\",\n strike: \"punch\", // Default strike to punch\n };\n\n return typeMap[techniqueTypeStr ?? \"punch\"] ?? \"punch\";\n }\n\n /**\n * Initializes bounding boxes for all anatomical regions.\n *\n * Creates collision volumes for the 5 anatomical regions using the\n * ANATOMICAL_DIMENSIONS constants from the physics types module.\n *\n * @private\n * @korean 경계상자초기화\n */\n private initializeBoundingBoxes(): void {\n // Head: Sphere (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"head\", {\n type: ANATOMICAL_DIMENSIONS.head.type,\n center: ANATOMICAL_DIMENSIONS.head.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.head.radius,\n y: 0,\n z: 0,\n }, // radius only\n region: \"head\",\n });\n\n // Neck: Capsule (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"neck\", {\n type: ANATOMICAL_DIMENSIONS.neck.type,\n center: ANATOMICAL_DIMENSIONS.neck.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.neck.radius,\n y: ANATOMICAL_DIMENSIONS.neck.height,\n z: 0,\n }, // radius and height\n region: \"neck\",\n });\n\n // Torso: Box (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"torso\", {\n type: ANATOMICAL_DIMENSIONS.torso.type,\n center: ANATOMICAL_DIMENSIONS.torso.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.torso.width,\n y: ANATOMICAL_DIMENSIONS.torso.height,\n z: ANATOMICAL_DIMENSIONS.torso.depth,\n }, // width, height, depth\n region: \"torso\",\n });\n\n // Arms: Capsules (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"arms\", {\n type: ANATOMICAL_DIMENSIONS.arms.type,\n center: ANATOMICAL_DIMENSIONS.arms.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.arms.radius,\n y: ANATOMICAL_DIMENSIONS.arms.height,\n z: 0,\n }, // radius and length\n region: \"arms\",\n });\n\n // Legs: Capsules (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"legs\", {\n type: ANATOMICAL_DIMENSIONS.legs.type,\n center: ANATOMICAL_DIMENSIONS.legs.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.legs.radius,\n y: ANATOMICAL_DIMENSIONS.legs.height,\n z: 0,\n }, // radius and length\n region: \"legs\",\n });\n }\n\n /**\n * Initializes geometry cache for object pooling.\n *\n * Pre-creates all geometries needed for raycasting to avoid repeated\n * allocations during combat. Critical for maintaining 60fps with up to\n * 100 collision checks per frame.\n *\n * @private\n * @korean 지오메트리캐시초기화\n */\n private initializeGeometryCache(): void {\n // Pre-create and cache geometries for all bounding boxes\n for (const box of this.boundingBoxes.values()) {\n const cacheKey = `${box.type}-${box.region}`;\n const geometry = this.createGeometryForBox(box);\n this.geometryCache.set(cacheKey, geometry);\n }\n }\n\n /**\n * Organizes vital points by anatomical region for efficient lookup.\n *\n * NOTE: This categorization currently uses y-coordinate thresholds that assume\n * positions are in meters. However, VITAL_POINTS_DATA uses pixel coordinates\n * (e.g., y: 50), which will cause incorrect categorization. Most vital points\n * will end up in the \"legs\" region since pixel y-coordinates are typically less\n * than 0.8.\n *\n * TODO: After implementing 2D→3D coordinate mapping, update this method to use\n * the converted 3D positions, or use the anatomical region data that may already\n * exist in the vital points data structure.\n *\n * Categorizes the 70 vital points into their respective regions:\n * - Head: 10 vital points\n * - Neck: 8 vital points\n * - Torso: 20 vital points\n * - Arms: 16 vital points\n * - Legs: 16 vital points\n *\n * @private\n * @korean 급소영역별정리\n */\n private organizeVitalPointsByRegion(): void {\n const regionMap: Map<AnatomicalRegionPhysics, VitalPoint[]> = new Map([\n [\"head\", []],\n [\"neck\", []],\n [\"torso\", []],\n [\"arms\", []],\n [\"legs\", []],\n ]);\n\n // Use existing VitalPointsData categorization by ID prefix\n // This is more reliable than coordinate-based heuristics\n for (const vp of VITAL_POINTS_DATA) {\n let region: AnatomicalRegionPhysics;\n\n // Categorize by ID prefix (existing VitalPointsData convention)\n // Check neck-related patterns first (more specific) before head (broader)\n if (\n vp.id.includes(\"_neck\") ||\n vp.id.includes(\"_throat\") ||\n vp.id === \"head_side_neck\" ||\n vp.id === \"head_throat\"\n ) {\n region = \"neck\";\n } else if (vp.id.startsWith(\"head_\")) {\n region = \"head\";\n } else if (vp.id.startsWith(\"torso_\")) {\n region = \"torso\";\n } else if (\n vp.id.startsWith(\"arm_left_\") ||\n vp.id.startsWith(\"arm_right_\")\n ) {\n region = \"arms\";\n } else if (\n vp.id.startsWith(\"leg_left_\") ||\n vp.id.startsWith(\"leg_right_\")\n ) {\n region = \"legs\";\n } else {\n // Default to torso for uncategorized points (core region)\n region = \"torso\";\n }\n\n const list = regionMap.get(region);\n if (list) {\n list.push(vp);\n }\n }\n\n this.vitalPointsByRegion = regionMap;\n }\n\n /**\n * Gets the bounding box for an anatomical region.\n *\n * @param region - Anatomical region\n * @returns Bounding box or undefined if not found\n *\n * @public\n * @korean 경계상자조회\n */\n getBoundingBox(region: AnatomicalRegionPhysics): BoundingBox | undefined {\n return this.boundingBoxes.get(region);\n }\n\n /**\n * Gets all bounding boxes.\n *\n * @returns Map of all bounding boxes by region\n *\n * @public\n * @korean 모든경계상자조회\n */\n getAllBoundingBoxes(): ReadonlyMap<AnatomicalRegionPhysics, BoundingBox> {\n return this.boundingBoxes;\n }\n\n /**\n * Gets vital points for a specific region.\n *\n * @param region - Anatomical region\n * @returns Array of vital points in that region\n *\n * @public\n * @korean 영역별급소조회\n */\n getVitalPointsInRegion(\n region: AnatomicalRegionPhysics,\n ): readonly VitalPoint[] {\n return this.vitalPointsByRegion.get(region) ?? [];\n }\n}\n\nexport default CollisionDetection;\n"],"mappings":"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"}