blacktrigram 0.7.39 → 0.7.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/audio/AudioAssetLoader.js.map +1 -1
  3. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  4. package/lib/audio/AudioCache.js.map +1 -1
  5. package/lib/audio/AudioManager.js.map +1 -1
  6. package/lib/audio/AudioMonitor.js.map +1 -1
  7. package/lib/audio/AudioPool.js.map +1 -1
  8. package/lib/audio/AudioProvider.js.map +1 -1
  9. package/lib/audio/AudioUtils.js.map +1 -1
  10. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  11. package/lib/audio/VariantSelector.js.map +1 -1
  12. package/lib/audio/types.js.map +1 -1
  13. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  14. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  15. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  31. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  32. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  36. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  39. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  42. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  44. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  45. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  46. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  47. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  48. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  49. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  50. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  51. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  52. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  53. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  54. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  55. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  56. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  57. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  58. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  59. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  60. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  61. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  62. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  63. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  64. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  65. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  66. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  67. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  68. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  69. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  70. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  71. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  72. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  74. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  75. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  76. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  77. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  78. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  79. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  84. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  85. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  86. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  87. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  88. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  89. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  90. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  91. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  93. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  94. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  96. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  97. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  98. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  99. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  100. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  101. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  102. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  103. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  104. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  105. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  106. package/lib/components/shared/base/BaseButton.js.map +1 -1
  107. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  108. package/lib/components/shared/base/BasePanel.js.map +1 -1
  109. package/lib/components/shared/base/BaseText.js.map +1 -1
  110. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  111. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  112. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  113. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  114. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  115. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  116. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  117. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  118. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  119. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  120. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  121. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  122. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  123. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  124. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  125. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  126. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  127. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  128. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  129. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  130. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  131. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  132. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  133. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  134. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  135. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  136. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  137. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  138. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  139. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  140. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  141. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  142. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  143. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  144. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  145. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  146. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  147. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  148. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  149. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  150. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  151. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  152. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  153. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  154. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  155. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  156. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  157. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  158. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  159. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  160. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  161. package/lib/components/shared/ui/BackButton.js.map +1 -1
  162. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  163. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  164. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  165. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  166. package/lib/components/shared/ui/SplashScreen.js +2 -2
  167. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  168. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  169. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  170. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  171. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  172. package/lib/constants/bodyDimensions.js.map +1 -1
  173. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  174. package/lib/data/archetypeClothing.js.map +1 -1
  175. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  176. package/lib/data/techniqueMappings.js.map +1 -1
  177. package/lib/data/techniques.js.map +1 -1
  178. package/lib/hooks/useActionFeedback.js.map +1 -1
  179. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  180. package/lib/hooks/useCombatTimer.js.map +1 -1
  181. package/lib/hooks/useDebounce.js.map +1 -1
  182. package/lib/hooks/useHUDLayout.js.map +1 -1
  183. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  184. package/lib/hooks/useKeyboardControls.js.map +1 -1
  185. package/lib/hooks/useMatchCountdown.js.map +1 -1
  186. package/lib/hooks/useMuscleActivation.js.map +1 -1
  187. package/lib/hooks/usePauseMenu.js.map +1 -1
  188. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  189. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  190. package/lib/hooks/useRoundTransition.js.map +1 -1
  191. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  192. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  193. package/lib/hooks/useThrottle.js.map +1 -1
  194. package/lib/hooks/useTouchControls.js.map +1 -1
  195. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  196. package/lib/hooks/useWindowSize.js.map +1 -1
  197. package/lib/systems/CombatSystem.js.map +1 -1
  198. package/lib/systems/EffectCalculator.js.map +1 -1
  199. package/lib/systems/LayoutSystem.js.map +1 -1
  200. package/lib/systems/PlayerEffectManager.js.map +1 -1
  201. package/lib/systems/ResponsiveScaling.js.map +1 -1
  202. package/lib/systems/TrigramSystem.js.map +1 -1
  203. package/lib/systems/VitalPointSystem.js.map +1 -1
  204. package/lib/systems/ai/AIPersonality.js.map +1 -1
  205. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  206. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  207. package/lib/systems/ai/ComboSystem.js.map +1 -1
  208. package/lib/systems/ai/DecisionTree.js.map +1 -1
  209. package/lib/systems/ai/TrainingAI.js.map +1 -1
  210. package/lib/systems/ai/types.js.map +1 -1
  211. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  212. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  213. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  214. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  215. package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
  216. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  217. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  218. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  219. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  220. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  221. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  222. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  223. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  224. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  225. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  226. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  227. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  228. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  229. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  230. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  231. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  232. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  233. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  234. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  235. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  236. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  237. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  238. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  239. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  240. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  241. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  242. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  243. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  244. package/lib/systems/animation/core/types.js.map +1 -1
  245. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  246. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  247. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  248. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  249. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  250. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  251. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  252. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  253. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  254. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  255. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  256. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  257. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  258. package/lib/systems/bodypart/types.js.map +1 -1
  259. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  260. package/lib/systems/breathing/feedback.js.map +1 -1
  261. package/lib/systems/breathing/integration.js.map +1 -1
  262. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  263. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  264. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  265. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  266. package/lib/systems/combat/FallIntegration.js.map +1 -1
  267. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  268. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  269. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  270. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  271. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  272. package/lib/systems/combat/typeGuards.js.map +1 -1
  273. package/lib/systems/effects.js.map +1 -1
  274. package/lib/systems/game.js.map +1 -1
  275. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  276. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  277. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  278. package/lib/systems/movement/integration.js.map +1 -1
  279. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  280. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  281. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  282. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  283. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  284. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  285. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  286. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  287. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  288. package/lib/systems/trigram/StanceManager.js.map +1 -1
  289. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  290. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  291. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  292. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  293. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  294. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  295. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  296. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  297. package/lib/systems/trigram/techniques/index.js.map +1 -1
  298. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  299. package/lib/systems/trigram/types.js.map +1 -1
  300. package/lib/systems/types.js.map +1 -1
  301. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  302. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  303. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  304. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  305. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  306. package/lib/types/AccessibilityTypes.js.map +1 -1
  307. package/lib/types/PhysicsTypes.js.map +1 -1
  308. package/lib/types/common.js.map +1 -1
  309. package/lib/types/constants/colors.js.map +1 -1
  310. package/lib/types/constants/designSystem.js.map +1 -1
  311. package/lib/types/constants/layout.js.map +1 -1
  312. package/lib/types/constants/performance.js.map +1 -1
  313. package/lib/types/constants/typography.js.map +1 -1
  314. package/lib/types/facial.js.map +1 -1
  315. package/lib/types/hand-animation.js.map +1 -1
  316. package/lib/types/injury.js.map +1 -1
  317. package/lib/types/physics.js.map +1 -1
  318. package/lib/types/skeletal.js.map +1 -1
  319. package/lib/types/techniqueId.js.map +1 -1
  320. package/lib/utils/accessibility.js.map +1 -1
  321. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  322. package/lib/utils/assetConfig.js.map +1 -1
  323. package/lib/utils/characterScaling.js.map +1 -1
  324. package/lib/utils/colorHelpers.js.map +1 -1
  325. package/lib/utils/colorUtils.js.map +1 -1
  326. package/lib/utils/combatReadiness.js.map +1 -1
  327. package/lib/utils/controlMapping.js.map +1 -1
  328. package/lib/utils/deviceDetection.js.map +1 -1
  329. package/lib/utils/effectUtils.js.map +1 -1
  330. package/lib/utils/fabricTextures.js.map +1 -1
  331. package/lib/utils/hapticFeedback.js.map +1 -1
  332. package/lib/utils/haptics.js.map +1 -1
  333. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  334. package/lib/utils/inputSystem.js.map +1 -1
  335. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  336. package/lib/utils/math.js.map +1 -1
  337. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  338. package/lib/utils/mobileUIUtils.js.map +1 -1
  339. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  340. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  341. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  342. package/lib/utils/performanceOptimization.js.map +1 -1
  343. package/lib/utils/player3DHelpers.js.map +1 -1
  344. package/lib/utils/playerUtils.js.map +1 -1
  345. package/lib/utils/responsiveLayout.js.map +1 -1
  346. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  347. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  348. package/lib/utils/safeAreaUtils.js.map +1 -1
  349. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  350. package/lib/utils/skeletonScaling.js.map +1 -1
  351. package/lib/utils/stanceHelpers.js.map +1 -1
  352. package/lib/utils/threeObjectPool.js.map +1 -1
  353. package/lib/utils/visualEffects.js.map +1 -1
  354. package/package.json +8 -8
