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.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanCulture.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingScreen3D.js","names":[],"sources":["../../../../src/components/screens/training/TrainingScreen3D.tsx"],"sourcesContent":["/**\n * TrainingScreen3D - Three.js-based training screen\n *\n * Refactored to use consolidated hooks matching CombatScreen architecture.\n * Provides 3D training dummy with vital point targeting and UI overlays.\n *\n * UI Rendering: All HUD elements are rendered in an absolute-positioned div\n * OUTSIDE the Canvas, matching CombatScreen's reliable rendering pattern.\n * This eliminates the need for Html overlays inside Three.js and ensures\n * HUDs appear immediately without waiting for Canvas initialization.\n *\n * Architecture (Consolidated in PR #1394 + Issue #1398):\n * - TrainingLeftHUD: Anatomy controls, guard indicator\n * - TrainingRightHUD: Training stats, mode selector, vital point selection\n * - TrainingTopHUD: Training controls, archetype selector, return button\n * - TrainingBottomHUD: Technique bar, feedback messages, mobile controls\n * - VitalPointOverlayControlsPure: Vital point overlay controls (pure DOM)\n *\n * All UI components render as pure DOM in the HUD overlay div (lines 1230+).\n * NO Html components from @react-three/drei are used inside the Canvas.\n * This ensures clean separation of 3D rendering and UI layers.\n *\n * @korean 훈련화면3D - 훈련 상태 훅을 사용한 리팩토링된 3D 훈련 화면\n */\n\n// UI renders outside Canvas in absolute-positioned div - no Html needed\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport { AccelerationUpdater } from \"../../../systems/movement/helpers/AccelerationUpdater\";\nimport {\n isRunningSpeed,\n STEP_DISTANCE_THRESHOLDS,\n} from \"../../../systems/movement/helpers/accelerationUtils\";\nimport {\n Bloom,\n EffectComposer,\n Noise,\n Vignette,\n} from \"@react-three/postprocessing\";\nimport * as THREE from \"three\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useCombatAudio } from \"../../screens/combat/hooks/useCombatAudio\";\nimport { getArchetypePhysicalAttributes } from \"../../../data/archetypePhysicalAttributes\";\nimport { usePlayerAnimation } from \"../../../hooks/usePlayerAnimation\";\nimport { useTechniqueSelection } from \"../../../hooks/useTechniqueSelection\";\nimport { GestureEvent } from \"../../../hooks/useTouchControls\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { PlayerState } from \"../../../systems\";\nimport {\n AnimationEvents,\n AnimationState,\n AnimationType,\n resolveTechniqueAnimation,\n} from \"../../../systems/animation\";\nimport { getAnimationForTechniqueOrDefault } from \"../../../systems/animation/core/TechniqueAnimationMapping\";\nimport { physicalReachCalculator } from \"../../../systems/physics\";\nimport {\n MovementType,\n SpeedModifierSystem,\n} from \"../../../systems/physics/SpeedModifierSystem\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../systems/trigram/types\";\nimport {\n CombatState,\n PlayerArchetype,\n Position,\n Technique,\n TrigramStance,\n} from \"../../../types\";\nimport { getPerformanceSettings } from \"../../../types/constants\";\nimport { getMobileControlsBottom } from \"../../../types/constants/layout\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { DEFAULT_BODY_RADIUS_METERS } from \"../../../types/physicsConstants\";\nimport { usePlayerMovement } from \"../../../utils/inputSystem\";\nimport { calculateDistance3D } from \"../../../utils/math\";\nimport { createCameraConfig } from \"../../../utils/sharedPhysicsConfig\";\nimport {\n animationStateToPlayerAnimation,\n convertPlayerStateToProps,\n} from \"../../../utils/player3DHelpers\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport {\n GestureRecognizerPure,\n StanceWheelPure,\n} from \"../../shared/mobile\";\nimport {\n MobileControlsOverlay,\n type ButtonEventType,\n type Direction,\n type DPadEventType,\n} from \"../../shared/mobile/MobileControlsPure\";\nimport {\n Player3DWithTransitions,\n VitalPointMarkers3D,\n type BodyRegionFilter,\n} from \"../../shared/three\";\nimport { StanceChangeIndicator } from \"../../shared/three/indicators/StanceChangeIndicator\";\nimport { CombatArena3D } from \"../../shared/three/scene/CombatArena3D\";\nimport { VitalPointOverlayControlsPure } from \"../../shared/ui/VitalPointOverlayControlsPure\";\nimport AnatomyOverlay3D, {\n type AnatomyLayer,\n} from \"./components/AnatomyOverlay3D\";\nimport FootPlacementMarkers3D from \"./components/FootPlacementMarkers3D\";\nimport HitFeedbackEffect3D from \"./components/HitFeedbackEffect3D\";\nimport type { DifficultyMode } from \"./components/TrainingDummy3D\";\nimport TrainingDummy3D from \"./components/TrainingDummy3D\";\n// HUD Components - Organized UI layout\nimport {\n TrainingBottomHUD,\n TrainingLeftHUD,\n TrainingRightHUD,\n TrainingTopHUD,\n} from \"./components/hud\";\n// Attack movement hook for player forward momentum\nimport { useAttackMovement } from \"./hooks/useAttackMovement\";\nimport useTrainingActions from \"./hooks/useTrainingActions\";\nimport { useTrainingLayout } from \"./hooks/useTrainingLayout\";\nimport useTrainingState from \"./hooks/useTrainingState\";\n\n/**\n * AnimationUpdater - Component that updates player animation at 60fps\n *\n * @korean 훈련애니메이션업데이터 - 60fps로 플레이어 애니메이션을 업데이트하는 컴포넌트\n */\ninterface TrainingAnimationUpdaterProps {\n readonly playerAnimation: ReturnType<typeof usePlayerAnimation>;\n}\n\nconst TrainingAnimationUpdater: React.FC<TrainingAnimationUpdaterProps> = ({\n playerAnimation,\n}) => {\n useFrame((_state, delta) => {\n playerAnimation.update(delta);\n });\n\n return null;\n};\n\n/**\n * Props for the TrainingScreen3D component\n */\nexport interface TrainingScreen3DProps {\n /** Callback to update player state */\n readonly onPlayerUpdate: (updates: Partial<PlayerState>) => void;\n /** Callback when returning to menu */\n readonly onReturnToMenu: () => void;\n /** Canvas width in pixels. Defaults to 1200 */\n readonly width?: number;\n /** Canvas height in pixels. Defaults to 800 */\n readonly height?: number;\n /** Initial archetype from IntroScreen selection. Defaults to MUSA */\n readonly initialArchetype?: PlayerArchetype;\n}\n\n/**\n * TrainingScreen3D Component\n * Three.js-based training screen with 3D dummy and Html UI\n *\n * Uses consolidated hooks for state management matching CombatScreen architecture.\n */\nexport const TrainingScreen3D: React.FC<TrainingScreen3DProps> = ({\n onPlayerUpdate,\n onReturnToMenu,\n width = 1200,\n height = 800,\n initialArchetype = PlayerArchetype.MUSA,\n}) => {\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 1: Core State Management (Hooks)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // UI overlays now render outside Canvas in absolute-positioned div\n // This matches CombatScreen pattern for reliable, immediate rendering\n // No mount delay needed - UI is not dependent on Three.js render loop\n\n // Consolidated training state management (matches useCombatState pattern)\n const { state: trainingState, actions: trainingActions } = useTrainingState();\n\n // Audio context\n const audio = useAudio();\n \n // Combat audio for bone impact sounds\n const { playBoneImpactSound, playAttackSound, playStanceChangeSound } =\n useCombatAudio();\n\n // Responsive detection and layout (using dedicated training layout hook)\n const { trainingAreaBounds, isMobile, isPortrait, screenSize } =\n useTrainingLayout(width, height);\n\n // Use Korean theme hook for consistent theming\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n // Screen size scaling for 4K and large displays\n // Uses SPACING_SCALE_MAP values: mobile=0.5, tablet=0.75, desktop=1.0, large=1.25, xlarge=1.5\n const positionScale = React.useMemo(() => {\n if (isMobile) {\n return 1.0;\n }\n\n switch (screenSize) {\n case \"mobile\":\n return 1.0; // Mobile already has special handling\n case \"tablet\":\n return 1.0;\n case \"desktop\":\n return 1.0;\n case \"large\":\n return 1.25;\n case \"xlarge\":\n return 1.5; // 4K displays need 1.5x offsets\n default:\n return 1.0;\n }\n }, [isMobile, screenSize]);\n\n // Training difficulty and vital point configuration\n const difficulty: DifficultyMode = \"normal\";\n const vitalPointCount = 70; // Show all 70 vital points\n\n // Archetype selection for training (allows testing different body types)\n // 원형 선택 - 다양한 체형 테스트 가능\n // Uses initialArchetype from IntroScreen selection, can be changed locally\n const [selectedArchetype, setSelectedArchetype] =\n React.useState<PlayerArchetype>(initialArchetype);\n\n // Vital point overlay state\n const [overlayVisible, setOverlayVisible] = React.useState(false);\n const [severityFilters, setSeverityFilters] = React.useState<\n import(\"../../../types/common\").VitalPointSeverity[]\n >([]);\n const [regionFilter, setRegionFilter] =\n React.useState<BodyRegionFilter>(\"all\");\n const [searchQuery, setSearchQuery] = React.useState(\"\");\n const [showLabels, setShowLabels] = React.useState(true);\n const [animated, setAnimated] = React.useState(true);\n // Use combat-consistent scale (1.2) for better visibility across screens\n const [scale, setScale] = React.useState(1.2);\n\n\n // Track current attack animation for technique-specific animations\n // 기술별 애니메이션을 위한 현재 공격 애니메이션 추적\n const [attackAnimation, setAttackAnimation] = React.useState<\n string | undefined\n >(undefined);\n\n // Keyboard shortcut for toggling overlay (V key only - Ctrl removed)\n React.useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"v\" || e.key === \"V\") {\n setOverlayVisible((prev) => !prev);\n audio.playSFX(\"menu_select\");\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [audio]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 2: WebGL Context Management\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Track context loss for recovery\n const contextLossCountRef = useRef(0);\n\n // Handle WebGL context loss and restoration (for 3D scene only)\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in TrainingScreen\");\n contextLossCountRef.current += 1;\n },\n onContextRestored: () => {\n console.log(\"✓ WebGL context restored in TrainingScreen\");\n },\n autoRestore: true,\n });\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 2B: Speed Modifier System (matching CombatScreen pattern)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Speed Modifier System for dynamic movement speed calculations\n const speedModifierSystem = useMemo(() => new SpeedModifierSystem(), []);\n\n // Track speed modifiers for movement (simplified for training - no injuries)\n // Updated dynamically based on acceleration-based running\n const [speedModifiers, setSpeedModifiers] = useState({\n finalSpeed: 6.0, // BASE_WALK_SPEED (6.0 m/s for responsive combat)\n baseSpeed: 6.0,\n finalAcceleration: 12.0, // BASE_ACCELERATION (12.0 m/s² for quick response)\n });\n\n // Track walk/run speeds for acceleration interpolation (archetype-aware)\n const [walkRunSpeeds, setWalkRunSpeeds] = useState({\n walkSpeed: 6.0,\n runSpeed: 10.0,\n });\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 3: Movement & Position Management\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Initial player position in pixel space (left side of arena, centered vertically)\n // Physics-first: initial position in METERS (relative to arena center)\n // 0% from center (centered laterally) creates ~1.2m distance to dummy\n // This allows most kicks to land immediately, punches require 1-2 steps (realistic)\n const initialPositionMeters = useMemo<Position>(\n () => ({\n x: trainingAreaBounds.worldWidthMeters * 0.0, // Centered laterally\n y: 0, // Centered vertically\n }),\n [trainingAreaBounds],\n );\n\n // CRITICAL FIX: Memoize onPositionChange to prevent usePlayerMovement callback recreation\n // Without this, a new function is created every render, causing animation frame cancellation\n const handlePositionChange = useCallback(\n (newPosition: Position) => {\n onPlayerUpdate({ position: newPosition });\n },\n [onPlayerUpdate],\n );\n\n // CRITICAL FIX: Memoize bounds object to prevent usePlayerMovement callback recreation\n // Without this, a new object reference is created every render, causing animation frame cancellation\n const movementBounds = useMemo(\n () => ({\n worldWidthMeters: trainingAreaBounds.worldWidthMeters,\n worldDepthMeters: trainingAreaBounds.worldDepthMeters,\n }),\n [trainingAreaBounds.worldWidthMeters, trainingAreaBounds.worldDepthMeters],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 3A: Acceleration-Based Running System\n // ═══════════════════════════════════════════════════════════════════════════\n \n // Track continuous movement time for acceleration-based running\n // 가속 기반 달리기를 위한 연속 이동 시간 추적\n const movementTimeRef = useRef(0);\n const lastDirectionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n \n // Track acceleration-based speed (interpolated between walk and run speeds)\n // This applies archetype speeds and stance modifiers\n const [accelerationBasedSpeed, setAccelerationBasedSpeed] = useState(\n walkRunSpeeds.walkSpeed\n );\n \n // Determine if currently running using utility function with archetype run speed\n const isRunning = isRunningSpeed(accelerationBasedSpeed, walkRunSpeeds.runSpeed);\n\n // Player movement with physics-based acceleration and stance modifiers\n // All positions are in METERS - no pixel conversions\n const { playerPosition, isMoving, velocity } = usePlayerMovement({\n enabled: true, // Always allow movement in training screen\n bounds: movementBounds, // Use memoized bounds object\n onPositionChange: handlePositionChange, // Use memoized callback\n initialPositionMeters,\n // Physics parameters for realistic training movement (always enabled)\n currentStance: TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n legInjuryFactor: 0, // No injury in training mode\n isRunning, // Use computed acceleration-based running state\n // Use interpolated speed between modifier-aware walk/run speeds\n // This preserves archetype speeds and stance modifiers\n maxSpeedOverride: accelerationBasedSpeed,\n accelerationOverride: speedModifiers.finalAcceleration,\n });\n\n // Physics-first: playerPosition is already in METERS (x = lateral, y = forward/backward)\n // Direct conversion to 3D world coordinates - no pixel math needed\n const player3DPosition = useMemo<[number, number, number]>(() => {\n // playerPosition.x is lateral position in meters (- = left, + = right)\n // playerPosition.y is forward/backward position in meters (- = toward camera, + = away)\n return [playerPosition.x, 0, playerPosition.y];\n }, [playerPosition]);\n\n // Dummy position in meters (right side, creating optimal training distance)\n // Positioned at 15% from center to give ~1.2-1.6m distance depending on archetype\n // Allows kicks to hit from starting position, punches with slight approach\n // Uses world dimensions for physics-consistent positioning\n const dummyPosition = useMemo<[number, number, number]>(\n () => [trainingAreaBounds.worldWidthMeters * 0.15, 0, 0],\n [trainingAreaBounds.worldWidthMeters],\n );\n\n // Calculate center-to-center distance to dummy in meters\n const centerToCenterDistance = useMemo(\n () => calculateDistance3D(player3DPosition, dummyPosition),\n [player3DPosition, dummyPosition],\n );\n\n // Calculate effective distance (adjusted for body radius)\n // Attacks hit the body surface, not the center point\n // Training dummy uses DEFAULT_BODY_RADIUS_METERS since it has no archetype\n // For combat between players, use calculateBodyRadius(targetPhysicalAttributes)\n // 실제 타격거리 = 중심간거리 - 목표체 반경\n const distanceToDummy = useMemo(\n () => Math.max(0, centerToCenterDistance - DEFAULT_BODY_RADIUS_METERS),\n [centerToCenterDistance],\n );\n\n // Track last facing rotation for when movement stops\n const lastFacingRotationRef = useRef<number>(0);\n\n // Calculate rotation: face movement direction when moving, face dummy when idle\n // 이동 중에는 이동 방향을, 정지 시에는 더미를 향함\n const playerRotation = useMemo(() => {\n if (isMoving && velocity && (velocity.x !== 0 || velocity.y !== 0)) {\n // When moving: face the direction of movement\n // velocity.x is lateral (left/right), velocity.y is forward/backward (Z in 3D)\n // Use velocity.y directly (not negated) so down arrow faces correctly\n return Math.atan2(velocity.x, velocity.y);\n } else {\n // When idle: face the dummy (target)\n const dx = dummyPosition[0] - player3DPosition[0];\n const dz = dummyPosition[2] - player3DPosition[2];\n return Math.atan2(dx, dz);\n }\n }, [isMoving, velocity, player3DPosition, dummyPosition]);\n\n // Update ref in effect to avoid updating during render\n useEffect(() => {\n lastFacingRotationRef.current = playerRotation;\n }, [playerRotation]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 3B: Foot Laterality Alternation (발바닥 교대)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Track current laterality (which foot is forward)\n // Alternates with each step during walking/running for realistic footwork\n // 왼발서기 (left) ↔ 오른발서기 (right)\n const [currentLaterality, setCurrentLaterality] = useState<\"left\" | \"right\">(\"right\");\n \n // Track step counter for alternating feet\n const stepCounterRef = useRef(0);\n const lastPositionRef = useRef<Position>(playerPosition);\n \n // Alternate laterality based on movement distance\n useEffect(() => {\n if (!isMoving) {\n // Reset step counter when not moving and sync last position\n stepCounterRef.current = 0;\n lastPositionRef.current = playerPosition;\n return;\n }\n\n // Calculate distance traveled since last check\n const dx = playerPosition.x - lastPositionRef.current.x;\n const dy = playerPosition.y - lastPositionRef.current.y;\n const distanceMoved = Math.sqrt(dx * dx + dy * dy);\n \n // Accumulate distance into step counter\n const stepThreshold = isRunning \n ? STEP_DISTANCE_THRESHOLDS.RUN \n : STEP_DISTANCE_THRESHOLDS.WALK;\n stepCounterRef.current += distanceMoved;\n \n // Determine how many step thresholds were crossed in this update\n const stepsCrossed = Math.floor(stepCounterRef.current / stepThreshold);\n if (stepsCrossed > 0) {\n // Net laterality change depends on whether the number of steps is odd or even\n // Odd steps = toggle once, even steps = no net change\n if (stepsCrossed % 2 === 1) {\n setCurrentLaterality(prev => prev === \"right\" ? \"left\" : \"right\");\n }\n // Preserve remainder distance after accounting for full steps\n stepCounterRef.current -= stepsCrossed * stepThreshold;\n }\n \n // Update last position\n lastPositionRef.current = playerPosition;\n }, [playerPosition, isMoving, isRunning]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 4: Player Animation State Machine\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Reference for pending attack (executed at animation frame 6)\n // Includes animationType and startTime for distance-based hit detection\n // matching CombatSystem behavior\n const pendingAttackRef = useRef<{\n accuracy: number;\n vitalPoint: string;\n animationType?: AnimationType;\n startTime?: number;\n techniqueId?: string;\n } | null>(null);\n\n // Forward ref for handleDummyHit (defined in actions hook)\n const handleDummyHitRef = useRef<\n (\n vitalPointId: string,\n attackContext?: {\n animationType?: AnimationType;\n techniqueId?: string;\n },\n ) => boolean\n >(() => false);\n\n // Ref for playerAnimation to avoid circular dependencies in animation events\n const playerAnimationRef = useRef<ReturnType<\n typeof usePlayerAnimation\n > | null>(null);\n\n // Player animation events (matches CombatScreen pattern)\n const playerAnimationEvents = useMemo<AnimationEvents>(\n () => ({\n onFrame: (frame, state) => {\n // Execute attack at midpoint of animation (frame 6 of 12)\n if (state === \"attack\" && frame === 6 && pendingAttackRef.current) {\n const attackData = pendingAttackRef.current;\n // Pass attack context to handleDummyHit before clearing the ref\n // This ensures animationType and techniqueId are available for reach calculation\n handleDummyHitRef.current(attackData.vitalPoint, {\n animationType: attackData.animationType,\n techniqueId: attackData.techniqueId,\n });\n pendingAttackRef.current = null;\n }\n },\n onAnimationComplete: (state) => {\n if (state === \"stance_change\") {\n // Stance change animation completed - transition to stance guard\n // 자세 변경 완료 - 자세 가드로 전환\n playStanceChangeSound();\n const currentStance =\n TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex];\n if (currentStance && playerAnimationRef.current) {\n playerAnimationRef.current.transitionToStanceGuard(currentStance);\n }\n }\n },\n }),\n [playStanceChangeSound, trainingState.currentStanceIndex],\n );\n\n const playerAnimation = usePlayerAnimation({\n events: playerAnimationEvents,\n });\n\n // Store animation ref for use in event callbacks\n useEffect(() => {\n playerAnimationRef.current = playerAnimation;\n }, [playerAnimation]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 5: Training Actions (Hook-based)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Get current stance for animation transitions (needed before useTrainingActions)\n // 현재 자세 (애니메이션 전환용)\n const currentStance = useMemo(\n () => TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n [trainingState.currentStanceIndex],\n );\n\n // Track previous stance for visual feedback (StanceChangeIndicator)\n // 이전 자세 추적 - 자세 변경 표시기 시각적 피드백용\n const [previousStanceIndex, setPreviousStanceIndex] = useState<number>(0);\n\n // Ref to track current technique's animation type (updated by technique selection)\n // This allows useTrainingActions to access the current technique's animation type\n // without creating circular dependencies\n const currentTechniqueAnimationTypeRef = useRef<AnimationType>(\n AnimationType.JAB,\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 7: Training Player State (Visual Display)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Training player state for visualization\n const trainingPlayerState = useMemo<PlayerState>(() => {\n return {\n id: \"training-player\",\n name: { korean: \"훈련생\", english: \"Trainee\" },\n archetype: selectedArchetype,\n health: 100,\n maxHealth: 100,\n ki: 100,\n maxKi: 100,\n stamina: 100,\n maxStamina: 100,\n energy: 100,\n maxEnergy: 100,\n attackPower: 10,\n defense: 10,\n speed: 10,\n technique: 10,\n pain: 0,\n consciousness: 100,\n balance: 100,\n momentum: 0,\n currentStance: TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n combatState: CombatState.IDLE,\n position: playerPosition,\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: trainingState.stats.hits,\n perfectStrikes: trainingState.perfectStrikes,\n vitalPointHits: 0,\n misses: trainingState.stats.misses,\n accuracy: trainingState.stats.accuracy,\n comboCount: trainingState.stats.combo,\n };\n }, [playerPosition, trainingState, selectedArchetype]);\n\n // Calculate speed modifiers when player state changes\n // Updates at 5Hz (every 200ms) matching CombatScreen pattern\n // Get both walk and run speeds for acceleration interpolation\n useEffect(() => {\n const updateSpeedModifiers = () => {\n // Calculate modifiers for both walking and running to get archetype-aware speeds\n const walkModifiers = speedModifierSystem.calculateSpeedModifiers(\n trainingPlayerState,\n MovementType.WALKING,\n false, // isCrouching\n );\n\n const runModifiers = speedModifierSystem.calculateSpeedModifiers(\n trainingPlayerState,\n MovementType.RUNNING,\n false, // isCrouching\n );\n\n setSpeedModifiers({\n finalSpeed: walkModifiers.finalSpeed,\n baseSpeed: walkModifiers.baseSpeed,\n finalAcceleration: walkModifiers.finalAcceleration,\n });\n\n // Store walk/run speeds for acceleration interpolation\n // These account for archetype speeds and stance modifiers\n setWalkRunSpeeds({\n walkSpeed: walkModifiers.finalSpeed,\n runSpeed: runModifiers.finalSpeed,\n });\n };\n\n // Initial calculation\n updateSpeedModifiers();\n\n // Update every 200ms (5Hz) for responsive feedback without excessive re-renders\n const intervalId = setInterval(updateSpeedModifiers, 200);\n\n return () => clearInterval(intervalId);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [trainingPlayerState]); // speedModifierSystem is memoized and never changes\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 5: Player Attack Movement (Forward Momentum)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Determine if player is currently attacking based on animation state\n const isPlayerAttacking = useMemo(\n () => playerAnimation?.currentState === \"attack\",\n [playerAnimation],\n );\n\n // Calculate attack direction (toward dummy)\n // Note: Direction is calculated on every position change to ensure attacks\n // always target the current dummy position, even if the player is moving.\n // This is intentional for responsive gameplay where attacks can be initiated mid-movement.\n const attackDirection = useMemo(() => {\n // Only calculate if attacking to avoid unnecessary work\n if (!isPlayerAttacking) {\n return new THREE.Vector3(0, 0, 1); // Default forward direction\n }\n const dx = dummyPosition[0] - player3DPosition[0];\n const dz = dummyPosition[2] - player3DPosition[2];\n return new THREE.Vector3(dx, 0, dz).normalize();\n }, [dummyPosition, player3DPosition, isPlayerAttacking]);\n\n // Apply attack movement physics to player position\n // Note: currentTechniqueAnimationTypeRef.current is intentionally a ref to avoid\n // unnecessary re-renders. The animation type is read at attack start (in useAttackMovement's\n // internal effect) and doesn't need to be reactive. It's always set before isPlayerAttacking\n // becomes true via the handleAttack action.\n const {\n currentPosition: player3DPositionWithAttackMovement,\n } = useAttackMovement({\n isAttacking: isPlayerAttacking,\n // eslint-disable-next-line react-hooks/refs -- ref value is set synchronously before isAttacking becomes true; hook only reads this at attack start\n animationType: currentTechniqueAnimationTypeRef.current,\n currentStance: trainingPlayerState.currentStance,\n basePosition: player3DPosition,\n attackDirection,\n animationDuration: 0.4,\n });\n\n // Use position with attack movement for rendering\n const finalPlayer3DPosition = isPlayerAttacking\n ? player3DPositionWithAttackMovement\n : player3DPosition;\n\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Ref to store handleAttack for use in useTechniqueSelection callback\n // This breaks circular dependency between useTechniqueSelection and useTrainingActions\n const handleAttackRef = useRef<(() => void) | null>(null);\n\n // Technique selection and execution for training\n // Moved before useTrainingActions to provide selectedTechniqueId\n const techniqueSelection = useTechniqueSelection({\n player: trainingPlayerState,\n enabled: trainingState.isTraining,\n onTechniqueExecute: useCallback(\n (technique: Technique) => {\n // Show technique usage feedback\n trainingActions.setFeedback(\n `${technique.name.korean} 사용! | Used ${technique.name.english}!`,\n );\n\n // Set attack animation based on technique\n // 기술에 따른 공격 애니메이션 설정\n // Uses resolveTechniqueAnimation so stance-specific animations\n // (e.g. \"geon_heaven_strike\", \"li_precision_jab\") are selected,\n // instead of every technique collapsing to the generic \"jab\" visual.\n const animationName = resolveTechniqueAnimation(technique);\n setAttackAnimation(animationName);\n\n // In training mode, do not deduct resources to allow continuous practice\n // Resources are displayed for educational purposes only\n\n // Execute attack with technique (visual feedback)\n // Use ref to avoid circular dependency\n handleAttackRef.current?.();\n },\n [trainingActions],\n ),\n });\n\n // Derive selected technique ID for intensity-based attack sounds\n const selectedTechniqueId = useMemo(() => {\n const techniques = techniqueSelection.availableTechniques;\n const selectedIdx = techniqueSelection.selectedIndex;\n if (techniques.length === 0 || selectedIdx < 0 || selectedIdx >= techniques.length) {\n return undefined;\n }\n return techniques[selectedIdx]?.id;\n }, [techniqueSelection.availableTechniques, techniqueSelection.selectedIndex]);\n\n // Training actions hook (matches useCombatActions pattern)\n const {\n handleStartTraining,\n handleStopTraining,\n handleDummyHit,\n handleDummyDefeated,\n handleStanceChange,\n handleAttack,\n } = useTrainingActions({\n state: trainingState,\n actions: trainingActions,\n playerPosition,\n player3DPosition,\n dummyPosition,\n playerArchetype: selectedArchetype,\n playerStance: currentStance,\n currentTechniqueAnimationTypeRef, // Ref for technique's animation type\n audio,\n playBoneImpactSound, // Pass bone impact audio function from useCombatAudio\n playAttackSound, // Pass attack sound function from useCombatAudio\n selectedTechniqueId, // Pass selected technique ID for intensity-based attack sounds\n onPlayerUpdate: (updates) => {\n onPlayerUpdate(updates);\n },\n playerAnimation: {\n transitionTo: playerAnimation.transitionTo,\n transitionToStanceGuard: playerAnimation.transitionToStanceGuard,\n currentState: playerAnimation.currentState,\n },\n pendingAttackRef, // Share the ref with animation events\n });\n\n // Update handleAttack ref for useTechniqueSelection callback\n useEffect(() => {\n handleAttackRef.current = handleAttack;\n }, [handleAttack]);\n\n // Update the ref so animation events can call handleDummyHit\n useEffect(() => {\n handleDummyHitRef.current = handleDummyHit;\n }, [handleDummyHit]);\n\n // Wrapped stance change handler with visual feedback tracking\n // 시각적 피드백 추적을 포함한 자세 변경 핸들러 래퍼\n const handleStanceChangeWithVisualFeedback = useCallback(\n (stanceIndex: number) => {\n // Capture previous stance before the change for visual indicator\n setPreviousStanceIndex(trainingState.currentStanceIndex);\n // Execute the actual stance change\n handleStanceChange(stanceIndex);\n },\n [handleStanceChange, trainingState.currentStanceIndex],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 6: Movement-Animation Synchronization\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Sync movement with animation (matches CombatScreen pattern)\n const prevIsMovingRef = useRef<boolean>(isMoving);\n const prevIsRunningRef = useRef<boolean>(isRunning);\n const prevStanceRef = useRef<TrigramStance>(currentStance);\n \n useEffect(() => {\n const isMovingChanged = prevIsMovingRef.current !== isMoving;\n const isRunningChanged = prevIsRunningRef.current !== isRunning;\n const stanceChanged = prevStanceRef.current !== currentStance;\n \n if (isMovingChanged || isRunningChanged) {\n if (isMoving) {\n // Transition to running or walking animation based on state\n if (isRunning) {\n playerAnimation.transitionTo(AnimationState.RUN);\n } else {\n playerAnimation.transitionTo(AnimationState.WALK);\n }\n } else if (playerAnimation.currentState === AnimationState.WALK || \n playerAnimation.currentState === AnimationState.RUN) {\n // When stopping movement, transition to stance-specific guard animation\n // 이동 중지 시 자세별 가드 애니메이션으로 전환\n playerAnimation.transitionToStanceGuard(currentStance);\n }\n prevIsMovingRef.current = isMoving;\n prevIsRunningRef.current = isRunning;\n }\n \n // Update idle/guard animation when stance changes\n if (stanceChanged && !isMoving) {\n // If idle, update to new stance guard\n if (playerAnimation.currentState === AnimationState.IDLE || \n playerAnimation.isInStanceGuard()) {\n playerAnimation.transitionToStanceGuard(currentStance);\n }\n prevStanceRef.current = currentStance;\n }\n }, [isMoving, isRunning, currentStance, playerAnimation]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 7B: Technique Selection System (Moved earlier - see before useTrainingActions)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Convert cooldowns to Map for TechniqueBar\n const cooldownsMap = useMemo(() => {\n const map = new Map<string, number>();\n techniqueSelection.activeCooldowns.forEach((cd) => {\n map.set(cd.techniqueId, cd.remaining);\n });\n return map;\n }, [techniqueSelection.activeCooldowns]);\n\n // Calculate effective reach based on selected technique (matches CombatSystem)\n // 선택된 기술에 따른 유효 사정거리 계산 (전투 시스템과 동일)\n const { currentTechniqueReach, currentAnimationType } = useMemo(() => {\n const techniques = techniqueSelection.availableTechniques;\n const selectedIdx = techniqueSelection.selectedIndex;\n if (techniques.length === 0) {\n return {\n currentTechniqueReach: 0.7,\n currentAnimationType: AnimationType.JAB,\n };\n }\n const currentTechnique =\n techniques[Math.min(selectedIdx, techniques.length - 1)];\n if (!currentTechnique) {\n return {\n currentTechniqueReach: 0.7,\n currentAnimationType: AnimationType.JAB,\n };\n }\n // Get animation type from technique ID\n const animConfig = getAnimationForTechniqueOrDefault(currentTechnique.id);\n // Calculate max reach using physical attributes and stance\n const physicalAttributes =\n getArchetypePhysicalAttributes(selectedArchetype);\n const reach = physicalReachCalculator.calculateMaxReach(\n physicalAttributes,\n animConfig.type,\n currentStance,\n );\n return {\n currentTechniqueReach: reach,\n currentAnimationType: animConfig.type,\n };\n }, [\n techniqueSelection.availableTechniques,\n techniqueSelection.selectedIndex,\n selectedArchetype,\n currentStance,\n ]);\n\n // Update the animation type ref in an effect (not during render)\n // 렌더링 중이 아닌 effect에서 ref 업데이트\n useEffect(() => {\n currentTechniqueAnimationTypeRef.current = currentAnimationType;\n }, [currentAnimationType]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 8: Mobile Touch Controls\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Reference for tracking active mobile movement key (prevents stuck keys)\n const activeMobileKeyRef = useRef<string | null>(null);\n\n // Enable mobile controls always in training (allow movement even before starting training)\n const mobileControlsEnabled = isMobile;\n\n // Mobile D-pad movement handler (matches CombatScreen implementation)\n const handleMobileMove = useCallback(\n (direction: Direction | null, eventType: DPadEventType) => {\n // Map D-pad directions to movement keys (WASD)\n const directionMap: Record<Direction, string> = {\n up: \"w\",\n \"up-right\": \"w\",\n right: \"d\",\n \"down-right\": \"s\",\n down: \"s\",\n \"down-left\": \"s\",\n left: \"a\",\n \"up-left\": \"w\",\n };\n\n if (eventType === \"start\" && direction) {\n // Release previous key if different (prevents stuck keys)\n if (\n activeMobileKeyRef.current &&\n activeMobileKeyRef.current !== directionMap[direction]\n ) {\n const prevKey = activeMobileKeyRef.current;\n window.dispatchEvent(\n new KeyboardEvent(\"keyup\", {\n key: prevKey,\n code: `Key${prevKey.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n }\n\n // Press new key with proper keyboard event properties\n const key = directionMap[direction];\n activeMobileKeyRef.current = key;\n window.dispatchEvent(\n new KeyboardEvent(\"keydown\", {\n key,\n code: `Key${key.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n } else if (eventType === \"end\") {\n // Release active key when D-pad released\n if (activeMobileKeyRef.current) {\n const key = activeMobileKeyRef.current;\n window.dispatchEvent(\n new KeyboardEvent(\"keyup\", {\n key,\n code: `Key${key.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n activeMobileKeyRef.current = null;\n }\n }\n },\n [],\n );\n\n // Mobile attack handler - uses the same handleAttack from training actions\n const handleMobileAttack = useCallback(() => {\n handleAttack();\n }, [handleAttack]);\n\n // Mobile block handler\n const handleMobileBlock = useCallback(\n (eventType: ButtonEventType) => {\n if (eventType === \"start\") {\n audio.playSFX(\"block\");\n }\n },\n [audio],\n );\n\n // Mobile gesture handler\n const handleMobileGesture = useCallback(\n (gesture: GestureEvent) => {\n switch (gesture.type) {\n case \"swipe-right\":\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"d\" }));\n break;\n case \"swipe-left\":\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"a\" }));\n break;\n case \"swipe-up\":\n if (trainingState.isTraining) {\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \" \" }));\n }\n break;\n case \"swipe-down\":\n trainingActions.resetDummy();\n break;\n case \"two-finger-tap\":\n trainingActions.setTrainingMode(\n trainingState.trainingMode === \"vital_point\"\n ? \"basics\"\n : \"vital_point\",\n );\n audio.playSFX(\"menu_select\");\n break;\n }\n },\n [trainingState, trainingActions, audio],\n );\n\n // Mobile stance change handler\n const handleMobileStanceChange = useCallback(\n (stanceIndex: number) => {\n handleStanceChangeWithVisualFeedback(stanceIndex);\n },\n [handleStanceChangeWithVisualFeedback],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 9: Keyboard Input Handling\n // ═══════════════════════════════════════════════════════════════════════════\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const key = event.key.toLowerCase();\n\n // ESC key - return to menu\n if (key === \"escape\") {\n onReturnToMenu();\n return;\n }\n\n // Handle stance changes (1-8) - always available for exploration\n if (key >= \"1\" && key <= \"8\") {\n const stanceIndex = parseInt(key) - 1;\n handleStanceChangeWithVisualFeedback(stanceIndex);\n event.preventDefault();\n return;\n }\n\n // Handle attacks (Space key) - always available for exploration\n if (key === \" \") {\n handleAttack();\n event.preventDefault();\n return;\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onReturnToMenu, handleStanceChangeWithVisualFeedback, handleAttack]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 10: Audio Lifecycle Management & Auto-Start Training\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Track if component has mounted to enable auto-start once\n const hasMountedRef = useRef(false);\n\n useEffect(() => {\n let audioStarted = false;\n\n const startMusic = async () => {\n try {\n // Start training music with a smooth 2s fade-in for better UX\n await audio.fadeIn(\"cyberpunk_fusion\", 2000);\n audioStarted = true;\n } catch (err) {\n console.warn(\"Failed to start training music:\", err);\n trainingActions.setFeedback(\n \"오디오 초기화 실패 | Audio initialization failed\",\n );\n }\n };\n\n void startMusic();\n\n return () => {\n if (audioStarted) {\n void audio\n .fadeOut(2000)\n .then(() => audio.stopMusic())\n .catch((err) => console.warn(\"Failed to stop training music:\", err));\n }\n };\n }, [audio, trainingActions]);\n\n // Auto-start training on mount (only once) - separate effect to avoid re-runs\n useEffect(() => {\n if (!hasMountedRef.current) {\n hasMountedRef.current = true;\n handleStartTraining();\n }\n }, [handleStartTraining]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 11: Feedback & Session Timer Effects\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Hide feedback after delay - 1500ms provides adequate time for bilingual text readability\n // IMPORTANT: We depend on BOTH showFeedback AND feedback message so the timer resets\n // when a new message arrives (even if showFeedback was already true)\n useEffect(() => {\n if (trainingState.showFeedback) {\n const timer = setTimeout(() => trainingActions.hideFeedback(), 1500);\n return () => clearTimeout(timer);\n }\n }, [trainingState.showFeedback, trainingState.feedback, trainingActions]);\n\n // Update session duration\n useEffect(() => {\n if (!trainingState.isTraining || !trainingState.sessionStartTime) return;\n\n const interval = setInterval(() => {\n trainingActions.updateSessionDuration(\n Math.floor((Date.now() - (trainingState.sessionStartTime ?? 0)) / 1000),\n );\n }, 1000);\n\n return () => clearInterval(interval);\n }, [\n trainingState.isTraining,\n trainingState.sessionStartTime,\n trainingActions,\n ]);\n\n // Auto-restart training when mode changes\n const prevTrainingModeRef = useRef<typeof trainingState.trainingMode>(\n trainingState.trainingMode,\n );\n const isFirstModeEffectRef = useRef<boolean>(true);\n const isTrainingRef = useRef<boolean>(trainingState.isTraining);\n const modeChangeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Keep a ref in sync with the latest training state for use inside timeouts\n useEffect(() => {\n isTrainingRef.current = trainingState.isTraining;\n }, [trainingState.isTraining]);\n\n // Store callbacks in refs to avoid effect re-runs when they change\n const handleStartTrainingRef = useRef(handleStartTraining);\n const handleStopTrainingRef = useRef(handleStopTraining);\n\n useEffect(() => {\n handleStartTrainingRef.current = handleStartTraining;\n handleStopTrainingRef.current = handleStopTraining;\n }, [handleStartTraining, handleStopTraining]);\n\n useEffect(() => {\n // Explicitly skip the first execution on initial mount\n if (isFirstModeEffectRef.current) {\n isFirstModeEffectRef.current = false;\n prevTrainingModeRef.current = trainingState.trainingMode;\n return;\n }\n\n const previousMode = prevTrainingModeRef.current;\n const modeChanged = previousMode !== trainingState.trainingMode;\n\n if (!modeChanged) {\n return;\n }\n\n // Update previous mode only when an actual change is detected\n prevTrainingModeRef.current = trainingState.trainingMode;\n\n // Clear any existing timer to prevent stale callbacks\n if (modeChangeTimerRef.current) {\n clearTimeout(modeChangeTimerRef.current);\n modeChangeTimerRef.current = null;\n }\n\n // Restart training on mode change (matches UI message \"Auto-restarts on mode change\")\n if (isTrainingRef.current) {\n handleStopTrainingRef.current();\n }\n\n // Small delay to allow state to settle, then (re)start training unconditionally\n modeChangeTimerRef.current = setTimeout(() => {\n handleStartTrainingRef.current();\n modeChangeTimerRef.current = null;\n }, 100);\n\n return () => {\n if (modeChangeTimerRef.current) {\n clearTimeout(modeChangeTimerRef.current);\n modeChangeTimerRef.current = null;\n }\n };\n }, [trainingState.trainingMode]); // Only depend on training mode to avoid unnecessary re-runs\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 12: Hit Effect Management\n // ═══════════════════════════════════════════════════════════════════════════\n\n const handleEffectComplete = useCallback(\n (effectId: number) => {\n trainingActions.removeHitEffect(effectId);\n },\n [trainingActions],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 13: Anatomy Layer Toggle\n // ═══════════════════════════════════════════════════════════════════════════\n\n const handleAnatomyLayerToggle = useCallback(\n (layer: AnatomyLayer) => {\n trainingActions.toggleAnatomyLayer(layer);\n audio.playSFX(\"menu_click\");\n },\n [trainingActions, audio],\n );\n\n const handleVitalPointClick = useCallback(\n (pointId: string) => {\n trainingActions.setSelectedVitalPoint(pointId);\n audio.playSFX(\"menu_select\");\n },\n [trainingActions, audio],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 14: Camera Configuration\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Use shared physics config for consistent camera setup across screens\n // Mobile: tighter FOV and closer camera for better framing\n // Desktop: wider FOV and further camera for full view\n // Portrait: pull camera back on Z and widen FOV so the dummy + both\n // side overlays fit in the narrow viewport.\n const cameraConfig = useMemo(() => {\n const base = createCameraConfig(isMobile);\n if (!isPortrait) return base;\n return {\n ...base,\n fov: Math.min(80, base.fov + 15),\n position: [base.position[0], base.position[1], base.position[2] + 4] as [\n number,\n number,\n number,\n ],\n };\n }, [isMobile, isPortrait]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 15: RENDER\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Performance settings based on device tier\n const performanceSettings = useMemo(() => {\n return getPerformanceSettings(width, isMobile);\n }, [width, isMobile]);\n\n // SSAO removed - was causing WebGL context loss without NormalPass\n\n return (\n <div\n style={{\n width: `${width}px`,\n height: `${height}px`,\n position: \"relative\",\n overflow: \"hidden\", // Prevent content from extending beyond container\n }}\n data-testid=\"training-screen-3d\"\n >\n <Canvas\n style={{ width: `${width}px`, height: `${height}px` }}\n gl={{\n antialias: performanceSettings.antialias,\n alpha: false,\n powerPreference: \"high-performance\",\n failIfMajorPerformanceCaveat: false, // Don't fail in software renderer\n preserveDrawingBuffer: true, // Help with context stability\n }}\n dpr={performanceSettings.dpr}\n shadows={false} // Temporarily disable shadows\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n // Disable fog temporarily for debugging\n }}\n camera={cameraConfig}\n >\n {/* Lighting - base lighting, arena provides additional */}\n <ambientLight intensity={0.6} />\n <directionalLight position={[10, 10, 5]} intensity={1.2} />\n\n {/* Combat Arena 3D Environment - uses physics-based world dimensions */}\n <CombatArena3D\n lighting=\"cyberpunk\"\n scale={trainingAreaBounds.scale}\n worldWidthMeters={trainingAreaBounds.worldWidthMeters}\n worldDepthMeters={trainingAreaBounds.worldDepthMeters}\n />\n\n {/* Animation updater - 60fps updates */}\n <TrainingAnimationUpdater playerAnimation={playerAnimation} />\n\n {/* Acceleration updater - tracks movement time and updates speed */}\n <AccelerationUpdater\n isMoving={isMoving}\n velocity={velocity}\n movementTimeRef={movementTimeRef}\n lastDirectionRef={lastDirectionRef}\n onSpeedUpdate={setAccelerationBasedSpeed}\n walkSpeed={walkRunSpeeds.walkSpeed}\n runSpeed={walkRunSpeeds.runSpeed}\n />\n\n {/* Training dummy at fixed position */}\n <TrainingDummy3D\n position={dummyPosition}\n selectedVitalPoint={trainingState.selectedVitalPoint}\n isTraining={trainingState.isTraining}\n health={trainingState.dummyHealth}\n onVitalPointHit={handleDummyHit}\n onDefeated={handleDummyDefeated}\n difficulty={difficulty}\n vitalPointCount={vitalPointCount}\n isMobile={isMobile}\n />\n\n {/* Anatomy overlay for educational visualization */}\n {trainingState.visibleAnatomyLayers.length > 0 && (\n <AnatomyOverlay3D\n position={dummyPosition}\n visibleLayers={trainingState.visibleAnatomyLayers}\n opacity={0.6}\n isMobile={isMobile}\n />\n )}\n\n {/* Vital Point Overlay - Show all 70 points on dummy */}\n {overlayVisible && (\n <VitalPointMarkers3D\n position={dummyPosition}\n visible={overlayVisible}\n severityFilter={severityFilters}\n regionFilter={regionFilter}\n searchQuery={searchQuery}\n showLabels={showLabels}\n scale={scale}\n animated={animated}\n selectedPoint={trainingState.selectedVitalPoint}\n onPointClick={handleVitalPointClick}\n />\n )}\n\n {/* Player model */}\n <Player3DWithTransitions\n {...convertPlayerStateToProps(\n trainingPlayerState,\n finalPlayer3DPosition,\n playerRotation,\n {\n isMobile,\n facing: \"right\",\n enableFacialExpressions: true,\n enableEyeTracking: true,\n opponentPosition: dummyPosition,\n },\n )}\n currentAnimation={animationStateToPlayerAnimation(\n playerAnimation.currentState,\n )}\n attackAnimation={attackAnimation}\n laterality={currentLaterality}\n enableTransitionEffects={!isMobile}\n enableStanceSymbol={true}\n enableStanceAudio={true}\n />\n\n {/* Foot Placement Markers for Footwork Drills */}\n {trainingState.trainingMode === \"footwork\" &&\n trainingState.footworkDrillActive && (\n <FootPlacementMarkers3D\n centerPosition={dummyPosition}\n pattern={\n trainingState.footworkDrillType === \"free_practice\"\n ? \"none\"\n : trainingState.footworkDrillType\n }\n currentStep={trainingState.footworkDrillStep}\n visible={true}\n scale={1.0}\n animated={true}\n />\n )}\n\n {/* Hit effects */}\n {trainingState.hitEffects.map((effect) => (\n <HitFeedbackEffect3D\n key={effect.id}\n position={effect.position}\n type={effect.type}\n damage={effect.damage}\n visible={effect.visible}\n onComplete={() => handleEffectComplete(effect.id)}\n isMobile={isMobile}\n />\n ))}\n\n {/* Stance Change Visual Indicator */}\n <StanceChangeIndicator\n currentStance={trainingState.currentStanceIndex}\n previousStance={previousStanceIndex}\n isMobile={isMobile}\n />\n\n {/* NOTE: Mobile controls moved OUTSIDE Canvas for reliable touch events */}\n {/* See MobileControlsPure component rendered after HUDs */}\n\n {/* Post-processing Effects - desktop high tier only for Android WebGL stability */}\n {performanceSettings.postProcessing && (\n <EffectComposer multisampling={4}>\n <Bloom\n luminanceThreshold={0.9}\n luminanceSmoothing={0.9}\n mipmapBlur\n intensity={0.8}\n radius={0.4}\n />\n <Noise opacity={0.03} />\n <Vignette eskil={false} offset={0.1} darkness={0.3} />\n </EffectComposer>\n )}\n </Canvas>\n\n {/* Html UI Overlays (positioned absolutely over Canvas) - matches CombatScreen pattern */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: Z_INDEX.HUD,\n // Use 'clip' for pure clipping without creating a scroll container\n // Note: Both 'clip' and 'hidden' will clip box/text shadows; ensure\n // any required shadow space is handled via padding on parent containers.\n overflow: \"clip\",\n }}\n data-testid=\"training-hud-overlay\"\n >\n {/* Left HUD - Anatomy Controls, Guard Indicator.\n Hidden on mobile because the side HUD occludes the compressed\n arena in both portrait and landscape. Anatomy layer toggles remain\n available on larger viewports where there is room for side panels. */}\n {!isMobile && (\n <TrainingLeftHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n visibleAnatomyLayers={trainingState.visibleAnatomyLayers}\n onAnatomyLayerToggle={handleAnatomyLayerToggle}\n currentStanceIndex={trainingState.currentStanceIndex}\n isInGuard={playerAnimation.isInStanceGuard()}\n />\n )}\n\n {/* Top HUD - Training Controls, Archetype Selector, Return Button. */}\n <TrainingTopHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n isTraining={trainingState.isTraining}\n onStartTraining={handleStartTraining}\n onStopTraining={handleStopTraining}\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={setSelectedArchetype}\n overlayVisible={overlayVisible}\n onReturnToMenu={onReturnToMenu}\n onPlaySFX={(sound) => audio.playSFX(sound)}\n />\n\n {/* Right HUD - Mode Selector, Stats, Vital Point Selection.\n Hidden on mobile to keep the training dojang visible and usable.\n The core start/stop, archetype, vital-point toggle, technique bar,\n stance wheel, gestures, and touch controls remain available. */}\n {!isMobile && (\n <TrainingRightHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n trainingMode={trainingState.trainingMode}\n onModeChange={trainingActions.setTrainingMode}\n stats={{\n ...trainingState.stats,\n sessionDuration: trainingState.sessionDuration,\n bestCombo: trainingState.bestCombo,\n perfectStrikes: trainingState.perfectStrikes,\n }}\n distanceToDummy={distanceToDummy}\n effectiveReach={currentTechniqueReach}\n selectedVitalPoint={trainingState.selectedVitalPoint}\n onVitalPointSelect={trainingActions.setSelectedVitalPoint}\n footworkDrillType={trainingState.footworkDrillType}\n footworkDrillStep={trainingState.footworkDrillStep}\n footworkDrillActive={trainingState.footworkDrillActive}\n onStartFootworkDrill={trainingActions.startFootworkDrill}\n onStopFootworkDrill={trainingActions.stopFootworkDrill}\n onAdvanceFootworkStep={trainingActions.advanceFootworkStep}\n />\n )}\n {/* Bottom HUD - Technique Bar, Feedback Messages, Mobile Controls */}\n <TrainingBottomHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n techniques={techniqueSelection.availableTechniques}\n player={trainingPlayerState}\n selectedIndex={techniqueSelection.selectedIndex}\n cooldowns={cooldownsMap}\n onTechniqueSelect={techniqueSelection.selectTechnique}\n showFeedback={trainingState.showFeedback}\n feedbackMessage={trainingState.feedback}\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={setSelectedArchetype}\n onPlaySFX={(sound) => audio.playSFX(sound)}\n />\n\n {/* Vital Point Overlay Controls - Pure DOM overlay (outside Canvas) */}\n {overlayVisible && (\n <VitalPointOverlayControlsPure\n visible={overlayVisible}\n onVisibleChange={setOverlayVisible}\n severityFilters={severityFilters}\n onSeverityFiltersChange={setSeverityFilters}\n regionFilter={regionFilter}\n onRegionFilterChange={setRegionFilter}\n searchQuery={searchQuery}\n onSearchQueryChange={setSearchQuery}\n showLabels={showLabels}\n onShowLabelsChange={setShowLabels}\n animated={animated}\n onAnimatedChange={setAnimated}\n scale={scale}\n onScaleChange={setScale}\n screenPosition={{ top: \"180px\", left: \"20px\" }}\n isMobile={isMobile}\n />\n )}\n\n {/* Mobile Controls - Pure DOM overlay (outside Canvas for reliable touch) */}\n {isMobile && (\n <>\n <MobileControlsOverlay\n onMove={handleMobileMove}\n onAttack={handleMobileAttack}\n onBlock={handleMobileBlock}\n disabled={!mobileControlsEnabled}\n bottom={getMobileControlsBottom(height)}\n opacity={0.85}\n viewportWidth={width}\n viewportHeight={height}\n />\n\n <StanceWheelPure\n currentStance={trainingState.currentStanceIndex}\n onStanceChange={handleMobileStanceChange}\n expanded={trainingState.stanceWheelExpanded}\n onToggle={trainingActions.toggleStanceWheel}\n disabled={!mobileControlsEnabled}\n opacity={0.8}\n />\n\n <GestureRecognizerPure\n onGesture={handleMobileGesture}\n enabled={mobileControlsEnabled}\n showFeedback={true}\n minSwipeDistance={50}\n />\n </>\n )}\n </div>\n </div>\n );\n};\n\nexport default TrainingScreen3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrainingScreen3D.js","names":[],"sources":["../../../../src/components/screens/training/TrainingScreen3D.tsx"],"sourcesContent":["/**\n * TrainingScreen3D - Three.js-based training screen\n *\n * Refactored to use consolidated hooks matching CombatScreen architecture.\n * Provides 3D training dummy with vital point targeting and UI overlays.\n *\n * UI Rendering: All HUD elements are rendered in an absolute-positioned div\n * OUTSIDE the Canvas, matching CombatScreen's reliable rendering pattern.\n * This eliminates the need for Html overlays inside Three.js and ensures\n * HUDs appear immediately without waiting for Canvas initialization.\n *\n * Architecture (Consolidated in PR #1394 + Issue #1398):\n * - TrainingLeftHUD: Anatomy controls, guard indicator\n * - TrainingRightHUD: Training stats, mode selector, vital point selection\n * - TrainingTopHUD: Training controls, archetype selector, return button\n * - TrainingBottomHUD: Technique bar, feedback messages, mobile controls\n * - VitalPointOverlayControlsPure: Vital point overlay controls (pure DOM)\n *\n * All UI components render as pure DOM in the HUD overlay div (lines 1230+).\n * NO Html components from @react-three/drei are used inside the Canvas.\n * This ensures clean separation of 3D rendering and UI layers.\n *\n * @korean 훈련화면3D - 훈련 상태 훅을 사용한 리팩토링된 3D 훈련 화면\n */\n\n// UI renders outside Canvas in absolute-positioned div - no Html needed\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport { AccelerationUpdater } from \"../../../systems/movement/helpers/AccelerationUpdater\";\nimport {\n isRunningSpeed,\n STEP_DISTANCE_THRESHOLDS,\n} from \"../../../systems/movement/helpers/accelerationUtils\";\nimport {\n Bloom,\n EffectComposer,\n Noise,\n Vignette,\n} from \"@react-three/postprocessing\";\nimport * as THREE from \"three\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useCombatAudio } from \"../../screens/combat/hooks/useCombatAudio\";\nimport { getArchetypePhysicalAttributes } from \"../../../data/archetypePhysicalAttributes\";\nimport { usePlayerAnimation } from \"../../../hooks/usePlayerAnimation\";\nimport { useTechniqueSelection } from \"../../../hooks/useTechniqueSelection\";\nimport { GestureEvent } from \"../../../hooks/useTouchControls\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { PlayerState } from \"../../../systems\";\nimport {\n AnimationEvents,\n AnimationState,\n AnimationType,\n resolveTechniqueAnimation,\n} from \"../../../systems/animation\";\nimport { getAnimationForTechniqueOrDefault } from \"../../../systems/animation/core/TechniqueAnimationMapping\";\nimport { physicalReachCalculator } from \"../../../systems/physics\";\nimport {\n MovementType,\n SpeedModifierSystem,\n} from \"../../../systems/physics/SpeedModifierSystem\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../systems/trigram/types\";\nimport {\n CombatState,\n PlayerArchetype,\n Position,\n Technique,\n TrigramStance,\n} from \"../../../types\";\nimport { getPerformanceSettings } from \"../../../types/constants\";\nimport { getMobileControlsBottom } from \"../../../types/constants/layout\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { DEFAULT_BODY_RADIUS_METERS } from \"../../../types/physicsConstants\";\nimport { usePlayerMovement } from \"../../../utils/inputSystem\";\nimport { calculateDistance3D } from \"../../../utils/math\";\nimport { createCameraConfig } from \"../../../utils/sharedPhysicsConfig\";\nimport {\n animationStateToPlayerAnimation,\n convertPlayerStateToProps,\n} from \"../../../utils/player3DHelpers\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport {\n GestureRecognizerPure,\n StanceWheelPure,\n} from \"../../shared/mobile\";\nimport {\n MobileControlsOverlay,\n type ButtonEventType,\n type Direction,\n type DPadEventType,\n} from \"../../shared/mobile/MobileControlsPure\";\nimport {\n Player3DWithTransitions,\n VitalPointMarkers3D,\n type BodyRegionFilter,\n} from \"../../shared/three\";\nimport { StanceChangeIndicator } from \"../../shared/three/indicators/StanceChangeIndicator\";\nimport { CombatArena3D } from \"../../shared/three/scene/CombatArena3D\";\nimport { VitalPointOverlayControlsPure } from \"../../shared/ui/VitalPointOverlayControlsPure\";\nimport AnatomyOverlay3D, {\n type AnatomyLayer,\n} from \"./components/AnatomyOverlay3D\";\nimport FootPlacementMarkers3D from \"./components/FootPlacementMarkers3D\";\nimport HitFeedbackEffect3D from \"./components/HitFeedbackEffect3D\";\nimport type { DifficultyMode } from \"./components/TrainingDummy3D\";\nimport TrainingDummy3D from \"./components/TrainingDummy3D\";\n// HUD Components - Organized UI layout\nimport {\n TrainingBottomHUD,\n TrainingLeftHUD,\n TrainingRightHUD,\n TrainingTopHUD,\n} from \"./components/hud\";\n// Attack movement hook for player forward momentum\nimport { useAttackMovement } from \"./hooks/useAttackMovement\";\nimport useTrainingActions from \"./hooks/useTrainingActions\";\nimport { useTrainingLayout } from \"./hooks/useTrainingLayout\";\nimport useTrainingState from \"./hooks/useTrainingState\";\n\n/**\n * AnimationUpdater - Component that updates player animation at 60fps\n *\n * @korean 훈련애니메이션업데이터 - 60fps로 플레이어 애니메이션을 업데이트하는 컴포넌트\n */\ninterface TrainingAnimationUpdaterProps {\n readonly playerAnimation: ReturnType<typeof usePlayerAnimation>;\n}\n\nconst TrainingAnimationUpdater: React.FC<TrainingAnimationUpdaterProps> = ({\n playerAnimation,\n}) => {\n useFrame((_state, delta) => {\n playerAnimation.update(delta);\n });\n\n return null;\n};\n\n/**\n * Props for the TrainingScreen3D component\n */\nexport interface TrainingScreen3DProps {\n /** Callback to update player state */\n readonly onPlayerUpdate: (updates: Partial<PlayerState>) => void;\n /** Callback when returning to menu */\n readonly onReturnToMenu: () => void;\n /** Canvas width in pixels. Defaults to 1200 */\n readonly width?: number;\n /** Canvas height in pixels. Defaults to 800 */\n readonly height?: number;\n /** Initial archetype from IntroScreen selection. Defaults to MUSA */\n readonly initialArchetype?: PlayerArchetype;\n}\n\n/**\n * TrainingScreen3D Component\n * Three.js-based training screen with 3D dummy and Html UI\n *\n * Uses consolidated hooks for state management matching CombatScreen architecture.\n */\nexport const TrainingScreen3D: React.FC<TrainingScreen3DProps> = ({\n onPlayerUpdate,\n onReturnToMenu,\n width = 1200,\n height = 800,\n initialArchetype = PlayerArchetype.MUSA,\n}) => {\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 1: Core State Management (Hooks)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // UI overlays now render outside Canvas in absolute-positioned div\n // This matches CombatScreen pattern for reliable, immediate rendering\n // No mount delay needed - UI is not dependent on Three.js render loop\n\n // Consolidated training state management (matches useCombatState pattern)\n const { state: trainingState, actions: trainingActions } = useTrainingState();\n\n // Audio context\n const audio = useAudio();\n \n // Combat audio for bone impact sounds\n const { playBoneImpactSound, playAttackSound, playStanceChangeSound } =\n useCombatAudio();\n\n // Responsive detection and layout (using dedicated training layout hook)\n const { trainingAreaBounds, isMobile, isPortrait, screenSize } =\n useTrainingLayout(width, height);\n\n // Use Korean theme hook for consistent theming\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n // Screen size scaling for 4K and large displays\n // Uses SPACING_SCALE_MAP values: mobile=0.5, tablet=0.75, desktop=1.0, large=1.25, xlarge=1.5\n const positionScale = React.useMemo(() => {\n if (isMobile) {\n return 1.0;\n }\n\n switch (screenSize) {\n case \"mobile\":\n return 1.0; // Mobile already has special handling\n case \"tablet\":\n return 1.0;\n case \"desktop\":\n return 1.0;\n case \"large\":\n return 1.25;\n case \"xlarge\":\n return 1.5; // 4K displays need 1.5x offsets\n default:\n return 1.0;\n }\n }, [isMobile, screenSize]);\n\n // Training difficulty and vital point configuration\n const difficulty: DifficultyMode = \"normal\";\n const vitalPointCount = 70; // Show all 70 vital points\n\n // Archetype selection for training (allows testing different body types)\n // 원형 선택 - 다양한 체형 테스트 가능\n // Uses initialArchetype from IntroScreen selection, can be changed locally\n const [selectedArchetype, setSelectedArchetype] =\n React.useState<PlayerArchetype>(initialArchetype);\n\n // Vital point overlay state\n const [overlayVisible, setOverlayVisible] = React.useState(false);\n const [severityFilters, setSeverityFilters] = React.useState<\n import(\"../../../types/common\").VitalPointSeverity[]\n >([]);\n const [regionFilter, setRegionFilter] =\n React.useState<BodyRegionFilter>(\"all\");\n const [searchQuery, setSearchQuery] = React.useState(\"\");\n const [showLabels, setShowLabels] = React.useState(true);\n const [animated, setAnimated] = React.useState(true);\n // Use combat-consistent scale (1.2) for better visibility across screens\n const [scale, setScale] = React.useState(1.2);\n\n\n // Track current attack animation for technique-specific animations\n // 기술별 애니메이션을 위한 현재 공격 애니메이션 추적\n const [attackAnimation, setAttackAnimation] = React.useState<\n string | undefined\n >(undefined);\n\n // Keyboard shortcut for toggling overlay (V key only - Ctrl removed)\n React.useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"v\" || e.key === \"V\") {\n setOverlayVisible((prev) => !prev);\n audio.playSFX(\"menu_select\");\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [audio]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 2: WebGL Context Management\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Track context loss for recovery\n const contextLossCountRef = useRef(0);\n\n // Handle WebGL context loss and restoration (for 3D scene only)\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in TrainingScreen\");\n contextLossCountRef.current += 1;\n },\n onContextRestored: () => {\n console.log(\"✓ WebGL context restored in TrainingScreen\");\n },\n autoRestore: true,\n });\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 2B: Speed Modifier System (matching CombatScreen pattern)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Speed Modifier System for dynamic movement speed calculations\n const speedModifierSystem = useMemo(() => new SpeedModifierSystem(), []);\n\n // Track speed modifiers for movement (simplified for training - no injuries)\n // Updated dynamically based on acceleration-based running\n const [speedModifiers, setSpeedModifiers] = useState({\n finalSpeed: 6.0, // BASE_WALK_SPEED (6.0 m/s for responsive combat)\n baseSpeed: 6.0,\n finalAcceleration: 12.0, // BASE_ACCELERATION (12.0 m/s² for quick response)\n });\n\n // Track walk/run speeds for acceleration interpolation (archetype-aware)\n const [walkRunSpeeds, setWalkRunSpeeds] = useState({\n walkSpeed: 6.0,\n runSpeed: 10.0,\n });\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 3: Movement & Position Management\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Initial player position in pixel space (left side of arena, centered vertically)\n // Physics-first: initial position in METERS (relative to arena center)\n // 0% from center (centered laterally) creates ~1.2m distance to dummy\n // This allows most kicks to land immediately, punches require 1-2 steps (realistic)\n const initialPositionMeters = useMemo<Position>(\n () => ({\n x: trainingAreaBounds.worldWidthMeters * 0.0, // Centered laterally\n y: 0, // Centered vertically\n }),\n [trainingAreaBounds],\n );\n\n // CRITICAL FIX: Memoize onPositionChange to prevent usePlayerMovement callback recreation\n // Without this, a new function is created every render, causing animation frame cancellation\n const handlePositionChange = useCallback(\n (newPosition: Position) => {\n onPlayerUpdate({ position: newPosition });\n },\n [onPlayerUpdate],\n );\n\n // CRITICAL FIX: Memoize bounds object to prevent usePlayerMovement callback recreation\n // Without this, a new object reference is created every render, causing animation frame cancellation\n const movementBounds = useMemo(\n () => ({\n worldWidthMeters: trainingAreaBounds.worldWidthMeters,\n worldDepthMeters: trainingAreaBounds.worldDepthMeters,\n }),\n [trainingAreaBounds.worldWidthMeters, trainingAreaBounds.worldDepthMeters],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 3A: Acceleration-Based Running System\n // ═══════════════════════════════════════════════════════════════════════════\n \n // Track continuous movement time for acceleration-based running\n // 가속 기반 달리기를 위한 연속 이동 시간 추적\n const movementTimeRef = useRef(0);\n const lastDirectionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n \n // Track acceleration-based speed (interpolated between walk and run speeds)\n // This applies archetype speeds and stance modifiers\n const [accelerationBasedSpeed, setAccelerationBasedSpeed] = useState(\n walkRunSpeeds.walkSpeed\n );\n \n // Determine if currently running using utility function with archetype run speed\n const isRunning = isRunningSpeed(accelerationBasedSpeed, walkRunSpeeds.runSpeed);\n\n // Player movement with physics-based acceleration and stance modifiers\n // All positions are in METERS - no pixel conversions\n const { playerPosition, isMoving, velocity } = usePlayerMovement({\n enabled: true, // Always allow movement in training screen\n bounds: movementBounds, // Use memoized bounds object\n onPositionChange: handlePositionChange, // Use memoized callback\n initialPositionMeters,\n // Physics parameters for realistic training movement (always enabled)\n currentStance: TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n legInjuryFactor: 0, // No injury in training mode\n isRunning, // Use computed acceleration-based running state\n // Use interpolated speed between modifier-aware walk/run speeds\n // This preserves archetype speeds and stance modifiers\n maxSpeedOverride: accelerationBasedSpeed,\n accelerationOverride: speedModifiers.finalAcceleration,\n });\n\n // Physics-first: playerPosition is already in METERS (x = lateral, y = forward/backward)\n // Direct conversion to 3D world coordinates - no pixel math needed\n const player3DPosition = useMemo<[number, number, number]>(() => {\n // playerPosition.x is lateral position in meters (- = left, + = right)\n // playerPosition.y is forward/backward position in meters (- = toward camera, + = away)\n return [playerPosition.x, 0, playerPosition.y];\n }, [playerPosition]);\n\n // Dummy position in meters (right side, creating optimal training distance)\n // Positioned at 15% from center to give ~1.2-1.6m distance depending on archetype\n // Allows kicks to hit from starting position, punches with slight approach\n // Uses world dimensions for physics-consistent positioning\n const dummyPosition = useMemo<[number, number, number]>(\n () => [trainingAreaBounds.worldWidthMeters * 0.15, 0, 0],\n [trainingAreaBounds.worldWidthMeters],\n );\n\n // Calculate center-to-center distance to dummy in meters\n const centerToCenterDistance = useMemo(\n () => calculateDistance3D(player3DPosition, dummyPosition),\n [player3DPosition, dummyPosition],\n );\n\n // Calculate effective distance (adjusted for body radius)\n // Attacks hit the body surface, not the center point\n // Training dummy uses DEFAULT_BODY_RADIUS_METERS since it has no archetype\n // For combat between players, use calculateBodyRadius(targetPhysicalAttributes)\n // 실제 타격거리 = 중심간거리 - 목표체 반경\n const distanceToDummy = useMemo(\n () => Math.max(0, centerToCenterDistance - DEFAULT_BODY_RADIUS_METERS),\n [centerToCenterDistance],\n );\n\n // Track last facing rotation for when movement stops\n const lastFacingRotationRef = useRef<number>(0);\n\n // Calculate rotation: face movement direction when moving, face dummy when idle\n // 이동 중에는 이동 방향을, 정지 시에는 더미를 향함\n const playerRotation = useMemo(() => {\n if (isMoving && velocity && (velocity.x !== 0 || velocity.y !== 0)) {\n // When moving: face the direction of movement\n // velocity.x is lateral (left/right), velocity.y is forward/backward (Z in 3D)\n // Use velocity.y directly (not negated) so down arrow faces correctly\n return Math.atan2(velocity.x, velocity.y);\n } else {\n // When idle: face the dummy (target)\n const dx = dummyPosition[0] - player3DPosition[0];\n const dz = dummyPosition[2] - player3DPosition[2];\n return Math.atan2(dx, dz);\n }\n }, [isMoving, velocity, player3DPosition, dummyPosition]);\n\n // Update ref in effect to avoid updating during render\n useEffect(() => {\n lastFacingRotationRef.current = playerRotation;\n }, [playerRotation]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 3B: Foot Laterality Alternation (발바닥 교대)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Track current laterality (which foot is forward)\n // Alternates with each step during walking/running for realistic footwork\n // 왼발서기 (left) ↔ 오른발서기 (right)\n const [currentLaterality, setCurrentLaterality] = useState<\"left\" | \"right\">(\"right\");\n \n // Track step counter for alternating feet\n const stepCounterRef = useRef(0);\n const lastPositionRef = useRef<Position>(playerPosition);\n \n // Alternate laterality based on movement distance\n useEffect(() => {\n if (!isMoving) {\n // Reset step counter when not moving and sync last position\n stepCounterRef.current = 0;\n lastPositionRef.current = playerPosition;\n return;\n }\n\n // Calculate distance traveled since last check\n const dx = playerPosition.x - lastPositionRef.current.x;\n const dy = playerPosition.y - lastPositionRef.current.y;\n const distanceMoved = Math.sqrt(dx * dx + dy * dy);\n \n // Accumulate distance into step counter\n const stepThreshold = isRunning \n ? STEP_DISTANCE_THRESHOLDS.RUN \n : STEP_DISTANCE_THRESHOLDS.WALK;\n stepCounterRef.current += distanceMoved;\n \n // Determine how many step thresholds were crossed in this update\n const stepsCrossed = Math.floor(stepCounterRef.current / stepThreshold);\n if (stepsCrossed > 0) {\n // Net laterality change depends on whether the number of steps is odd or even\n // Odd steps = toggle once, even steps = no net change\n if (stepsCrossed % 2 === 1) {\n setCurrentLaterality(prev => prev === \"right\" ? \"left\" : \"right\");\n }\n // Preserve remainder distance after accounting for full steps\n stepCounterRef.current -= stepsCrossed * stepThreshold;\n }\n \n // Update last position\n lastPositionRef.current = playerPosition;\n }, [playerPosition, isMoving, isRunning]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 4: Player Animation State Machine\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Reference for pending attack (executed at animation frame 6)\n // Includes animationType and startTime for distance-based hit detection\n // matching CombatSystem behavior\n const pendingAttackRef = useRef<{\n accuracy: number;\n vitalPoint: string;\n animationType?: AnimationType;\n startTime?: number;\n techniqueId?: string;\n } | null>(null);\n\n // Forward ref for handleDummyHit (defined in actions hook)\n const handleDummyHitRef = useRef<\n (\n vitalPointId: string,\n attackContext?: {\n animationType?: AnimationType;\n techniqueId?: string;\n },\n ) => boolean\n >(() => false);\n\n // Ref for playerAnimation to avoid circular dependencies in animation events\n const playerAnimationRef = useRef<ReturnType<\n typeof usePlayerAnimation\n > | null>(null);\n\n // Player animation events (matches CombatScreen pattern)\n const playerAnimationEvents = useMemo<AnimationEvents>(\n () => ({\n onFrame: (frame, state) => {\n // Execute attack at midpoint of animation (frame 6 of 12)\n if (state === \"attack\" && frame === 6 && pendingAttackRef.current) {\n const attackData = pendingAttackRef.current;\n // Pass attack context to handleDummyHit before clearing the ref\n // This ensures animationType and techniqueId are available for reach calculation\n handleDummyHitRef.current(attackData.vitalPoint, {\n animationType: attackData.animationType,\n techniqueId: attackData.techniqueId,\n });\n pendingAttackRef.current = null;\n }\n },\n onAnimationComplete: (state) => {\n if (state === \"stance_change\") {\n // Stance change animation completed - transition to stance guard\n // 자세 변경 완료 - 자세 가드로 전환\n playStanceChangeSound();\n const currentStance =\n TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex];\n if (currentStance && playerAnimationRef.current) {\n playerAnimationRef.current.transitionToStanceGuard(currentStance);\n }\n }\n },\n }),\n [playStanceChangeSound, trainingState.currentStanceIndex],\n );\n\n const playerAnimation = usePlayerAnimation({\n events: playerAnimationEvents,\n });\n\n // Store animation ref for use in event callbacks\n useEffect(() => {\n playerAnimationRef.current = playerAnimation;\n }, [playerAnimation]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 5: Training Actions (Hook-based)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Get current stance for animation transitions (needed before useTrainingActions)\n // 현재 자세 (애니메이션 전환용)\n const currentStance = useMemo(\n () => TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n [trainingState.currentStanceIndex],\n );\n\n // Track previous stance for visual feedback (StanceChangeIndicator)\n // 이전 자세 추적 - 자세 변경 표시기 시각적 피드백용\n const [previousStanceIndex, setPreviousStanceIndex] = useState<number>(0);\n\n // Ref to track current technique's animation type (updated by technique selection)\n // This allows useTrainingActions to access the current technique's animation type\n // without creating circular dependencies\n const currentTechniqueAnimationTypeRef = useRef<AnimationType>(\n AnimationType.JAB,\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 7: Training Player State (Visual Display)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Training player state for visualization\n const trainingPlayerState = useMemo<PlayerState>(() => {\n return {\n id: \"training-player\",\n name: { korean: \"훈련생\", english: \"Trainee\" },\n archetype: selectedArchetype,\n health: 100,\n maxHealth: 100,\n ki: 100,\n maxKi: 100,\n stamina: 100,\n maxStamina: 100,\n energy: 100,\n maxEnergy: 100,\n attackPower: 10,\n defense: 10,\n speed: 10,\n technique: 10,\n pain: 0,\n consciousness: 100,\n balance: 100,\n momentum: 0,\n currentStance: TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n combatState: CombatState.IDLE,\n position: playerPosition,\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: trainingState.stats.hits,\n perfectStrikes: trainingState.perfectStrikes,\n vitalPointHits: 0,\n misses: trainingState.stats.misses,\n accuracy: trainingState.stats.accuracy,\n comboCount: trainingState.stats.combo,\n };\n }, [playerPosition, trainingState, selectedArchetype]);\n\n // Calculate speed modifiers when player state changes\n // Updates at 5Hz (every 200ms) matching CombatScreen pattern\n // Get both walk and run speeds for acceleration interpolation\n useEffect(() => {\n const updateSpeedModifiers = () => {\n // Calculate modifiers for both walking and running to get archetype-aware speeds\n const walkModifiers = speedModifierSystem.calculateSpeedModifiers(\n trainingPlayerState,\n MovementType.WALKING,\n false, // isCrouching\n );\n\n const runModifiers = speedModifierSystem.calculateSpeedModifiers(\n trainingPlayerState,\n MovementType.RUNNING,\n false, // isCrouching\n );\n\n setSpeedModifiers({\n finalSpeed: walkModifiers.finalSpeed,\n baseSpeed: walkModifiers.baseSpeed,\n finalAcceleration: walkModifiers.finalAcceleration,\n });\n\n // Store walk/run speeds for acceleration interpolation\n // These account for archetype speeds and stance modifiers\n setWalkRunSpeeds({\n walkSpeed: walkModifiers.finalSpeed,\n runSpeed: runModifiers.finalSpeed,\n });\n };\n\n // Initial calculation\n updateSpeedModifiers();\n\n // Update every 200ms (5Hz) for responsive feedback without excessive re-renders\n const intervalId = setInterval(updateSpeedModifiers, 200);\n\n return () => clearInterval(intervalId);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [trainingPlayerState]); // speedModifierSystem is memoized and never changes\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 5: Player Attack Movement (Forward Momentum)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Determine if player is currently attacking based on animation state\n const isPlayerAttacking = useMemo(\n () => playerAnimation?.currentState === \"attack\",\n [playerAnimation],\n );\n\n // Calculate attack direction (toward dummy)\n // Note: Direction is calculated on every position change to ensure attacks\n // always target the current dummy position, even if the player is moving.\n // This is intentional for responsive gameplay where attacks can be initiated mid-movement.\n const attackDirection = useMemo(() => {\n // Only calculate if attacking to avoid unnecessary work\n if (!isPlayerAttacking) {\n return new THREE.Vector3(0, 0, 1); // Default forward direction\n }\n const dx = dummyPosition[0] - player3DPosition[0];\n const dz = dummyPosition[2] - player3DPosition[2];\n return new THREE.Vector3(dx, 0, dz).normalize();\n }, [dummyPosition, player3DPosition, isPlayerAttacking]);\n\n // Apply attack movement physics to player position\n // Note: currentTechniqueAnimationTypeRef.current is intentionally a ref to avoid\n // unnecessary re-renders. The animation type is read at attack start (in useAttackMovement's\n // internal effect) and doesn't need to be reactive. It's always set before isPlayerAttacking\n // becomes true via the handleAttack action.\n const {\n currentPosition: player3DPositionWithAttackMovement,\n } = useAttackMovement({\n isAttacking: isPlayerAttacking,\n // eslint-disable-next-line react-hooks/refs -- ref value is set synchronously before isAttacking becomes true; hook only reads this at attack start\n animationType: currentTechniqueAnimationTypeRef.current,\n currentStance: trainingPlayerState.currentStance,\n basePosition: player3DPosition,\n attackDirection,\n animationDuration: 0.4,\n });\n\n // Use position with attack movement for rendering\n const finalPlayer3DPosition = isPlayerAttacking\n ? player3DPositionWithAttackMovement\n : player3DPosition;\n\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Ref to store handleAttack for use in useTechniqueSelection callback\n // This breaks circular dependency between useTechniqueSelection and useTrainingActions\n const handleAttackRef = useRef<(() => void) | null>(null);\n\n // Technique selection and execution for training\n // Moved before useTrainingActions to provide selectedTechniqueId\n const techniqueSelection = useTechniqueSelection({\n player: trainingPlayerState,\n enabled: trainingState.isTraining,\n onTechniqueExecute: useCallback(\n (technique: Technique) => {\n // Show technique usage feedback\n trainingActions.setFeedback(\n `${technique.name.korean} 사용! | Used ${technique.name.english}!`,\n );\n\n // Set attack animation based on technique\n // 기술에 따른 공격 애니메이션 설정\n // Uses resolveTechniqueAnimation so stance-specific animations\n // (e.g. \"geon_heaven_strike\", \"li_precision_jab\") are selected,\n // instead of every technique collapsing to the generic \"jab\" visual.\n const animationName = resolveTechniqueAnimation(technique);\n setAttackAnimation(animationName);\n\n // In training mode, do not deduct resources to allow continuous practice\n // Resources are displayed for educational purposes only\n\n // Execute attack with technique (visual feedback)\n // Use ref to avoid circular dependency\n handleAttackRef.current?.();\n },\n [trainingActions],\n ),\n });\n\n // Derive selected technique ID for intensity-based attack sounds\n const selectedTechniqueId = useMemo(() => {\n const techniques = techniqueSelection.availableTechniques;\n const selectedIdx = techniqueSelection.selectedIndex;\n if (techniques.length === 0 || selectedIdx < 0 || selectedIdx >= techniques.length) {\n return undefined;\n }\n return techniques[selectedIdx]?.id;\n }, [techniqueSelection.availableTechniques, techniqueSelection.selectedIndex]);\n\n // Training actions hook (matches useCombatActions pattern)\n const {\n handleStartTraining,\n handleStopTraining,\n handleDummyHit,\n handleDummyDefeated,\n handleStanceChange,\n handleAttack,\n } = useTrainingActions({\n state: trainingState,\n actions: trainingActions,\n playerPosition,\n player3DPosition,\n dummyPosition,\n playerArchetype: selectedArchetype,\n playerStance: currentStance,\n currentTechniqueAnimationTypeRef, // Ref for technique's animation type\n audio,\n playBoneImpactSound, // Pass bone impact audio function from useCombatAudio\n playAttackSound, // Pass attack sound function from useCombatAudio\n selectedTechniqueId, // Pass selected technique ID for intensity-based attack sounds\n onPlayerUpdate: (updates) => {\n onPlayerUpdate(updates);\n },\n playerAnimation: {\n transitionTo: playerAnimation.transitionTo,\n transitionToStanceGuard: playerAnimation.transitionToStanceGuard,\n currentState: playerAnimation.currentState,\n },\n pendingAttackRef, // Share the ref with animation events\n });\n\n // Update handleAttack ref for useTechniqueSelection callback\n useEffect(() => {\n handleAttackRef.current = handleAttack;\n }, [handleAttack]);\n\n // Update the ref so animation events can call handleDummyHit\n useEffect(() => {\n handleDummyHitRef.current = handleDummyHit;\n }, [handleDummyHit]);\n\n // Wrapped stance change handler with visual feedback tracking\n // 시각적 피드백 추적을 포함한 자세 변경 핸들러 래퍼\n const handleStanceChangeWithVisualFeedback = useCallback(\n (stanceIndex: number) => {\n // Capture previous stance before the change for visual indicator\n setPreviousStanceIndex(trainingState.currentStanceIndex);\n // Execute the actual stance change\n handleStanceChange(stanceIndex);\n },\n [handleStanceChange, trainingState.currentStanceIndex],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 6: Movement-Animation Synchronization\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Sync movement with animation (matches CombatScreen pattern)\n const prevIsMovingRef = useRef<boolean>(isMoving);\n const prevIsRunningRef = useRef<boolean>(isRunning);\n const prevStanceRef = useRef<TrigramStance>(currentStance);\n \n useEffect(() => {\n const isMovingChanged = prevIsMovingRef.current !== isMoving;\n const isRunningChanged = prevIsRunningRef.current !== isRunning;\n const stanceChanged = prevStanceRef.current !== currentStance;\n \n if (isMovingChanged || isRunningChanged) {\n if (isMoving) {\n // Transition to running or walking animation based on state\n if (isRunning) {\n playerAnimation.transitionTo(AnimationState.RUN);\n } else {\n playerAnimation.transitionTo(AnimationState.WALK);\n }\n } else if (playerAnimation.currentState === AnimationState.WALK || \n playerAnimation.currentState === AnimationState.RUN) {\n // When stopping movement, transition to stance-specific guard animation\n // 이동 중지 시 자세별 가드 애니메이션으로 전환\n playerAnimation.transitionToStanceGuard(currentStance);\n }\n prevIsMovingRef.current = isMoving;\n prevIsRunningRef.current = isRunning;\n }\n \n // Update idle/guard animation when stance changes\n if (stanceChanged && !isMoving) {\n // If idle, update to new stance guard\n if (playerAnimation.currentState === AnimationState.IDLE || \n playerAnimation.isInStanceGuard()) {\n playerAnimation.transitionToStanceGuard(currentStance);\n }\n prevStanceRef.current = currentStance;\n }\n }, [isMoving, isRunning, currentStance, playerAnimation]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 7B: Technique Selection System (Moved earlier - see before useTrainingActions)\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Convert cooldowns to Map for TechniqueBar\n const cooldownsMap = useMemo(() => {\n const map = new Map<string, number>();\n techniqueSelection.activeCooldowns.forEach((cd) => {\n map.set(cd.techniqueId, cd.remaining);\n });\n return map;\n }, [techniqueSelection.activeCooldowns]);\n\n // Calculate effective reach based on selected technique (matches CombatSystem)\n // 선택된 기술에 따른 유효 사정거리 계산 (전투 시스템과 동일)\n const { currentTechniqueReach, currentAnimationType } = useMemo(() => {\n const techniques = techniqueSelection.availableTechniques;\n const selectedIdx = techniqueSelection.selectedIndex;\n if (techniques.length === 0) {\n return {\n currentTechniqueReach: 0.7,\n currentAnimationType: AnimationType.JAB,\n };\n }\n const currentTechnique =\n techniques[Math.min(selectedIdx, techniques.length - 1)];\n if (!currentTechnique) {\n return {\n currentTechniqueReach: 0.7,\n currentAnimationType: AnimationType.JAB,\n };\n }\n // Get animation type from technique ID\n const animConfig = getAnimationForTechniqueOrDefault(currentTechnique.id);\n // Calculate max reach using physical attributes and stance\n const physicalAttributes =\n getArchetypePhysicalAttributes(selectedArchetype);\n const reach = physicalReachCalculator.calculateMaxReach(\n physicalAttributes,\n animConfig.type,\n currentStance,\n );\n return {\n currentTechniqueReach: reach,\n currentAnimationType: animConfig.type,\n };\n }, [\n techniqueSelection.availableTechniques,\n techniqueSelection.selectedIndex,\n selectedArchetype,\n currentStance,\n ]);\n\n // Update the animation type ref in an effect (not during render)\n // 렌더링 중이 아닌 effect에서 ref 업데이트\n useEffect(() => {\n currentTechniqueAnimationTypeRef.current = currentAnimationType;\n }, [currentAnimationType]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 8: Mobile Touch Controls\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Reference for tracking active mobile movement key (prevents stuck keys)\n const activeMobileKeyRef = useRef<string | null>(null);\n\n // Enable mobile controls always in training (allow movement even before starting training)\n const mobileControlsEnabled = isMobile;\n\n // Mobile D-pad movement handler (matches CombatScreen implementation)\n const handleMobileMove = useCallback(\n (direction: Direction | null, eventType: DPadEventType) => {\n // Map D-pad directions to movement keys (WASD)\n const directionMap: Record<Direction, string> = {\n up: \"w\",\n \"up-right\": \"w\",\n right: \"d\",\n \"down-right\": \"s\",\n down: \"s\",\n \"down-left\": \"s\",\n left: \"a\",\n \"up-left\": \"w\",\n };\n\n if (eventType === \"start\" && direction) {\n // Release previous key if different (prevents stuck keys)\n if (\n activeMobileKeyRef.current &&\n activeMobileKeyRef.current !== directionMap[direction]\n ) {\n const prevKey = activeMobileKeyRef.current;\n window.dispatchEvent(\n new KeyboardEvent(\"keyup\", {\n key: prevKey,\n code: `Key${prevKey.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n }\n\n // Press new key with proper keyboard event properties\n const key = directionMap[direction];\n activeMobileKeyRef.current = key;\n window.dispatchEvent(\n new KeyboardEvent(\"keydown\", {\n key,\n code: `Key${key.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n } else if (eventType === \"end\") {\n // Release active key when D-pad released\n if (activeMobileKeyRef.current) {\n const key = activeMobileKeyRef.current;\n window.dispatchEvent(\n new KeyboardEvent(\"keyup\", {\n key,\n code: `Key${key.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n activeMobileKeyRef.current = null;\n }\n }\n },\n [],\n );\n\n // Mobile attack handler - uses the same handleAttack from training actions\n const handleMobileAttack = useCallback(() => {\n handleAttack();\n }, [handleAttack]);\n\n // Mobile block handler\n const handleMobileBlock = useCallback(\n (eventType: ButtonEventType) => {\n if (eventType === \"start\") {\n audio.playSFX(\"block\");\n }\n },\n [audio],\n );\n\n // Mobile gesture handler\n const handleMobileGesture = useCallback(\n (gesture: GestureEvent) => {\n switch (gesture.type) {\n case \"swipe-right\":\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"d\" }));\n break;\n case \"swipe-left\":\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"a\" }));\n break;\n case \"swipe-up\":\n if (trainingState.isTraining) {\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \" \" }));\n }\n break;\n case \"swipe-down\":\n trainingActions.resetDummy();\n break;\n case \"two-finger-tap\":\n trainingActions.setTrainingMode(\n trainingState.trainingMode === \"vital_point\"\n ? \"basics\"\n : \"vital_point\",\n );\n audio.playSFX(\"menu_select\");\n break;\n }\n },\n [trainingState, trainingActions, audio],\n );\n\n // Mobile stance change handler\n const handleMobileStanceChange = useCallback(\n (stanceIndex: number) => {\n handleStanceChangeWithVisualFeedback(stanceIndex);\n },\n [handleStanceChangeWithVisualFeedback],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 9: Keyboard Input Handling\n // ═══════════════════════════════════════════════════════════════════════════\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const key = event.key.toLowerCase();\n\n // ESC key - return to menu\n if (key === \"escape\") {\n onReturnToMenu();\n return;\n }\n\n // Handle stance changes (1-8) - always available for exploration\n if (key >= \"1\" && key <= \"8\") {\n const stanceIndex = parseInt(key) - 1;\n handleStanceChangeWithVisualFeedback(stanceIndex);\n event.preventDefault();\n return;\n }\n\n // Handle attacks (Space key) - always available for exploration\n if (key === \" \") {\n handleAttack();\n event.preventDefault();\n return;\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onReturnToMenu, handleStanceChangeWithVisualFeedback, handleAttack]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 10: Audio Lifecycle Management & Auto-Start Training\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Track if component has mounted to enable auto-start once\n const hasMountedRef = useRef(false);\n\n useEffect(() => {\n let audioStarted = false;\n\n const startMusic = async () => {\n try {\n // Start training music with a smooth 2s fade-in for better UX\n await audio.fadeIn(\"cyberpunk_fusion\", 2000);\n audioStarted = true;\n } catch (err) {\n console.warn(\"Failed to start training music:\", err);\n trainingActions.setFeedback(\n \"오디오 초기화 실패 | Audio initialization failed\",\n );\n }\n };\n\n void startMusic();\n\n return () => {\n if (audioStarted) {\n void audio\n .fadeOut(2000)\n .then(() => audio.stopMusic())\n .catch((err) => console.warn(\"Failed to stop training music:\", err));\n }\n };\n }, [audio, trainingActions]);\n\n // Auto-start training on mount (only once) - separate effect to avoid re-runs\n useEffect(() => {\n if (!hasMountedRef.current) {\n hasMountedRef.current = true;\n handleStartTraining();\n }\n }, [handleStartTraining]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 11: Feedback & Session Timer Effects\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Hide feedback after delay - 1500ms provides adequate time for bilingual text readability\n // IMPORTANT: We depend on BOTH showFeedback AND feedback message so the timer resets\n // when a new message arrives (even if showFeedback was already true)\n useEffect(() => {\n if (trainingState.showFeedback) {\n const timer = setTimeout(() => trainingActions.hideFeedback(), 1500);\n return () => clearTimeout(timer);\n }\n }, [trainingState.showFeedback, trainingState.feedback, trainingActions]);\n\n // Update session duration\n useEffect(() => {\n if (!trainingState.isTraining || !trainingState.sessionStartTime) return;\n\n const interval = setInterval(() => {\n trainingActions.updateSessionDuration(\n Math.floor((Date.now() - (trainingState.sessionStartTime ?? 0)) / 1000),\n );\n }, 1000);\n\n return () => clearInterval(interval);\n }, [\n trainingState.isTraining,\n trainingState.sessionStartTime,\n trainingActions,\n ]);\n\n // Auto-restart training when mode changes\n const prevTrainingModeRef = useRef<typeof trainingState.trainingMode>(\n trainingState.trainingMode,\n );\n const isFirstModeEffectRef = useRef<boolean>(true);\n const isTrainingRef = useRef<boolean>(trainingState.isTraining);\n const modeChangeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Keep a ref in sync with the latest training state for use inside timeouts\n useEffect(() => {\n isTrainingRef.current = trainingState.isTraining;\n }, [trainingState.isTraining]);\n\n // Store callbacks in refs to avoid effect re-runs when they change\n const handleStartTrainingRef = useRef(handleStartTraining);\n const handleStopTrainingRef = useRef(handleStopTraining);\n\n useEffect(() => {\n handleStartTrainingRef.current = handleStartTraining;\n handleStopTrainingRef.current = handleStopTraining;\n }, [handleStartTraining, handleStopTraining]);\n\n useEffect(() => {\n // Explicitly skip the first execution on initial mount\n if (isFirstModeEffectRef.current) {\n isFirstModeEffectRef.current = false;\n prevTrainingModeRef.current = trainingState.trainingMode;\n return;\n }\n\n const previousMode = prevTrainingModeRef.current;\n const modeChanged = previousMode !== trainingState.trainingMode;\n\n if (!modeChanged) {\n return;\n }\n\n // Update previous mode only when an actual change is detected\n prevTrainingModeRef.current = trainingState.trainingMode;\n\n // Clear any existing timer to prevent stale callbacks\n if (modeChangeTimerRef.current) {\n clearTimeout(modeChangeTimerRef.current);\n modeChangeTimerRef.current = null;\n }\n\n // Restart training on mode change (matches UI message \"Auto-restarts on mode change\")\n if (isTrainingRef.current) {\n handleStopTrainingRef.current();\n }\n\n // Small delay to allow state to settle, then (re)start training unconditionally\n modeChangeTimerRef.current = setTimeout(() => {\n handleStartTrainingRef.current();\n modeChangeTimerRef.current = null;\n }, 100);\n\n return () => {\n if (modeChangeTimerRef.current) {\n clearTimeout(modeChangeTimerRef.current);\n modeChangeTimerRef.current = null;\n }\n };\n }, [trainingState.trainingMode]); // Only depend on training mode to avoid unnecessary re-runs\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 12: Hit Effect Management\n // ═══════════════════════════════════════════════════════════════════════════\n\n const handleEffectComplete = useCallback(\n (effectId: number) => {\n trainingActions.removeHitEffect(effectId);\n },\n [trainingActions],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 13: Anatomy Layer Toggle\n // ═══════════════════════════════════════════════════════════════════════════\n\n const handleAnatomyLayerToggle = useCallback(\n (layer: AnatomyLayer) => {\n trainingActions.toggleAnatomyLayer(layer);\n audio.playSFX(\"menu_click\");\n },\n [trainingActions, audio],\n );\n\n const handleVitalPointClick = useCallback(\n (pointId: string) => {\n trainingActions.setSelectedVitalPoint(pointId);\n audio.playSFX(\"menu_select\");\n },\n [trainingActions, audio],\n );\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 14: Camera Configuration\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Use shared physics config for consistent camera setup across screens\n // Mobile: tighter FOV and closer camera for better framing\n // Desktop: wider FOV and further camera for full view\n // Portrait: pull camera back on Z and widen FOV so the dummy + both\n // side overlays fit in the narrow viewport.\n const cameraConfig = useMemo(() => {\n const base = createCameraConfig(isMobile);\n if (!isPortrait) return base;\n return {\n ...base,\n fov: Math.min(80, base.fov + 15),\n position: [base.position[0], base.position[1], base.position[2] + 4] as [\n number,\n number,\n number,\n ],\n };\n }, [isMobile, isPortrait]);\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SECTION 15: RENDER\n // ═══════════════════════════════════════════════════════════════════════════\n\n // Performance settings based on device tier\n const performanceSettings = useMemo(() => {\n return getPerformanceSettings(width, isMobile);\n }, [width, isMobile]);\n\n // SSAO removed - was causing WebGL context loss without NormalPass\n\n return (\n <div\n style={{\n width: `${width}px`,\n height: `${height}px`,\n position: \"relative\",\n overflow: \"hidden\", // Prevent content from extending beyond container\n }}\n data-testid=\"training-screen-3d\"\n >\n <Canvas\n style={{ width: `${width}px`, height: `${height}px` }}\n gl={{\n antialias: performanceSettings.antialias,\n alpha: false,\n powerPreference: \"high-performance\",\n failIfMajorPerformanceCaveat: false, // Don't fail in software renderer\n preserveDrawingBuffer: true, // Help with context stability\n }}\n dpr={performanceSettings.dpr}\n shadows={false} // Temporarily disable shadows\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n // Disable fog temporarily for debugging\n }}\n camera={cameraConfig}\n >\n {/* Lighting - base lighting, arena provides additional */}\n <ambientLight intensity={0.6} />\n <directionalLight position={[10, 10, 5]} intensity={1.2} />\n\n {/* Combat Arena 3D Environment - uses physics-based world dimensions */}\n <CombatArena3D\n lighting=\"cyberpunk\"\n scale={trainingAreaBounds.scale}\n worldWidthMeters={trainingAreaBounds.worldWidthMeters}\n worldDepthMeters={trainingAreaBounds.worldDepthMeters}\n />\n\n {/* Animation updater - 60fps updates */}\n <TrainingAnimationUpdater playerAnimation={playerAnimation} />\n\n {/* Acceleration updater - tracks movement time and updates speed */}\n <AccelerationUpdater\n isMoving={isMoving}\n velocity={velocity}\n movementTimeRef={movementTimeRef}\n lastDirectionRef={lastDirectionRef}\n onSpeedUpdate={setAccelerationBasedSpeed}\n walkSpeed={walkRunSpeeds.walkSpeed}\n runSpeed={walkRunSpeeds.runSpeed}\n />\n\n {/* Training dummy at fixed position */}\n <TrainingDummy3D\n position={dummyPosition}\n selectedVitalPoint={trainingState.selectedVitalPoint}\n isTraining={trainingState.isTraining}\n health={trainingState.dummyHealth}\n onVitalPointHit={handleDummyHit}\n onDefeated={handleDummyDefeated}\n difficulty={difficulty}\n vitalPointCount={vitalPointCount}\n isMobile={isMobile}\n />\n\n {/* Anatomy overlay for educational visualization */}\n {trainingState.visibleAnatomyLayers.length > 0 && (\n <AnatomyOverlay3D\n position={dummyPosition}\n visibleLayers={trainingState.visibleAnatomyLayers}\n opacity={0.6}\n isMobile={isMobile}\n />\n )}\n\n {/* Vital Point Overlay - Show all 70 points on dummy */}\n {overlayVisible && (\n <VitalPointMarkers3D\n position={dummyPosition}\n visible={overlayVisible}\n severityFilter={severityFilters}\n regionFilter={regionFilter}\n searchQuery={searchQuery}\n showLabels={showLabels}\n scale={scale}\n animated={animated}\n selectedPoint={trainingState.selectedVitalPoint}\n onPointClick={handleVitalPointClick}\n />\n )}\n\n {/* Player model */}\n <Player3DWithTransitions\n {...convertPlayerStateToProps(\n trainingPlayerState,\n finalPlayer3DPosition,\n playerRotation,\n {\n isMobile,\n facing: \"right\",\n enableFacialExpressions: true,\n enableEyeTracking: true,\n opponentPosition: dummyPosition,\n },\n )}\n currentAnimation={animationStateToPlayerAnimation(\n playerAnimation.currentState,\n )}\n attackAnimation={attackAnimation}\n laterality={currentLaterality}\n enableTransitionEffects={!isMobile}\n enableStanceSymbol={true}\n enableStanceAudio={true}\n />\n\n {/* Foot Placement Markers for Footwork Drills */}\n {trainingState.trainingMode === \"footwork\" &&\n trainingState.footworkDrillActive && (\n <FootPlacementMarkers3D\n centerPosition={dummyPosition}\n pattern={\n trainingState.footworkDrillType === \"free_practice\"\n ? \"none\"\n : trainingState.footworkDrillType\n }\n currentStep={trainingState.footworkDrillStep}\n visible={true}\n scale={1.0}\n animated={true}\n />\n )}\n\n {/* Hit effects */}\n {trainingState.hitEffects.map((effect) => (\n <HitFeedbackEffect3D\n key={effect.id}\n position={effect.position}\n type={effect.type}\n damage={effect.damage}\n visible={effect.visible}\n onComplete={() => handleEffectComplete(effect.id)}\n isMobile={isMobile}\n />\n ))}\n\n {/* Stance Change Visual Indicator */}\n <StanceChangeIndicator\n currentStance={trainingState.currentStanceIndex}\n previousStance={previousStanceIndex}\n isMobile={isMobile}\n />\n\n {/* NOTE: Mobile controls moved OUTSIDE Canvas for reliable touch events */}\n {/* See MobileControlsPure component rendered after HUDs */}\n\n {/* Post-processing Effects - desktop high tier only for Android WebGL stability */}\n {performanceSettings.postProcessing && (\n <EffectComposer multisampling={4}>\n <Bloom\n luminanceThreshold={0.9}\n luminanceSmoothing={0.9}\n mipmapBlur\n intensity={0.8}\n radius={0.4}\n />\n <Noise opacity={0.03} />\n <Vignette eskil={false} offset={0.1} darkness={0.3} />\n </EffectComposer>\n )}\n </Canvas>\n\n {/* Html UI Overlays (positioned absolutely over Canvas) - matches CombatScreen pattern */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: Z_INDEX.HUD,\n // Use 'clip' for pure clipping without creating a scroll container\n // Note: Both 'clip' and 'hidden' will clip box/text shadows; ensure\n // any required shadow space is handled via padding on parent containers.\n overflow: \"clip\",\n }}\n data-testid=\"training-hud-overlay\"\n >\n {/* Left HUD - Anatomy Controls, Guard Indicator.\n Hidden on mobile because the side HUD occludes the compressed\n arena in both portrait and landscape. Anatomy layer toggles remain\n available on larger viewports where there is room for side panels. */}\n {!isMobile && (\n <TrainingLeftHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n visibleAnatomyLayers={trainingState.visibleAnatomyLayers}\n onAnatomyLayerToggle={handleAnatomyLayerToggle}\n currentStanceIndex={trainingState.currentStanceIndex}\n isInGuard={playerAnimation.isInStanceGuard()}\n />\n )}\n\n {/* Top HUD - Training Controls, Archetype Selector, Return Button. */}\n <TrainingTopHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n isTraining={trainingState.isTraining}\n onStartTraining={handleStartTraining}\n onStopTraining={handleStopTraining}\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={setSelectedArchetype}\n overlayVisible={overlayVisible}\n onReturnToMenu={onReturnToMenu}\n onPlaySFX={(sound) => audio.playSFX(sound)}\n />\n\n {/* Right HUD - Mode Selector, Stats, Vital Point Selection.\n Hidden on mobile to keep the training dojang visible and usable.\n The core start/stop, archetype, vital-point toggle, technique bar,\n stance wheel, gestures, and touch controls remain available. */}\n {!isMobile && (\n <TrainingRightHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n trainingMode={trainingState.trainingMode}\n onModeChange={trainingActions.setTrainingMode}\n stats={{\n ...trainingState.stats,\n sessionDuration: trainingState.sessionDuration,\n bestCombo: trainingState.bestCombo,\n perfectStrikes: trainingState.perfectStrikes,\n }}\n distanceToDummy={distanceToDummy}\n effectiveReach={currentTechniqueReach}\n selectedVitalPoint={trainingState.selectedVitalPoint}\n onVitalPointSelect={trainingActions.setSelectedVitalPoint}\n footworkDrillType={trainingState.footworkDrillType}\n footworkDrillStep={trainingState.footworkDrillStep}\n footworkDrillActive={trainingState.footworkDrillActive}\n onStartFootworkDrill={trainingActions.startFootworkDrill}\n onStopFootworkDrill={trainingActions.stopFootworkDrill}\n onAdvanceFootworkStep={trainingActions.advanceFootworkStep}\n />\n )}\n {/* Bottom HUD - Technique Bar, Feedback Messages, Mobile Controls */}\n <TrainingBottomHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n techniques={techniqueSelection.availableTechniques}\n player={trainingPlayerState}\n selectedIndex={techniqueSelection.selectedIndex}\n cooldowns={cooldownsMap}\n onTechniqueSelect={techniqueSelection.selectTechnique}\n showFeedback={trainingState.showFeedback}\n feedbackMessage={trainingState.feedback}\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={setSelectedArchetype}\n onPlaySFX={(sound) => audio.playSFX(sound)}\n />\n\n {/* Vital Point Overlay Controls - Pure DOM overlay (outside Canvas) */}\n {overlayVisible && (\n <VitalPointOverlayControlsPure\n visible={overlayVisible}\n onVisibleChange={setOverlayVisible}\n severityFilters={severityFilters}\n onSeverityFiltersChange={setSeverityFilters}\n regionFilter={regionFilter}\n onRegionFilterChange={setRegionFilter}\n searchQuery={searchQuery}\n onSearchQueryChange={setSearchQuery}\n showLabels={showLabels}\n onShowLabelsChange={setShowLabels}\n animated={animated}\n onAnimatedChange={setAnimated}\n scale={scale}\n onScaleChange={setScale}\n screenPosition={{ top: \"180px\", left: \"20px\" }}\n isMobile={isMobile}\n />\n )}\n\n {/* Mobile Controls - Pure DOM overlay (outside Canvas for reliable touch) */}\n {isMobile && (\n <>\n <MobileControlsOverlay\n onMove={handleMobileMove}\n onAttack={handleMobileAttack}\n onBlock={handleMobileBlock}\n disabled={!mobileControlsEnabled}\n bottom={getMobileControlsBottom(height)}\n opacity={0.85}\n viewportWidth={width}\n viewportHeight={height}\n />\n\n <StanceWheelPure\n currentStance={trainingState.currentStanceIndex}\n onStanceChange={handleMobileStanceChange}\n expanded={trainingState.stanceWheelExpanded}\n onToggle={trainingActions.toggleStanceWheel}\n disabled={!mobileControlsEnabled}\n opacity={0.8}\n />\n\n <GestureRecognizerPure\n onGesture={handleMobileGesture}\n enabled={mobileControlsEnabled}\n showFeedback={true}\n minSwipeDistance={50}\n />\n </>\n )}\n </div>\n </div>\n );\n};\n\nexport default TrainingScreen3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnatomyControlsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/AnatomyControlsOverlayHtml.tsx"],"sourcesContent":["/**\n * AnatomyControlsOverlayHtml - Html UI for toggling anatomy visualization layers\n * \n * Provides buttons to toggle skeleton, nerves, vascular, and surface layers\n * with consistent Korean martial arts cyberpunk theming.\n * \n * @module components/screens/training/components/AnatomyControlsOverlayHtml\n * @category Training UI\n * @korean 해부학제어오버레이\n */\n\nimport React, { useCallback, useState } from \"react\";\nimport type { AnatomyLayer } from \"./AnatomyOverlay3D\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n getNeonGlowEffect,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Props for AnatomyControlsOverlayHtml component\n */\nexport interface AnatomyControlsOverlayHtmlProps {\n /** Currently visible anatomy layers */\n readonly visibleLayers: readonly AnatomyLayer[];\n /** Callback when layer visibility changes */\n readonly onLayerToggle: (layer: AnatomyLayer) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /** Optional width constraint from the parent HUD */\n readonly width?: number;\n}\n\n/**\n * Layer button configuration with Korean colors\n * \n * @korean 레이어설정\n */\ninterface LayerConfig {\n readonly id: AnatomyLayer;\n readonly korean: string;\n readonly english: string;\n readonly icon: string;\n readonly color: number; // Numeric hex color from KOREAN_COLORS\n}\n\nconst LAYER_CONFIGS: readonly LayerConfig[] = [\n {\n id: \"skeleton\",\n korean: \"골격\",\n english: \"Skeleton\",\n icon: \"🦴\",\n color: KOREAN_COLORS.TEXT_PRIMARY,\n },\n {\n id: \"nerves\",\n korean: \"신경\",\n english: \"Nerves\",\n icon: \"⚡\",\n color: KOREAN_COLORS.ACCENT_GOLD,\n },\n {\n id: \"vascular\",\n korean: \"혈관\",\n english: \"Vascular\",\n icon: \"❤️\",\n color: KOREAN_COLORS.ACCENT_RED,\n },\n {\n id: \"surface\",\n korean: \"표면\",\n english: \"Surface\",\n icon: \"👤\",\n color: KOREAN_COLORS.PRIMARY_CYAN,\n },\n];\n\n/**\n * AnatomyControlsOverlayHtml Component\n * UI controls for anatomy layer visibility\n */\nexport const AnatomyControlsOverlayHtml = React.memo<AnatomyControlsOverlayHtmlProps>(\n ({\n visibleLayers,\n onLayerToggle,\n isMobile = false,\n width,\n }) => {\n // State for hover effects\n const [hoveredLayer, setHoveredLayer] = useState<AnatomyLayer | null>(null);\n\n const handleToggle = useCallback(\n (layer: AnatomyLayer) => {\n onLayerToggle(layer);\n },\n [onLayerToggle]\n );\n\n const panelWidth = width ?? (isMobile ? 220 : 260);\n const padding = getResponsiveSpacing(\"md\", isMobile);\n\n // Enhanced panel styles with neon glow\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.88,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n width: `${panelWidth}px`,\n maxWidth: \"100%\",\n padding: `${padding}px`,\n boxSizing: \"border-box\",\n };\n\n return (\n <div\n style={panelStyle}\n data-testid=\"anatomy-controls-html\"\n >\n {/* Header with bilingual text */}\n <div style={{ marginBottom: `${SPACING.MD}px` }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"16px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n textShadow: getNeonTextShadow(KOREAN_COLORS.PRIMARY_CYAN, \"medium\"),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"해부학 표시\", \"Anatomy Display\", \"pipe\")}\n </div>\n </div>\n\n {/* Layer toggle buttons */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${SPACING.SM}px`,\n }}\n >\n {LAYER_CONFIGS.map((config) => {\n const isActive = visibleLayers.includes(config.id);\n const isHovered = hoveredLayer === config.id;\n const layerColor = hexToRgbaString(config.color);\n const activeBackground = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2);\n const inactiveBackground = hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6);\n const inactiveBorder = hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.2);\n\n // Enhanced glow effect for active/hovered states\n const glowEffect = (isActive || isHovered) \n ? getNeonGlowEffect(config.color, isActive ? \"strong\" : \"medium\", true)\n : undefined;\n\n return (\n <button\n key={config.id}\n onClick={() => handleToggle(config.id)}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: `${SPACING.SM}px`,\n background: isActive ? activeBackground : inactiveBackground,\n border: `2px solid ${isActive ? layerColor : inactiveBorder}`,\n borderRadius: `${SPACING.SM}px`,\n padding: isMobile ? `${SPACING.SM}px ${SPACING.SM}px` : `${SPACING.SM}px ${SPACING.MD}px`,\n cursor: \"pointer\",\n transition: getSmoothTransition(\"all\", \"normal\"),\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n width: \"100%\",\n transform: isHovered ? \"scale(1.03)\" : \"scale(1)\",\n boxShadow: glowEffect,\n }}\n onMouseEnter={() => setHoveredLayer(config.id)}\n onMouseLeave={() => setHoveredLayer(null)}\n data-testid={`anatomy-layer-${config.id}`}\n aria-label={`Toggle ${config.english} layer`}\n aria-pressed={isActive}\n >\n {/* Icon */}\n <span\n style={{\n fontSize: isMobile ? \"18px\" : \"20px\",\n filter: isActive ? \"none\" : \"grayscale(100%)\",\n opacity: isActive ? 1 : 0.5,\n }}\n >\n {config.icon}\n </span>\n\n {/* Labels */}\n <div\n style={{\n flex: 1,\n textAlign: \"left\",\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"13px\",\n fontWeight: \"bold\",\n color: isActive ? layerColor : hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n {config.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: isActive \n ? hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY) \n : hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.6),\n }}\n >\n {config.english}\n </div>\n </div>\n\n {/* Active indicator */}\n <div\n style={{\n width: isMobile ? \"8px\" : \"10px\",\n height: isMobile ? \"8px\" : \"10px\",\n borderRadius: \"50%\",\n background: isActive ? layerColor : hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.2),\n boxShadow: isActive ? `0 0 10px ${layerColor}` : \"none\",\n }}\n />\n </button>\n );\n })}\n </div>\n\n {/* Info text */}\n <div\n style={{\n marginTop: `${SPACING.MD}px`,\n paddingTop: `${SPACING.MD}px`,\n borderTop: `1px solid ${hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.1)}`,\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n textAlign: \"center\",\n lineHeight: \"1.4\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {formatBilingualText(\"클릭하여 표시/숨김\", \"Click to show/hide\", \"pipe\")}\n </div>\n </div>\n );\n},\n(prevProps, nextProps) => {\n // Re-render when visible layers or callback changes\n // Including callback prop prevents stale closures when parent provides\n // a new function that captures updated state.\n return (\n prevProps.visibleLayers === nextProps.visibleLayers &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.width === nextProps.width &&\n prevProps.onLayerToggle === nextProps.onLayerToggle\n );\n});\n\nAnatomyControlsOverlayHtml.displayName = \"AnatomyControlsOverlayHtml\";\n\nexport default AnatomyControlsOverlayHtml;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AnatomyControlsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/AnatomyControlsOverlayHtml.tsx"],"sourcesContent":["/**\n * AnatomyControlsOverlayHtml - Html UI for toggling anatomy visualization layers\n * \n * Provides buttons to toggle skeleton, nerves, vascular, and surface layers\n * with consistent Korean martial arts cyberpunk theming.\n * \n * @module components/screens/training/components/AnatomyControlsOverlayHtml\n * @category Training UI\n * @korean 해부학제어오버레이\n */\n\nimport React, { useCallback, useState } from \"react\";\nimport type { AnatomyLayer } from \"./AnatomyOverlay3D\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n getNeonGlowEffect,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Props for AnatomyControlsOverlayHtml component\n */\nexport interface AnatomyControlsOverlayHtmlProps {\n /** Currently visible anatomy layers */\n readonly visibleLayers: readonly AnatomyLayer[];\n /** Callback when layer visibility changes */\n readonly onLayerToggle: (layer: AnatomyLayer) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /** Optional width constraint from the parent HUD */\n readonly width?: number;\n}\n\n/**\n * Layer button configuration with Korean colors\n * \n * @korean 레이어설정\n */\ninterface LayerConfig {\n readonly id: AnatomyLayer;\n readonly korean: string;\n readonly english: string;\n readonly icon: string;\n readonly color: number; // Numeric hex color from KOREAN_COLORS\n}\n\nconst LAYER_CONFIGS: readonly LayerConfig[] = [\n {\n id: \"skeleton\",\n korean: \"골격\",\n english: \"Skeleton\",\n icon: \"🦴\",\n color: KOREAN_COLORS.TEXT_PRIMARY,\n },\n {\n id: \"nerves\",\n korean: \"신경\",\n english: \"Nerves\",\n icon: \"⚡\",\n color: KOREAN_COLORS.ACCENT_GOLD,\n },\n {\n id: \"vascular\",\n korean: \"혈관\",\n english: \"Vascular\",\n icon: \"❤️\",\n color: KOREAN_COLORS.ACCENT_RED,\n },\n {\n id: \"surface\",\n korean: \"표면\",\n english: \"Surface\",\n icon: \"👤\",\n color: KOREAN_COLORS.PRIMARY_CYAN,\n },\n];\n\n/**\n * AnatomyControlsOverlayHtml Component\n * UI controls for anatomy layer visibility\n */\nexport const AnatomyControlsOverlayHtml = React.memo<AnatomyControlsOverlayHtmlProps>(\n ({\n visibleLayers,\n onLayerToggle,\n isMobile = false,\n width,\n }) => {\n // State for hover effects\n const [hoveredLayer, setHoveredLayer] = useState<AnatomyLayer | null>(null);\n\n const handleToggle = useCallback(\n (layer: AnatomyLayer) => {\n onLayerToggle(layer);\n },\n [onLayerToggle]\n );\n\n const panelWidth = width ?? (isMobile ? 220 : 260);\n const padding = getResponsiveSpacing(\"md\", isMobile);\n\n // Enhanced panel styles with neon glow\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.88,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n width: `${panelWidth}px`,\n maxWidth: \"100%\",\n padding: `${padding}px`,\n boxSizing: \"border-box\",\n };\n\n return (\n <div\n style={panelStyle}\n data-testid=\"anatomy-controls-html\"\n >\n {/* Header with bilingual text */}\n <div style={{ marginBottom: `${SPACING.MD}px` }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"16px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n textShadow: getNeonTextShadow(KOREAN_COLORS.PRIMARY_CYAN, \"medium\"),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"해부학 표시\", \"Anatomy Display\", \"pipe\")}\n </div>\n </div>\n\n {/* Layer toggle buttons */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${SPACING.SM}px`,\n }}\n >\n {LAYER_CONFIGS.map((config) => {\n const isActive = visibleLayers.includes(config.id);\n const isHovered = hoveredLayer === config.id;\n const layerColor = hexToRgbaString(config.color);\n const activeBackground = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2);\n const inactiveBackground = hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6);\n const inactiveBorder = hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.2);\n\n // Enhanced glow effect for active/hovered states\n const glowEffect = (isActive || isHovered) \n ? getNeonGlowEffect(config.color, isActive ? \"strong\" : \"medium\", true)\n : undefined;\n\n return (\n <button\n key={config.id}\n onClick={() => handleToggle(config.id)}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: `${SPACING.SM}px`,\n background: isActive ? activeBackground : inactiveBackground,\n border: `2px solid ${isActive ? layerColor : inactiveBorder}`,\n borderRadius: `${SPACING.SM}px`,\n padding: isMobile ? `${SPACING.SM}px ${SPACING.SM}px` : `${SPACING.SM}px ${SPACING.MD}px`,\n cursor: \"pointer\",\n transition: getSmoothTransition(\"all\", \"normal\"),\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n width: \"100%\",\n transform: isHovered ? \"scale(1.03)\" : \"scale(1)\",\n boxShadow: glowEffect,\n }}\n onMouseEnter={() => setHoveredLayer(config.id)}\n onMouseLeave={() => setHoveredLayer(null)}\n data-testid={`anatomy-layer-${config.id}`}\n aria-label={`Toggle ${config.english} layer`}\n aria-pressed={isActive}\n >\n {/* Icon */}\n <span\n style={{\n fontSize: isMobile ? \"18px\" : \"20px\",\n filter: isActive ? \"none\" : \"grayscale(100%)\",\n opacity: isActive ? 1 : 0.5,\n }}\n >\n {config.icon}\n </span>\n\n {/* Labels */}\n <div\n style={{\n flex: 1,\n textAlign: \"left\",\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"13px\",\n fontWeight: \"bold\",\n color: isActive ? layerColor : hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n {config.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: isActive \n ? hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY) \n : hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.6),\n }}\n >\n {config.english}\n </div>\n </div>\n\n {/* Active indicator */}\n <div\n style={{\n width: isMobile ? \"8px\" : \"10px\",\n height: isMobile ? \"8px\" : \"10px\",\n borderRadius: \"50%\",\n background: isActive ? layerColor : hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.2),\n boxShadow: isActive ? `0 0 10px ${layerColor}` : \"none\",\n }}\n />\n </button>\n );\n })}\n </div>\n\n {/* Info text */}\n <div\n style={{\n marginTop: `${SPACING.MD}px`,\n paddingTop: `${SPACING.MD}px`,\n borderTop: `1px solid ${hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.1)}`,\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n textAlign: \"center\",\n lineHeight: \"1.4\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {formatBilingualText(\"클릭하여 표시/숨김\", \"Click to show/hide\", \"pipe\")}\n </div>\n </div>\n );\n},\n(prevProps, nextProps) => {\n // Re-render when visible layers or callback changes\n // Including callback prop prevents stale closures when parent provides\n // a new function that captures updated state.\n return (\n prevProps.visibleLayers === nextProps.visibleLayers &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.width === nextProps.width &&\n prevProps.onLayerToggle === nextProps.onLayerToggle\n );\n});\n\nAnatomyControlsOverlayHtml.displayName = \"AnatomyControlsOverlayHtml\";\n\nexport default AnatomyControlsOverlayHtml;\n"],"mappings":"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"}
|