@@ -1 +1 @@
1
- {"version":3,"file":"ConsciousnessSystem.js","names":[],"sources":["../../../src/systems/combat/ConsciousnessSystem.ts"],"sourcesContent":["/**\n * Consciousness System for managing awareness and cognitive function levels.\n *\n * **Korean**: 의식 시스템 (Consciousness System)\n *\n * Implements consciousness levels based on head trauma, blood flow restriction,\n * and neural damage. Consciousness affects reaction time, targeting precision,\n * and overall combat effectiveness.\n *\n * ## Consciousness Levels\n *\n * - **전투각성 (Combat Alert)**: 100% - Full awareness, optimal combat ability\n * - **혼란상태 (Disoriented)**: 70% - Reduced reaction, vulnerability window\n * - **기절직전 (Stunned)**: 40% - Severe impairment, incapacitation opportunity\n * - **무의식 (Unconscious)**: 0% - Complete incapacitation\n *\n * ## Fall System Integration\n *\n * When consciousness drops below 10%, the system triggers fall animations (낙법).\n * The character loses the ability to stand and collapses to the ground.\n * Fall direction is based on last impact location.\n *\n * @module systems/combat/ConsciousnessSystem\n * @category Combat System\n * @korean 의식시스템\n */\n\nimport { VitalPointCategory } from \"@/types\";\nimport type { TrigramStance } from \"@/types/common\";\nimport { determineFallFromStance } from \"../animation\";\nimport type { FallType } from \"../animation/core/types\";\nimport { PlayerState } from \"../player\";\n\n/**\n * Consciousness levels representing cognitive function and awareness.\n *\n * **Korean**: 의식 수준\n */\nexport enum ConsciousnessLevel {\n /** Full combat awareness (90-100%) */\n COMBAT_ALERT = \"combat_alert\",\n /** Disoriented and vulnerable (50-89%) */\n DISORIENTED = \"disoriented\",\n /** Severely stunned (10-49%) */\n STUNNED = \"stunned\",\n /** Completely unconscious (0-9%) */\n UNCONSCIOUS = \"unconscious\",\n}\n\n/**\n * Effects of consciousness levels on combat performance.\n */\ninterface ConsciousnessEffects {\n /** Consciousness value range */\n readonly range: readonly [number, number];\n /** Reaction time multiplier */\n readonly reactionTimeMultiplier: number;\n /** Accuracy penalty */\n readonly accuracyPenalty: number;\n /** Defense penalty */\n readonly defensePenalty: number;\n /** Can perform actions */\n readonly canAct: boolean;\n /** Vision clarity multiplier */\n readonly visionClarity: number;\n}\n\n/**\n * Consciousness System managing awareness and cognitive function.\n *\n * Tracks consciousness damage from:\n * - Head trauma (strikes to head region)\n * - Neurological vital point hits\n * - Blood flow restriction (vascular hits)\n * - Respiratory disruption\n *\n * Consciousness naturally recovers over time when not taking damage.\n *\n * @example\n * ```typescript\n * const consciousnessSystem = new ConsciousnessSystem();\n *\n * // Apply damage from head strike\n * const newPlayer = consciousnessSystem.applyDamage(\n * player,\n * 15,\n * VitalPointCategory.NEUROLOGICAL\n * );\n *\n * // Check consciousness level\n * const level = consciousnessSystem.getLevel(newPlayer.consciousness);\n * console.log(`Consciousness: ${level}`);\n *\n * // Apply recovery over time\n * const recovered = consciousnessSystem.applyRecovery(newPlayer, 1000);\n * ```\n *\n * @public\n * @korean 의식시스템\n */\nexport class ConsciousnessSystem {\n /**\n * Consciousness level effects and thresholds.\n */\n private readonly levelEffects: Record<\n ConsciousnessLevel,\n ConsciousnessEffects\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n range: [90, 100],\n reactionTimeMultiplier: 1.0,\n accuracyPenalty: 0.0,\n defensePenalty: 0.0,\n canAct: true,\n visionClarity: 1.0,\n },\n [ConsciousnessLevel.DISORIENTED]: {\n range: [50, 89],\n reactionTimeMultiplier: 1.3, // 30% slower reactions\n accuracyPenalty: 0.2, // -20% accuracy\n defensePenalty: 0.15, // -15% defense\n canAct: true,\n visionClarity: 0.7,\n },\n [ConsciousnessLevel.STUNNED]: {\n range: [20, 49], // Updated: Changed from [10, 49] to [20, 49]\n reactionTimeMultiplier: 2.0, // 2x slower reactions\n accuracyPenalty: 0.5, // -50% accuracy\n defensePenalty: 0.4, // -40% defense\n canAct: true,\n visionClarity: 0.4,\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n range: [0, 19], // Updated: Changed from [0, 9] to [0, 19] - incapacitation threshold <20%\n reactionTimeMultiplier: Infinity,\n accuracyPenalty: 1.0, // Complete inability\n defensePenalty: 1.0, // No defense\n canAct: false,\n visionClarity: 0.0,\n },\n };\n\n /**\n * Damage multipliers by vital point category affecting consciousness.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 3.0, // Nerve strikes heavily affect consciousness\n [VitalPointCategory.VASCULAR]: 2.0, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 1.5, // Breathing difficulty\n default: 0.5, // Other damage has minor effect\n };\n\n /**\n * Base recovery rate per second.\n */\n private readonly baseRecoveryRate = 5.0; // 5 points per second\n\n /**\n * Incapacitation threshold - consciousness <20% renders player helpless.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n */\n private readonly incapacitationThreshold = 20;\n\n /**\n * Duration of helplessness when consciousness drops below threshold (3-5 seconds).\n */\n private readonly helplessDuration = {\n min: 3000, // 3 seconds\n max: 5000, // 5 seconds\n };\n\n /**\n * Time without head trauma required for consciousness recovery (5 seconds).\n */\n private readonly noTraumaRecoveryTime = 5000; // 5 seconds\n\n /**\n * Applies consciousness damage from combat.\n *\n * Calculates consciousness reduction based on damage amount and\n * vital point category. Neurological and vascular damage have\n * the highest impact on consciousness.\n *\n * @param player - Current player state\n * @param damage - Base damage amount\n * @param category - Vital point category hit\n * @returns Updated player state with reduced consciousness\n *\n * @example\n * ```typescript\n * // Head strike causes significant consciousness damage\n * const newPlayer = system.applyDamage(\n * player,\n * 20,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * console.log(`Consciousness: ${newPlayer.consciousness}`);\n * ```\n *\n * @public\n * @korean 의식피해적용\n */\n applyDamage(\n player: PlayerState,\n damage: number,\n category?: VitalPointCategory\n ): PlayerState {\n // Get multiplier based on category\n const multiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate consciousness damage\n const consciousnessDamage = damage * multiplier;\n\n // Apply damage, clamped to 0-100 range\n const newConsciousness = Math.max(\n 0,\n Math.min(100, player.consciousness - consciousnessDamage)\n );\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Applies consciousness recovery over time.\n *\n * Consciousness naturally recovers when not taking damage.\n * Recovery rate depends on current level - severely stunned\n * players recover more slowly.\n *\n * **Note**: Full recovery only occurs after 5 seconds without head trauma.\n *\n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @param lastHeadTraumaTime - Timestamp of last head trauma (optional)\n * @returns Updated player state with recovered consciousness\n *\n * @example\n * ```typescript\n * // In game loop (60fps, ~16ms per frame)\n * player = system.applyRecovery(player, 16, player.lastActionTime);\n * ```\n *\n * @public\n * @korean 의식회복\n */\n applyRecovery(\n player: PlayerState,\n deltaTime: number,\n lastHeadTraumaTime?: number\n ): PlayerState {\n // Already at full consciousness\n if (player.consciousness >= 100) {\n return player;\n }\n\n // Check if enough time has passed since last head trauma\n const timeSinceTrauma = lastHeadTraumaTime\n ? Date.now() - lastHeadTraumaTime\n : Infinity;\n\n // Don't recover if head trauma is recent (within 5 seconds)\n if (timeSinceTrauma < this.noTraumaRecoveryTime) {\n return player;\n }\n\n // Calculate recovery based on time\n const deltaSeconds = deltaTime / 1000;\n const level = this.getLevel(player.consciousness);\n\n // Recovery rate modifier based on level\n let recoveryModifier = 1.0;\n if (level === ConsciousnessLevel.STUNNED) {\n recoveryModifier = 0.5; // Slower recovery when stunned\n } else if (level === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryModifier = 0.2; // Very slow recovery when unconscious\n }\n\n const recovery = this.baseRecoveryRate * deltaSeconds * recoveryModifier;\n const newConsciousness = Math.min(100, player.consciousness + recovery);\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Determines consciousness level from consciousness value.\n *\n * Updated thresholds:\n * - 90-100: Combat Alert\n * - 50-89: Disoriented\n * - 20-49: Stunned\n * - 0-19: Unconscious (incapacitation threshold <20%)\n *\n * @param consciousness - Consciousness value (0-100)\n * @returns Current consciousness level\n *\n * @example\n * ```typescript\n * const level = system.getLevel(75);\n * console.log(level); // DISORIENTED\n *\n * const level2 = system.getLevel(15);\n * console.log(level2); // UNCONSCIOUS (below incapacitation threshold)\n * ```\n *\n * @public\n * @korean 의식수준확인\n */\n getLevel(consciousness: number): ConsciousnessLevel {\n if (consciousness >= 90) return ConsciousnessLevel.COMBAT_ALERT;\n if (consciousness >= 50) return ConsciousnessLevel.DISORIENTED;\n if (consciousness >= 20) return ConsciousnessLevel.STUNNED;\n return ConsciousnessLevel.UNCONSCIOUS;\n }\n\n /**\n * Gets effects for a specific consciousness level.\n *\n * @param level - Consciousness level\n * @returns Effects applied at that level\n *\n * @public\n * @korean 의식효과\n */\n getEffects(level: ConsciousnessLevel): ConsciousnessEffects {\n return this.levelEffects[level];\n }\n\n /**\n * Applies consciousness effects to player state.\n *\n * Modifies player stats based on current consciousness level.\n *\n * @param player - Current player state\n * @returns Modified player state with consciousness effects\n *\n * @public\n * @korean 의식효과적용\n */\n applyEffects(player: PlayerState): PlayerState {\n const level = this.getLevel(player.consciousness);\n const effects = this.getEffects(level);\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - effects.accuracyPenalty)\n ),\n defense: Math.floor(player.defense * (1 - effects.defensePenalty)),\n technique: Math.floor(player.technique * (1 - effects.accuracyPenalty)),\n isStunned:\n player.isStunned ||\n level === ConsciousnessLevel.UNCONSCIOUS ||\n level === ConsciousnessLevel.STUNNED,\n };\n }\n\n /**\n * Checks if player is at incapacitation threshold.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n *\n * When consciousness drops below 20%, player becomes helpless for 3-5 seconds.\n *\n * @param player - Current player state\n * @returns True if consciousness is below incapacitation threshold (<20%)\n *\n * @example\n * ```typescript\n * if (system.isAtIncapacitationThreshold(player)) {\n * // Player is helpless, trigger helpless state\n * const helplessDuration = system.getHelplessDuration();\n * console.log(`Player helpless for ${helplessDuration}ms`);\n * }\n * ```\n *\n * @public\n * @korean 무력화한계확인\n */\n isAtIncapacitationThreshold(player: PlayerState): boolean {\n return player.consciousness < this.incapacitationThreshold;\n }\n\n /**\n * Gets random helpless duration when consciousness drops below threshold.\n *\n * Returns a random duration between 3-5 seconds (3000-5000ms).\n *\n * @returns Helpless duration in milliseconds\n *\n * @public\n * @korean 무력화지속시간\n */\n getHelplessDuration(): number {\n return (\n this.helplessDuration.min +\n Math.random() * (this.helplessDuration.max - this.helplessDuration.min)\n );\n }\n\n /**\n * Checks if enough time has passed for consciousness recovery.\n *\n * Consciousness only recovers after 5 seconds without head trauma.\n *\n * @param lastHeadTraumaTime - Timestamp of last head trauma\n * @returns True if recovery is allowed\n *\n * @public\n * @korean 회복가능확인\n */\n canRecover(lastHeadTraumaTime: number): boolean {\n const timeSinceTrauma = Date.now() - lastHeadTraumaTime;\n return timeSinceTrauma >= this.noTraumaRecoveryTime;\n }\n\n /**\n * Checks if player is incapacitated due to consciousness.\n *\n * @param player - Current player state\n * @returns True if player is unconscious\n *\n * @public\n * @korean 무의식확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return (\n this.getLevel(player.consciousness) === ConsciousnessLevel.UNCONSCIOUS\n );\n }\n\n /**\n * Gets bilingual name for consciousness level.\n *\n * @param level - Consciousness level\n * @returns Korean and English level names\n *\n * @public\n * @korean 의식이름\n */\n getLevelName(level: ConsciousnessLevel): { korean: string; english: string } {\n const names: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"전투각성\",\n english: \"Combat Alert\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"혼란상태\",\n english: \"Disoriented\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"기절직전\",\n english: \"Stunned\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"무의식\",\n english: \"Unconscious\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of consciousness level effects.\n *\n * @param level - Consciousness level\n * @returns Bilingual description\n *\n * @public\n * @korean 의식설명\n */\n getLevelDescription(level: ConsciousnessLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"완전한 의식과 전투 능력\",\n english: \"Full awareness and combat ability\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"반응 시간 저하 및 혼란\",\n english: \"Reduced reaction time and confusion\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"심각한 손상, 무력화 위험\",\n english: \"Severe impairment, incapacitation risk\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"완전한 무의식 상태\",\n english: \"Complete unconsciousness\",\n },\n };\n\n return descriptions[level];\n }\n\n /**\n * Gets color indicator for consciousness level (for UI).\n *\n * @param level - Consciousness level\n * @returns Hex color code\n *\n * @public\n * @korean 의식색상\n */\n getLevelColor(level: ConsciousnessLevel): number {\n const colors: Record<ConsciousnessLevel, number> = {\n [ConsciousnessLevel.COMBAT_ALERT]: 0x00ff00, // Green\n [ConsciousnessLevel.DISORIENTED]: 0xffaa00, // Orange\n [ConsciousnessLevel.STUNNED]: 0xff4400, // Red-orange\n [ConsciousnessLevel.UNCONSCIOUS]: 0xff0000, // Red\n };\n\n return colors[level];\n }\n\n /**\n * Checks if consciousness is low enough to trigger fall animation.\n *\n * Falls occur when consciousness drops below 10% (UNCONSCIOUS threshold).\n * This represents complete loss of ability to maintain standing position.\n *\n * Korean terminology:\n * - 의식상실낙법 (Uisik Sangsil Nakbeop): Consciousness loss fall\n * - 기절낙하 (Gijeol Nakha): Knockout collapse\n *\n * @param player - Current player state\n * @returns True if fall animation should trigger\n *\n * @example\n * ```typescript\n * if (consciousnessSystem.shouldTriggerFall(player)) {\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * lastImpactAngle\n * );\n * animationMachine.transitionTo(FALL_TYPE_TO_ANIMATION[fallType]);\n * }\n * ```\n *\n * @public\n * @korean 의식상실낙법확인\n */\n shouldTriggerFall(player: PlayerState): boolean {\n return player.consciousness < 10; // UNCONSCIOUS threshold\n }\n\n /**\n * Determines fall direction based on last impact and stance.\n *\n * When consciousness is lost, fall direction is determined by:\n * - Last attack angle (if available)\n * - Current stance bias (if no attack data)\n * - Default to backward fall (natural collapse)\n *\n * Korean falling from consciousness loss:\n * - 후방의식상실 (Hubang Uisik Sangsil): Backward consciousness loss fall\n * - 전방기절 (Jeonbang Gijeol): Forward knockout\n *\n * @param player - Current player state\n * @param lastImpactAngle - Angle of last hit (optional)\n * @returns Fall type to use for animation\n *\n * @example\n * ```typescript\n * // Player loses consciousness from frontal head strike\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * 0 // Front angle\n * );\n * // Returns: 'backward' (knocked back by frontal strike)\n *\n * // Player loses consciousness without specific impact\n * const fallType = consciousnessSystem.determineFallType(player);\n * // Returns: Stance-based fall or 'backward' default\n * ```\n *\n * @public\n * @korean 의식상실낙법결정\n */\n determineFallType(_player: PlayerState, lastImpactAngle?: number): FallType {\n // If we have impact angle, use simple direction logic\n if (lastImpactAngle !== undefined) {\n const normalizedAngle = Math.abs(lastImpactAngle);\n\n // Front impact (0° ±45°) causes backward fall\n if (normalizedAngle < Math.PI / 4) {\n return \"backward\";\n }\n\n // Rear impact (180° ±45°) causes forward fall\n // Since normalizedAngle is in [0, π], any angle > 3π/4 is a rear impact\n if (normalizedAngle > (3 * Math.PI) / 4) {\n return \"forward\";\n }\n\n // Side impacts: at this point normalizedAngle is in (π/4, 3π/4),\n // so lastImpactAngle cannot be 0 or ±π.\n if (lastImpactAngle < 0) {\n return \"side_left\";\n }\n if (lastImpactAngle > 0) {\n return \"side_right\";\n }\n\n // Fallback for exact 0 (or extremely small) angles in case future\n // changes alter the front/rear checks above. We treat this as a\n // natural backward collapse rather than a side fall.\n return \"backward\";\n }\n\n // Default to backward fall (natural collapse from consciousness loss)\n return \"backward\";\n }\n\n /**\n * Determines fall type based on stance when consciousness is lost.\n *\n * Uses stance bias to determine fall direction when consciousness\n * is lost without specific impact data.\n *\n * Korean terminology:\n * - 자세의식상실 (Jase Uisik Sangsil): Stance-based consciousness loss\n *\n * @param stance - Current trigram stance\n * @returns Fall type based on stance characteristics\n *\n * @example\n * ```typescript\n * // Player in Mountain stance (defensive back)\n * const fallType = consciousnessSystem.determineFallTypeFromStance(\n * TrigramStance.GAN\n * );\n * // Returns: 'backward' (Mountain has defensive backward bias)\n * ```\n *\n * @public\n * @korean 자세의식상실낙법\n */\n determineFallTypeFromStance(stance: TrigramStance): FallType {\n return determineFallFromStance(stance, \"backward\");\n }\n}\n\nexport default ConsciousnessSystem;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,IAAY,qBAAL,yBAAA,oBAAA;;AAEL,oBAAA,kBAAe;;AAEf,oBAAA,iBAAc;;AAEd,oBAAA,aAAU;;AAEV,oBAAA,iBAAc;;KACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDD,IAAa,sBAAb,MAAiC;;;;CAI/B,eAGI;GACD,mBAAmB,eAAe;GACjC,OAAO,CAAC,IAAI,IAAI;GAChB,wBAAwB;GACxB,iBAAiB;GACjB,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;GACA,mBAAmB,cAAc;GAChC,OAAO,CAAC,IAAI,GAAG;GACf,wBAAwB;GACxB,iBAAiB;GACjB,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;GACA,mBAAmB,UAAU;GAC5B,OAAO,CAAC,IAAI,GAAG;GACf,wBAAwB;GACxB,iBAAiB;GACjB,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;GACA,mBAAmB,cAAc;GAChC,OAAO,CAAC,GAAG,GAAG;GACd,wBAAwB;GACxB,iBAAiB;GACjB,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;EACF;;;;CAKD,sBAA+D;GAC5D,mBAAmB,eAAe;GAClC,mBAAmB,WAAW;GAC9B,mBAAmB,cAAc;EAClC,SAAS;EACV;;;;CAKD,mBAAoC;;;;;;CAOpC,0BAA2C;;;;CAK3C,mBAAoC;EAClC,KAAK;EACL,KAAK;EACN;;;;CAKD,uBAAwC;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BxC,YACE,QACA,QACA,UACa;EAOb,MAAM,sBAAsB,UALT,WACf,KAAK,oBAAoB,aAAa,KAAK,oBAAoB,UAC/D,KAAK,oBAAoB;EAM7B,MAAM,mBAAmB,KAAK,IAC5B,GACA,KAAK,IAAI,KAAK,OAAO,gBAAgB,oBAAoB,CAC1D;AAED,SAAO;GACL,GAAG;GACH,eAAe;GAChB;;;;;;;;;;;;;;;;;;;;;;;;;CA0BH,cACE,QACA,WACA,oBACa;AAEb,MAAI,OAAO,iBAAiB,IAC1B,QAAO;AAST,OALwB,qBACpB,KAAK,KAAK,GAAG,qBACb,YAGkB,KAAK,qBACzB,QAAO;EAIT,MAAM,eAAe,YAAY;EACjC,MAAM,QAAQ,KAAK,SAAS,OAAO,cAAc;EAGjD,IAAI,mBAAmB;AACvB,MAAI,UAAU,mBAAmB,QAC/B,oBAAmB;WACV,UAAU,mBAAmB,YACtC,oBAAmB;EAGrB,MAAM,WAAW,KAAK,mBAAmB,eAAe;EACxD,MAAM,mBAAmB,KAAK,IAAI,KAAK,OAAO,gBAAgB,SAAS;AAEvE,SAAO;GACL,GAAG;GACH,eAAe;GAChB;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BH,SAAS,eAA2C;AAClD,MAAI,iBAAiB,GAAI,QAAO,mBAAmB;AACnD,MAAI,iBAAiB,GAAI,QAAO,mBAAmB;AACnD,MAAI,iBAAiB,GAAI,QAAO,mBAAmB;AACnD,SAAO,mBAAmB;;;;;;;;;;;CAY5B,WAAW,OAAiD;AAC1D,SAAO,KAAK,aAAa;;;;;;;;;;;;;CAc3B,aAAa,QAAkC;EAC7C,MAAM,QAAQ,KAAK,SAAS,OAAO,cAAc;EACjD,MAAM,UAAU,KAAK,WAAW,MAAM;AAEtC,SAAO;GACL,GAAG;GACH,aAAa,KAAK,MAChB,OAAO,eAAe,IAAI,QAAQ,iBACnC;GACD,SAAS,KAAK,MAAM,OAAO,WAAW,IAAI,QAAQ,gBAAgB;GAClE,WAAW,KAAK,MAAM,OAAO,aAAa,IAAI,QAAQ,iBAAiB;GACvE,WACE,OAAO,aACP,UAAU,mBAAmB,eAC7B,UAAU,mBAAmB;GAChC;;;;;;;;;;;;;;;;;;;;;;;;CAyBH,4BAA4B,QAA8B;AACxD,SAAO,OAAO,gBAAgB,KAAK;;;;;;;;;;;;CAarC,sBAA8B;AAC5B,SACE,KAAK,iBAAiB,MACtB,KAAK,QAAQ,IAAI,KAAK,iBAAiB,MAAM,KAAK,iBAAiB;;;;;;;;;;;;;CAevE,WAAW,oBAAqC;AAE9C,SADwB,KAAK,KAAK,GAAG,sBACX,KAAK;;;;;;;;;;;CAYjC,gBAAgB,QAA8B;AAC5C,SACE,KAAK,SAAS,OAAO,cAAc,KAAK,mBAAmB;;;;;;;;;;;CAa/D,aAAa,OAAgE;AAuB3E,SAAO;IAlBJ,mBAAmB,eAAe;IACjC,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,cAAc;IAChC,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,UAAU;IAC5B,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,cAAc;IAChC,QAAQ;IACR,SAAS;IACV;GAGI,CAAM;;;;;;;;;;;CAYf,oBAAoB,OAGlB;AAuBA,SAAO;IAlBJ,mBAAmB,eAAe;IACjC,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,cAAc;IAChC,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,UAAU;IAC5B,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,cAAc;IAChC,QAAQ;IACR,SAAS;IACV;GAGI,CAAa;;;;;;;;;;;CAYtB,cAAc,OAAmC;AAQ/C,SAAO;IANJ,mBAAmB,eAAe;IAClC,mBAAmB,cAAc;IACjC,mBAAmB,UAAU;IAC7B,mBAAmB,cAAc;GAG7B,CAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BhB,kBAAkB,QAA8B;AAC9C,SAAO,OAAO,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoChC,kBAAkB,SAAsB,iBAAoC;AAE1E,MAAI,oBAAoB,KAAA,GAAW;GACjC,MAAM,kBAAkB,KAAK,IAAI,gBAAgB;AAGjD,OAAI,kBAAkB,KAAK,KAAK,EAC9B,QAAO;AAKT,OAAI,kBAAmB,IAAI,KAAK,KAAM,EACpC,QAAO;AAKT,OAAI,kBAAkB,EACpB,QAAO;AAET,OAAI,kBAAkB,EACpB,QAAO;AAMT,UAAO;;AAIT,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BT,4BAA4B,QAAiC;AAC3D,SAAO,wBAAwB,QAAQ,WAAW"}
1
+ {"version":3,"file":"ConsciousnessSystem.js","names":[],"sources":["../../../src/systems/combat/ConsciousnessSystem.ts"],"sourcesContent":["/**\n * Consciousness System for managing awareness and cognitive function levels.\n *\n * **Korean**: 의식 시스템 (Consciousness System)\n *\n * Implements consciousness levels based on head trauma, blood flow restriction,\n * and neural damage. Consciousness affects reaction time, targeting precision,\n * and overall combat effectiveness.\n *\n * ## Consciousness Levels\n *\n * - **전투각성 (Combat Alert)**: 100% - Full awareness, optimal combat ability\n * - **혼란상태 (Disoriented)**: 70% - Reduced reaction, vulnerability window\n * - **기절직전 (Stunned)**: 40% - Severe impairment, incapacitation opportunity\n * - **무의식 (Unconscious)**: 0% - Complete incapacitation\n *\n * ## Fall System Integration\n *\n * When consciousness drops below 10%, the system triggers fall animations (낙법).\n * The character loses the ability to stand and collapses to the ground.\n * Fall direction is based on last impact location.\n *\n * @module systems/combat/ConsciousnessSystem\n * @category Combat System\n * @korean 의식시스템\n */\n\nimport { VitalPointCategory } from \"@/types\";\nimport type { TrigramStance } from \"@/types/common\";\nimport { determineFallFromStance } from \"../animation\";\nimport type { FallType } from \"../animation/core/types\";\nimport { PlayerState } from \"../player\";\n\n/**\n * Consciousness levels representing cognitive function and awareness.\n *\n * **Korean**: 의식 수준\n */\nexport enum ConsciousnessLevel {\n /** Full combat awareness (90-100%) */\n COMBAT_ALERT = \"combat_alert\",\n /** Disoriented and vulnerable (50-89%) */\n DISORIENTED = \"disoriented\",\n /** Severely stunned (10-49%) */\n STUNNED = \"stunned\",\n /** Completely unconscious (0-9%) */\n UNCONSCIOUS = \"unconscious\",\n}\n\n/**\n * Effects of consciousness levels on combat performance.\n */\ninterface ConsciousnessEffects {\n /** Consciousness value range */\n readonly range: readonly [number, number];\n /** Reaction time multiplier */\n readonly reactionTimeMultiplier: number;\n /** Accuracy penalty */\n readonly accuracyPenalty: number;\n /** Defense penalty */\n readonly defensePenalty: number;\n /** Can perform actions */\n readonly canAct: boolean;\n /** Vision clarity multiplier */\n readonly visionClarity: number;\n}\n\n/**\n * Consciousness System managing awareness and cognitive function.\n *\n * Tracks consciousness damage from:\n * - Head trauma (strikes to head region)\n * - Neurological vital point hits\n * - Blood flow restriction (vascular hits)\n * - Respiratory disruption\n *\n * Consciousness naturally recovers over time when not taking damage.\n *\n * @example\n * ```typescript\n * const consciousnessSystem = new ConsciousnessSystem();\n *\n * // Apply damage from head strike\n * const newPlayer = consciousnessSystem.applyDamage(\n * player,\n * 15,\n * VitalPointCategory.NEUROLOGICAL\n * );\n *\n * // Check consciousness level\n * const level = consciousnessSystem.getLevel(newPlayer.consciousness);\n * console.log(`Consciousness: ${level}`);\n *\n * // Apply recovery over time\n * const recovered = consciousnessSystem.applyRecovery(newPlayer, 1000);\n * ```\n *\n * @public\n * @korean 의식시스템\n */\nexport class ConsciousnessSystem {\n /**\n * Consciousness level effects and thresholds.\n */\n private readonly levelEffects: Record<\n ConsciousnessLevel,\n ConsciousnessEffects\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n range: [90, 100],\n reactionTimeMultiplier: 1.0,\n accuracyPenalty: 0.0,\n defensePenalty: 0.0,\n canAct: true,\n visionClarity: 1.0,\n },\n [ConsciousnessLevel.DISORIENTED]: {\n range: [50, 89],\n reactionTimeMultiplier: 1.3, // 30% slower reactions\n accuracyPenalty: 0.2, // -20% accuracy\n defensePenalty: 0.15, // -15% defense\n canAct: true,\n visionClarity: 0.7,\n },\n [ConsciousnessLevel.STUNNED]: {\n range: [20, 49], // Updated: Changed from [10, 49] to [20, 49]\n reactionTimeMultiplier: 2.0, // 2x slower reactions\n accuracyPenalty: 0.5, // -50% accuracy\n defensePenalty: 0.4, // -40% defense\n canAct: true,\n visionClarity: 0.4,\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n range: [0, 19], // Updated: Changed from [0, 9] to [0, 19] - incapacitation threshold <20%\n reactionTimeMultiplier: Infinity,\n accuracyPenalty: 1.0, // Complete inability\n defensePenalty: 1.0, // No defense\n canAct: false,\n visionClarity: 0.0,\n },\n };\n\n /**\n * Damage multipliers by vital point category affecting consciousness.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 3.0, // Nerve strikes heavily affect consciousness\n [VitalPointCategory.VASCULAR]: 2.0, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 1.5, // Breathing difficulty\n default: 0.5, // Other damage has minor effect\n };\n\n /**\n * Base recovery rate per second.\n */\n private readonly baseRecoveryRate = 5.0; // 5 points per second\n\n /**\n * Incapacitation threshold - consciousness <20% renders player helpless.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n */\n private readonly incapacitationThreshold = 20;\n\n /**\n * Duration of helplessness when consciousness drops below threshold (3-5 seconds).\n */\n private readonly helplessDuration = {\n min: 3000, // 3 seconds\n max: 5000, // 5 seconds\n };\n\n /**\n * Time without head trauma required for consciousness recovery (5 seconds).\n */\n private readonly noTraumaRecoveryTime = 5000; // 5 seconds\n\n /**\n * Applies consciousness damage from combat.\n *\n * Calculates consciousness reduction based on damage amount and\n * vital point category. Neurological and vascular damage have\n * the highest impact on consciousness.\n *\n * @param player - Current player state\n * @param damage - Base damage amount\n * @param category - Vital point category hit\n * @returns Updated player state with reduced consciousness\n *\n * @example\n * ```typescript\n * // Head strike causes significant consciousness damage\n * const newPlayer = system.applyDamage(\n * player,\n * 20,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * console.log(`Consciousness: ${newPlayer.consciousness}`);\n * ```\n *\n * @public\n * @korean 의식피해적용\n */\n applyDamage(\n player: PlayerState,\n damage: number,\n category?: VitalPointCategory\n ): PlayerState {\n // Get multiplier based on category\n const multiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate consciousness damage\n const consciousnessDamage = damage * multiplier;\n\n // Apply damage, clamped to 0-100 range\n const newConsciousness = Math.max(\n 0,\n Math.min(100, player.consciousness - consciousnessDamage)\n );\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Applies consciousness recovery over time.\n *\n * Consciousness naturally recovers when not taking damage.\n * Recovery rate depends on current level - severely stunned\n * players recover more slowly.\n *\n * **Note**: Full recovery only occurs after 5 seconds without head trauma.\n *\n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @param lastHeadTraumaTime - Timestamp of last head trauma (optional)\n * @returns Updated player state with recovered consciousness\n *\n * @example\n * ```typescript\n * // In game loop (60fps, ~16ms per frame)\n * player = system.applyRecovery(player, 16, player.lastActionTime);\n * ```\n *\n * @public\n * @korean 의식회복\n */\n applyRecovery(\n player: PlayerState,\n deltaTime: number,\n lastHeadTraumaTime?: number\n ): PlayerState {\n // Already at full consciousness\n if (player.consciousness >= 100) {\n return player;\n }\n\n // Check if enough time has passed since last head trauma\n const timeSinceTrauma = lastHeadTraumaTime\n ? Date.now() - lastHeadTraumaTime\n : Infinity;\n\n // Don't recover if head trauma is recent (within 5 seconds)\n if (timeSinceTrauma < this.noTraumaRecoveryTime) {\n return player;\n }\n\n // Calculate recovery based on time\n const deltaSeconds = deltaTime / 1000;\n const level = this.getLevel(player.consciousness);\n\n // Recovery rate modifier based on level\n let recoveryModifier = 1.0;\n if (level === ConsciousnessLevel.STUNNED) {\n recoveryModifier = 0.5; // Slower recovery when stunned\n } else if (level === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryModifier = 0.2; // Very slow recovery when unconscious\n }\n\n const recovery = this.baseRecoveryRate * deltaSeconds * recoveryModifier;\n const newConsciousness = Math.min(100, player.consciousness + recovery);\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Determines consciousness level from consciousness value.\n *\n * Updated thresholds:\n * - 90-100: Combat Alert\n * - 50-89: Disoriented\n * - 20-49: Stunned\n * - 0-19: Unconscious (incapacitation threshold <20%)\n *\n * @param consciousness - Consciousness value (0-100)\n * @returns Current consciousness level\n *\n * @example\n * ```typescript\n * const level = system.getLevel(75);\n * console.log(level); // DISORIENTED\n *\n * const level2 = system.getLevel(15);\n * console.log(level2); // UNCONSCIOUS (below incapacitation threshold)\n * ```\n *\n * @public\n * @korean 의식수준확인\n */\n getLevel(consciousness: number): ConsciousnessLevel {\n if (consciousness >= 90) return ConsciousnessLevel.COMBAT_ALERT;\n if (consciousness >= 50) return ConsciousnessLevel.DISORIENTED;\n if (consciousness >= 20) return ConsciousnessLevel.STUNNED;\n return ConsciousnessLevel.UNCONSCIOUS;\n }\n\n /**\n * Gets effects for a specific consciousness level.\n *\n * @param level - Consciousness level\n * @returns Effects applied at that level\n *\n * @public\n * @korean 의식효과\n */\n getEffects(level: ConsciousnessLevel): ConsciousnessEffects {\n return this.levelEffects[level];\n }\n\n /**\n * Applies consciousness effects to player state.\n *\n * Modifies player stats based on current consciousness level.\n *\n * @param player - Current player state\n * @returns Modified player state with consciousness effects\n *\n * @public\n * @korean 의식효과적용\n */\n applyEffects(player: PlayerState): PlayerState {\n const level = this.getLevel(player.consciousness);\n const effects = this.getEffects(level);\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - effects.accuracyPenalty)\n ),\n defense: Math.floor(player.defense * (1 - effects.defensePenalty)),\n technique: Math.floor(player.technique * (1 - effects.accuracyPenalty)),\n isStunned:\n player.isStunned ||\n level === ConsciousnessLevel.UNCONSCIOUS ||\n level === ConsciousnessLevel.STUNNED,\n };\n }\n\n /**\n * Checks if player is at incapacitation threshold.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n *\n * When consciousness drops below 20%, player becomes helpless for 3-5 seconds.\n *\n * @param player - Current player state\n * @returns True if consciousness is below incapacitation threshold (<20%)\n *\n * @example\n * ```typescript\n * if (system.isAtIncapacitationThreshold(player)) {\n * // Player is helpless, trigger helpless state\n * const helplessDuration = system.getHelplessDuration();\n * console.log(`Player helpless for ${helplessDuration}ms`);\n * }\n * ```\n *\n * @public\n * @korean 무력화한계확인\n */\n isAtIncapacitationThreshold(player: PlayerState): boolean {\n return player.consciousness < this.incapacitationThreshold;\n }\n\n /**\n * Gets random helpless duration when consciousness drops below threshold.\n *\n * Returns a random duration between 3-5 seconds (3000-5000ms).\n *\n * @returns Helpless duration in milliseconds\n *\n * @public\n * @korean 무력화지속시간\n */\n getHelplessDuration(): number {\n return (\n this.helplessDuration.min +\n Math.random() * (this.helplessDuration.max - this.helplessDuration.min)\n );\n }\n\n /**\n * Checks if enough time has passed for consciousness recovery.\n *\n * Consciousness only recovers after 5 seconds without head trauma.\n *\n * @param lastHeadTraumaTime - Timestamp of last head trauma\n * @returns True if recovery is allowed\n *\n * @public\n * @korean 회복가능확인\n */\n canRecover(lastHeadTraumaTime: number): boolean {\n const timeSinceTrauma = Date.now() - lastHeadTraumaTime;\n return timeSinceTrauma >= this.noTraumaRecoveryTime;\n }\n\n /**\n * Checks if player is incapacitated due to consciousness.\n *\n * @param player - Current player state\n * @returns True if player is unconscious\n *\n * @public\n * @korean 무의식확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return (\n this.getLevel(player.consciousness) === ConsciousnessLevel.UNCONSCIOUS\n );\n }\n\n /**\n * Gets bilingual name for consciousness level.\n *\n * @param level - Consciousness level\n * @returns Korean and English level names\n *\n * @public\n * @korean 의식이름\n */\n getLevelName(level: ConsciousnessLevel): { korean: string; english: string } {\n const names: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"전투각성\",\n english: \"Combat Alert\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"혼란상태\",\n english: \"Disoriented\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"기절직전\",\n english: \"Stunned\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"무의식\",\n english: \"Unconscious\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of consciousness level effects.\n *\n * @param level - Consciousness level\n * @returns Bilingual description\n *\n * @public\n * @korean 의식설명\n */\n getLevelDescription(level: ConsciousnessLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"완전한 의식과 전투 능력\",\n english: \"Full awareness and combat ability\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"반응 시간 저하 및 혼란\",\n english: \"Reduced reaction time and confusion\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"심각한 손상, 무력화 위험\",\n english: \"Severe impairment, incapacitation risk\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"완전한 무의식 상태\",\n english: \"Complete unconsciousness\",\n },\n };\n\n return descriptions[level];\n }\n\n /**\n * Gets color indicator for consciousness level (for UI).\n *\n * @param level - Consciousness level\n * @returns Hex color code\n *\n * @public\n * @korean 의식색상\n */\n getLevelColor(level: ConsciousnessLevel): number {\n const colors: Record<ConsciousnessLevel, number> = {\n [ConsciousnessLevel.COMBAT_ALERT]: 0x00ff00, // Green\n [ConsciousnessLevel.DISORIENTED]: 0xffaa00, // Orange\n [ConsciousnessLevel.STUNNED]: 0xff4400, // Red-orange\n [ConsciousnessLevel.UNCONSCIOUS]: 0xff0000, // Red\n };\n\n return colors[level];\n }\n\n /**\n * Checks if consciousness is low enough to trigger fall animation.\n *\n * Falls occur when consciousness drops below 10% (UNCONSCIOUS threshold).\n * This represents complete loss of ability to maintain standing position.\n *\n * Korean terminology:\n * - 의식상실낙법 (Uisik Sangsil Nakbeop): Consciousness loss fall\n * - 기절낙하 (Gijeol Nakha): Knockout collapse\n *\n * @param player - Current player state\n * @returns True if fall animation should trigger\n *\n * @example\n * ```typescript\n * if (consciousnessSystem.shouldTriggerFall(player)) {\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * lastImpactAngle\n * );\n * animationMachine.transitionTo(FALL_TYPE_TO_ANIMATION[fallType]);\n * }\n * ```\n *\n * @public\n * @korean 의식상실낙법확인\n */\n shouldTriggerFall(player: PlayerState): boolean {\n return player.consciousness < 10; // UNCONSCIOUS threshold\n }\n\n /**\n * Determines fall direction based on last impact and stance.\n *\n * When consciousness is lost, fall direction is determined by:\n * - Last attack angle (if available)\n * - Current stance bias (if no attack data)\n * - Default to backward fall (natural collapse)\n *\n * Korean falling from consciousness loss:\n * - 후방의식상실 (Hubang Uisik Sangsil): Backward consciousness loss fall\n * - 전방기절 (Jeonbang Gijeol): Forward knockout\n *\n * @param player - Current player state\n * @param lastImpactAngle - Angle of last hit (optional)\n * @returns Fall type to use for animation\n *\n * @example\n * ```typescript\n * // Player loses consciousness from frontal head strike\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * 0 // Front angle\n * );\n * // Returns: 'backward' (knocked back by frontal strike)\n *\n * // Player loses consciousness without specific impact\n * const fallType = consciousnessSystem.determineFallType(player);\n * // Returns: Stance-based fall or 'backward' default\n * ```\n *\n * @public\n * @korean 의식상실낙법결정\n */\n determineFallType(_player: PlayerState, lastImpactAngle?: number): FallType {\n // If we have impact angle, use simple direction logic\n if (lastImpactAngle !== undefined) {\n const normalizedAngle = Math.abs(lastImpactAngle);\n\n // Front impact (0° ±45°) causes backward fall\n if (normalizedAngle < Math.PI / 4) {\n return \"backward\";\n }\n\n // Rear impact (180° ±45°) causes forward fall\n // Since normalizedAngle is in [0, π], any angle > 3π/4 is a rear impact\n if (normalizedAngle > (3 * Math.PI) / 4) {\n return \"forward\";\n }\n\n // Side impacts: at this point normalizedAngle is in (π/4, 3π/4),\n // so lastImpactAngle cannot be 0 or ±π.\n if (lastImpactAngle < 0) {\n return \"side_left\";\n }\n if (lastImpactAngle > 0) {\n return \"side_right\";\n }\n\n // Fallback for exact 0 (or extremely small) angles in case future\n // changes alter the front/rear checks above. We treat this as a\n // natural backward collapse rather than a side fall.\n return \"backward\";\n }\n\n // Default to backward fall (natural collapse from consciousness loss)\n return \"backward\";\n }\n\n /**\n * Determines fall type based on stance when consciousness is lost.\n *\n * Uses stance bias to determine fall direction when consciousness\n * is lost without specific impact data.\n *\n * Korean terminology:\n * - 자세의식상실 (Jase Uisik Sangsil): Stance-based consciousness loss\n *\n * @param stance - Current trigram stance\n * @returns Fall type based on stance characteristics\n *\n * @example\n * ```typescript\n * // Player in Mountain stance (defensive back)\n * const fallType = consciousnessSystem.determineFallTypeFromStance(\n * TrigramStance.GAN\n * );\n * // Returns: 'backward' (Mountain has defensive backward bias)\n * ```\n *\n * @public\n * @korean 자세의식상실낙법\n */\n determineFallTypeFromStance(stance: TrigramStance): FallType {\n return determineFallFromStance(stance, \"backward\");\n }\n}\n\nexport default ConsciousnessSystem;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,IAAY,qBAAL,yBAAA,oBAAA;;CAEL,mBAAA,kBAAe;;CAEf,mBAAA,iBAAc;;CAEd,mBAAA,aAAU;;CAEV,mBAAA,iBAAc;;KACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDD,IAAa,sBAAb,MAAiC;;;;CAI/B,eAGI;GACD,mBAAmB,eAAe;GACjC,OAAO,CAAC,IAAI,IAAI;GAChB,wBAAwB;GACxB,iBAAiB;GACjB,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;GACA,mBAAmB,cAAc;GAChC,OAAO,CAAC,IAAI,GAAG;GACf,wBAAwB;GACxB,iBAAiB;GACjB,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;GACA,mBAAmB,UAAU;GAC5B,OAAO,CAAC,IAAI,GAAG;GACf,wBAAwB;GACxB,iBAAiB;GACjB,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;GACA,mBAAmB,cAAc;GAChC,OAAO,CAAC,GAAG,GAAG;GACd,wBAAwB;GACxB,iBAAiB;GACjB,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;EACF;;;;CAKD,sBAA+D;GAC5D,mBAAmB,eAAe;GAClC,mBAAmB,WAAW;GAC9B,mBAAmB,cAAc;EAClC,SAAS;EACV;;;;CAKD,mBAAoC;;;;;;CAOpC,0BAA2C;;;;CAK3C,mBAAoC;EAClC,KAAK;EACL,KAAK;EACN;;;;CAKD,uBAAwC;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BxC,YACE,QACA,QACA,UACa;EAOb,MAAM,sBAAsB,UALT,WACf,KAAK,oBAAoB,aAAa,KAAK,oBAAoB,UAC/D,KAAK,oBAAoB;EAM7B,MAAM,mBAAmB,KAAK,IAC5B,GACA,KAAK,IAAI,KAAK,OAAO,gBAAgB,oBAAoB,CAC1D;EAED,OAAO;GACL,GAAG;GACH,eAAe;GAChB;;;;;;;;;;;;;;;;;;;;;;;;;CA0BH,cACE,QACA,WACA,oBACa;EAEb,IAAI,OAAO,iBAAiB,KAC1B,OAAO;EAST,KALwB,qBACpB,KAAK,KAAK,GAAG,qBACb,YAGkB,KAAK,sBACzB,OAAO;EAIT,MAAM,eAAe,YAAY;EACjC,MAAM,QAAQ,KAAK,SAAS,OAAO,cAAc;EAGjD,IAAI,mBAAmB;EACvB,IAAI,UAAU,mBAAmB,SAC/B,mBAAmB;OACd,IAAI,UAAU,mBAAmB,aACtC,mBAAmB;EAGrB,MAAM,WAAW,KAAK,mBAAmB,eAAe;EACxD,MAAM,mBAAmB,KAAK,IAAI,KAAK,OAAO,gBAAgB,SAAS;EAEvE,OAAO;GACL,GAAG;GACH,eAAe;GAChB;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BH,SAAS,eAA2C;EAClD,IAAI,iBAAiB,IAAI,OAAO,mBAAmB;EACnD,IAAI,iBAAiB,IAAI,OAAO,mBAAmB;EACnD,IAAI,iBAAiB,IAAI,OAAO,mBAAmB;EACnD,OAAO,mBAAmB;;;;;;;;;;;CAY5B,WAAW,OAAiD;EAC1D,OAAO,KAAK,aAAa;;;;;;;;;;;;;CAc3B,aAAa,QAAkC;EAC7C,MAAM,QAAQ,KAAK,SAAS,OAAO,cAAc;EACjD,MAAM,UAAU,KAAK,WAAW,MAAM;EAEtC,OAAO;GACL,GAAG;GACH,aAAa,KAAK,MAChB,OAAO,eAAe,IAAI,QAAQ,iBACnC;GACD,SAAS,KAAK,MAAM,OAAO,WAAW,IAAI,QAAQ,gBAAgB;GAClE,WAAW,KAAK,MAAM,OAAO,aAAa,IAAI,QAAQ,iBAAiB;GACvE,WACE,OAAO,aACP,UAAU,mBAAmB,eAC7B,UAAU,mBAAmB;GAChC;;;;;;;;;;;;;;;;;;;;;;;;CAyBH,4BAA4B,QAA8B;EACxD,OAAO,OAAO,gBAAgB,KAAK;;;;;;;;;;;;CAarC,sBAA8B;EAC5B,OACE,KAAK,iBAAiB,MACtB,KAAK,QAAQ,IAAI,KAAK,iBAAiB,MAAM,KAAK,iBAAiB;;;;;;;;;;;;;CAevE,WAAW,oBAAqC;EAE9C,OADwB,KAAK,KAAK,GAAG,sBACX,KAAK;;;;;;;;;;;CAYjC,gBAAgB,QAA8B;EAC5C,OACE,KAAK,SAAS,OAAO,cAAc,KAAK,mBAAmB;;;;;;;;;;;CAa/D,aAAa,OAAgE;EAuB3E,OAAO;IAlBJ,mBAAmB,eAAe;IACjC,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,cAAc;IAChC,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,UAAU;IAC5B,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,cAAc;IAChC,QAAQ;IACR,SAAS;IACV;GAGI,CAAM;;;;;;;;;;;CAYf,oBAAoB,OAGlB;EAuBA,OAAO;IAlBJ,mBAAmB,eAAe;IACjC,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,cAAc;IAChC,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,UAAU;IAC5B,QAAQ;IACR,SAAS;IACV;IACA,mBAAmB,cAAc;IAChC,QAAQ;IACR,SAAS;IACV;GAGI,CAAa;;;;;;;;;;;CAYtB,cAAc,OAAmC;EAQ/C,OAAO;IANJ,mBAAmB,eAAe;IAClC,mBAAmB,cAAc;IACjC,mBAAmB,UAAU;IAC7B,mBAAmB,cAAc;GAG7B,CAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BhB,kBAAkB,QAA8B;EAC9C,OAAO,OAAO,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoChC,kBAAkB,SAAsB,iBAAoC;EAE1E,IAAI,oBAAoB,KAAA,GAAW;GACjC,MAAM,kBAAkB,KAAK,IAAI,gBAAgB;GAGjD,IAAI,kBAAkB,KAAK,KAAK,GAC9B,OAAO;GAKT,IAAI,kBAAmB,IAAI,KAAK,KAAM,GACpC,OAAO;GAKT,IAAI,kBAAkB,GACpB,OAAO;GAET,IAAI,kBAAkB,GACpB,OAAO;GAMT,OAAO;;EAIT,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BT,4BAA4B,QAAiC;EAC3D,OAAO,wBAAwB,QAAQ,WAAW"}
@@ -1 +1 @@
1
- {"version":3,"file":"FallIntegration.js","names":[],"sources":["../../../src/systems/combat/FallIntegration.ts"],"sourcesContent":["/**\n * Fall Integration Utilities\n *\n * Helper functions to check for fall conditions and determine fall animations\n * based on balance and consciousness systems.\n *\n * @module systems/combat/FallIntegration\n * @category Combat System\n * @korean 낙법통합\n */\n\nimport type { AnimationState, FallType } from \"../animation/core/types\";\nimport {\n FALL_TYPE_TO_ANIMATION,\n isFallState,\n isGroundState,\n} from \"../animation/core/types\";\nimport type { CombatSystem } from \"../CombatSystem\";\nimport type { PlayerState } from \"../player\";\n\n/**\n * Result of fall check with animation state\n * @korean 낙법확인결과\n */\nexport interface FallCheckResult {\n /** Whether a fall should be triggered */\n readonly shouldFall: boolean;\n /** Fall type if falling */\n readonly fallType?: FallType;\n /** Animation state to transition to */\n readonly animationState?: AnimationState;\n /** Reason for fall */\n readonly reason?: \"balance\" | \"consciousness\";\n}\n\n/**\n * Checks if player should fall and determines fall animation.\n *\n * Integrates BalanceSystem and ConsciousnessSystem to check fall conditions.\n * Returns animation state to transition to if fall is triggered.\n *\n * Korean terminology:\n * - 균형상실 (Gyunhyeong Sangsil): Balance loss\n * - 의식상실 (Uisik Sangsil): Consciousness loss\n *\n * @param player - Player state to check\n * @param combatSystem - Combat system with balance and consciousness systems\n * @param lastImpactAngle - Optional angle of last impact (for consciousness falls)\n * @param attackAngle - Optional angle of current attack (for balance falls)\n * @returns Fall check result with animation state\n *\n * @example\n * ```typescript\n * const fallCheck = checkForFall(player, combatSystem, undefined, attackAngle);\n * if (fallCheck.shouldFall && fallCheck.animationState) {\n * animationMachine.transitionTo(fallCheck.animationState);\n * console.log(`Player falling: ${fallCheck.reason}`);\n * }\n * ```\n *\n * @public\n * @korean 낙법확인\n */\nexport function checkForFall(\n player: PlayerState,\n combatSystem: CombatSystem,\n lastImpactAngle?: number,\n attackAngle?: number\n): FallCheckResult {\n const balanceSystem = combatSystem.getBalanceSystem();\n const consciousnessSystem = combatSystem.getConsciousnessSystem();\n\n // Check consciousness first (higher priority - complete loss of control)\n if (consciousnessSystem.shouldTriggerFall(player)) {\n const fallType = consciousnessSystem.determineFallType(\n player,\n lastImpactAngle\n );\n return {\n shouldFall: true,\n fallType,\n animationState: FALL_TYPE_TO_ANIMATION[fallType],\n reason: \"consciousness\",\n };\n }\n\n // Check balance (lower priority - still some control)\n if (balanceSystem.shouldTriggerFall(player)) {\n // Use attack angle if available, otherwise use stance-based fall\n const fallType =\n attackAngle !== undefined\n ? balanceSystem.determineFallType(player, attackAngle, \"mid\")\n : balanceSystem.determineFallTypeFromStance(player.currentStance);\n\n return {\n shouldFall: true,\n fallType,\n animationState: FALL_TYPE_TO_ANIMATION[fallType],\n reason: \"balance\",\n };\n }\n\n // No fall condition met\n return {\n shouldFall: false,\n };\n}\n\n/**\n * Checks if player is currently in a fall or ground animation state.\n *\n * Used to prevent other actions during falls and ground states.\n *\n * @param animationState - Current animation state\n * @returns True if player is falling or on ground\n *\n * @public\n * @korean 낙법중확인\n */\nexport function isInFallOrGroundState(animationState: AnimationState): boolean {\n return isFallState(animationState) || isGroundState(animationState);\n}\n\n/**\n * Gets Korean description for fall type.\n *\n * @param fallType - Type of fall\n * @returns Korean and English names\n *\n * @public\n * @korean 낙법이름\n */\nexport function getFallTypeName(fallType: FallType): {\n korean: string;\n english: string;\n} {\n const names: Record<FallType, { korean: string; english: string }> = {\n forward: { korean: \"전방낙법\", english: \"Forward Fall\" },\n backward: { korean: \"후방낙법\", english: \"Backward Fall\" },\n side_left: { korean: \"좌측낙법\", english: \"Left Side Fall\" },\n side_right: { korean: \"우측낙법\", english: \"Right Side Fall\" },\n };\n\n return names[fallType];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAgB,aACd,QACA,cACA,iBACA,aACiB;CACjB,MAAM,gBAAgB,aAAa,kBAAkB;CACrD,MAAM,sBAAsB,aAAa,wBAAwB;AAGjE,KAAI,oBAAoB,kBAAkB,OAAO,EAAE;EACjD,MAAM,WAAW,oBAAoB,kBACnC,QACA,gBACD;AACD,SAAO;GACL,YAAY;GACZ;GACA,gBAAgB,uBAAuB;GACvC,QAAQ;GACT;;AAIH,KAAI,cAAc,kBAAkB,OAAO,EAAE;EAE3C,MAAM,WACJ,gBAAgB,KAAA,IACZ,cAAc,kBAAkB,QAAQ,aAAa,MAAM,GAC3D,cAAc,4BAA4B,OAAO,cAAc;AAErE,SAAO;GACL,YAAY;GACZ;GACA,gBAAgB,uBAAuB;GACvC,QAAQ;GACT;;AAIH,QAAO,EACL,YAAY,OACb;;;;;;;;;;;;;AAcH,SAAgB,sBAAsB,gBAAyC;AAC7E,QAAO,YAAY,eAAe,IAAI,cAAc,eAAe;;;;;;;;;;;AAYrE,SAAgB,gBAAgB,UAG9B;AAQA,QAAO;EANL,SAAS;GAAE,QAAQ;GAAQ,SAAS;GAAgB;EACpD,UAAU;GAAE,QAAQ;GAAQ,SAAS;GAAiB;EACtD,WAAW;GAAE,QAAQ;GAAQ,SAAS;GAAkB;EACxD,YAAY;GAAE,QAAQ;GAAQ,SAAS;GAAmB;EAGrD,CAAM"}
1
+ {"version":3,"file":"FallIntegration.js","names":[],"sources":["../../../src/systems/combat/FallIntegration.ts"],"sourcesContent":["/**\n * Fall Integration Utilities\n *\n * Helper functions to check for fall conditions and determine fall animations\n * based on balance and consciousness systems.\n *\n * @module systems/combat/FallIntegration\n * @category Combat System\n * @korean 낙법통합\n */\n\nimport type { AnimationState, FallType } from \"../animation/core/types\";\nimport {\n FALL_TYPE_TO_ANIMATION,\n isFallState,\n isGroundState,\n} from \"../animation/core/types\";\nimport type { CombatSystem } from \"../CombatSystem\";\nimport type { PlayerState } from \"../player\";\n\n/**\n * Result of fall check with animation state\n * @korean 낙법확인결과\n */\nexport interface FallCheckResult {\n /** Whether a fall should be triggered */\n readonly shouldFall: boolean;\n /** Fall type if falling */\n readonly fallType?: FallType;\n /** Animation state to transition to */\n readonly animationState?: AnimationState;\n /** Reason for fall */\n readonly reason?: \"balance\" | \"consciousness\";\n}\n\n/**\n * Checks if player should fall and determines fall animation.\n *\n * Integrates BalanceSystem and ConsciousnessSystem to check fall conditions.\n * Returns animation state to transition to if fall is triggered.\n *\n * Korean terminology:\n * - 균형상실 (Gyunhyeong Sangsil): Balance loss\n * - 의식상실 (Uisik Sangsil): Consciousness loss\n *\n * @param player - Player state to check\n * @param combatSystem - Combat system with balance and consciousness systems\n * @param lastImpactAngle - Optional angle of last impact (for consciousness falls)\n * @param attackAngle - Optional angle of current attack (for balance falls)\n * @returns Fall check result with animation state\n *\n * @example\n * ```typescript\n * const fallCheck = checkForFall(player, combatSystem, undefined, attackAngle);\n * if (fallCheck.shouldFall && fallCheck.animationState) {\n * animationMachine.transitionTo(fallCheck.animationState);\n * console.log(`Player falling: ${fallCheck.reason}`);\n * }\n * ```\n *\n * @public\n * @korean 낙법확인\n */\nexport function checkForFall(\n player: PlayerState,\n combatSystem: CombatSystem,\n lastImpactAngle?: number,\n attackAngle?: number\n): FallCheckResult {\n const balanceSystem = combatSystem.getBalanceSystem();\n const consciousnessSystem = combatSystem.getConsciousnessSystem();\n\n // Check consciousness first (higher priority - complete loss of control)\n if (consciousnessSystem.shouldTriggerFall(player)) {\n const fallType = consciousnessSystem.determineFallType(\n player,\n lastImpactAngle\n );\n return {\n shouldFall: true,\n fallType,\n animationState: FALL_TYPE_TO_ANIMATION[fallType],\n reason: \"consciousness\",\n };\n }\n\n // Check balance (lower priority - still some control)\n if (balanceSystem.shouldTriggerFall(player)) {\n // Use attack angle if available, otherwise use stance-based fall\n const fallType =\n attackAngle !== undefined\n ? balanceSystem.determineFallType(player, attackAngle, \"mid\")\n : balanceSystem.determineFallTypeFromStance(player.currentStance);\n\n return {\n shouldFall: true,\n fallType,\n animationState: FALL_TYPE_TO_ANIMATION[fallType],\n reason: \"balance\",\n };\n }\n\n // No fall condition met\n return {\n shouldFall: false,\n };\n}\n\n/**\n * Checks if player is currently in a fall or ground animation state.\n *\n * Used to prevent other actions during falls and ground states.\n *\n * @param animationState - Current animation state\n * @returns True if player is falling or on ground\n *\n * @public\n * @korean 낙법중확인\n */\nexport function isInFallOrGroundState(animationState: AnimationState): boolean {\n return isFallState(animationState) || isGroundState(animationState);\n}\n\n/**\n * Gets Korean description for fall type.\n *\n * @param fallType - Type of fall\n * @returns Korean and English names\n *\n * @public\n * @korean 낙법이름\n */\nexport function getFallTypeName(fallType: FallType): {\n korean: string;\n english: string;\n} {\n const names: Record<FallType, { korean: string; english: string }> = {\n forward: { korean: \"전방낙법\", english: \"Forward Fall\" },\n backward: { korean: \"후방낙법\", english: \"Backward Fall\" },\n side_left: { korean: \"좌측낙법\", english: \"Left Side Fall\" },\n side_right: { korean: \"우측낙법\", english: \"Right Side Fall\" },\n };\n\n return names[fallType];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAgB,aACd,QACA,cACA,iBACA,aACiB;CACjB,MAAM,gBAAgB,aAAa,kBAAkB;CACrD,MAAM,sBAAsB,aAAa,wBAAwB;CAGjE,IAAI,oBAAoB,kBAAkB,OAAO,EAAE;EACjD,MAAM,WAAW,oBAAoB,kBACnC,QACA,gBACD;EACD,OAAO;GACL,YAAY;GACZ;GACA,gBAAgB,uBAAuB;GACvC,QAAQ;GACT;;CAIH,IAAI,cAAc,kBAAkB,OAAO,EAAE;EAE3C,MAAM,WACJ,gBAAgB,KAAA,IACZ,cAAc,kBAAkB,QAAQ,aAAa,MAAM,GAC3D,cAAc,4BAA4B,OAAO,cAAc;EAErE,OAAO;GACL,YAAY;GACZ;GACA,gBAAgB,uBAAuB;GACvC,QAAQ;GACT;;CAIH,OAAO,EACL,YAAY,OACb;;;;;;;;;;;;;AAcH,SAAgB,sBAAsB,gBAAyC;CAC7E,OAAO,YAAY,eAAe,IAAI,cAAc,eAAe;;;;;;;;;;;AAYrE,SAAgB,gBAAgB,UAG9B;CAQA,OAAO;EANL,SAAS;GAAE,QAAQ;GAAQ,SAAS;GAAgB;EACpD,UAAU;GAAE,QAAQ;GAAQ,SAAS;GAAiB;EACtD,WAAW;GAAE,QAAQ;GAAQ,SAAS;GAAkB;EACxD,YAAY;GAAE,QAAQ;GAAQ,SAAS;GAAmB;EAGrD,CAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"GrappleSystem.js","names":[],"sources":["../../../src/systems/combat/GrappleSystem.ts"],"sourcesContent":["/**\n * Grappling System for Korean Martial Arts Combat\n *\n * **Korean**: 잡기 시스템 (Grapple System)\n *\n * Implements realistic grappling and takedown mechanics based on:\n * - **Hapkido (합기도)**: Joint locks and control techniques\n * - **Ssireum (씨름)**: Traditional Korean wrestling\n * - **GON Stance (곤괘)**: Earth stance grounding and control\n *\n * ## Core Mechanics\n *\n * 1. **Grab Initiation**: Establish grip on opponent's limb/body\n * 2. **Control Duration**: Maintain control with stamina cost\n * 3. **Grip Strength**: Affects escape difficulty and follow-up options\n * 4. **Escape Mechanics**: Based on strength, technique, and stamina\n * 5. **Follow-up Techniques**: Throws, takedowns, joint locks from control\n *\n * ## Phase 2 Enhancement\n *\n * Enhanced with Gon (Earth) trigram metadata integration:\n * - Uses `controlDuration` from ExtendedGonTechnique\n * - Supports authentic Ssireum/Hapkido control durations\n * - Maintains backward compatibility with non-Gon techniques\n *\n * @module systems/combat/GrappleSystem\n * @category Combat System\n * @korean 잡기시스템\n */\n\nimport type { PlayerState } from \"@/systems/player\";\nimport {\n CombatState,\n GrappleControl,\n GrappleState,\n GrappleTarget,\n TrigramStance,\n} from \"@/types\";\nimport { asExtendedGonTechnique } from \"../trigram/types/GonTechniqueExtensions\";\nimport type { KoreanTechnique } from \"../vitalpoint/types\";\n\n/**\n * Configuration for grappling mechanics.\n *\n * **Korean**: 잡기 설정 (Grapple Configuration)\n */\nexport interface GrappleConfig {\n /** Base stamina cost per second to maintain control */\n readonly baseStaminaCostPerSecond: number;\n /** Minimum grip strength to maintain control */\n readonly minGripStrength: number;\n /** Maximum grip strength value */\n readonly maxGripStrength: number;\n /** Base escape difficulty multiplier */\n readonly baseEscapeDifficulty: number;\n /** Stamina cost to attempt escape */\n readonly escapeStaminaCost: number;\n /** Minimum duration before escape is possible (ms) */\n readonly minControlDuration: number;\n}\n\n/**\n * Default grappling configuration values.\n */\nexport const DEFAULT_GRAPPLE_CONFIG: GrappleConfig = {\n baseStaminaCostPerSecond: 5,\n minGripStrength: 20,\n maxGripStrength: 100,\n baseEscapeDifficulty: 1.5,\n escapeStaminaCost: 15,\n minControlDuration: 500, // 0.5 seconds\n};\n\n/**\n * Grapple attempt result.\n *\n * **Korean**: 잡기 시도 결과 (Grapple Attempt Result)\n */\nexport interface GrappleAttemptResult {\n /** Whether the grapple was successful */\n readonly success: boolean;\n /** New grapple control state if successful */\n readonly grappleControl?: GrappleControl;\n /** Reason for failure if unsuccessful */\n readonly reason?: string;\n /** Stamina cost for the attempt */\n readonly staminaCost: number;\n}\n\n/**\n * Escape attempt result.\n *\n * **Korean**: 탈출 시도 결과 (Escape Attempt Result)\n */\nexport interface EscapeAttemptResult {\n /** Whether the escape was successful */\n readonly success: boolean;\n /** Updated grapple control state */\n readonly grappleControl: GrappleControl | null;\n /** Reason for failure if unsuccessful */\n readonly reason?: string;\n /** Stamina cost for the attempt */\n readonly staminaCost: number;\n}\n\n/**\n * Grappling System for control and hold mechanics.\n *\n * **Korean**: 잡기 시스템 (Grapple System)\n *\n * Manages grappling state between combatants, including grip strength,\n * control duration, escape mechanics, and follow-up techniques.\n */\nexport class GrappleSystem {\n private readonly config: GrappleConfig;\n\n constructor(config: Partial<GrappleConfig> = {}) {\n this.config = { ...DEFAULT_GRAPPLE_CONFIG, ...config };\n }\n\n /**\n * Attempt to initiate a grapple on target.\n *\n * **Korean**: 잡기 시도 (Attempt Grapple)\n *\n * @param attacker - Player attempting the grapple\n * @param defender - Target player\n * @param target - Body part to grapple\n * @param currentTime - Current game time in milliseconds\n * @returns Result of the grapple attempt\n */\n attemptGrapple(\n attacker: PlayerState,\n defender: PlayerState,\n target: GrappleTarget,\n currentTime: number\n ): GrappleAttemptResult {\n // Check if attacker can grapple\n if (attacker.combatState === CombatState.STUNNED) {\n return {\n success: false,\n reason: \"Attacker is stunned\",\n staminaCost: 0,\n };\n }\n\n // Check if attacker is already grappling someone else\n if (attacker.combatState === CombatState.GRAPPLING) {\n return {\n success: false,\n reason: \"Attacker is already grappling another opponent\",\n staminaCost: 0,\n };\n }\n\n if (attacker.stamina < this.config.escapeStaminaCost) {\n return {\n success: false,\n reason: \"Insufficient stamina\",\n staminaCost: 0,\n };\n }\n\n // Check if defender is already being grappled\n if (defender.combatState === CombatState.GRAPPLED) {\n return {\n success: false,\n reason: \"Target is already being grappled\",\n staminaCost: 0,\n };\n }\n\n // Calculate grip strength based on attacker attributes and stance\n const gripStrength = this.calculateGripStrength(attacker, target);\n\n // Calculate success chance based on attributes\n const successChance = this.calculateGrappleSuccessChance(\n attacker,\n defender,\n target\n );\n\n const roll = Math.random();\n const success = roll < successChance;\n\n if (success) {\n // Create grapple control - start with GRABBING state during initiation\n const grappleControl: GrappleControl = {\n state: GrappleState.GRABBING,\n target,\n controllerId: attacker.id,\n targetId: defender.id,\n gripStrength,\n duration: 0,\n startTime: currentTime,\n canEscape: false, // Initially cannot escape\n staminaCostPerSecond: this.calculateStaminaCost(target, gripStrength),\n };\n\n return {\n success: true,\n grappleControl,\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n return {\n success: false,\n reason: `Grapple attempt failed (${Math.round(successChance * 100)}% chance)`,\n staminaCost: this.config.escapeStaminaCost * 0.5, // Half cost on failure\n };\n }\n\n /**\n * Update grapple control state over time.\n *\n * **Korean**: 잡기 업데이트 (Update Grapple)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player maintaining control\n * @param target - Player being controlled\n * @param deltaTime - Time elapsed since last update (seconds)\n * @param currentTime - Current game time in milliseconds\n * @returns Updated grapple control or null if broken\n */\n updateGrapple(\n grappleControl: GrappleControl,\n controller: PlayerState,\n _target: PlayerState,\n deltaTime: number,\n currentTime: number\n ): GrappleControl | null {\n // Check if controller can maintain control\n if (\n controller.stamina < grappleControl.staminaCostPerSecond * deltaTime ||\n controller.combatState === CombatState.STUNNED\n ) {\n // Control broken due to stamina or state\n return null;\n }\n\n // Update duration\n const newDuration = currentTime - grappleControl.startTime;\n\n // Transition from GRABBING to CONTROLLING after initial establishment\n let newState = grappleControl.state;\n if (grappleControl.state === GrappleState.GRABBING && newDuration > 0) {\n newState = GrappleState.CONTROLLING;\n }\n\n // Decay grip strength slightly over time\n const gripDecayRate = 2; // points per second\n const rawGripStrength = grappleControl.gripStrength - gripDecayRate * deltaTime;\n\n // Check if grip strength has dropped too low\n if (rawGripStrength < this.config.minGripStrength) {\n return null;\n }\n\n const newGripStrength = rawGripStrength;\n\n // Allow escape attempts after minimum duration\n const canEscape = newDuration >= this.config.minControlDuration;\n\n return {\n ...grappleControl,\n state: newState,\n gripStrength: newGripStrength,\n duration: newDuration,\n canEscape,\n };\n }\n\n /**\n * Attempt to escape from a grapple.\n *\n * **Korean**: 탈출 시도 (Attempt Escape)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player maintaining control\n * @param target - Player attempting to escape\n * @returns Result of the escape attempt\n */\n attemptEscape(\n grappleControl: GrappleControl,\n controller: PlayerState,\n target: PlayerState\n ): EscapeAttemptResult {\n // Check if escape is possible\n if (!grappleControl.canEscape) {\n return {\n success: false,\n grappleControl,\n reason: \"Cannot escape yet\",\n staminaCost: 0,\n };\n }\n\n // Check stamina\n if (target.stamina < this.config.escapeStaminaCost) {\n return {\n success: false,\n grappleControl,\n reason: \"Insufficient stamina\",\n staminaCost: 0,\n };\n }\n\n // Calculate escape chance\n const escapeChance = this.calculateEscapeChance(\n grappleControl,\n controller,\n target\n );\n\n const roll = Math.random();\n const success = roll < escapeChance;\n\n if (success) {\n return {\n success: true,\n grappleControl: null, // Escape successful, control broken\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n // Escape failed, weaken grip slightly\n const weakenedGripStrength = Math.max(\n this.config.minGripStrength,\n grappleControl.gripStrength - 10\n );\n\n return {\n success: false,\n grappleControl: {\n ...grappleControl,\n gripStrength: weakenedGripStrength,\n },\n reason: `Escape failed (${Math.round(escapeChance * 100)}% chance)`,\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n /**\n * Calculate grip strength based on attacker attributes and target.\n *\n * **Korean**: 악력 계산 (Calculate Grip Strength)\n *\n * @private\n */\n private calculateGripStrength(\n attacker: PlayerState,\n target: GrappleTarget\n ): number {\n // Base strength from attack power\n let strength = attacker.attackPower * 3;\n\n // Stance modifiers\n if (attacker.currentStance === TrigramStance.GON) {\n strength *= 1.3; // GON stance excels at grappling\n } else if (attacker.currentStance === TrigramStance.GAN) {\n strength *= 1.15; // GAN stance is also good for control\n }\n\n // Target modifiers\n switch (target) {\n case GrappleTarget.HAND:\n strength *= 1.1; // Easier to control hand\n break;\n case GrappleTarget.ARM:\n strength *= 1.0; // Standard difficulty\n break;\n case GrappleTarget.LEG:\n strength *= 0.9; // Harder to control leg\n break;\n case GrappleTarget.TORSO:\n strength *= 0.85; // Harder to control torso\n break;\n case GrappleTarget.NECK:\n strength *= 1.2; // High control but risky\n break;\n case GrappleTarget.BOTH_ARMS:\n strength *= 0.7; // Very hard to control both arms\n break;\n }\n\n return Math.min(this.config.maxGripStrength, strength);\n }\n\n /**\n * Calculate grapple success chance.\n *\n * **Korean**: 잡기 성공률 계산 (Calculate Grapple Success Chance)\n *\n * @private\n */\n private calculateGrappleSuccessChance(\n attacker: PlayerState,\n defender: PlayerState,\n target: GrappleTarget\n ): number {\n // Base chance from technique vs defense\n const attributeDiff = attacker.technique - defender.defense;\n let baseChance = 0.5 + attributeDiff * 0.02; // ±2% per point difference\n\n // Speed advantage\n if (attacker.speed > defender.speed) {\n baseChance += (attacker.speed - defender.speed) * 0.01;\n }\n\n // Stance modifiers\n if (attacker.currentStance === TrigramStance.GON) {\n baseChance += 0.15; // GON stance excels at grappling\n }\n\n if (defender.combatState === CombatState.ATTACKING) {\n baseChance += 0.2; // Easier to grapple during attack\n } else if (defender.combatState === CombatState.DEFENDING) {\n baseChance -= 0.15; // Harder to grapple defensive opponent\n }\n\n // Target difficulty\n switch (target) {\n case GrappleTarget.HAND:\n baseChance += 0.1; // Easier to grab hand\n break;\n case GrappleTarget.BOTH_ARMS:\n baseChance -= 0.2; // Much harder to grab both arms\n break;\n case GrappleTarget.NECK:\n baseChance -= 0.1; // Risky target\n break;\n }\n\n // Clamp between 0.05 and 0.95\n return Math.max(0.05, Math.min(0.95, baseChance));\n }\n\n /**\n * Calculate escape chance from grapple.\n *\n * **Korean**: 탈출 성공률 계산 (Calculate Escape Chance)\n *\n * @private\n */\n private calculateEscapeChance(\n grappleControl: GrappleControl,\n controller: PlayerState,\n target: PlayerState\n ): number {\n // Base chance from strength vs grip\n const gripFactor = grappleControl.gripStrength / this.config.maxGripStrength;\n let baseChance = 0.5 - gripFactor * 0.3; // Stronger grip = harder escape\n\n // Attribute comparison\n const strengthDiff = target.attackPower - controller.attackPower;\n baseChance += strengthDiff * 0.02;\n\n // Speed advantage helps escape\n if (target.speed > controller.speed) {\n baseChance += (target.speed - controller.speed) * 0.015;\n }\n\n // Technique helps find weaknesses in grip\n baseChance += target.technique * 0.005;\n\n // Stance modifiers - certain stances are better at escaping grapples\n // **Korean**: 곤(坤) 자세는 탈출에 유리 (+30%), 간(艮) 자세는 방어적인 탈출 (+15%)\n if (target.currentStance === TrigramStance.GON) {\n baseChance += 0.3; // GON stance excels at grounding and escape\n } else if (target.currentStance === TrigramStance.GAN) {\n baseChance += 0.15; // GAN stance has defensive escape advantage\n }\n\n // Time factor - longer control makes escape harder\n const durationSeconds = grappleControl.duration / 1000;\n const timePenalty = Math.min(0.2, durationSeconds * 0.02);\n baseChance -= timePenalty;\n\n // Clamp between 0.05 and 0.85\n return Math.max(0.05, Math.min(0.85, baseChance));\n }\n\n /**\n * Calculate stamina cost to maintain control.\n *\n * **Korean**: 체력 소모 계산 (Calculate Stamina Cost)\n *\n * @private\n */\n private calculateStaminaCost(\n target: GrappleTarget,\n gripStrength: number\n ): number {\n let cost = this.config.baseStaminaCostPerSecond;\n\n // Target affects stamina cost\n switch (target) {\n case GrappleTarget.HAND:\n cost *= 0.8; // Easier to maintain\n break;\n case GrappleTarget.ARM:\n cost *= 1.0; // Standard\n break;\n case GrappleTarget.LEG:\n cost *= 1.2; // Harder to maintain\n break;\n case GrappleTarget.TORSO:\n cost *= 1.3; // Much harder to maintain\n break;\n case GrappleTarget.NECK:\n cost *= 1.5; // Very demanding\n break;\n case GrappleTarget.BOTH_ARMS:\n cost *= 1.8; // Extremely demanding\n break;\n }\n\n // Higher grip strength costs more stamina\n const gripFactor = gripStrength / this.config.maxGripStrength;\n cost *= 0.7 + gripFactor * 0.6; // 70-130% based on grip\n\n return cost;\n }\n\n /**\n * Check if a grapple can be transitioned to a throw.\n *\n * **Korean**: 던지기 전환 확인 (Check Throw Transition)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player attempting throw\n * @returns Whether throw transition is possible\n */\n canTransitionToThrow(\n grappleControl: GrappleControl,\n controller: PlayerState\n ): boolean {\n // Need sufficient control duration\n if (grappleControl.duration < 1000) {\n return false; // Need at least 1 second of control\n }\n\n // Need sufficient grip strength\n if (grappleControl.gripStrength < 60) {\n return false;\n }\n\n // Need sufficient stamina for throw\n if (controller.stamina < 20) {\n return false;\n }\n\n // Certain targets are better for throws\n const goodThrowTargets = [\n GrappleTarget.ARM,\n GrappleTarget.TORSO,\n GrappleTarget.BOTH_ARMS,\n ];\n\n return goodThrowTargets.includes(grappleControl.target);\n }\n\n /**\n * Check if a grapple can be transitioned to a joint lock.\n *\n * **Korean**: 관절기 전환 확인 (Check Joint Lock Transition)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player attempting lock\n * @returns Whether joint lock transition is possible\n */\n canTransitionToJointLock(\n grappleControl: GrappleControl,\n controller: PlayerState\n ): boolean {\n // Need sufficient control duration\n if (grappleControl.duration < 800) {\n return false;\n }\n\n // Need high technique for joint locks\n if (controller.technique < 10) {\n return false;\n }\n\n // Joint locks work on limbs\n const lockTargets = [\n GrappleTarget.HAND,\n GrappleTarget.ARM,\n GrappleTarget.LEG,\n ];\n\n return lockTargets.includes(grappleControl.target);\n }\n\n /**\n * Get technique-specific control duration from Gon metadata.\n *\n * **Korean**: 기술별 제어 시간 조회 (Get Technique Control Duration)\n *\n * Retrieves the post-throw positional advantage duration from\n * ExtendedGonTechnique metadata. Falls back to default duration\n * if technique doesn't have Gon-specific enhancements.\n *\n * **Control Duration Philosophy**:\n * - Traditional Ssireum techniques (800-2000ms) provide longer control\n * - Aggressive slam techniques (800-1200ms) provide brief control\n * - Sacrifice throws (1500-2000ms) provide extended ground control\n *\n * **Use Cases**:\n * - Determining follow-up attack windows after throws\n * - Calculating defender recovery time before counterattack\n * - Applying post-throw positional advantage in game state\n *\n * @param technique - Korean martial arts technique (checks for ExtendedGonTechnique)\n * @param defaultDuration - Fallback duration if technique has no metadata (default: 1000ms)\n * @returns Control duration in milliseconds\n *\n * @example\n * ```typescript\n * const controlTime = grappleSystem.getTechniqueControlDuration(\n * ssireumThrowTechnique, // controlDuration: 1800\n * 1000 // default fallback\n * );\n * // Result: 1800ms (uses technique metadata)\n * ```\n *\n * @public\n * @korean 기술별제어시간조회\n */\n getTechniqueControlDuration(\n technique: KoreanTechnique,\n defaultDuration: number = 1000,\n ): number {\n // Use helper function for safe type casting\n const gonTechnique = asExtendedGonTechnique(technique);\n if (!gonTechnique) {\n // Non-Gon technique: use default duration\n return defaultDuration;\n }\n\n // Extract control duration from validated Gon technique\n return gonTechnique.controlDuration;\n }\n\n /**\n * Apply post-throw control advantage window.\n *\n * **Korean**: 던지기 후 우세 적용 (Apply Post-Throw Advantage)\n *\n * Creates a control advantage state after a successful throw,\n * using the technique's `controlDuration` to determine how long\n * the attacker maintains positional dominance.\n *\n * **Advantage Effects**:\n * - Attacker gains priority for follow-up attacks\n * - Defender must wait for control duration to expire\n * - Applies to throw transitions from grapple control\n *\n * @param technique - Throw technique executed\n * @param attackerId - Player who executed throw\n * @param defenderId - Player who was thrown\n * @param currentTime - Current game time in milliseconds\n * @returns Control advantage state with duration from technique metadata\n *\n * @example\n * ```typescript\n * const advantage = grappleSystem.applyPostThrowAdvantage(\n * ssireumThrowTechnique,\n * \"player1\",\n * \"player2\",\n * Date.now()\n * );\n * // advantage.duration = 1800ms (from technique metadata)\n * ```\n *\n * @public\n * @korean 던지기후우세적용\n */\n applyPostThrowAdvantage(\n technique: KoreanTechnique,\n attackerId: string,\n defenderId: string,\n currentTime: number,\n ): {\n controllerId: string;\n targetId: string;\n duration: number;\n startTime: number;\n endTime: number;\n } {\n // Get technique-specific control duration with 1200ms fallback\n const controlDuration = this.getTechniqueControlDuration(technique, 1200);\n\n return {\n controllerId: attackerId,\n targetId: defenderId,\n duration: controlDuration,\n startTime: currentTime,\n endTime: currentTime + controlDuration,\n };\n }\n}\n\nexport default GrappleSystem;\n"],"mappings":"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"}
1
+ {"version":3,"file":"GrappleSystem.js","names":[],"sources":["../../../src/systems/combat/GrappleSystem.ts"],"sourcesContent":["/**\n * Grappling System for Korean Martial Arts Combat\n *\n * **Korean**: 잡기 시스템 (Grapple System)\n *\n * Implements realistic grappling and takedown mechanics based on:\n * - **Hapkido (합기도)**: Joint locks and control techniques\n * - **Ssireum (씨름)**: Traditional Korean wrestling\n * - **GON Stance (곤괘)**: Earth stance grounding and control\n *\n * ## Core Mechanics\n *\n * 1. **Grab Initiation**: Establish grip on opponent's limb/body\n * 2. **Control Duration**: Maintain control with stamina cost\n * 3. **Grip Strength**: Affects escape difficulty and follow-up options\n * 4. **Escape Mechanics**: Based on strength, technique, and stamina\n * 5. **Follow-up Techniques**: Throws, takedowns, joint locks from control\n *\n * ## Phase 2 Enhancement\n *\n * Enhanced with Gon (Earth) trigram metadata integration:\n * - Uses `controlDuration` from ExtendedGonTechnique\n * - Supports authentic Ssireum/Hapkido control durations\n * - Maintains backward compatibility with non-Gon techniques\n *\n * @module systems/combat/GrappleSystem\n * @category Combat System\n * @korean 잡기시스템\n */\n\nimport type { PlayerState } from \"@/systems/player\";\nimport {\n CombatState,\n GrappleControl,\n GrappleState,\n GrappleTarget,\n TrigramStance,\n} from \"@/types\";\nimport { asExtendedGonTechnique } from \"../trigram/types/GonTechniqueExtensions\";\nimport type { KoreanTechnique } from \"../vitalpoint/types\";\n\n/**\n * Configuration for grappling mechanics.\n *\n * **Korean**: 잡기 설정 (Grapple Configuration)\n */\nexport interface GrappleConfig {\n /** Base stamina cost per second to maintain control */\n readonly baseStaminaCostPerSecond: number;\n /** Minimum grip strength to maintain control */\n readonly minGripStrength: number;\n /** Maximum grip strength value */\n readonly maxGripStrength: number;\n /** Base escape difficulty multiplier */\n readonly baseEscapeDifficulty: number;\n /** Stamina cost to attempt escape */\n readonly escapeStaminaCost: number;\n /** Minimum duration before escape is possible (ms) */\n readonly minControlDuration: number;\n}\n\n/**\n * Default grappling configuration values.\n */\nexport const DEFAULT_GRAPPLE_CONFIG: GrappleConfig = {\n baseStaminaCostPerSecond: 5,\n minGripStrength: 20,\n maxGripStrength: 100,\n baseEscapeDifficulty: 1.5,\n escapeStaminaCost: 15,\n minControlDuration: 500, // 0.5 seconds\n};\n\n/**\n * Grapple attempt result.\n *\n * **Korean**: 잡기 시도 결과 (Grapple Attempt Result)\n */\nexport interface GrappleAttemptResult {\n /** Whether the grapple was successful */\n readonly success: boolean;\n /** New grapple control state if successful */\n readonly grappleControl?: GrappleControl;\n /** Reason for failure if unsuccessful */\n readonly reason?: string;\n /** Stamina cost for the attempt */\n readonly staminaCost: number;\n}\n\n/**\n * Escape attempt result.\n *\n * **Korean**: 탈출 시도 결과 (Escape Attempt Result)\n */\nexport interface EscapeAttemptResult {\n /** Whether the escape was successful */\n readonly success: boolean;\n /** Updated grapple control state */\n readonly grappleControl: GrappleControl | null;\n /** Reason for failure if unsuccessful */\n readonly reason?: string;\n /** Stamina cost for the attempt */\n readonly staminaCost: number;\n}\n\n/**\n * Grappling System for control and hold mechanics.\n *\n * **Korean**: 잡기 시스템 (Grapple System)\n *\n * Manages grappling state between combatants, including grip strength,\n * control duration, escape mechanics, and follow-up techniques.\n */\nexport class GrappleSystem {\n private readonly config: GrappleConfig;\n\n constructor(config: Partial<GrappleConfig> = {}) {\n this.config = { ...DEFAULT_GRAPPLE_CONFIG, ...config };\n }\n\n /**\n * Attempt to initiate a grapple on target.\n *\n * **Korean**: 잡기 시도 (Attempt Grapple)\n *\n * @param attacker - Player attempting the grapple\n * @param defender - Target player\n * @param target - Body part to grapple\n * @param currentTime - Current game time in milliseconds\n * @returns Result of the grapple attempt\n */\n attemptGrapple(\n attacker: PlayerState,\n defender: PlayerState,\n target: GrappleTarget,\n currentTime: number\n ): GrappleAttemptResult {\n // Check if attacker can grapple\n if (attacker.combatState === CombatState.STUNNED) {\n return {\n success: false,\n reason: \"Attacker is stunned\",\n staminaCost: 0,\n };\n }\n\n // Check if attacker is already grappling someone else\n if (attacker.combatState === CombatState.GRAPPLING) {\n return {\n success: false,\n reason: \"Attacker is already grappling another opponent\",\n staminaCost: 0,\n };\n }\n\n if (attacker.stamina < this.config.escapeStaminaCost) {\n return {\n success: false,\n reason: \"Insufficient stamina\",\n staminaCost: 0,\n };\n }\n\n // Check if defender is already being grappled\n if (defender.combatState === CombatState.GRAPPLED) {\n return {\n success: false,\n reason: \"Target is already being grappled\",\n staminaCost: 0,\n };\n }\n\n // Calculate grip strength based on attacker attributes and stance\n const gripStrength = this.calculateGripStrength(attacker, target);\n\n // Calculate success chance based on attributes\n const successChance = this.calculateGrappleSuccessChance(\n attacker,\n defender,\n target\n );\n\n const roll = Math.random();\n const success = roll < successChance;\n\n if (success) {\n // Create grapple control - start with GRABBING state during initiation\n const grappleControl: GrappleControl = {\n state: GrappleState.GRABBING,\n target,\n controllerId: attacker.id,\n targetId: defender.id,\n gripStrength,\n duration: 0,\n startTime: currentTime,\n canEscape: false, // Initially cannot escape\n staminaCostPerSecond: this.calculateStaminaCost(target, gripStrength),\n };\n\n return {\n success: true,\n grappleControl,\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n return {\n success: false,\n reason: `Grapple attempt failed (${Math.round(successChance * 100)}% chance)`,\n staminaCost: this.config.escapeStaminaCost * 0.5, // Half cost on failure\n };\n }\n\n /**\n * Update grapple control state over time.\n *\n * **Korean**: 잡기 업데이트 (Update Grapple)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player maintaining control\n * @param target - Player being controlled\n * @param deltaTime - Time elapsed since last update (seconds)\n * @param currentTime - Current game time in milliseconds\n * @returns Updated grapple control or null if broken\n */\n updateGrapple(\n grappleControl: GrappleControl,\n controller: PlayerState,\n _target: PlayerState,\n deltaTime: number,\n currentTime: number\n ): GrappleControl | null {\n // Check if controller can maintain control\n if (\n controller.stamina < grappleControl.staminaCostPerSecond * deltaTime ||\n controller.combatState === CombatState.STUNNED\n ) {\n // Control broken due to stamina or state\n return null;\n }\n\n // Update duration\n const newDuration = currentTime - grappleControl.startTime;\n\n // Transition from GRABBING to CONTROLLING after initial establishment\n let newState = grappleControl.state;\n if (grappleControl.state === GrappleState.GRABBING && newDuration > 0) {\n newState = GrappleState.CONTROLLING;\n }\n\n // Decay grip strength slightly over time\n const gripDecayRate = 2; // points per second\n const rawGripStrength = grappleControl.gripStrength - gripDecayRate * deltaTime;\n\n // Check if grip strength has dropped too low\n if (rawGripStrength < this.config.minGripStrength) {\n return null;\n }\n\n const newGripStrength = rawGripStrength;\n\n // Allow escape attempts after minimum duration\n const canEscape = newDuration >= this.config.minControlDuration;\n\n return {\n ...grappleControl,\n state: newState,\n gripStrength: newGripStrength,\n duration: newDuration,\n canEscape,\n };\n }\n\n /**\n * Attempt to escape from a grapple.\n *\n * **Korean**: 탈출 시도 (Attempt Escape)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player maintaining control\n * @param target - Player attempting to escape\n * @returns Result of the escape attempt\n */\n attemptEscape(\n grappleControl: GrappleControl,\n controller: PlayerState,\n target: PlayerState\n ): EscapeAttemptResult {\n // Check if escape is possible\n if (!grappleControl.canEscape) {\n return {\n success: false,\n grappleControl,\n reason: \"Cannot escape yet\",\n staminaCost: 0,\n };\n }\n\n // Check stamina\n if (target.stamina < this.config.escapeStaminaCost) {\n return {\n success: false,\n grappleControl,\n reason: \"Insufficient stamina\",\n staminaCost: 0,\n };\n }\n\n // Calculate escape chance\n const escapeChance = this.calculateEscapeChance(\n grappleControl,\n controller,\n target\n );\n\n const roll = Math.random();\n const success = roll < escapeChance;\n\n if (success) {\n return {\n success: true,\n grappleControl: null, // Escape successful, control broken\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n // Escape failed, weaken grip slightly\n const weakenedGripStrength = Math.max(\n this.config.minGripStrength,\n grappleControl.gripStrength - 10\n );\n\n return {\n success: false,\n grappleControl: {\n ...grappleControl,\n gripStrength: weakenedGripStrength,\n },\n reason: `Escape failed (${Math.round(escapeChance * 100)}% chance)`,\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n /**\n * Calculate grip strength based on attacker attributes and target.\n *\n * **Korean**: 악력 계산 (Calculate Grip Strength)\n *\n * @private\n */\n private calculateGripStrength(\n attacker: PlayerState,\n target: GrappleTarget\n ): number {\n // Base strength from attack power\n let strength = attacker.attackPower * 3;\n\n // Stance modifiers\n if (attacker.currentStance === TrigramStance.GON) {\n strength *= 1.3; // GON stance excels at grappling\n } else if (attacker.currentStance === TrigramStance.GAN) {\n strength *= 1.15; // GAN stance is also good for control\n }\n\n // Target modifiers\n switch (target) {\n case GrappleTarget.HAND:\n strength *= 1.1; // Easier to control hand\n break;\n case GrappleTarget.ARM:\n strength *= 1.0; // Standard difficulty\n break;\n case GrappleTarget.LEG:\n strength *= 0.9; // Harder to control leg\n break;\n case GrappleTarget.TORSO:\n strength *= 0.85; // Harder to control torso\n break;\n case GrappleTarget.NECK:\n strength *= 1.2; // High control but risky\n break;\n case GrappleTarget.BOTH_ARMS:\n strength *= 0.7; // Very hard to control both arms\n break;\n }\n\n return Math.min(this.config.maxGripStrength, strength);\n }\n\n /**\n * Calculate grapple success chance.\n *\n * **Korean**: 잡기 성공률 계산 (Calculate Grapple Success Chance)\n *\n * @private\n */\n private calculateGrappleSuccessChance(\n attacker: PlayerState,\n defender: PlayerState,\n target: GrappleTarget\n ): number {\n // Base chance from technique vs defense\n const attributeDiff = attacker.technique - defender.defense;\n let baseChance = 0.5 + attributeDiff * 0.02; // ±2% per point difference\n\n // Speed advantage\n if (attacker.speed > defender.speed) {\n baseChance += (attacker.speed - defender.speed) * 0.01;\n }\n\n // Stance modifiers\n if (attacker.currentStance === TrigramStance.GON) {\n baseChance += 0.15; // GON stance excels at grappling\n }\n\n if (defender.combatState === CombatState.ATTACKING) {\n baseChance += 0.2; // Easier to grapple during attack\n } else if (defender.combatState === CombatState.DEFENDING) {\n baseChance -= 0.15; // Harder to grapple defensive opponent\n }\n\n // Target difficulty\n switch (target) {\n case GrappleTarget.HAND:\n baseChance += 0.1; // Easier to grab hand\n break;\n case GrappleTarget.BOTH_ARMS:\n baseChance -= 0.2; // Much harder to grab both arms\n break;\n case GrappleTarget.NECK:\n baseChance -= 0.1; // Risky target\n break;\n }\n\n // Clamp between 0.05 and 0.95\n return Math.max(0.05, Math.min(0.95, baseChance));\n }\n\n /**\n * Calculate escape chance from grapple.\n *\n * **Korean**: 탈출 성공률 계산 (Calculate Escape Chance)\n *\n * @private\n */\n private calculateEscapeChance(\n grappleControl: GrappleControl,\n controller: PlayerState,\n target: PlayerState\n ): number {\n // Base chance from strength vs grip\n const gripFactor = grappleControl.gripStrength / this.config.maxGripStrength;\n let baseChance = 0.5 - gripFactor * 0.3; // Stronger grip = harder escape\n\n // Attribute comparison\n const strengthDiff = target.attackPower - controller.attackPower;\n baseChance += strengthDiff * 0.02;\n\n // Speed advantage helps escape\n if (target.speed > controller.speed) {\n baseChance += (target.speed - controller.speed) * 0.015;\n }\n\n // Technique helps find weaknesses in grip\n baseChance += target.technique * 0.005;\n\n // Stance modifiers - certain stances are better at escaping grapples\n // **Korean**: 곤(坤) 자세는 탈출에 유리 (+30%), 간(艮) 자세는 방어적인 탈출 (+15%)\n if (target.currentStance === TrigramStance.GON) {\n baseChance += 0.3; // GON stance excels at grounding and escape\n } else if (target.currentStance === TrigramStance.GAN) {\n baseChance += 0.15; // GAN stance has defensive escape advantage\n }\n\n // Time factor - longer control makes escape harder\n const durationSeconds = grappleControl.duration / 1000;\n const timePenalty = Math.min(0.2, durationSeconds * 0.02);\n baseChance -= timePenalty;\n\n // Clamp between 0.05 and 0.85\n return Math.max(0.05, Math.min(0.85, baseChance));\n }\n\n /**\n * Calculate stamina cost to maintain control.\n *\n * **Korean**: 체력 소모 계산 (Calculate Stamina Cost)\n *\n * @private\n */\n private calculateStaminaCost(\n target: GrappleTarget,\n gripStrength: number\n ): number {\n let cost = this.config.baseStaminaCostPerSecond;\n\n // Target affects stamina cost\n switch (target) {\n case GrappleTarget.HAND:\n cost *= 0.8; // Easier to maintain\n break;\n case GrappleTarget.ARM:\n cost *= 1.0; // Standard\n break;\n case GrappleTarget.LEG:\n cost *= 1.2; // Harder to maintain\n break;\n case GrappleTarget.TORSO:\n cost *= 1.3; // Much harder to maintain\n break;\n case GrappleTarget.NECK:\n cost *= 1.5; // Very demanding\n break;\n case GrappleTarget.BOTH_ARMS:\n cost *= 1.8; // Extremely demanding\n break;\n }\n\n // Higher grip strength costs more stamina\n const gripFactor = gripStrength / this.config.maxGripStrength;\n cost *= 0.7 + gripFactor * 0.6; // 70-130% based on grip\n\n return cost;\n }\n\n /**\n * Check if a grapple can be transitioned to a throw.\n *\n * **Korean**: 던지기 전환 확인 (Check Throw Transition)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player attempting throw\n * @returns Whether throw transition is possible\n */\n canTransitionToThrow(\n grappleControl: GrappleControl,\n controller: PlayerState\n ): boolean {\n // Need sufficient control duration\n if (grappleControl.duration < 1000) {\n return false; // Need at least 1 second of control\n }\n\n // Need sufficient grip strength\n if (grappleControl.gripStrength < 60) {\n return false;\n }\n\n // Need sufficient stamina for throw\n if (controller.stamina < 20) {\n return false;\n }\n\n // Certain targets are better for throws\n const goodThrowTargets = [\n GrappleTarget.ARM,\n GrappleTarget.TORSO,\n GrappleTarget.BOTH_ARMS,\n ];\n\n return goodThrowTargets.includes(grappleControl.target);\n }\n\n /**\n * Check if a grapple can be transitioned to a joint lock.\n *\n * **Korean**: 관절기 전환 확인 (Check Joint Lock Transition)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player attempting lock\n * @returns Whether joint lock transition is possible\n */\n canTransitionToJointLock(\n grappleControl: GrappleControl,\n controller: PlayerState\n ): boolean {\n // Need sufficient control duration\n if (grappleControl.duration < 800) {\n return false;\n }\n\n // Need high technique for joint locks\n if (controller.technique < 10) {\n return false;\n }\n\n // Joint locks work on limbs\n const lockTargets = [\n GrappleTarget.HAND,\n GrappleTarget.ARM,\n GrappleTarget.LEG,\n ];\n\n return lockTargets.includes(grappleControl.target);\n }\n\n /**\n * Get technique-specific control duration from Gon metadata.\n *\n * **Korean**: 기술별 제어 시간 조회 (Get Technique Control Duration)\n *\n * Retrieves the post-throw positional advantage duration from\n * ExtendedGonTechnique metadata. Falls back to default duration\n * if technique doesn't have Gon-specific enhancements.\n *\n * **Control Duration Philosophy**:\n * - Traditional Ssireum techniques (800-2000ms) provide longer control\n * - Aggressive slam techniques (800-1200ms) provide brief control\n * - Sacrifice throws (1500-2000ms) provide extended ground control\n *\n * **Use Cases**:\n * - Determining follow-up attack windows after throws\n * - Calculating defender recovery time before counterattack\n * - Applying post-throw positional advantage in game state\n *\n * @param technique - Korean martial arts technique (checks for ExtendedGonTechnique)\n * @param defaultDuration - Fallback duration if technique has no metadata (default: 1000ms)\n * @returns Control duration in milliseconds\n *\n * @example\n * ```typescript\n * const controlTime = grappleSystem.getTechniqueControlDuration(\n * ssireumThrowTechnique, // controlDuration: 1800\n * 1000 // default fallback\n * );\n * // Result: 1800ms (uses technique metadata)\n * ```\n *\n * @public\n * @korean 기술별제어시간조회\n */\n getTechniqueControlDuration(\n technique: KoreanTechnique,\n defaultDuration: number = 1000,\n ): number {\n // Use helper function for safe type casting\n const gonTechnique = asExtendedGonTechnique(technique);\n if (!gonTechnique) {\n // Non-Gon technique: use default duration\n return defaultDuration;\n }\n\n // Extract control duration from validated Gon technique\n return gonTechnique.controlDuration;\n }\n\n /**\n * Apply post-throw control advantage window.\n *\n * **Korean**: 던지기 후 우세 적용 (Apply Post-Throw Advantage)\n *\n * Creates a control advantage state after a successful throw,\n * using the technique's `controlDuration` to determine how long\n * the attacker maintains positional dominance.\n *\n * **Advantage Effects**:\n * - Attacker gains priority for follow-up attacks\n * - Defender must wait for control duration to expire\n * - Applies to throw transitions from grapple control\n *\n * @param technique - Throw technique executed\n * @param attackerId - Player who executed throw\n * @param defenderId - Player who was thrown\n * @param currentTime - Current game time in milliseconds\n * @returns Control advantage state with duration from technique metadata\n *\n * @example\n * ```typescript\n * const advantage = grappleSystem.applyPostThrowAdvantage(\n * ssireumThrowTechnique,\n * \"player1\",\n * \"player2\",\n * Date.now()\n * );\n * // advantage.duration = 1800ms (from technique metadata)\n * ```\n *\n * @public\n * @korean 던지기후우세적용\n */\n applyPostThrowAdvantage(\n technique: KoreanTechnique,\n attackerId: string,\n defenderId: string,\n currentTime: number,\n ): {\n controllerId: string;\n targetId: string;\n duration: number;\n startTime: number;\n endTime: number;\n } {\n // Get technique-specific control duration with 1200ms fallback\n const controlDuration = this.getTechniqueControlDuration(technique, 1200);\n\n return {\n controllerId: attackerId,\n targetId: defenderId,\n duration: controlDuration,\n startTime: currentTime,\n endTime: currentTime + controlDuration,\n };\n }\n}\n\nexport default GrappleSystem;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"LimbExposureSystem.js","names":[],"sources":["../../../src/systems/combat/LimbExposureSystem.ts"],"sourcesContent":["/**\n * Limb Exposure System for Counter-Attack Detection\n *\n * **Korean**: 사지 노출 시스템 (Saji Nochul System)\n *\n * Analyzes technique animations to determine when limbs become exposed and vulnerable\n * during attack execution. This enables:\n * - Defensive counter-attacks (반격 - banggyeok)\n * - Breaking techniques (파쇄기 - paswaegi)\n * - Exploiting overextension (과다신장 약점 - gwada sinjang yakjeom)\n *\n * **Martial Arts Philosophy**:\n * - **허점공격** (Heojeom Gonggyeok) - Attacking openings/weaknesses\n * - **후수필승** (Husu Pilseung) - Victory through counter-attack timing\n * - **이순응변** (Isun Eungbyeon) - Adapt and exploit opponent's mistakes\n *\n * @module systems/combat/LimbExposureSystem\n * @korean 사지노출시스템\n */\n\nimport type { KoreanTechnique } from \"../vitalpoint/types\";\nimport type {\n BreakingResult,\n BreakingTarget,\n CounterOpportunity,\n ExposedLimbType,\n LimbExposureWindow,\n PhysicalReachConfig,\n} from \"../../types/physics\";\nimport { BREAKING_STATUS_EFFECT_IDS } from \"./BreakingStatusEffects\";\n\n/**\n * Breaking technique constants\n * **Korean**: 파쇄기술 상수\n */\n\n/**\n * Minimum effective force required to successfully execute a breaking technique.\n * Force below this threshold will not break joints/bones.\n * Typical damage values range from 20-80, with 40 being the minimum for effective breaks.\n * @korean 파쇄최소힘\n */\nconst BREAKING_FORCE_THRESHOLD = 40;\n\n/**\n * Maximum effective force for calculating break severity on 0-1 scale.\n * Force values above 80 are clamped to 1.0 severity (complete break).\n * @korean 파쇄최대힘\n */\nconst BREAKING_MAX_FORCE = 80;\n\n/**\n * Maximum range in meters for executing counter-attacks.\n * Counter-attacks are close-range defensive techniques requiring proximity.\n * @korean 반격최대거리\n */\nconst MAX_COUNTER_RANGE_METERS = 1.0;\n\n/**\n * Calculate counter-attack opportunities from a technique's reach configuration.\n *\n * **Korean**: 반격 기회 계산\n *\n * Analyzes the technique's execution timeline and limb exposure to identify\n * windows where the opponent is vulnerable to counter-attacks.\n *\n * @param technique - The technique being executed\n * @param currentTime - Current time in technique execution (ms)\n * @returns Counter opportunity if one exists, undefined otherwise\n *\n * @public\n * @korean 반격기회계산\n */\nexport function calculateCounterOpportunity(\n technique: KoreanTechnique,\n currentTime: number\n): CounterOpportunity | undefined {\n const { reachConfig, executionTime } = technique;\n\n // No exposure window defined, no counter opportunity\n if (!reachConfig.exposureWindow) {\n return undefined;\n }\n\n const exposure = reachConfig.exposureWindow;\n\n // Calculate absolute timing of exposure window\n const windowStart = executionTime * exposure.startTime;\n const windowEnd = windowStart + exposure.duration;\n\n // Check if current time is within the exposure window\n if (currentTime < windowStart || currentTime > windowEnd) {\n return undefined;\n }\n\n // Determine recommended counter techniques based on exposed limb\n const recommendedCounters = getRecommendedCounters(\n exposure.exposedLimb,\n reachConfig,\n exposure.allowsBreaking\n );\n\n return {\n exposedLimb: exposure.exposedLimb,\n windowStart,\n windowDuration: exposure.duration,\n vulnerabilityMultiplier: exposure.vulnerabilityMultiplier,\n allowsBreaking: exposure.allowsBreaking,\n recommendedCounters,\n };\n}\n\n/**\n * Get recommended counter-technique IDs for an exposed limb.\n *\n * **Korean**: 추천 반격 기술 조회\n *\n * Returns technique IDs that would be effective against the exposed limb.\n * These IDs are string-based and should match techniques in the game's\n * technique library. When implementing, validate these IDs exist.\n *\n * **Note**: These are example/placeholder IDs. Actual implementation should\n * reference real technique definitions or use a typed enum of technique IDs.\n *\n * @param exposedLimb - The limb that is exposed\n * @param reachConfig - Reach configuration of the attacking technique\n * @param allowsBreaking - Whether breaking techniques are viable\n * @returns Array of recommended counter-technique IDs (unvalidated strings)\n *\n * @internal\n * @korean 추천반격기술조회\n */\nfunction getRecommendedCounters(\n exposedLimb: ExposedLimbType,\n reachConfig: PhysicalReachConfig,\n allowsBreaking: boolean\n): readonly string[] {\n const counters: string[] = [];\n\n // Breaking techniques for overextended limbs\n if (allowsBreaking) {\n if (exposedLimb.includes(\"leg\") || exposedLimb.includes(\"ankle\")) {\n counters.push(\"leg_sweep\", \"ankle_break\", \"knee_stomp\");\n } else if (exposedLimb.includes(\"knee\")) {\n counters.push(\"knee_break\", \"hyperextension_kick\");\n } else if (exposedLimb.includes(\"arm\") || exposedLimb.includes(\"elbow\")) {\n counters.push(\"arm_bar\", \"elbow_break\", \"joint_lock\");\n } else if (exposedLimb.includes(\"wrist\")) {\n counters.push(\"wrist_lock\", \"wrist_break\");\n }\n }\n\n // Vital point strikes on exposed limbs\n if (exposedLimb.includes(\"arm\")) {\n counters.push(\"ulnar_nerve_strike\", \"radial_nerve_pressure\");\n } else if (exposedLimb.includes(\"leg\")) {\n counters.push(\"peroneal_nerve_strike\", \"thigh_pressure_point\");\n }\n\n // Counter-strikes based on body part type\n if (reachConfig.bodyPart === \"leg\") {\n counters.push(\"leg_parry_counter\", \"inside_leg_kick\");\n } else if (reachConfig.bodyPart === \"arm\") {\n counters.push(\"arm_trap_counter\", \"redirect_punch\");\n }\n\n return counters;\n}\n\n/**\n * Calculate vulnerability multiplier for a technique at a specific time.\n *\n * **Korean**: 취약성 배수 계산\n *\n * Determines how vulnerable the attacker is at a given point in their\n * technique execution. Useful for damage calculation during counter-attacks.\n *\n * @param technique - The technique being executed\n * @param currentTime - Current time in technique execution (ms)\n * @returns Vulnerability multiplier (1.0 = normal, >1.0 = more vulnerable)\n *\n * @public\n * @korean 취약성배수계산\n */\nexport function calculateVulnerabilityMultiplier(\n technique: KoreanTechnique,\n currentTime: number\n): number {\n const { reachConfig, executionTime, recoveryTime } = technique;\n\n // No exposure window, return baseline vulnerability\n if (!reachConfig.exposureWindow) {\n return 1.0;\n }\n\n const exposure = reachConfig.exposureWindow;\n const windowStart = executionTime * exposure.startTime;\n const windowEnd = windowStart + exposure.duration;\n\n // Before exposure window: slight vulnerability during wind-up\n if (currentTime < windowStart) {\n return 1.1;\n }\n\n // Within exposure window: maximum vulnerability\n if (currentTime <= windowEnd) {\n return exposure.vulnerabilityMultiplier;\n }\n\n // Recovery phase: moderate vulnerability\n const recoveryStart = executionTime;\n const recoveryEnd = executionTime + recoveryTime;\n if (currentTime > recoveryStart && currentTime <= recoveryEnd) {\n const recoveryProgress = (currentTime - recoveryStart) / recoveryTime;\n // Linearly decrease from max vulnerability to baseline\n return (\n exposure.vulnerabilityMultiplier -\n (exposure.vulnerabilityMultiplier - 1.0) * recoveryProgress\n );\n }\n\n // After recovery: baseline vulnerability\n return 1.0;\n}\n\n/**\n * Determine which limb is exposed based on technique and animation state.\n *\n * **Korean**: 노출된 사지 결정\n *\n * Analyzes the technique's body part and animation to determine which\n * specific limb (left/right) is currently exposed.\n *\n * @param technique - The technique being executed\n * @param isLeftSided - Whether the technique uses the left side\n * @returns The exposed limb type\n *\n * @public\n * @korean 노출사지결정\n */\nexport function determineExposedLimb(\n technique: KoreanTechnique,\n isLeftSided: boolean = false\n): ExposedLimbType {\n const { reachConfig } = technique;\n\n // If exposure window explicitly defines the limb, use it\n if (reachConfig.exposureWindow?.exposedLimb) {\n return reachConfig.exposureWindow.exposedLimb;\n }\n\n // Otherwise, infer from body part and sidedness\n const side = isLeftSided ? \"left\" : \"right\";\n\n switch (reachConfig.bodyPart) {\n case \"arm\":\n // Determine joint based on reach extension\n if (reachConfig.baseExtension > 0.8) {\n return `${side}_arm` as ExposedLimbType;\n } else if (reachConfig.baseExtension > 0.5) {\n return `${side}_elbow` as ExposedLimbType;\n } else {\n return `${side}_wrist` as ExposedLimbType;\n }\n\n case \"leg\":\n // Kicks expose different parts based on extension\n if (reachConfig.baseExtension > 1.0) {\n return `${side}_leg` as ExposedLimbType;\n } else if (reachConfig.baseExtension > 0.7) {\n return `${side}_knee` as ExposedLimbType;\n } else {\n return `${side}_ankle` as ExposedLimbType;\n }\n\n case \"torso\":\n // Torso techniques typically expose arms\n return `${side}_arm` as ExposedLimbType;\n\n default:\n return \"right_arm\";\n }\n}\n\n/**\n * Map exposed limb to breaking technique target.\n *\n * **Korean**: 노출 사지를 파쇄 목표로 매핑\n *\n * Converts an exposed limb type to the appropriate breaking target\n * for joint and limb breaking techniques.\n *\n * @param exposedLimb - The exposed limb\n * @returns The breaking target joint/bone\n *\n * @public\n * @korean 파쇄목표매핑\n */\nexport function mapLimbToBreakingTarget(\n exposedLimb: ExposedLimbType\n): BreakingTarget {\n if (exposedLimb.includes(\"ankle\")) return \"ankle\";\n if (exposedLimb.includes(\"knee\")) return \"knee\";\n if (exposedLimb.includes(\"elbow\")) return \"elbow\";\n if (exposedLimb.includes(\"wrist\")) return \"wrist\";\n\n // Default mapping for general limbs\n if (exposedLimb.includes(\"leg\")) return \"knee\";\n if (exposedLimb.includes(\"arm\")) return \"elbow\";\n\n return \"elbow\"; // Default fallback\n}\n\n/**\n * Calculate breaking technique effectiveness.\n *\n * **Korean**: 파쇄 기술 효과 계산\n *\n * Determines the result of a breaking technique applied to an exposed limb,\n * considering the vulnerability window, force applied, and target joint.\n *\n * @param breakingTechnique - The breaking technique being applied (reserved for future use)\n * @param counterOpportunity - The counter opportunity being exploited\n * @param force - Force/damage of the breaking technique (typically 20-80 range)\n * @returns Result of the breaking attempt\n *\n * @remarks\n * The breakingTechnique parameter is currently unused but reserved for future\n * extensibility. It may be used to apply technique-specific modifiers or\n * validate technique compatibility with the breaking target.\n *\n * @public\n * @korean 파쇄기술효과계산\n */\nexport function calculateBreakingResult(\n breakingTechnique: KoreanTechnique,\n counterOpportunity: CounterOpportunity,\n force: number\n): BreakingResult {\n // Note: breakingTechnique parameter reserved for future use\n // (e.g., technique-specific breaking modifiers)\n void breakingTechnique;\n const target = mapLimbToBreakingTarget(counterOpportunity.exposedLimb);\n\n // Breaking only works during valid windows\n if (!counterOpportunity.allowsBreaking) {\n return {\n success: false,\n target,\n severity: 0,\n damage: force * 0.5, // Reduced damage without breaking\n mobilityReduction: 0,\n statusEffects: [],\n };\n }\n\n // Calculate breaking severity based on force and vulnerability\n const effectiveForce = force * counterOpportunity.vulnerabilityMultiplier;\n const baseSuccess = effectiveForce > BREAKING_FORCE_THRESHOLD;\n const severity = Math.min(effectiveForce / BREAKING_MAX_FORCE, 1.0);\n\n if (!baseSuccess) {\n return {\n success: false,\n target,\n severity: 0,\n damage: effectiveForce * 0.7,\n mobilityReduction: 0,\n statusEffects: [],\n };\n }\n\n // Successful break - apply appropriate effects\n // Status effect IDs from BREAKING_STATUS_EFFECT_IDS constants\n // See src/systems/combat/BreakingStatusEffects.ts for all available IDs\n const statusEffects: string[] = [BREAKING_STATUS_EFFECT_IDS.PAIN];\n let mobilityReduction: number;\n\n // Severity-based effects\n if (severity > 0.8) {\n // Severe break - dislocation or fracture\n statusEffects.push(\n BREAKING_STATUS_EFFECT_IDS.SEVERE_INJURY,\n BREAKING_STATUS_EFFECT_IDS.DISABLED_LIMB\n );\n mobilityReduction = 0.6;\n\n if (target === \"ankle\" || target === \"knee\") {\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.IMPAIRED_MOBILITY);\n mobilityReduction = 0.8; // Leg breaks severely impact movement\n }\n } else if (severity > 0.5) {\n // Moderate break\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.INJURED_LIMB);\n mobilityReduction = 0.4;\n } else {\n // Minor break - sprain or strain\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.SPRAINED_JOINT);\n mobilityReduction = 0.2;\n }\n\n // Bleeding for bone breaks\n if (severity > 0.6) {\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.BLEEDING);\n }\n\n return {\n success: true,\n target,\n severity,\n damage: effectiveForce,\n mobilityReduction,\n statusEffects,\n };\n}\n\n/**\n * Check if a defender can execute a counter-attack.\n *\n * **Korean**: 반격 실행 가능 여부 확인\n *\n * Validates whether a defender is in position and has the resources\n * to execute a counter-attack against an exposed limb.\n *\n * @param defenderStamina - Defender's current stamina\n * @param counterTechnique - The counter technique to execute\n * @param distance - Distance between attacker and defender (meters)\n * @returns Whether the counter can be executed\n *\n * @public\n * @korean 반격실행가능확인\n */\nexport function canExecuteCounter(\n defenderStamina: number,\n counterTechnique: KoreanTechnique,\n distance: number\n): boolean {\n // Check stamina requirement\n if (defenderStamina < counterTechnique.staminaCost) {\n return false;\n }\n\n // Counter must be executable at current distance\n if (distance > MAX_COUNTER_RANGE_METERS) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Generate limb exposure window for a technique.\n *\n * **Korean**: 사지 노출 시간 생성\n *\n * Creates a default limb exposure window for techniques that don't\n * explicitly define one, based on heuristics from the technique type\n * and reach configuration.\n *\n * @param technique - The technique to generate exposure for\n * @returns Generated limb exposure window\n *\n * @public\n * @korean 사지노출시간생성\n */\nexport function generateLimbExposureWindow(\n technique: KoreanTechnique\n): LimbExposureWindow {\n const { reachConfig, executionTime } = technique;\n const exposedLimb = determineExposedLimb(technique);\n\n // High extension = high vulnerability\n const isHighExtension = reachConfig.baseExtension > 1.0;\n const isMediumExtension = reachConfig.baseExtension > 0.8;\n\n // Determine vulnerability based on extension\n let vulnerabilityMultiplier: number;\n let allowsBreaking: boolean;\n\n if (isHighExtension) {\n vulnerabilityMultiplier = 2.2; // Very vulnerable\n allowsBreaking = true;\n } else if (isMediumExtension) {\n vulnerabilityMultiplier = 1.6;\n allowsBreaking = false;\n } else {\n vulnerabilityMultiplier = 1.2; // Low extension, low vulnerability\n allowsBreaking = false;\n }\n\n // Exposure starts during peak extension (typically 40-60% of execution)\n const startTime = 0.5;\n\n // Duration scales with execution time (20-40% of execution)\n const durationRatio = isHighExtension ? 0.4 : 0.25;\n const duration = Math.floor(executionTime * durationRatio);\n\n return {\n exposedLimb,\n startTime,\n duration,\n vulnerabilityMultiplier,\n allowsBreaking,\n };\n}\n\nexport default {\n calculateCounterOpportunity,\n calculateVulnerabilityMultiplier,\n determineExposedLimb,\n mapLimbToBreakingTarget,\n calculateBreakingResult,\n canExecuteCounter,\n generateLimbExposureWindow,\n};\n"],"mappings":";;;;;;;;;;;;AA0CA,IAAM,2BAA2B;;;;;;AAOjC,IAAM,qBAAqB;;;;;;AAO3B,IAAM,2BAA2B;;;;;;;;;;;;;;;;AAiBjC,SAAgB,4BACd,WACA,aACgC;CAChC,MAAM,EAAE,aAAa,kBAAkB;AAGvC,KAAI,CAAC,YAAY,eACf;CAGF,MAAM,WAAW,YAAY;CAG7B,MAAM,cAAc,gBAAgB,SAAS;CAC7C,MAAM,YAAY,cAAc,SAAS;AAGzC,KAAI,cAAc,eAAe,cAAc,UAC7C;CAIF,MAAM,sBAAsB,uBAC1B,SAAS,aACT,aACA,SAAS,eACV;AAED,QAAO;EACL,aAAa,SAAS;EACtB;EACA,gBAAgB,SAAS;EACzB,yBAAyB,SAAS;EAClC,gBAAgB,SAAS;EACzB;EACD;;;;;;;;;;;;;;;;;;;;;;AAuBH,SAAS,uBACP,aACA,aACA,gBACmB;CACnB,MAAM,WAAqB,EAAE;AAG7B,KAAI;MACE,YAAY,SAAS,MAAM,IAAI,YAAY,SAAS,QAAQ,CAC9D,UAAS,KAAK,aAAa,eAAe,aAAa;WAC9C,YAAY,SAAS,OAAO,CACrC,UAAS,KAAK,cAAc,sBAAsB;WACzC,YAAY,SAAS,MAAM,IAAI,YAAY,SAAS,QAAQ,CACrE,UAAS,KAAK,WAAW,eAAe,aAAa;WAC5C,YAAY,SAAS,QAAQ,CACtC,UAAS,KAAK,cAAc,cAAc;;AAK9C,KAAI,YAAY,SAAS,MAAM,CAC7B,UAAS,KAAK,sBAAsB,wBAAwB;UACnD,YAAY,SAAS,MAAM,CACpC,UAAS,KAAK,yBAAyB,uBAAuB;AAIhE,KAAI,YAAY,aAAa,MAC3B,UAAS,KAAK,qBAAqB,kBAAkB;UAC5C,YAAY,aAAa,MAClC,UAAS,KAAK,oBAAoB,iBAAiB;AAGrD,QAAO;;;;;;;;;;;;;;;;;AAkBT,SAAgB,iCACd,WACA,aACQ;CACR,MAAM,EAAE,aAAa,eAAe,iBAAiB;AAGrD,KAAI,CAAC,YAAY,eACf,QAAO;CAGT,MAAM,WAAW,YAAY;CAC7B,MAAM,cAAc,gBAAgB,SAAS;CAC7C,MAAM,YAAY,cAAc,SAAS;AAGzC,KAAI,cAAc,YAChB,QAAO;AAIT,KAAI,eAAe,UACjB,QAAO,SAAS;CAIlB,MAAM,gBAAgB;CACtB,MAAM,cAAc,gBAAgB;AACpC,KAAI,cAAc,iBAAiB,eAAe,aAAa;EAC7D,MAAM,oBAAoB,cAAc,iBAAiB;AAEzD,SACE,SAAS,2BACR,SAAS,0BAA0B,KAAO;;AAK/C,QAAO;;;;;;;;;;;;;;;;;AAkBT,SAAgB,qBACd,WACA,cAAuB,OACN;CACjB,MAAM,EAAE,gBAAgB;AAGxB,KAAI,YAAY,gBAAgB,YAC9B,QAAO,YAAY,eAAe;CAIpC,MAAM,OAAO,cAAc,SAAS;AAEpC,SAAQ,YAAY,UAApB;EACE,KAAK,MAEH,KAAI,YAAY,gBAAgB,GAC9B,QAAO,GAAG,KAAK;WACN,YAAY,gBAAgB,GACrC,QAAO,GAAG,KAAK;MAEf,QAAO,GAAG,KAAK;EAGnB,KAAK,MAEH,KAAI,YAAY,gBAAgB,EAC9B,QAAO,GAAG,KAAK;WACN,YAAY,gBAAgB,GACrC,QAAO,GAAG,KAAK;MAEf,QAAO,GAAG,KAAK;EAGnB,KAAK,QAEH,QAAO,GAAG,KAAK;EAEjB,QACE,QAAO;;;;;;;;;;;;;;;;;AAkBb,SAAgB,wBACd,aACgB;AAChB,KAAI,YAAY,SAAS,QAAQ,CAAE,QAAO;AAC1C,KAAI,YAAY,SAAS,OAAO,CAAE,QAAO;AACzC,KAAI,YAAY,SAAS,QAAQ,CAAE,QAAO;AAC1C,KAAI,YAAY,SAAS,QAAQ,CAAE,QAAO;AAG1C,KAAI,YAAY,SAAS,MAAM,CAAE,QAAO;AACxC,KAAI,YAAY,SAAS,MAAM,CAAE,QAAO;AAExC,QAAO;;;;;;;;;;;;;;;;;;;;;;;AAwBT,SAAgB,wBACd,mBACA,oBACA,OACgB;CAIhB,MAAM,SAAS,wBAAwB,mBAAmB,YAAY;AAGtE,KAAI,CAAC,mBAAmB,eACtB,QAAO;EACL,SAAS;EACT;EACA,UAAU;EACV,QAAQ,QAAQ;EAChB,mBAAmB;EACnB,eAAe,EAAE;EAClB;CAIH,MAAM,iBAAiB,QAAQ,mBAAmB;CAClD,MAAM,cAAc,iBAAiB;CACrC,MAAM,WAAW,KAAK,IAAI,iBAAiB,oBAAoB,EAAI;AAEnE,KAAI,CAAC,YACH,QAAO;EACL,SAAS;EACT;EACA,UAAU;EACV,QAAQ,iBAAiB;EACzB,mBAAmB;EACnB,eAAe,EAAE;EAClB;CAMH,MAAM,gBAA0B,CAAC,2BAA2B,KAAK;CACjE,IAAI;AAGJ,KAAI,WAAW,IAAK;AAElB,gBAAc,KACZ,2BAA2B,eAC3B,2BAA2B,cAC5B;AACD,sBAAoB;AAEpB,MAAI,WAAW,WAAW,WAAW,QAAQ;AAC3C,iBAAc,KAAK,2BAA2B,kBAAkB;AAChE,uBAAoB;;YAEb,WAAW,IAAK;AAEzB,gBAAc,KAAK,2BAA2B,aAAa;AAC3D,sBAAoB;QACf;AAEL,gBAAc,KAAK,2BAA2B,eAAe;AAC7D,sBAAoB;;AAItB,KAAI,WAAW,GACb,eAAc,KAAK,2BAA2B,SAAS;AAGzD,QAAO;EACL,SAAS;EACT;EACA;EACA,QAAQ;EACR;EACA;EACD;;;;;;;;;;;;;;;;;;AAmBH,SAAgB,kBACd,iBACA,kBACA,UACS;AAET,KAAI,kBAAkB,iBAAiB,YACrC,QAAO;AAIT,KAAI,WAAW,yBACb,QAAO;AAGT,QAAO;;;;;;;;;;;;;;;;;AAkBT,SAAgB,2BACd,WACoB;CACpB,MAAM,EAAE,aAAa,kBAAkB;CACvC,MAAM,cAAc,qBAAqB,UAAU;CAGnD,MAAM,kBAAkB,YAAY,gBAAgB;CACpD,MAAM,oBAAoB,YAAY,gBAAgB;CAGtD,IAAI;CACJ,IAAI;AAEJ,KAAI,iBAAiB;AACnB,4BAA0B;AAC1B,mBAAiB;YACR,mBAAmB;AAC5B,4BAA0B;AAC1B,mBAAiB;QACZ;AACL,4BAA0B;AAC1B,mBAAiB;;AAUnB,QAAO;EACL;EACA,WAAA;EACA,UALe,KAAK,MAAM,iBADN,kBAAkB,KAAM,KAM5C;EACA;EACA;EACD"}
1
+ {"version":3,"file":"LimbExposureSystem.js","names":[],"sources":["../../../src/systems/combat/LimbExposureSystem.ts"],"sourcesContent":["/**\n * Limb Exposure System for Counter-Attack Detection\n *\n * **Korean**: 사지 노출 시스템 (Saji Nochul System)\n *\n * Analyzes technique animations to determine when limbs become exposed and vulnerable\n * during attack execution. This enables:\n * - Defensive counter-attacks (반격 - banggyeok)\n * - Breaking techniques (파쇄기 - paswaegi)\n * - Exploiting overextension (과다신장 약점 - gwada sinjang yakjeom)\n *\n * **Martial Arts Philosophy**:\n * - **허점공격** (Heojeom Gonggyeok) - Attacking openings/weaknesses\n * - **후수필승** (Husu Pilseung) - Victory through counter-attack timing\n * - **이순응변** (Isun Eungbyeon) - Adapt and exploit opponent's mistakes\n *\n * @module systems/combat/LimbExposureSystem\n * @korean 사지노출시스템\n */\n\nimport type { KoreanTechnique } from \"../vitalpoint/types\";\nimport type {\n BreakingResult,\n BreakingTarget,\n CounterOpportunity,\n ExposedLimbType,\n LimbExposureWindow,\n PhysicalReachConfig,\n} from \"../../types/physics\";\nimport { BREAKING_STATUS_EFFECT_IDS } from \"./BreakingStatusEffects\";\n\n/**\n * Breaking technique constants\n * **Korean**: 파쇄기술 상수\n */\n\n/**\n * Minimum effective force required to successfully execute a breaking technique.\n * Force below this threshold will not break joints/bones.\n * Typical damage values range from 20-80, with 40 being the minimum for effective breaks.\n * @korean 파쇄최소힘\n */\nconst BREAKING_FORCE_THRESHOLD = 40;\n\n/**\n * Maximum effective force for calculating break severity on 0-1 scale.\n * Force values above 80 are clamped to 1.0 severity (complete break).\n * @korean 파쇄최대힘\n */\nconst BREAKING_MAX_FORCE = 80;\n\n/**\n * Maximum range in meters for executing counter-attacks.\n * Counter-attacks are close-range defensive techniques requiring proximity.\n * @korean 반격최대거리\n */\nconst MAX_COUNTER_RANGE_METERS = 1.0;\n\n/**\n * Calculate counter-attack opportunities from a technique's reach configuration.\n *\n * **Korean**: 반격 기회 계산\n *\n * Analyzes the technique's execution timeline and limb exposure to identify\n * windows where the opponent is vulnerable to counter-attacks.\n *\n * @param technique - The technique being executed\n * @param currentTime - Current time in technique execution (ms)\n * @returns Counter opportunity if one exists, undefined otherwise\n *\n * @public\n * @korean 반격기회계산\n */\nexport function calculateCounterOpportunity(\n technique: KoreanTechnique,\n currentTime: number\n): CounterOpportunity | undefined {\n const { reachConfig, executionTime } = technique;\n\n // No exposure window defined, no counter opportunity\n if (!reachConfig.exposureWindow) {\n return undefined;\n }\n\n const exposure = reachConfig.exposureWindow;\n\n // Calculate absolute timing of exposure window\n const windowStart = executionTime * exposure.startTime;\n const windowEnd = windowStart + exposure.duration;\n\n // Check if current time is within the exposure window\n if (currentTime < windowStart || currentTime > windowEnd) {\n return undefined;\n }\n\n // Determine recommended counter techniques based on exposed limb\n const recommendedCounters = getRecommendedCounters(\n exposure.exposedLimb,\n reachConfig,\n exposure.allowsBreaking\n );\n\n return {\n exposedLimb: exposure.exposedLimb,\n windowStart,\n windowDuration: exposure.duration,\n vulnerabilityMultiplier: exposure.vulnerabilityMultiplier,\n allowsBreaking: exposure.allowsBreaking,\n recommendedCounters,\n };\n}\n\n/**\n * Get recommended counter-technique IDs for an exposed limb.\n *\n * **Korean**: 추천 반격 기술 조회\n *\n * Returns technique IDs that would be effective against the exposed limb.\n * These IDs are string-based and should match techniques in the game's\n * technique library. When implementing, validate these IDs exist.\n *\n * **Note**: These are example/placeholder IDs. Actual implementation should\n * reference real technique definitions or use a typed enum of technique IDs.\n *\n * @param exposedLimb - The limb that is exposed\n * @param reachConfig - Reach configuration of the attacking technique\n * @param allowsBreaking - Whether breaking techniques are viable\n * @returns Array of recommended counter-technique IDs (unvalidated strings)\n *\n * @internal\n * @korean 추천반격기술조회\n */\nfunction getRecommendedCounters(\n exposedLimb: ExposedLimbType,\n reachConfig: PhysicalReachConfig,\n allowsBreaking: boolean\n): readonly string[] {\n const counters: string[] = [];\n\n // Breaking techniques for overextended limbs\n if (allowsBreaking) {\n if (exposedLimb.includes(\"leg\") || exposedLimb.includes(\"ankle\")) {\n counters.push(\"leg_sweep\", \"ankle_break\", \"knee_stomp\");\n } else if (exposedLimb.includes(\"knee\")) {\n counters.push(\"knee_break\", \"hyperextension_kick\");\n } else if (exposedLimb.includes(\"arm\") || exposedLimb.includes(\"elbow\")) {\n counters.push(\"arm_bar\", \"elbow_break\", \"joint_lock\");\n } else if (exposedLimb.includes(\"wrist\")) {\n counters.push(\"wrist_lock\", \"wrist_break\");\n }\n }\n\n // Vital point strikes on exposed limbs\n if (exposedLimb.includes(\"arm\")) {\n counters.push(\"ulnar_nerve_strike\", \"radial_nerve_pressure\");\n } else if (exposedLimb.includes(\"leg\")) {\n counters.push(\"peroneal_nerve_strike\", \"thigh_pressure_point\");\n }\n\n // Counter-strikes based on body part type\n if (reachConfig.bodyPart === \"leg\") {\n counters.push(\"leg_parry_counter\", \"inside_leg_kick\");\n } else if (reachConfig.bodyPart === \"arm\") {\n counters.push(\"arm_trap_counter\", \"redirect_punch\");\n }\n\n return counters;\n}\n\n/**\n * Calculate vulnerability multiplier for a technique at a specific time.\n *\n * **Korean**: 취약성 배수 계산\n *\n * Determines how vulnerable the attacker is at a given point in their\n * technique execution. Useful for damage calculation during counter-attacks.\n *\n * @param technique - The technique being executed\n * @param currentTime - Current time in technique execution (ms)\n * @returns Vulnerability multiplier (1.0 = normal, >1.0 = more vulnerable)\n *\n * @public\n * @korean 취약성배수계산\n */\nexport function calculateVulnerabilityMultiplier(\n technique: KoreanTechnique,\n currentTime: number\n): number {\n const { reachConfig, executionTime, recoveryTime } = technique;\n\n // No exposure window, return baseline vulnerability\n if (!reachConfig.exposureWindow) {\n return 1.0;\n }\n\n const exposure = reachConfig.exposureWindow;\n const windowStart = executionTime * exposure.startTime;\n const windowEnd = windowStart + exposure.duration;\n\n // Before exposure window: slight vulnerability during wind-up\n if (currentTime < windowStart) {\n return 1.1;\n }\n\n // Within exposure window: maximum vulnerability\n if (currentTime <= windowEnd) {\n return exposure.vulnerabilityMultiplier;\n }\n\n // Recovery phase: moderate vulnerability\n const recoveryStart = executionTime;\n const recoveryEnd = executionTime + recoveryTime;\n if (currentTime > recoveryStart && currentTime <= recoveryEnd) {\n const recoveryProgress = (currentTime - recoveryStart) / recoveryTime;\n // Linearly decrease from max vulnerability to baseline\n return (\n exposure.vulnerabilityMultiplier -\n (exposure.vulnerabilityMultiplier - 1.0) * recoveryProgress\n );\n }\n\n // After recovery: baseline vulnerability\n return 1.0;\n}\n\n/**\n * Determine which limb is exposed based on technique and animation state.\n *\n * **Korean**: 노출된 사지 결정\n *\n * Analyzes the technique's body part and animation to determine which\n * specific limb (left/right) is currently exposed.\n *\n * @param technique - The technique being executed\n * @param isLeftSided - Whether the technique uses the left side\n * @returns The exposed limb type\n *\n * @public\n * @korean 노출사지결정\n */\nexport function determineExposedLimb(\n technique: KoreanTechnique,\n isLeftSided: boolean = false\n): ExposedLimbType {\n const { reachConfig } = technique;\n\n // If exposure window explicitly defines the limb, use it\n if (reachConfig.exposureWindow?.exposedLimb) {\n return reachConfig.exposureWindow.exposedLimb;\n }\n\n // Otherwise, infer from body part and sidedness\n const side = isLeftSided ? \"left\" : \"right\";\n\n switch (reachConfig.bodyPart) {\n case \"arm\":\n // Determine joint based on reach extension\n if (reachConfig.baseExtension > 0.8) {\n return `${side}_arm` as ExposedLimbType;\n } else if (reachConfig.baseExtension > 0.5) {\n return `${side}_elbow` as ExposedLimbType;\n } else {\n return `${side}_wrist` as ExposedLimbType;\n }\n\n case \"leg\":\n // Kicks expose different parts based on extension\n if (reachConfig.baseExtension > 1.0) {\n return `${side}_leg` as ExposedLimbType;\n } else if (reachConfig.baseExtension > 0.7) {\n return `${side}_knee` as ExposedLimbType;\n } else {\n return `${side}_ankle` as ExposedLimbType;\n }\n\n case \"torso\":\n // Torso techniques typically expose arms\n return `${side}_arm` as ExposedLimbType;\n\n default:\n return \"right_arm\";\n }\n}\n\n/**\n * Map exposed limb to breaking technique target.\n *\n * **Korean**: 노출 사지를 파쇄 목표로 매핑\n *\n * Converts an exposed limb type to the appropriate breaking target\n * for joint and limb breaking techniques.\n *\n * @param exposedLimb - The exposed limb\n * @returns The breaking target joint/bone\n *\n * @public\n * @korean 파쇄목표매핑\n */\nexport function mapLimbToBreakingTarget(\n exposedLimb: ExposedLimbType\n): BreakingTarget {\n if (exposedLimb.includes(\"ankle\")) return \"ankle\";\n if (exposedLimb.includes(\"knee\")) return \"knee\";\n if (exposedLimb.includes(\"elbow\")) return \"elbow\";\n if (exposedLimb.includes(\"wrist\")) return \"wrist\";\n\n // Default mapping for general limbs\n if (exposedLimb.includes(\"leg\")) return \"knee\";\n if (exposedLimb.includes(\"arm\")) return \"elbow\";\n\n return \"elbow\"; // Default fallback\n}\n\n/**\n * Calculate breaking technique effectiveness.\n *\n * **Korean**: 파쇄 기술 효과 계산\n *\n * Determines the result of a breaking technique applied to an exposed limb,\n * considering the vulnerability window, force applied, and target joint.\n *\n * @param breakingTechnique - The breaking technique being applied (reserved for future use)\n * @param counterOpportunity - The counter opportunity being exploited\n * @param force - Force/damage of the breaking technique (typically 20-80 range)\n * @returns Result of the breaking attempt\n *\n * @remarks\n * The breakingTechnique parameter is currently unused but reserved for future\n * extensibility. It may be used to apply technique-specific modifiers or\n * validate technique compatibility with the breaking target.\n *\n * @public\n * @korean 파쇄기술효과계산\n */\nexport function calculateBreakingResult(\n breakingTechnique: KoreanTechnique,\n counterOpportunity: CounterOpportunity,\n force: number\n): BreakingResult {\n // Note: breakingTechnique parameter reserved for future use\n // (e.g., technique-specific breaking modifiers)\n void breakingTechnique;\n const target = mapLimbToBreakingTarget(counterOpportunity.exposedLimb);\n\n // Breaking only works during valid windows\n if (!counterOpportunity.allowsBreaking) {\n return {\n success: false,\n target,\n severity: 0,\n damage: force * 0.5, // Reduced damage without breaking\n mobilityReduction: 0,\n statusEffects: [],\n };\n }\n\n // Calculate breaking severity based on force and vulnerability\n const effectiveForce = force * counterOpportunity.vulnerabilityMultiplier;\n const baseSuccess = effectiveForce > BREAKING_FORCE_THRESHOLD;\n const severity = Math.min(effectiveForce / BREAKING_MAX_FORCE, 1.0);\n\n if (!baseSuccess) {\n return {\n success: false,\n target,\n severity: 0,\n damage: effectiveForce * 0.7,\n mobilityReduction: 0,\n statusEffects: [],\n };\n }\n\n // Successful break - apply appropriate effects\n // Status effect IDs from BREAKING_STATUS_EFFECT_IDS constants\n // See src/systems/combat/BreakingStatusEffects.ts for all available IDs\n const statusEffects: string[] = [BREAKING_STATUS_EFFECT_IDS.PAIN];\n let mobilityReduction: number;\n\n // Severity-based effects\n if (severity > 0.8) {\n // Severe break - dislocation or fracture\n statusEffects.push(\n BREAKING_STATUS_EFFECT_IDS.SEVERE_INJURY,\n BREAKING_STATUS_EFFECT_IDS.DISABLED_LIMB\n );\n mobilityReduction = 0.6;\n\n if (target === \"ankle\" || target === \"knee\") {\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.IMPAIRED_MOBILITY);\n mobilityReduction = 0.8; // Leg breaks severely impact movement\n }\n } else if (severity > 0.5) {\n // Moderate break\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.INJURED_LIMB);\n mobilityReduction = 0.4;\n } else {\n // Minor break - sprain or strain\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.SPRAINED_JOINT);\n mobilityReduction = 0.2;\n }\n\n // Bleeding for bone breaks\n if (severity > 0.6) {\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.BLEEDING);\n }\n\n return {\n success: true,\n target,\n severity,\n damage: effectiveForce,\n mobilityReduction,\n statusEffects,\n };\n}\n\n/**\n * Check if a defender can execute a counter-attack.\n *\n * **Korean**: 반격 실행 가능 여부 확인\n *\n * Validates whether a defender is in position and has the resources\n * to execute a counter-attack against an exposed limb.\n *\n * @param defenderStamina - Defender's current stamina\n * @param counterTechnique - The counter technique to execute\n * @param distance - Distance between attacker and defender (meters)\n * @returns Whether the counter can be executed\n *\n * @public\n * @korean 반격실행가능확인\n */\nexport function canExecuteCounter(\n defenderStamina: number,\n counterTechnique: KoreanTechnique,\n distance: number\n): boolean {\n // Check stamina requirement\n if (defenderStamina < counterTechnique.staminaCost) {\n return false;\n }\n\n // Counter must be executable at current distance\n if (distance > MAX_COUNTER_RANGE_METERS) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Generate limb exposure window for a technique.\n *\n * **Korean**: 사지 노출 시간 생성\n *\n * Creates a default limb exposure window for techniques that don't\n * explicitly define one, based on heuristics from the technique type\n * and reach configuration.\n *\n * @param technique - The technique to generate exposure for\n * @returns Generated limb exposure window\n *\n * @public\n * @korean 사지노출시간생성\n */\nexport function generateLimbExposureWindow(\n technique: KoreanTechnique\n): LimbExposureWindow {\n const { reachConfig, executionTime } = technique;\n const exposedLimb = determineExposedLimb(technique);\n\n // High extension = high vulnerability\n const isHighExtension = reachConfig.baseExtension > 1.0;\n const isMediumExtension = reachConfig.baseExtension > 0.8;\n\n // Determine vulnerability based on extension\n let vulnerabilityMultiplier: number;\n let allowsBreaking: boolean;\n\n if (isHighExtension) {\n vulnerabilityMultiplier = 2.2; // Very vulnerable\n allowsBreaking = true;\n } else if (isMediumExtension) {\n vulnerabilityMultiplier = 1.6;\n allowsBreaking = false;\n } else {\n vulnerabilityMultiplier = 1.2; // Low extension, low vulnerability\n allowsBreaking = false;\n }\n\n // Exposure starts during peak extension (typically 40-60% of execution)\n const startTime = 0.5;\n\n // Duration scales with execution time (20-40% of execution)\n const durationRatio = isHighExtension ? 0.4 : 0.25;\n const duration = Math.floor(executionTime * durationRatio);\n\n return {\n exposedLimb,\n startTime,\n duration,\n vulnerabilityMultiplier,\n allowsBreaking,\n };\n}\n\nexport default {\n calculateCounterOpportunity,\n calculateVulnerabilityMultiplier,\n determineExposedLimb,\n mapLimbToBreakingTarget,\n calculateBreakingResult,\n canExecuteCounter,\n generateLimbExposureWindow,\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PainResponseSystem.js","names":[],"sources":["../../../src/systems/combat/PainResponseSystem.ts"],"sourcesContent":["/**\n * Pain Response System for managing cumulative pain effects in combat.\n * \n * **Korean**: 고통 반응 시스템 (Pain Response System)\n * \n * Implements realistic pain accumulation that progressively reduces combat\n * effectiveness. Unlike health, pain represents the body's protective response\n * to damage, reducing capabilities before critical injury occurs.\n * \n * ## Pain Mechanics\n * \n * - **충격통 (Shock Pain)**: Instant reaction affecting all abilities (10-30% reduction for 2-3 seconds)\n * - **누적외상 (Cumulative Trauma)**: Progressive damage impairment\n * - **통증과부하 (Pain Overload)**: Complete incapacitation from overwhelming pain (>80 pain)\n * - **무력화한계 (Incapacitation Threshold)**: Point of complete combat inability\n * \n * ## Pain Levels\n * \n * - **0-20**: Minimal pain - no significant effects\n * - **20-40**: Moderate pain - slight performance reduction (-10%)\n * - **40-60**: Significant pain - noticeable impairment (-20%)\n * - **60-80**: Severe pain - major limitations (-35%)\n * - **80-100**: Pain overload - chance of stun/unconsciousness, extreme impairment (-50%)\n * \n * @module systems/combat/PainResponseSystem\n * @category Combat System\n * @korean 고통반응시스템\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointSeverity, VitalPointCategory } from \"@/types\";\n\n/**\n * Pain level categories for status indication.\n */\nexport enum PainLevel {\n /** Minimal pain (0-20) - no significant effects */\n MINIMAL = \"minimal\",\n /** Moderate pain (20-40) - slight performance reduction (-10%) */\n MODERATE = \"moderate\",\n /** Significant pain (40-60) - noticeable impairment (-20%) */\n SIGNIFICANT = \"significant\",\n /** Severe pain (60-80) - major limitations (-35%) */\n SEVERE = \"severe\",\n /** Pain overload (80-100) - chance of stun/unconsciousness, extreme impairment (-50%) */\n OVERLOAD = \"overload\",\n}\n\n/**\n * Shock pain effect from sudden trauma.\n * \n * **Korean**: 충격통 효과 (Shock Pain Effect)\n * \n * Represents instant reaction to damage affecting all abilities for 2-3 seconds.\n */\nexport interface ShockPainEffect {\n /** Intensity of shock pain (0.1-0.3 for 10-30% reduction) */\n readonly intensity: number;\n /** Duration remaining in milliseconds (2000-3000ms) */\n readonly duration: number;\n /** Timestamp when shock pain was applied */\n readonly startTime: number;\n /** Damage value that caused the shock */\n readonly causedByDamage: number;\n}\n\n/**\n * Effects of pain on combat performance.\n */\ninterface PainEffects {\n /** Pain value range */\n readonly range: readonly [number, number];\n /** Overall performance penalty multiplier */\n readonly performancePenalty: number;\n /** Accuracy reduction */\n readonly accuracyReduction: number;\n /** Damage output reduction */\n readonly damageReduction: number;\n /** Movement speed reduction */\n readonly speedReduction: number;\n /** Chance of involuntary stun (0-1) */\n readonly stunChance: number;\n}\n\n/**\n * Pain Response System managing cumulative pain effects.\n * \n * Pain accumulates from all damage sources and reduces combat effectiveness\n * progressively. High pain can incapacitate even if health remains adequate.\n * Pain dissipates slowly over time.\n * \n * @example\n * ```typescript\n * const painSystem = new PainResponseSystem();\n * \n * // Apply pain from damage\n * const newPlayer = painSystem.applyPain(player, 15, VitalPointSeverity.MAJOR);\n * \n * // Check pain level\n * const level = painSystem.getPainLevel(newPlayer.pain);\n * \n * // Apply dissipation over time\n * const recovered = painSystem.applyDissipation(newPlayer, 1000);\n * ```\n * \n * @public\n * @korean 고통반응시스템\n */\nexport class PainResponseSystem {\n /**\n * Pain level effects and thresholds matching requirements.\n */\n private readonly painEffects: Record<PainLevel, PainEffects> = {\n [PainLevel.MINIMAL]: {\n range: [0, 20],\n performancePenalty: 0.0,\n accuracyReduction: 0.0,\n damageReduction: 0.0,\n speedReduction: 0.0,\n stunChance: 0.0,\n },\n [PainLevel.MODERATE]: {\n range: [20, 40],\n performancePenalty: 0.1, // -10% overall\n accuracyReduction: 0.1,\n damageReduction: 0.1,\n speedReduction: 0.05,\n stunChance: 0.0,\n },\n [PainLevel.SIGNIFICANT]: {\n range: [40, 60],\n performancePenalty: 0.2, // -20% overall\n accuracyReduction: 0.2,\n damageReduction: 0.2,\n speedReduction: 0.15,\n stunChance: 0.05, // 5% chance\n },\n [PainLevel.SEVERE]: {\n range: [60, 80],\n performancePenalty: 0.35, // -35% overall\n accuracyReduction: 0.35,\n damageReduction: 0.35,\n speedReduction: 0.25,\n stunChance: 0.15, // 15% chance\n },\n [PainLevel.OVERLOAD]: {\n range: [80, 100],\n performancePenalty: 0.5, // -50% overall\n accuracyReduction: 0.5,\n damageReduction: 0.5,\n speedReduction: 0.4,\n stunChance: 0.3, // 30% chance per hit at pain >80\n },\n };\n\n /**\n * Pain multipliers by vital point severity.\n */\n private readonly severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 0.5,\n [VitalPointSeverity.MODERATE]: 1.0,\n [VitalPointSeverity.MAJOR]: 1.5,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n /**\n * Pain multipliers by vital point category.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 2.5, // Nerve strikes cause intense pain\n [VitalPointCategory.VASCULAR]: 1.5, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 2.0, // Breathing difficulty causes panic\n [VitalPointCategory.SKELETAL]: 1.8, // Bone/structural damage\n [VitalPointCategory.MUSCULAR]: 1.4, // Muscle trauma\n [VitalPointCategory.ORGAN]: 2.2, // Internal organ trauma\n [VitalPointCategory.JOINT]: 1.9, // Joint damage\n [VitalPointCategory.CIRCULATORY]: 1.6, // Circulatory disruption\n default: 1.0, // Standard damage\n };\n\n /**\n * Base pain dissipation rate per second (requirement: -5 pain/second).\n */\n private readonly BASE_DISSIPATION_RATE = 5.0; // -5 pain per second\n\n /**\n * Shock pain duration range in milliseconds (requirement: 2-3 seconds).\n */\n private readonly SHOCK_PAIN_DURATION = {\n min: 2000, // 2 seconds\n max: 3000, // 3 seconds\n };\n\n /**\n * Minimum damage to trigger shock pain.\n */\n private readonly SHOCK_PAIN_THRESHOLD = 10;\n\n /**\n * Pain overload threshold for stun chance (requirement: >80).\n */\n private readonly PAIN_OVERLOAD_THRESHOLD = 80;\n\n /**\n * Shock pain intensity constants.\n */\n private readonly MAX_SHOCK_INTENSITY = 0.3; // 30% max reduction\n private readonly MIN_SHOCK_INTENSITY = 0.1; // 10% min reduction\n private readonly DAMAGE_SCALE_FACTOR = 100; // for normalizing damage to 0-1 range\n private readonly INTENSITY_RANGE = 0.2; // 20% variable range\n\n /**\n * Pain to damage ratio for cumulative trauma calculation.\n */\n private readonly PAIN_TO_DAMAGE_RATIO = 0.5;\n\n /**\n * Applies pain from combat damage with shock pain effects.\n * \n * Calculates pain increase based on damage amount, vital point severity,\n * and vital point category. Also determines if shock pain effect should be triggered.\n * \n * **Shock Pain**: Instant 10-30% reduction for 2-3 seconds on significant hits (>=10 damage)\n * **Cumulative Trauma**: Progressive pain accumulation that reduces performance\n * **Pain Overload**: At >80 pain, chance of stun/unconsciousness\n * \n * @param player - Current player state\n * @param damage - Base damage amount\n * @param severity - Optional vital point severity\n * @param category - Optional vital point category for more accurate pain calculation\n * @returns Updated player state with increased pain and optional shock effect\n * \n * @example\n * ```typescript\n * // Normal hit\n * const result = system.applyPain(player, 10);\n * \n * // Vital point neurological hit with shock pain\n * const result = system.applyPain(\n * player, \n * 20, \n * VitalPointSeverity.MAJOR,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * if (result.shockEffect) {\n * console.log(`Shock pain active for ${result.shockEffect.duration}ms`);\n * }\n * ```\n * \n * @public\n * @korean 고통적용\n */\n applyPain(\n player: PlayerState,\n damage: number,\n severity?: VitalPointSeverity,\n category?: VitalPointCategory\n ): { player: PlayerState; shockEffect?: ShockPainEffect } {\n // Get multipliers based on severity and category\n const severityMultiplier = severity\n ? this.severityMultipliers[severity]\n : 1.0;\n \n const categoryMultiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate pain increase (cumulative trauma)\n const painIncrease = damage * severityMultiplier * categoryMultiplier * this.PAIN_TO_DAMAGE_RATIO;\n\n // Apply pain, clamped to 0-100\n const newPain = Math.max(0, Math.min(100, player.pain + painIncrease));\n\n let shockEffect: ShockPainEffect | undefined;\n\n // Trigger shock pain for significant hits (>=10 damage)\n if (damage >= this.SHOCK_PAIN_THRESHOLD) {\n // Calculate shock intensity: 10-30% reduction based on damage\n const shockIntensity = Math.min(\n this.MAX_SHOCK_INTENSITY,\n this.MIN_SHOCK_INTENSITY + (damage / this.DAMAGE_SCALE_FACTOR) * this.INTENSITY_RANGE\n );\n \n // Random duration between 2-3 seconds\n const shockDuration =\n this.SHOCK_PAIN_DURATION.min +\n Math.random() *\n (this.SHOCK_PAIN_DURATION.max - this.SHOCK_PAIN_DURATION.min);\n\n shockEffect = {\n intensity: shockIntensity,\n duration: shockDuration,\n startTime: Date.now(),\n causedByDamage: damage,\n };\n }\n\n const updatedPlayer: PlayerState = {\n ...player,\n pain: newPain,\n };\n\n return { player: updatedPlayer, shockEffect };\n }\n\n /**\n * Applies pain dissipation over time (requirement: -5 pain/second).\n * \n * Pain naturally dissipates when not taking damage at a constant rate\n * of -5 pain per second.\n * \n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @returns Updated player state with reduced pain\n * \n * @example\n * ```typescript\n * // In game loop (~60fps, 16ms per frame)\n * player = system.applyDissipation(player, 16);\n * ```\n * \n * @public\n * @korean 고통감소\n */\n applyDissipation(player: PlayerState, deltaTime: number): PlayerState {\n // Already at zero pain\n if (player.pain <= 0) {\n return player;\n }\n\n const deltaSeconds = deltaTime / 1000;\n \n // Constant dissipation rate of -5 pain/second\n const dissipation = this.BASE_DISSIPATION_RATE * deltaSeconds;\n const newPain = Math.max(0, player.pain - dissipation);\n\n return {\n ...player,\n pain: newPain,\n };\n }\n\n /**\n * Determines pain level from pain value.\n * \n * @param pain - Pain value (0-100)\n * @returns Current pain level\n * \n * @public\n * @korean 고통수준확인\n */\n getPainLevel(pain: number): PainLevel {\n if (pain >= 80) return PainLevel.OVERLOAD;\n if (pain >= 60) return PainLevel.SEVERE;\n if (pain >= 40) return PainLevel.SIGNIFICANT;\n if (pain >= 20) return PainLevel.MODERATE;\n return PainLevel.MINIMAL;\n }\n\n /**\n * Gets effects for a specific pain level.\n * \n * @param level - Pain level\n * @returns Effects applied at that level\n * \n * @public\n * @korean 고통효과\n */\n getEffects(level: PainLevel): PainEffects {\n return this.painEffects[level];\n }\n\n /**\n * Applies pain effects to player state, including shock pain.\n * \n * Modifies player stats based on current pain level. When shock pain\n * is active, additional penalties are applied temporarily.\n * \n * @param player - Current player state\n * @param shockEffect - Optional active shock pain effect\n * @returns Modified player state with pain effects\n * \n * @public\n * @korean 고통효과적용\n */\n applyEffects(\n player: PlayerState,\n shockEffect?: ShockPainEffect\n ): PlayerState {\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Calculate total penalties (base pain + shock pain if active)\n let totalAccuracyPenalty = effects.accuracyReduction;\n let totalDamagePenalty = effects.damageReduction;\n\n // Apply shock pain if still active\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed < shockEffect.duration) {\n // Add shock pain intensity to penalties\n totalAccuracyPenalty = Math.min(\n 1.0,\n totalAccuracyPenalty + shockEffect.intensity\n );\n totalDamagePenalty = Math.min(\n 1.0,\n totalDamagePenalty + shockEffect.intensity\n );\n }\n }\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - totalDamagePenalty)\n ),\n defense: Math.floor(\n player.defense * (1 - effects.performancePenalty)\n ),\n speed: Math.floor(\n player.speed * (1 - effects.speedReduction)\n ),\n technique: Math.floor(\n player.technique * (1 - totalAccuracyPenalty)\n ),\n };\n }\n\n /**\n * Checks if pain overload should trigger stun.\n * \n * At pain >80, there's a chance per hit to trigger stun based on pain level.\n * Pain Overload level has 30% chance to trigger stun.\n * \n * @param player - Current player state\n * @returns True if player should be stunned from pain\n * \n * @public\n * @korean 고통기절확인\n */\n shouldTriggerStun(player: PlayerState): boolean {\n if (player.pain < this.PAIN_OVERLOAD_THRESHOLD) {\n return false;\n }\n\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Roll for stun chance\n return Math.random() < effects.stunChance;\n }\n\n /**\n * Checks if player is in pain overload state (>80 pain).\n * \n * @param player - Current player state\n * @returns True if pain is above overload threshold\n * \n * @public\n * @korean 고통과부하확인\n */\n isInPainOverload(player: PlayerState): boolean {\n return player.pain >= this.PAIN_OVERLOAD_THRESHOLD;\n }\n\n /**\n * Checks if player is incapacitated by pain.\n * \n * @param player - Current player state\n * @returns True if pain is at overload level\n * \n * @public\n * @korean 고통무력화확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return this.getPainLevel(player.pain) === PainLevel.OVERLOAD;\n }\n\n /**\n * Gets bilingual name for pain level.\n * \n * @param level - Pain level\n * @returns Korean and English level names\n * \n * @public\n * @korean 고통이름\n */\n getLevelName(level: PainLevel): { korean: string; english: string } {\n const names: Record<PainLevel, { korean: string; english: string }> = {\n [PainLevel.MINIMAL]: {\n korean: \"최소\",\n english: \"Minimal\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통\",\n english: \"Moderate\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당\",\n english: \"Significant\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각\",\n english: \"Severe\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"과부하\",\n english: \"Overload\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of pain level effects.\n * \n * @param level - Pain level\n * @returns Bilingual description\n * \n * @public\n * @korean 고통설명\n */\n getLevelDescription(level: PainLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n PainLevel,\n { korean: string; english: string }\n > = {\n [PainLevel.MINIMAL]: {\n korean: \"최소한의 고통, 전투 능력에 영향 없음\",\n english: \"Minimal pain, no significant effects on combat\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통 고통, 약간의 능력 저하 (-10%)\",\n english: \"Moderate pain, slight performance reduction (-10%)\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당한 고통, 명확한 손상 (-20%)\",\n english: \"Significant pain, noticeable impairment (-20%)\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각한 고통, 주요 제한 (-35%)\",\n english: \"Severe pain, major limitations (-35%)\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"고통 과부하, 기절 위험 (-50%, 30% 기절 확률)\",\n english: \"Pain overload, stun risk (-50%, 30% stun chance)\",\n },\n };\n\n return descriptions[level];\n }\n}\n\nexport default PainResponseSystem;\n"],"mappings":"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"}
1
+ {"version":3,"file":"PainResponseSystem.js","names":[],"sources":["../../../src/systems/combat/PainResponseSystem.ts"],"sourcesContent":["/**\n * Pain Response System for managing cumulative pain effects in combat.\n * \n * **Korean**: 고통 반응 시스템 (Pain Response System)\n * \n * Implements realistic pain accumulation that progressively reduces combat\n * effectiveness. Unlike health, pain represents the body's protective response\n * to damage, reducing capabilities before critical injury occurs.\n * \n * ## Pain Mechanics\n * \n * - **충격통 (Shock Pain)**: Instant reaction affecting all abilities (10-30% reduction for 2-3 seconds)\n * - **누적외상 (Cumulative Trauma)**: Progressive damage impairment\n * - **통증과부하 (Pain Overload)**: Complete incapacitation from overwhelming pain (>80 pain)\n * - **무력화한계 (Incapacitation Threshold)**: Point of complete combat inability\n * \n * ## Pain Levels\n * \n * - **0-20**: Minimal pain - no significant effects\n * - **20-40**: Moderate pain - slight performance reduction (-10%)\n * - **40-60**: Significant pain - noticeable impairment (-20%)\n * - **60-80**: Severe pain - major limitations (-35%)\n * - **80-100**: Pain overload - chance of stun/unconsciousness, extreme impairment (-50%)\n * \n * @module systems/combat/PainResponseSystem\n * @category Combat System\n * @korean 고통반응시스템\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointSeverity, VitalPointCategory } from \"@/types\";\n\n/**\n * Pain level categories for status indication.\n */\nexport enum PainLevel {\n /** Minimal pain (0-20) - no significant effects */\n MINIMAL = \"minimal\",\n /** Moderate pain (20-40) - slight performance reduction (-10%) */\n MODERATE = \"moderate\",\n /** Significant pain (40-60) - noticeable impairment (-20%) */\n SIGNIFICANT = \"significant\",\n /** Severe pain (60-80) - major limitations (-35%) */\n SEVERE = \"severe\",\n /** Pain overload (80-100) - chance of stun/unconsciousness, extreme impairment (-50%) */\n OVERLOAD = \"overload\",\n}\n\n/**\n * Shock pain effect from sudden trauma.\n * \n * **Korean**: 충격통 효과 (Shock Pain Effect)\n * \n * Represents instant reaction to damage affecting all abilities for 2-3 seconds.\n */\nexport interface ShockPainEffect {\n /** Intensity of shock pain (0.1-0.3 for 10-30% reduction) */\n readonly intensity: number;\n /** Duration remaining in milliseconds (2000-3000ms) */\n readonly duration: number;\n /** Timestamp when shock pain was applied */\n readonly startTime: number;\n /** Damage value that caused the shock */\n readonly causedByDamage: number;\n}\n\n/**\n * Effects of pain on combat performance.\n */\ninterface PainEffects {\n /** Pain value range */\n readonly range: readonly [number, number];\n /** Overall performance penalty multiplier */\n readonly performancePenalty: number;\n /** Accuracy reduction */\n readonly accuracyReduction: number;\n /** Damage output reduction */\n readonly damageReduction: number;\n /** Movement speed reduction */\n readonly speedReduction: number;\n /** Chance of involuntary stun (0-1) */\n readonly stunChance: number;\n}\n\n/**\n * Pain Response System managing cumulative pain effects.\n * \n * Pain accumulates from all damage sources and reduces combat effectiveness\n * progressively. High pain can incapacitate even if health remains adequate.\n * Pain dissipates slowly over time.\n * \n * @example\n * ```typescript\n * const painSystem = new PainResponseSystem();\n * \n * // Apply pain from damage\n * const newPlayer = painSystem.applyPain(player, 15, VitalPointSeverity.MAJOR);\n * \n * // Check pain level\n * const level = painSystem.getPainLevel(newPlayer.pain);\n * \n * // Apply dissipation over time\n * const recovered = painSystem.applyDissipation(newPlayer, 1000);\n * ```\n * \n * @public\n * @korean 고통반응시스템\n */\nexport class PainResponseSystem {\n /**\n * Pain level effects and thresholds matching requirements.\n */\n private readonly painEffects: Record<PainLevel, PainEffects> = {\n [PainLevel.MINIMAL]: {\n range: [0, 20],\n performancePenalty: 0.0,\n accuracyReduction: 0.0,\n damageReduction: 0.0,\n speedReduction: 0.0,\n stunChance: 0.0,\n },\n [PainLevel.MODERATE]: {\n range: [20, 40],\n performancePenalty: 0.1, // -10% overall\n accuracyReduction: 0.1,\n damageReduction: 0.1,\n speedReduction: 0.05,\n stunChance: 0.0,\n },\n [PainLevel.SIGNIFICANT]: {\n range: [40, 60],\n performancePenalty: 0.2, // -20% overall\n accuracyReduction: 0.2,\n damageReduction: 0.2,\n speedReduction: 0.15,\n stunChance: 0.05, // 5% chance\n },\n [PainLevel.SEVERE]: {\n range: [60, 80],\n performancePenalty: 0.35, // -35% overall\n accuracyReduction: 0.35,\n damageReduction: 0.35,\n speedReduction: 0.25,\n stunChance: 0.15, // 15% chance\n },\n [PainLevel.OVERLOAD]: {\n range: [80, 100],\n performancePenalty: 0.5, // -50% overall\n accuracyReduction: 0.5,\n damageReduction: 0.5,\n speedReduction: 0.4,\n stunChance: 0.3, // 30% chance per hit at pain >80\n },\n };\n\n /**\n * Pain multipliers by vital point severity.\n */\n private readonly severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 0.5,\n [VitalPointSeverity.MODERATE]: 1.0,\n [VitalPointSeverity.MAJOR]: 1.5,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n /**\n * Pain multipliers by vital point category.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 2.5, // Nerve strikes cause intense pain\n [VitalPointCategory.VASCULAR]: 1.5, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 2.0, // Breathing difficulty causes panic\n [VitalPointCategory.SKELETAL]: 1.8, // Bone/structural damage\n [VitalPointCategory.MUSCULAR]: 1.4, // Muscle trauma\n [VitalPointCategory.ORGAN]: 2.2, // Internal organ trauma\n [VitalPointCategory.JOINT]: 1.9, // Joint damage\n [VitalPointCategory.CIRCULATORY]: 1.6, // Circulatory disruption\n default: 1.0, // Standard damage\n };\n\n /**\n * Base pain dissipation rate per second (requirement: -5 pain/second).\n */\n private readonly BASE_DISSIPATION_RATE = 5.0; // -5 pain per second\n\n /**\n * Shock pain duration range in milliseconds (requirement: 2-3 seconds).\n */\n private readonly SHOCK_PAIN_DURATION = {\n min: 2000, // 2 seconds\n max: 3000, // 3 seconds\n };\n\n /**\n * Minimum damage to trigger shock pain.\n */\n private readonly SHOCK_PAIN_THRESHOLD = 10;\n\n /**\n * Pain overload threshold for stun chance (requirement: >80).\n */\n private readonly PAIN_OVERLOAD_THRESHOLD = 80;\n\n /**\n * Shock pain intensity constants.\n */\n private readonly MAX_SHOCK_INTENSITY = 0.3; // 30% max reduction\n private readonly MIN_SHOCK_INTENSITY = 0.1; // 10% min reduction\n private readonly DAMAGE_SCALE_FACTOR = 100; // for normalizing damage to 0-1 range\n private readonly INTENSITY_RANGE = 0.2; // 20% variable range\n\n /**\n * Pain to damage ratio for cumulative trauma calculation.\n */\n private readonly PAIN_TO_DAMAGE_RATIO = 0.5;\n\n /**\n * Applies pain from combat damage with shock pain effects.\n * \n * Calculates pain increase based on damage amount, vital point severity,\n * and vital point category. Also determines if shock pain effect should be triggered.\n * \n * **Shock Pain**: Instant 10-30% reduction for 2-3 seconds on significant hits (>=10 damage)\n * **Cumulative Trauma**: Progressive pain accumulation that reduces performance\n * **Pain Overload**: At >80 pain, chance of stun/unconsciousness\n * \n * @param player - Current player state\n * @param damage - Base damage amount\n * @param severity - Optional vital point severity\n * @param category - Optional vital point category for more accurate pain calculation\n * @returns Updated player state with increased pain and optional shock effect\n * \n * @example\n * ```typescript\n * // Normal hit\n * const result = system.applyPain(player, 10);\n * \n * // Vital point neurological hit with shock pain\n * const result = system.applyPain(\n * player, \n * 20, \n * VitalPointSeverity.MAJOR,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * if (result.shockEffect) {\n * console.log(`Shock pain active for ${result.shockEffect.duration}ms`);\n * }\n * ```\n * \n * @public\n * @korean 고통적용\n */\n applyPain(\n player: PlayerState,\n damage: number,\n severity?: VitalPointSeverity,\n category?: VitalPointCategory\n ): { player: PlayerState; shockEffect?: ShockPainEffect } {\n // Get multipliers based on severity and category\n const severityMultiplier = severity\n ? this.severityMultipliers[severity]\n : 1.0;\n \n const categoryMultiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate pain increase (cumulative trauma)\n const painIncrease = damage * severityMultiplier * categoryMultiplier * this.PAIN_TO_DAMAGE_RATIO;\n\n // Apply pain, clamped to 0-100\n const newPain = Math.max(0, Math.min(100, player.pain + painIncrease));\n\n let shockEffect: ShockPainEffect | undefined;\n\n // Trigger shock pain for significant hits (>=10 damage)\n if (damage >= this.SHOCK_PAIN_THRESHOLD) {\n // Calculate shock intensity: 10-30% reduction based on damage\n const shockIntensity = Math.min(\n this.MAX_SHOCK_INTENSITY,\n this.MIN_SHOCK_INTENSITY + (damage / this.DAMAGE_SCALE_FACTOR) * this.INTENSITY_RANGE\n );\n \n // Random duration between 2-3 seconds\n const shockDuration =\n this.SHOCK_PAIN_DURATION.min +\n Math.random() *\n (this.SHOCK_PAIN_DURATION.max - this.SHOCK_PAIN_DURATION.min);\n\n shockEffect = {\n intensity: shockIntensity,\n duration: shockDuration,\n startTime: Date.now(),\n causedByDamage: damage,\n };\n }\n\n const updatedPlayer: PlayerState = {\n ...player,\n pain: newPain,\n };\n\n return { player: updatedPlayer, shockEffect };\n }\n\n /**\n * Applies pain dissipation over time (requirement: -5 pain/second).\n * \n * Pain naturally dissipates when not taking damage at a constant rate\n * of -5 pain per second.\n * \n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @returns Updated player state with reduced pain\n * \n * @example\n * ```typescript\n * // In game loop (~60fps, 16ms per frame)\n * player = system.applyDissipation(player, 16);\n * ```\n * \n * @public\n * @korean 고통감소\n */\n applyDissipation(player: PlayerState, deltaTime: number): PlayerState {\n // Already at zero pain\n if (player.pain <= 0) {\n return player;\n }\n\n const deltaSeconds = deltaTime / 1000;\n \n // Constant dissipation rate of -5 pain/second\n const dissipation = this.BASE_DISSIPATION_RATE * deltaSeconds;\n const newPain = Math.max(0, player.pain - dissipation);\n\n return {\n ...player,\n pain: newPain,\n };\n }\n\n /**\n * Determines pain level from pain value.\n * \n * @param pain - Pain value (0-100)\n * @returns Current pain level\n * \n * @public\n * @korean 고통수준확인\n */\n getPainLevel(pain: number): PainLevel {\n if (pain >= 80) return PainLevel.OVERLOAD;\n if (pain >= 60) return PainLevel.SEVERE;\n if (pain >= 40) return PainLevel.SIGNIFICANT;\n if (pain >= 20) return PainLevel.MODERATE;\n return PainLevel.MINIMAL;\n }\n\n /**\n * Gets effects for a specific pain level.\n * \n * @param level - Pain level\n * @returns Effects applied at that level\n * \n * @public\n * @korean 고통효과\n */\n getEffects(level: PainLevel): PainEffects {\n return this.painEffects[level];\n }\n\n /**\n * Applies pain effects to player state, including shock pain.\n * \n * Modifies player stats based on current pain level. When shock pain\n * is active, additional penalties are applied temporarily.\n * \n * @param player - Current player state\n * @param shockEffect - Optional active shock pain effect\n * @returns Modified player state with pain effects\n * \n * @public\n * @korean 고통효과적용\n */\n applyEffects(\n player: PlayerState,\n shockEffect?: ShockPainEffect\n ): PlayerState {\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Calculate total penalties (base pain + shock pain if active)\n let totalAccuracyPenalty = effects.accuracyReduction;\n let totalDamagePenalty = effects.damageReduction;\n\n // Apply shock pain if still active\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed < shockEffect.duration) {\n // Add shock pain intensity to penalties\n totalAccuracyPenalty = Math.min(\n 1.0,\n totalAccuracyPenalty + shockEffect.intensity\n );\n totalDamagePenalty = Math.min(\n 1.0,\n totalDamagePenalty + shockEffect.intensity\n );\n }\n }\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - totalDamagePenalty)\n ),\n defense: Math.floor(\n player.defense * (1 - effects.performancePenalty)\n ),\n speed: Math.floor(\n player.speed * (1 - effects.speedReduction)\n ),\n technique: Math.floor(\n player.technique * (1 - totalAccuracyPenalty)\n ),\n };\n }\n\n /**\n * Checks if pain overload should trigger stun.\n * \n * At pain >80, there's a chance per hit to trigger stun based on pain level.\n * Pain Overload level has 30% chance to trigger stun.\n * \n * @param player - Current player state\n * @returns True if player should be stunned from pain\n * \n * @public\n * @korean 고통기절확인\n */\n shouldTriggerStun(player: PlayerState): boolean {\n if (player.pain < this.PAIN_OVERLOAD_THRESHOLD) {\n return false;\n }\n\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Roll for stun chance\n return Math.random() < effects.stunChance;\n }\n\n /**\n * Checks if player is in pain overload state (>80 pain).\n * \n * @param player - Current player state\n * @returns True if pain is above overload threshold\n * \n * @public\n * @korean 고통과부하확인\n */\n isInPainOverload(player: PlayerState): boolean {\n return player.pain >= this.PAIN_OVERLOAD_THRESHOLD;\n }\n\n /**\n * Checks if player is incapacitated by pain.\n * \n * @param player - Current player state\n * @returns True if pain is at overload level\n * \n * @public\n * @korean 고통무력화확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return this.getPainLevel(player.pain) === PainLevel.OVERLOAD;\n }\n\n /**\n * Gets bilingual name for pain level.\n * \n * @param level - Pain level\n * @returns Korean and English level names\n * \n * @public\n * @korean 고통이름\n */\n getLevelName(level: PainLevel): { korean: string; english: string } {\n const names: Record<PainLevel, { korean: string; english: string }> = {\n [PainLevel.MINIMAL]: {\n korean: \"최소\",\n english: \"Minimal\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통\",\n english: \"Moderate\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당\",\n english: \"Significant\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각\",\n english: \"Severe\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"과부하\",\n english: \"Overload\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of pain level effects.\n * \n * @param level - Pain level\n * @returns Bilingual description\n * \n * @public\n * @korean 고통설명\n */\n getLevelDescription(level: PainLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n PainLevel,\n { korean: string; english: string }\n > = {\n [PainLevel.MINIMAL]: {\n korean: \"최소한의 고통, 전투 능력에 영향 없음\",\n english: \"Minimal pain, no significant effects on combat\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통 고통, 약간의 능력 저하 (-10%)\",\n english: \"Moderate pain, slight performance reduction (-10%)\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당한 고통, 명확한 손상 (-20%)\",\n english: \"Significant pain, noticeable impairment (-20%)\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각한 고통, 주요 제한 (-35%)\",\n english: \"Severe pain, major limitations (-35%)\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"고통 과부하, 기절 위험 (-50%, 30% 기절 확률)\",\n english: \"Pain overload, stun risk (-50%, 30% stun chance)\",\n },\n };\n\n return descriptions[level];\n }\n}\n\nexport default PainResponseSystem;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TrainingCombatSystem.js","names":[],"sources":["../../../src/systems/combat/TrainingCombatSystem.ts"],"sourcesContent":["import { PlayerArchetype, TrigramStance } from \"../../types/common\";\nimport { AnimationType } from \"../animation\";\nimport { CombatSystem } from \"../CombatSystem\";\nimport { PlayerState } from \"../player\";\nimport { KoreanTechnique } from \"../vitalpoint\";\nimport { TrainingCombatResult } from \"./types\";\n\n/**\n * Animation context for timing-based hit detection\n */\ninterface AnimationContext {\n readonly animationType: AnimationType;\n readonly currentTime: number;\n}\n\n/**\n * Training-specific combat system for Korean martial arts practice\n * Focuses on technique accuracy, form analysis, and educational feedback\n */\nexport class TrainingCombatSystem extends CombatSystem {\n private trainingDummy: PlayerState;\n private accuracyHistory: number[] = [];\n private techniqueAttempts: number = 0;\n private successfulTechniques: number = 0;\n\n constructor() {\n super();\n this.trainingDummy = this.createTrainingDummy();\n }\n\n /**\n * Create a training dummy with infinite health for practice\n */\n private createTrainingDummy(): PlayerState {\n return {\n id: \"training_dummy\",\n name: { korean: \"훈련 더미\", english: \"Training Dummy\" },\n archetype: PlayerArchetype.MUSA,\n health: 1000,\n maxHealth: 1000,\n ki: 1000,\n maxKi: 1000,\n stamina: 1000,\n maxStamina: 1000,\n energy: 1000,\n maxEnergy: 1000,\n attackPower: 50,\n defense: 100,\n speed: 30,\n technique: 50,\n pain: 0,\n consciousness: 100,\n balance: 100,\n momentum: 0,\n currentStance: TrigramStance.GAN, // Mountain stance - defensive\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- CombatState type is narrower than string literal\n combatState: \"idle\" as any,\n position: { x: 600, y: 400 },\n isBlocking: false,\n isStunned: false,\n isCountering: false,\n lastActionTime: 0,\n recoveryTime: 0,\n lastStanceChangeTime: 0,\n statusEffects: [],\n activeEffects: [],\n vitalPoints: [],\n totalDamageReceived: 0,\n totalDamageDealt: 0,\n hitsTaken: 0,\n hitsLanded: 0,\n perfectStrikes: 0,\n vitalPointHits: 0,\n };\n }\n\n /**\n * Execute a training technique with detailed analysis\n * Supports optional animation context for physics-based hit detection\n */\n executeTrainingTechnique(\n player: PlayerState,\n technique: KoreanTechnique,\n targetedVitalPointId?: string,\n animationContext?: AnimationContext,\n ): TrainingCombatResult {\n this.techniqueAttempts++;\n\n // Base combat resolution with animation context\n const baseResult = this.resolveAttack(\n player,\n this.trainingDummy,\n technique,\n targetedVitalPointId,\n animationContext,\n );\n\n // Calculate training-specific scores\n const accuracyScore = this.calculateAccuracyScore(player, technique);\n const techniqueScore = this.calculateTechniqueScore(player, technique);\n const formScore = this.calculateFormScore(player, technique);\n\n this.accuracyHistory.push(accuracyScore);\n if (this.accuracyHistory.length > 10) {\n this.accuracyHistory.shift(); // Keep last 10 attempts\n }\n\n if (baseResult.hit) {\n this.successfulTechniques++;\n }\n\n // Generate improvement feedback\n const improvementAreas = this.generateImprovementAreas(\n accuracyScore,\n techniqueScore,\n formScore,\n );\n\n const nextTrainingGoals = this.generateTrainingGoals(\n player,\n technique,\n accuracyScore,\n );\n\n return {\n ...baseResult,\n accuracyScore,\n techniqueScore,\n formScore,\n improvementAreas,\n nextTrainingGoals,\n };\n }\n\n /**\n * Calculate accuracy score based on stance, timing, and targeting\n */\n private calculateAccuracyScore(\n player: PlayerState,\n _technique: KoreanTechnique,\n ): number {\n let score = 0.5; // Base score\n\n // Player technique skill\n score += (player.technique / 100) * 0.2;\n\n return Math.min(1.0, Math.max(0.0, score));\n }\n\n /**\n * Calculate technique execution score\n */\n private calculateTechniqueScore(\n player: PlayerState,\n _technique: KoreanTechnique,\n ): number {\n let score = 0.6; // Base execution\n\n // Ki and stamina availability\n const kiRatio = player.ki / player.maxKi;\n const staminaRatio = player.stamina / player.maxStamina;\n\n score += kiRatio * 0.2;\n score += staminaRatio * 0.2;\n\n return Math.min(1.0, Math.max(0.0, score));\n }\n\n /**\n * Calculate form score based on balance and positioning\n */\n private calculateFormScore(\n _player: PlayerState,\n _technique: KoreanTechnique,\n ): number {\n const score = 0.5; // Base form\n\n // For now, return base form score\n return Math.min(1.0, Math.max(0.0, score));\n }\n\n /**\n * Generate areas for improvement based on scores\n */\n private generateImprovementAreas(\n accuracy: number,\n technique: number,\n form: number,\n ): string[] {\n const areas: string[] = [];\n\n if (accuracy < 0.7) {\n areas.push(\"targeting_precision\");\n }\n if (technique < 0.7) {\n areas.push(\"technique_execution\");\n }\n if (form < 0.7) {\n areas.push(\"stance_stability\");\n }\n\n // Korean translations\n const koreanAreas = areas.map((area) => {\n switch (area) {\n case \"targeting_precision\":\n return \"타겟팅 정확도 - Targeting Precision\";\n case \"technique_execution\":\n return \"기법 실행 - Technique Execution\";\n case \"stance_stability\":\n return \"자세 안정성 - Stance Stability\";\n default:\n return area;\n }\n });\n\n return koreanAreas.length > 0\n ? koreanAreas\n : [\"전반적 향상 - Overall Improvement\"];\n }\n\n /**\n * Generate next training goals\n */\n private generateTrainingGoals(\n // Fix: Remove unused player parameter\n _player: PlayerState,\n technique: KoreanTechnique,\n accuracy: number,\n ): string[] {\n const goals: string[] = [];\n\n if (accuracy > 0.8) {\n goals.push(\"고급 기법 연습 - Practice Advanced Techniques\");\n } else if (accuracy > 0.6) {\n goals.push(\"연속 기법 연습 - Practice Combo Techniques\");\n } else {\n goals.push(\"기본 자세 연습 - Practice Basic Stances\");\n }\n\n // Add stance-specific goals\n const stanceGoals = this.getStanceSpecificGoals(technique.stance);\n goals.push(...stanceGoals);\n\n return goals.slice(0, 3); // Limit to 3 goals\n }\n\n /**\n * Get stance-specific training goals\n */\n private getStanceSpecificGoals(stance: TrigramStance): string[] {\n const stanceGoals: Record<TrigramStance, string[]> = {\n [TrigramStance.GEON]: [\n \"천괘 직선 공격 마스터 - Master Heaven's Direct Attacks\",\n ],\n [TrigramStance.TAE]: [\"태괘 유동성 향상 - Improve Lake's Fluidity\"],\n [TrigramStance.LI]: [\"리괘 정확성 훈련 - Train Fire's Precision\"],\n [TrigramStance.JIN]: [\"진괘 폭발력 개발 - Develop Thunder's Power\"],\n [TrigramStance.SON]: [\"손괘 연속성 연습 - Practice Wind's Continuity\"],\n [TrigramStance.GAM]: [\"감괘 적응력 향상 - Improve Water's Adaptability\"],\n [TrigramStance.GAN]: [\"간괘 방어력 강화 - Strengthen Mountain's Defense\"],\n [TrigramStance.GON]: [\"곤괘 제압력 훈련 - Train Earth's Control\"],\n };\n\n return stanceGoals[stance] || [\"기본 자세 완성 - Perfect Basic Stance\"];\n }\n\n /**\n * Get training statistics\n */\n getTrainingStats() {\n const overallAccuracy =\n this.accuracyHistory.length > 0\n ? this.accuracyHistory.reduce((a, b) => a + b, 0) /\n this.accuracyHistory.length\n : 0;\n\n const successRate =\n this.techniqueAttempts > 0\n ? this.successfulTechniques / this.techniqueAttempts\n : 0;\n\n return {\n totalAttempts: this.techniqueAttempts,\n successfulTechniques: this.successfulTechniques,\n successRate,\n overallAccuracy,\n recentAccuracy: this.accuracyHistory.slice(-5),\n improvementTrend: this.calculateImprovementTrend(),\n };\n }\n\n /**\n * Calculate improvement trend from recent accuracy scores\n */\n private calculateImprovementTrend(): \"improving\" | \"stable\" | \"declining\" {\n if (this.accuracyHistory.length < 5) return \"stable\";\n\n const recent = this.accuracyHistory.slice(-3);\n const earlier = this.accuracyHistory.slice(-6, -3);\n\n if (earlier.length === 0) return \"stable\";\n\n const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;\n const earlierAvg = earlier.reduce((a, b) => a + b, 0) / earlier.length;\n\n const diff = recentAvg - earlierAvg;\n\n if (diff > 0.1) return \"improving\";\n if (diff < -0.1) return \"declining\";\n return \"stable\";\n }\n\n /**\n * Reset training session\n */\n resetTrainingSession() {\n this.accuracyHistory = [];\n this.techniqueAttempts = 0;\n this.successfulTechniques = 0;\n this.trainingDummy = this.createTrainingDummy();\n }\n\n /**\n * Fix: Add the missing resetTrainingDummy method that tests expect\n */\n resetTrainingDummy() {\n this.trainingDummy = this.createTrainingDummy();\n }\n\n /**\n * Get training dummy for display\n */\n getTrainingDummy(): PlayerState {\n return this.trainingDummy;\n }\n\n /**\n * Update training dummy health (for visual feedback)\n */\n updateTrainingDummy(updates: Partial<PlayerState>) {\n this.trainingDummy = {\n ...this.trainingDummy,\n ...updates,\n // Always keep high health for training\n health: Math.max(this.trainingDummy.health, 900),\n };\n }\n}\n\nexport default TrainingCombatSystem;\n"],"mappings":"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"}
1
+ {"version":3,"file":"TrainingCombatSystem.js","names":[],"sources":["../../../src/systems/combat/TrainingCombatSystem.ts"],"sourcesContent":["import { PlayerArchetype, TrigramStance } from \"../../types/common\";\nimport { AnimationType } from \"../animation\";\nimport { CombatSystem } from \"../CombatSystem\";\nimport { PlayerState } from \"../player\";\nimport { KoreanTechnique } from \"../vitalpoint\";\nimport { TrainingCombatResult } from \"./types\";\n\n/**\n * Animation context for timing-based hit detection\n */\ninterface AnimationContext {\n readonly animationType: AnimationType;\n readonly currentTime: number;\n}\n\n/**\n * Training-specific combat system for Korean martial arts practice\n * Focuses on technique accuracy, form analysis, and educational feedback\n */\nexport class TrainingCombatSystem extends CombatSystem {\n private trainingDummy: PlayerState;\n private accuracyHistory: number[] = [];\n private techniqueAttempts: number = 0;\n private successfulTechniques: number = 0;\n\n constructor() {\n super();\n this.trainingDummy = this.createTrainingDummy();\n }\n\n /**\n * Create a training dummy with infinite health for practice\n */\n private createTrainingDummy(): PlayerState {\n return {\n id: \"training_dummy\",\n name: { korean: \"훈련 더미\", english: \"Training Dummy\" },\n archetype: PlayerArchetype.MUSA,\n health: 1000,\n maxHealth: 1000,\n ki: 1000,\n maxKi: 1000,\n stamina: 1000,\n maxStamina: 1000,\n energy: 1000,\n maxEnergy: 1000,\n attackPower: 50,\n defense: 100,\n speed: 30,\n technique: 50,\n pain: 0,\n consciousness: 100,\n balance: 100,\n momentum: 0,\n currentStance: TrigramStance.GAN, // Mountain stance - defensive\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- CombatState type is narrower than string literal\n combatState: \"idle\" as any,\n position: { x: 600, y: 400 },\n isBlocking: false,\n isStunned: false,\n isCountering: false,\n lastActionTime: 0,\n recoveryTime: 0,\n lastStanceChangeTime: 0,\n statusEffects: [],\n activeEffects: [],\n vitalPoints: [],\n totalDamageReceived: 0,\n totalDamageDealt: 0,\n hitsTaken: 0,\n hitsLanded: 0,\n perfectStrikes: 0,\n vitalPointHits: 0,\n };\n }\n\n /**\n * Execute a training technique with detailed analysis\n * Supports optional animation context for physics-based hit detection\n */\n executeTrainingTechnique(\n player: PlayerState,\n technique: KoreanTechnique,\n targetedVitalPointId?: string,\n animationContext?: AnimationContext,\n ): TrainingCombatResult {\n this.techniqueAttempts++;\n\n // Base combat resolution with animation context\n const baseResult = this.resolveAttack(\n player,\n this.trainingDummy,\n technique,\n targetedVitalPointId,\n animationContext,\n );\n\n // Calculate training-specific scores\n const accuracyScore = this.calculateAccuracyScore(player, technique);\n const techniqueScore = this.calculateTechniqueScore(player, technique);\n const formScore = this.calculateFormScore(player, technique);\n\n this.accuracyHistory.push(accuracyScore);\n if (this.accuracyHistory.length > 10) {\n this.accuracyHistory.shift(); // Keep last 10 attempts\n }\n\n if (baseResult.hit) {\n this.successfulTechniques++;\n }\n\n // Generate improvement feedback\n const improvementAreas = this.generateImprovementAreas(\n accuracyScore,\n techniqueScore,\n formScore,\n );\n\n const nextTrainingGoals = this.generateTrainingGoals(\n player,\n technique,\n accuracyScore,\n );\n\n return {\n ...baseResult,\n accuracyScore,\n techniqueScore,\n formScore,\n improvementAreas,\n nextTrainingGoals,\n };\n }\n\n /**\n * Calculate accuracy score based on stance, timing, and targeting\n */\n private calculateAccuracyScore(\n player: PlayerState,\n _technique: KoreanTechnique,\n ): number {\n let score = 0.5; // Base score\n\n // Player technique skill\n score += (player.technique / 100) * 0.2;\n\n return Math.min(1.0, Math.max(0.0, score));\n }\n\n /**\n * Calculate technique execution score\n */\n private calculateTechniqueScore(\n player: PlayerState,\n _technique: KoreanTechnique,\n ): number {\n let score = 0.6; // Base execution\n\n // Ki and stamina availability\n const kiRatio = player.ki / player.maxKi;\n const staminaRatio = player.stamina / player.maxStamina;\n\n score += kiRatio * 0.2;\n score += staminaRatio * 0.2;\n\n return Math.min(1.0, Math.max(0.0, score));\n }\n\n /**\n * Calculate form score based on balance and positioning\n */\n private calculateFormScore(\n _player: PlayerState,\n _technique: KoreanTechnique,\n ): number {\n const score = 0.5; // Base form\n\n // For now, return base form score\n return Math.min(1.0, Math.max(0.0, score));\n }\n\n /**\n * Generate areas for improvement based on scores\n */\n private generateImprovementAreas(\n accuracy: number,\n technique: number,\n form: number,\n ): string[] {\n const areas: string[] = [];\n\n if (accuracy < 0.7) {\n areas.push(\"targeting_precision\");\n }\n if (technique < 0.7) {\n areas.push(\"technique_execution\");\n }\n if (form < 0.7) {\n areas.push(\"stance_stability\");\n }\n\n // Korean translations\n const koreanAreas = areas.map((area) => {\n switch (area) {\n case \"targeting_precision\":\n return \"타겟팅 정확도 - Targeting Precision\";\n case \"technique_execution\":\n return \"기법 실행 - Technique Execution\";\n case \"stance_stability\":\n return \"자세 안정성 - Stance Stability\";\n default:\n return area;\n }\n });\n\n return koreanAreas.length > 0\n ? koreanAreas\n : [\"전반적 향상 - Overall Improvement\"];\n }\n\n /**\n * Generate next training goals\n */\n private generateTrainingGoals(\n // Fix: Remove unused player parameter\n _player: PlayerState,\n technique: KoreanTechnique,\n accuracy: number,\n ): string[] {\n const goals: string[] = [];\n\n if (accuracy > 0.8) {\n goals.push(\"고급 기법 연습 - Practice Advanced Techniques\");\n } else if (accuracy > 0.6) {\n goals.push(\"연속 기법 연습 - Practice Combo Techniques\");\n } else {\n goals.push(\"기본 자세 연습 - Practice Basic Stances\");\n }\n\n // Add stance-specific goals\n const stanceGoals = this.getStanceSpecificGoals(technique.stance);\n goals.push(...stanceGoals);\n\n return goals.slice(0, 3); // Limit to 3 goals\n }\n\n /**\n * Get stance-specific training goals\n */\n private getStanceSpecificGoals(stance: TrigramStance): string[] {\n const stanceGoals: Record<TrigramStance, string[]> = {\n [TrigramStance.GEON]: [\n \"천괘 직선 공격 마스터 - Master Heaven's Direct Attacks\",\n ],\n [TrigramStance.TAE]: [\"태괘 유동성 향상 - Improve Lake's Fluidity\"],\n [TrigramStance.LI]: [\"리괘 정확성 훈련 - Train Fire's Precision\"],\n [TrigramStance.JIN]: [\"진괘 폭발력 개발 - Develop Thunder's Power\"],\n [TrigramStance.SON]: [\"손괘 연속성 연습 - Practice Wind's Continuity\"],\n [TrigramStance.GAM]: [\"감괘 적응력 향상 - Improve Water's Adaptability\"],\n [TrigramStance.GAN]: [\"간괘 방어력 강화 - Strengthen Mountain's Defense\"],\n [TrigramStance.GON]: [\"곤괘 제압력 훈련 - Train Earth's Control\"],\n };\n\n return stanceGoals[stance] || [\"기본 자세 완성 - Perfect Basic Stance\"];\n }\n\n /**\n * Get training statistics\n */\n getTrainingStats() {\n const overallAccuracy =\n this.accuracyHistory.length > 0\n ? this.accuracyHistory.reduce((a, b) => a + b, 0) /\n this.accuracyHistory.length\n : 0;\n\n const successRate =\n this.techniqueAttempts > 0\n ? this.successfulTechniques / this.techniqueAttempts\n : 0;\n\n return {\n totalAttempts: this.techniqueAttempts,\n successfulTechniques: this.successfulTechniques,\n successRate,\n overallAccuracy,\n recentAccuracy: this.accuracyHistory.slice(-5),\n improvementTrend: this.calculateImprovementTrend(),\n };\n }\n\n /**\n * Calculate improvement trend from recent accuracy scores\n */\n private calculateImprovementTrend(): \"improving\" | \"stable\" | \"declining\" {\n if (this.accuracyHistory.length < 5) return \"stable\";\n\n const recent = this.accuracyHistory.slice(-3);\n const earlier = this.accuracyHistory.slice(-6, -3);\n\n if (earlier.length === 0) return \"stable\";\n\n const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;\n const earlierAvg = earlier.reduce((a, b) => a + b, 0) / earlier.length;\n\n const diff = recentAvg - earlierAvg;\n\n if (diff > 0.1) return \"improving\";\n if (diff < -0.1) return \"declining\";\n return \"stable\";\n }\n\n /**\n * Reset training session\n */\n resetTrainingSession() {\n this.accuracyHistory = [];\n this.techniqueAttempts = 0;\n this.successfulTechniques = 0;\n this.trainingDummy = this.createTrainingDummy();\n }\n\n /**\n * Fix: Add the missing resetTrainingDummy method that tests expect\n */\n resetTrainingDummy() {\n this.trainingDummy = this.createTrainingDummy();\n }\n\n /**\n * Get training dummy for display\n */\n getTrainingDummy(): PlayerState {\n return this.trainingDummy;\n }\n\n /**\n * Update training dummy health (for visual feedback)\n */\n updateTrainingDummy(updates: Partial<PlayerState>) {\n this.trainingDummy = {\n ...this.trainingDummy,\n ...updates,\n // Always keep high health for training\n health: Math.max(this.trainingDummy.health, 900),\n };\n }\n}\n\nexport default TrainingCombatSystem;\n"],"mappings":"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"}