blacktrigram 0.7.47 → 0.7.49
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.d.ts.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +29 -25
- 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.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js +2 -2
- 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 +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.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js +2 -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.d.ts.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
- 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.d.ts.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js +11 -5
- 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 +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 +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js +3 -11
- 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.d.ts.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js +2 -2
- 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.d.ts +1 -0
- package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js +11 -5
- 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.d.ts.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
- 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.d.ts.map +1 -1
- package/lib/hooks/useHUDLayout.js +3 -2
- 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.d.ts.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +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 +16 -16
- 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.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.d.ts +6 -0
- package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
- 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/AttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamStanceAnimations.js +21 -0
- package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.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.d.ts +6 -6
- package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
- 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/catalogs/TaeJointLockAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
- package/lib/systems/animation/constants/AnatomicalLimits.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 +15 -15
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
- package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js +74 -12
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js +34 -0
- 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/index.d.ts +1 -1
- package/lib/systems/animation/core/index.d.ts.map +1 -1
- package/lib/systems/animation/core/types.d.ts +24 -0
- package/lib/systems/animation/core/types.d.ts.map +1 -1
- package/lib/systems/animation/core/types.js +27 -11
- 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 +19 -19
- 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 +19 -19
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js +17 -17
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js +24 -24
- 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 +21 -21
- 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 +6 -6
- 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/GeonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TaeTechniques.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/systems/vitalpoint/VitalPointsData.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/LayoutTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/animations.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/index.js.map +1 -1
- package/lib/types/constants/layout.d.ts +21 -0
- package/lib/types/constants/layout.d.ts.map +1 -1
- package/lib/types/constants/layout.js +22 -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/constants/ui.js.map +1 -1
- package/lib/types/facial.js +19 -19
- 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/muscle.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/physicsConstants.js.map +1 -1
- package/lib/types/player-visual.d.ts +1 -1
- package/lib/types/player-visual.d.ts.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 +6 -7
- 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.d.ts +7 -0
- package/lib/utils/responsiveLayoutHelpers.d.ts.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js +16 -2
- 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 +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"performanceOptimization.js","names":[],"sources":["../../src/utils/performanceOptimization.ts"],"sourcesContent":["/**\n * Performance Optimization Utilities\n * \n * Collection of utilities for optimizing React rendering performance:\n * - Memoization helpers for expensive calculations\n * - Shallow comparison utilities for React.memo\n * - GPU acceleration style helpers\n * - Performance measurement utilities\n * \n * @module utils/performanceOptimization\n * @category Performance\n * @korean 성능 최적화 유틸리티\n */\n\nimport React from 'react';\n\n/**\n * Shallow comparison for props\n * Used with React.memo for performance optimization\n * \n * @param prevProps - Previous props\n * @param nextProps - Next props\n * @returns true if props are equal (skip re-render), false otherwise\n */\nexport function shallowCompare<T extends Record<string, unknown>>(\n prevProps: T,\n nextProps: T\n): boolean {\n const prevKeys = Object.keys(prevProps);\n const nextKeys = Object.keys(nextProps);\n\n if (prevKeys.length !== nextKeys.length) {\n return false;\n }\n\n for (const key of prevKeys) {\n if (prevProps[key] !== nextProps[key]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Create a memoized component with custom comparison\n * \n * @param component - Component to memoize\n * @param compareKeys - Top-level prop keys to compare for equality\n * @returns Memoized component\n * \n * @example\n * ```tsx\n * // If PlayerHUD receives props like { playerHealth, playerStamina }\n * const MemoizedHUD = memoizeComponent(PlayerHUD, ['playerHealth', 'playerStamina']);\n * ```\n */\nexport function memoizeComponent<P extends Record<string, unknown>>(\n component: React.FC<P>,\n compareKeys?: (keyof P)[]\n): React.NamedExoticComponent<P> {\n if (!compareKeys || compareKeys.length === 0) {\n return React.memo(component);\n }\n\n return React.memo(component, (prevProps, nextProps) => {\n for (const key of compareKeys) {\n if (prevProps[key] !== nextProps[key]) {\n return false; // Props changed, re-render\n }\n }\n return true; // Props same, skip re-render\n });\n}\n\n/**\n * GPU acceleration styles\n * Forces GPU layer creation for smooth animations\n */\nexport const GPU_ACCELERATION_STYLES = {\n /**\n * Force GPU layer with translateZ\n */\n transform: 'translateZ(0)',\n \n /**\n * Enable hardware acceleration\n */\n backfaceVisibility: 'hidden' as const,\n \n /**\n * Hint browser about upcoming changes\n */\n willChange: 'transform, opacity' as const,\n} as const;\n\n/**\n * Supported CSS properties for GPU-accelerated transitions\n * 제한된 전환 대상 속성 (transform, opacity)\n */\nexport type GPUTransitionProperty = 'transform' | 'opacity';\n\n/**\n * Create a valid GPU-accelerated CSS transition string\n * \n * Ensures correct syntax such as:\n * \"transform 0.2s ease, opacity 0.2s ease\"\n * instead of invalid:\n * \"transform, opacity 0.2s ease\"\n * \n * @param properties - CSS properties to animate (default: ['transform', 'opacity'])\n * @param duration - Transition duration (default: '0.2s')\n * @param timingFunction - Timing function (default: 'ease')\n * @returns Valid CSS transition shorthand string\n */\nexport function createGPUAcceleratedTransition(\n properties: readonly GPUTransitionProperty[] = ['transform', 'opacity'],\n duration: string = '0.2s',\n timingFunction: string = 'ease'\n): string {\n return properties\n .map((property) => `${property} ${duration} ${timingFunction}`)\n .join(', ');\n}\n\n/**\n * Apply GPU acceleration to CSS-in-JS style object\n * \n * @param styles - Base styles\n * @returns Styles with GPU acceleration\n */\nexport function withGPUAcceleration<T extends React.CSSProperties>(\n styles: T\n): T & typeof GPU_ACCELERATION_STYLES {\n return {\n ...styles,\n ...GPU_ACCELERATION_STYLES,\n };\n}\n\n/**\n * Performance-optimized animation styles\n * Uses only transform and opacity for 60fps animations\n * \n * @param translateX - X translation in px\n * @param translateY - Y translation in px\n * @param opacity - Opacity (0-1)\n * @param scale - Scale factor (default: 1)\n * @returns Optimized style object\n */\nexport function optimizedAnimationStyle(\n translateX = 0,\n translateY = 0,\n opacity = 1,\n scale = 1\n): React.CSSProperties {\n return {\n transform: `translate3d(${translateX}px, ${translateY}px, 0) scale(${scale})`,\n opacity,\n willChange: 'transform, opacity',\n backfaceVisibility: 'hidden',\n };\n}\n\n/**\n * Measure component render time\n * Only runs in development mode\n * \n * @param componentName - Name of component being measured\n * @param callback - Function to measure\n * @returns Result of callback\n */\nexport function measureRender<T>(\n componentName: string,\n callback: () => T\n): T {\n if (import.meta.env.DEV) {\n const start = performance.now();\n const result = callback();\n const end = performance.now();\n const duration = end - start;\n \n if (duration > 16.67) { // Slower than 60fps frame budget\n console.warn(\n `[Performance] ${componentName} render took ${duration.toFixed(2)}ms (>16.67ms budget)`\n );\n }\n \n return result;\n }\n \n return callback();\n}\n\n/**\n * Create a render counter for debugging\n * Tracks how many times a component renders\n * \n * Note: This is intentionally commented out due to React compiler\n * limitations with ref mutations during render. For production use,\n * consider using React DevTools Profiler instead.\n * \n * @param componentName - Name of component\n * @returns Render count (always returns 0)\n */\nexport function useRenderCount(componentName: string): number {\n // Disabled due to ESLint react-compiler rules\n // See: https://react.dev/reference/react/useRef#caveats\n if (import.meta.env.DEV) {\n console.log(`[Render] ${componentName} is rendering (counter disabled)`);\n }\n return 0;\n \n /* Original implementation disabled due to lint errors\n const renderCountRef = React.useRef(0);\n renderCountRef.current += 1;\n \n React.useEffect(() => {\n if (import.meta.env.DEV) {\n console.log(`[Render] ${componentName} rendered ${renderCountRef.current} times`);\n }\n }, [componentName]);\n \n return renderCountRef.current;\n */\n}\n\n/**\n * Batch state updates to reduce re-renders\n * \n * @param updates - Array of state update functions\n */\nexport function batchUpdates(updates: (() => void)[]): void {\n React.startTransition(() => {\n updates.forEach(update => update());\n });\n}\n\n/**\n * Check if selected object properties have changed using shallow/reference equality\n *\n * This compares only the top-level values at the specified keys using strict\n * equality (`===`). If a key refers to an object or array, only the reference\n * is compared, not its contents. This is suitable for React.memo-style\n * performance optimizations where prop references are kept stable.\n * \n * @param prev - Previous object\n * @param next - Next object\n * @param keys - Keys to check with shallow/reference equality\n * @returns true if any specified key value changed by reference\n */\nexport function hasPropsChanged<T extends Record<string, unknown>>(\n prev: T,\n next: T,\n keys: (keyof T)[]\n): boolean {\n for (const key of keys) {\n if (prev[key] !== next[key]) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Create a stable callback reference that doesn't change between renders\n * Similar to useCallback but with automatic dependency detection\n * \n * @param callback - Callback function\n * @returns Stable callback reference\n */\nexport function useStableCallback<T extends (...args: never[]) => unknown>(\n callback: T\n): T {\n const callbackRef = React.useRef(callback);\n \n // Update ref on each render\n React.useLayoutEffect(() => {\n callbackRef.current = callback;\n });\n \n // Return stable function that calls latest callback\n const stableCallback = React.useCallback(\n (...args: Parameters<T>) => callbackRef.current(...args),\n []\n ) as T;\n \n return stableCallback;\n}\n"],"mappings":";;;;;;AA+EA,IAAa,0BAA0B;;;;CAIrC,WAAW;;;;CAKX,oBAAoB;;;;CAKpB,YAAY;
|
|
1
|
+
{"version":3,"file":"performanceOptimization.js","names":[],"sources":["../../src/utils/performanceOptimization.ts"],"sourcesContent":["/**\n * Performance Optimization Utilities\n * \n * Collection of utilities for optimizing React rendering performance:\n * - Memoization helpers for expensive calculations\n * - Shallow comparison utilities for React.memo\n * - GPU acceleration style helpers\n * - Performance measurement utilities\n * \n * @module utils/performanceOptimization\n * @category Performance\n * @korean 성능 최적화 유틸리티\n */\n\nimport React from 'react';\n\n/**\n * Shallow comparison for props\n * Used with React.memo for performance optimization\n * \n * @param prevProps - Previous props\n * @param nextProps - Next props\n * @returns true if props are equal (skip re-render), false otherwise\n */\nexport function shallowCompare<T extends Record<string, unknown>>(\n prevProps: T,\n nextProps: T\n): boolean {\n const prevKeys = Object.keys(prevProps);\n const nextKeys = Object.keys(nextProps);\n\n if (prevKeys.length !== nextKeys.length) {\n return false;\n }\n\n for (const key of prevKeys) {\n if (prevProps[key] !== nextProps[key]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Create a memoized component with custom comparison\n * \n * @param component - Component to memoize\n * @param compareKeys - Top-level prop keys to compare for equality\n * @returns Memoized component\n * \n * @example\n * ```tsx\n * // If PlayerHUD receives props like { playerHealth, playerStamina }\n * const MemoizedHUD = memoizeComponent(PlayerHUD, ['playerHealth', 'playerStamina']);\n * ```\n */\nexport function memoizeComponent<P extends Record<string, unknown>>(\n component: React.FC<P>,\n compareKeys?: (keyof P)[]\n): React.NamedExoticComponent<P> {\n if (!compareKeys || compareKeys.length === 0) {\n return React.memo(component);\n }\n\n return React.memo(component, (prevProps, nextProps) => {\n for (const key of compareKeys) {\n if (prevProps[key] !== nextProps[key]) {\n return false; // Props changed, re-render\n }\n }\n return true; // Props same, skip re-render\n });\n}\n\n/**\n * GPU acceleration styles\n * Forces GPU layer creation for smooth animations\n */\nexport const GPU_ACCELERATION_STYLES = {\n /**\n * Force GPU layer with translateZ\n */\n transform: 'translateZ(0)',\n \n /**\n * Enable hardware acceleration\n */\n backfaceVisibility: 'hidden' as const,\n \n /**\n * Hint browser about upcoming changes\n */\n willChange: 'transform, opacity' as const,\n} as const;\n\n/**\n * Supported CSS properties for GPU-accelerated transitions\n * 제한된 전환 대상 속성 (transform, opacity)\n */\nexport type GPUTransitionProperty = 'transform' | 'opacity';\n\n/**\n * Create a valid GPU-accelerated CSS transition string\n * \n * Ensures correct syntax such as:\n * \"transform 0.2s ease, opacity 0.2s ease\"\n * instead of invalid:\n * \"transform, opacity 0.2s ease\"\n * \n * @param properties - CSS properties to animate (default: ['transform', 'opacity'])\n * @param duration - Transition duration (default: '0.2s')\n * @param timingFunction - Timing function (default: 'ease')\n * @returns Valid CSS transition shorthand string\n */\nexport function createGPUAcceleratedTransition(\n properties: readonly GPUTransitionProperty[] = ['transform', 'opacity'],\n duration: string = '0.2s',\n timingFunction: string = 'ease'\n): string {\n return properties\n .map((property) => `${property} ${duration} ${timingFunction}`)\n .join(', ');\n}\n\n/**\n * Apply GPU acceleration to CSS-in-JS style object\n * \n * @param styles - Base styles\n * @returns Styles with GPU acceleration\n */\nexport function withGPUAcceleration<T extends React.CSSProperties>(\n styles: T\n): T & typeof GPU_ACCELERATION_STYLES {\n return {\n ...styles,\n ...GPU_ACCELERATION_STYLES,\n };\n}\n\n/**\n * Performance-optimized animation styles\n * Uses only transform and opacity for 60fps animations\n * \n * @param translateX - X translation in px\n * @param translateY - Y translation in px\n * @param opacity - Opacity (0-1)\n * @param scale - Scale factor (default: 1)\n * @returns Optimized style object\n */\nexport function optimizedAnimationStyle(\n translateX = 0,\n translateY = 0,\n opacity = 1,\n scale = 1\n): React.CSSProperties {\n return {\n transform: `translate3d(${translateX}px, ${translateY}px, 0) scale(${scale})`,\n opacity,\n willChange: 'transform, opacity',\n backfaceVisibility: 'hidden',\n };\n}\n\n/**\n * Measure component render time\n * Only runs in development mode\n * \n * @param componentName - Name of component being measured\n * @param callback - Function to measure\n * @returns Result of callback\n */\nexport function measureRender<T>(\n componentName: string,\n callback: () => T\n): T {\n if (import.meta.env.DEV) {\n const start = performance.now();\n const result = callback();\n const end = performance.now();\n const duration = end - start;\n \n if (duration > 16.67) { // Slower than 60fps frame budget\n console.warn(\n `[Performance] ${componentName} render took ${duration.toFixed(2)}ms (>16.67ms budget)`\n );\n }\n \n return result;\n }\n \n return callback();\n}\n\n/**\n * Create a render counter for debugging\n * Tracks how many times a component renders\n * \n * Note: This is intentionally commented out due to React compiler\n * limitations with ref mutations during render. For production use,\n * consider using React DevTools Profiler instead.\n * \n * @param componentName - Name of component\n * @returns Render count (always returns 0)\n */\nexport function useRenderCount(componentName: string): number {\n // Disabled due to ESLint react-compiler rules\n // See: https://react.dev/reference/react/useRef#caveats\n if (import.meta.env.DEV) {\n console.log(`[Render] ${componentName} is rendering (counter disabled)`);\n }\n return 0;\n \n /* Original implementation disabled due to lint errors\n const renderCountRef = React.useRef(0);\n renderCountRef.current += 1;\n \n React.useEffect(() => {\n if (import.meta.env.DEV) {\n console.log(`[Render] ${componentName} rendered ${renderCountRef.current} times`);\n }\n }, [componentName]);\n \n return renderCountRef.current;\n */\n}\n\n/**\n * Batch state updates to reduce re-renders\n * \n * @param updates - Array of state update functions\n */\nexport function batchUpdates(updates: (() => void)[]): void {\n React.startTransition(() => {\n updates.forEach(update => update());\n });\n}\n\n/**\n * Check if selected object properties have changed using shallow/reference equality\n *\n * This compares only the top-level values at the specified keys using strict\n * equality (`===`). If a key refers to an object or array, only the reference\n * is compared, not its contents. This is suitable for React.memo-style\n * performance optimizations where prop references are kept stable.\n * \n * @param prev - Previous object\n * @param next - Next object\n * @param keys - Keys to check with shallow/reference equality\n * @returns true if any specified key value changed by reference\n */\nexport function hasPropsChanged<T extends Record<string, unknown>>(\n prev: T,\n next: T,\n keys: (keyof T)[]\n): boolean {\n for (const key of keys) {\n if (prev[key] !== next[key]) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Create a stable callback reference that doesn't change between renders\n * Similar to useCallback but with automatic dependency detection\n * \n * @param callback - Callback function\n * @returns Stable callback reference\n */\nexport function useStableCallback<T extends (...args: never[]) => unknown>(\n callback: T\n): T {\n const callbackRef = React.useRef(callback);\n \n // Update ref on each render\n React.useLayoutEffect(() => {\n callbackRef.current = callback;\n });\n \n // Return stable function that calls latest callback\n const stableCallback = React.useCallback(\n (...args: Parameters<T>) => callbackRef.current(...args),\n []\n ) as T;\n \n return stableCallback;\n}\n"],"mappings":";;;;;;AA+EA,IAAa,0BAA0B;;;;CAIrC,WAAW;;;;CAKX,oBAAoB;;;;CAKpB,YAAY;AACd;;;;;;;AAqCA,SAAgB,oBACd,QACoC;CACpC,OAAO;EACL,GAAG;EACH,GAAG;CACL;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player3DHelpers.js","names":[],"sources":["../../src/utils/player3DHelpers.ts"],"sourcesContent":["/**\n * Utility functions for Player3D component integration\n *\n * Converts PlayerState from combat system to Player3DUnifiedProps for rendering\n * with SkeletalPlayer3D (28-bone articulated body model).\n *\n * @module utils/player3DHelpers\n * @category Utilities\n * @korean 플레이어3D도우미\n */\n\nimport type { PlayerState } from \"../systems\";\nimport type { AnimationState } from \"../systems/animation/core/types\";\nimport type {\n BalanceState,\n Player3DUnifiedProps,\n PlayerAnimation,\n} from \"../types/player-visual\";\n\n/**\n * Static mapping from AnimationState to PlayerAnimation\n *\n * Stance guard animations map to \"idle\" since SkeletalPlayer3D\n * will handle the stance-specific guard rendering.\n * Tactical steps now use dedicated step animations with guard maintenance.\n *\n * @korean 애니메이션상태맵\n */\nconst ANIMATION_STATE_MAP: Record<AnimationState, PlayerAnimation> = {\n idle: \"idle\",\n walk: \"walk\",\n run: \"run\", // Now uses dedicated RUN_ANIMATION from BasicAnimations\n attack: \"attack\",\n defend: \"defend\",\n // Defensive animations (방어 애니메이션) - map to defend with variations handled by skeletal system\n defend_block_success: \"defend\",\n defend_parry: \"defend\",\n defend_guard_break: \"defend\",\n defend_recovery: \"defend\",\n hit: \"hit\",\n stance_change: \"stance_change\",\n stance_side_switch: \"stance_change\", // Map to stance_change animation (mirroring guard)\n ko: \"death\", // Map ko to death\n // Stance guard animations map to stance-specific idle animations with proper biomechanics\n stance_guard_geon: \"stance_geon\",\n stance_guard_tae: \"stance_tae\",\n stance_guard_li: \"stance_li\",\n stance_guard_jin: \"stance_jin\",\n stance_guard_son: \"stance_son\",\n stance_guard_gam: \"stance_gam\",\n stance_guard_gan: \"stance_gan\",\n stance_guard_gon: \"stance_gon\",\n // Tactical step animations now map to dedicated step animations\n step_forward: \"step_forward\",\n step_back: \"step_back\",\n step_left: \"step_left\",\n step_right: \"step_right\",\n step_forward_left: \"step_forward_left\",\n step_forward_right: \"step_forward_right\",\n step_back_left: \"step_back_left\",\n step_back_right: \"step_back_right\",\n // Fall animations: Now using dedicated FALL_*_ANIMATION from BasicAnimations\n fall_forward: \"fall_forward\",\n fall_backward: \"fall_backward\",\n fall_side_left: \"fall_side_left\",\n fall_side_right: \"fall_side_right\",\n // Ground states map to idle with minimal movement\n ground_prone: \"idle\",\n ground_supine: \"idle\",\n ground_side_left: \"idle\",\n ground_side_right: \"idle\",\n // 180-degree turn animations map to stance_change (body pivot animation)\n turn_left: \"stance_change\",\n turn_right: \"stance_change\",\n // Footwork patterns (보법) - Korean martial arts specialized footwork\n footwork_circular_left: \"walk\", // Lateral movement\n footwork_circular_right: \"walk\",\n footwork_pivot_left: \"walk\", // Rotation movement\n footwork_pivot_right: \"walk\",\n footwork_slide_forward: \"walk\", // Sliding movement\n footwork_slide_back: \"walk\",\n footwork_slide_left: \"walk\",\n footwork_slide_right: \"walk\",\n footwork_shuffle: \"walk\", // Quick adjustment\n // Recovery animations (기상 애니메이션) - Getting up from ground states\n // Map to idle for now, custom 3D recovery animations will be added in future\n recovery_prone_standup: \"idle\",\n recovery_supine_standup: \"idle\",\n recovery_roll: \"walk\", // Rolling motion approximated by walk\n recovery_defensive: \"defend\", // Guarded getup approximated by defend\n // Grappling animations (잡기 애니메이션) - map to attack/defend based on action\n grapple_entry: \"attack\", // Initiating grab uses attack animation\n grapple_control: \"defend\", // Maintaining control uses defensive stance\n grapple_struggle: \"hit\", // Struggling escape uses hit reaction\n grapple_escape: \"attack\", // Successful escape uses attack burst\n};\n\n/**\n * Convert AnimationState to PlayerAnimation\n *\n * Maps the animation system's state types to SkeletalPlayer3D's animation types.\n *\n * @param animState - Animation state from the animation system\n * @returns Corresponding PlayerAnimation type\n * @korean 애니메이션상태변환\n */\nexport function animationStateToPlayerAnimation(\n animState: AnimationState\n): PlayerAnimation {\n return ANIMATION_STATE_MAP[animState];\n}\n\n/**\n * Convert balance number (0-100) to BalanceState enum\n *\n * @param balance - Balance value from PlayerState (0-100)\n * @returns BalanceState enum value\n * @korean 균형상태변환\n */\nexport function getBalanceState(balance: number): BalanceState {\n if (balance >= 80) return \"READY\";\n if (balance >= 50) return \"SHAKEN\";\n if (balance >= 20) return \"VULNERABLE\";\n return \"HELPLESS\";\n}\n\n/**\n * Get current animation state from PlayerState\n *\n * Returns stance-specific idle animations when player is in idle/recovering state.\n * This ensures each trigram stance displays the correct guard pose with proper\n * leg positioning and breathing animation.\n *\n * @param player - Current player state\n * @returns PlayerAnimation enum value (stance-specific for idle states)\n * @korean 애니메이션상태가져오기\n */\nexport function getPlayerAnimation(player: PlayerState): PlayerAnimation {\n if (player.isStunned) return \"hit\";\n if (player.isBlocking) return \"defend\";\n if (player.isCountering) return \"counter\";\n\n switch (player.combatState) {\n case \"attacking\":\n return \"attack\";\n case \"defending\":\n return \"defend\";\n case \"stunned\":\n return \"hit\";\n case \"recovering\":\n case \"idle\":\n default:\n return `stance_${player.currentStance}` as PlayerAnimation;\n }\n}\n\n/**\n * Converts PlayerState to Player3DUnifiedProps for visual rendering.\n *\n * Note: This function converts base PlayerState properties used in combat.\n * Training-specific stats (misses, accuracy, comboCount) are optional in PlayerState\n * and handled separately in training contexts.\n *\n * @param player - The player state to convert\n * @param position - 3D position [x, y, z]\n * @param rotation - Rotation in radians\n * @param options - Display and behavior options\n * @returns Props for SkeletalPlayer3D component (28-bone articulated body model)\n * @korean 플레이어상태변환\n *\n * @example\n * ```tsx\n * const playerProps = convertPlayerStateToProps(\n * playerState,\n * [-3, 0, 0],\n * 0,\n * { isMobile: false, showVitalPoints: false }\n * );\n *\n * <SkeletalPlayer3D {...playerProps} />\n * ```\n */\nexport function convertPlayerStateToProps(\n player: PlayerState,\n position: [number, number, number],\n rotation: number,\n options: {\n readonly isMobile?: boolean;\n readonly facing?: \"left\" | \"right\";\n readonly scale?: number;\n readonly showDetails?: boolean;\n readonly showHealthBar?: boolean;\n readonly showStanceIndicator?: boolean;\n readonly onAnimationComplete?: () => void;\n // Facial expression options - 얼굴 표정 옵션\n readonly enableFacialExpressions?: boolean;\n readonly enableEyeTracking?: boolean;\n readonly opponentPosition?: [number, number, number];\n } = {}\n): Player3DUnifiedProps {\n return {\n playerId: player.id,\n archetype: player.archetype,\n stance: player.currentStance,\n position,\n rotation,\n\n health: player.health,\n maxHealth: player.maxHealth,\n stamina: player.stamina,\n ki: player.ki,\n\n pain: player.pain,\n balance: getBalanceState(player.balance),\n consciousness: player.consciousness,\n\n isBlocking: player.isBlocking,\n isStunned: player.isStunned,\n isCountering: player.isCountering,\n\n currentAnimation: getPlayerAnimation(player),\n\n name: player.name,\n isMobile: options.isMobile ?? false,\n facing: options.facing ?? \"right\",\n scale: options.scale ?? 1,\n showDetails: options.showDetails,\n showHealthBar: options.showHealthBar,\n showStanceIndicator: options.showStanceIndicator,\n onAnimationComplete: options.onAnimationComplete,\n\n enableFacialExpressions: options.enableFacialExpressions ?? false,\n enableEyeTracking: options.enableEyeTracking ?? true,\n opponentPosition: options.opponentPosition,\n };\n}\n"],"mappings":";;;;;;;;;;AA4BA,IAAM,sBAA+D;CACnE,MAAM;CACN,MAAM;CACN,KAAK;CACL,QAAQ;CACR,QAAQ;CAER,sBAAsB;CACtB,cAAc;CACd,oBAAoB;CACpB,iBAAiB;CACjB,KAAK;CACL,eAAe;CACf,oBAAoB;CACpB,IAAI;CAEJ,mBAAmB;CACnB,kBAAkB;CAClB,iBAAiB;CACjB,kBAAkB;CAClB,kBAAkB;CAClB,kBAAkB;CAClB,kBAAkB;CAClB,kBAAkB;CAElB,cAAc;CACd,WAAW;CACX,WAAW;CACX,YAAY;CACZ,mBAAmB;CACnB,oBAAoB;CACpB,gBAAgB;CAChB,iBAAiB;CAEjB,cAAc;CACd,eAAe;CACf,gBAAgB;CAChB,iBAAiB;CAEjB,cAAc;CACd,eAAe;CACf,kBAAkB;CAClB,mBAAmB;CAEnB,WAAW;CACX,YAAY;CAEZ,wBAAwB;CACxB,yBAAyB;CACzB,qBAAqB;CACrB,sBAAsB;CACtB,wBAAwB;CACxB,qBAAqB;CACrB,qBAAqB;CACrB,sBAAsB;CACtB,kBAAkB;CAGlB,wBAAwB;CACxB,yBAAyB;CACzB,eAAe;CACf,oBAAoB;CAEpB,eAAe;CACf,iBAAiB;CACjB,kBAAkB;CAClB,gBAAgB;CACjB;;;;;;;;;;AAWD,SAAgB,gCACd,WACiB;CACjB,OAAO,oBAAoB;;;;;;;;;AAU7B,SAAgB,gBAAgB,SAA+B;CAC7D,IAAI,WAAW,IAAI,OAAO;CAC1B,IAAI,WAAW,IAAI,OAAO;CAC1B,IAAI,WAAW,IAAI,OAAO;CAC1B,OAAO;;;;;;;;;;;;;AAcT,SAAgB,mBAAmB,QAAsC;CACvE,IAAI,OAAO,WAAW,OAAO;CAC7B,IAAI,OAAO,YAAY,OAAO;CAC9B,IAAI,OAAO,cAAc,OAAO;CAEhC,QAAQ,OAAO,aAAf;EACE,KAAK,aACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,WACH,OAAO;EAGT,SACE,OAAO,UAAU,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B9B,SAAgB,0BACd,QACA,UACA,UACA,UAYI,EAAE,EACgB;CACtB,OAAO;EACL,UAAU,OAAO;EACjB,WAAW,OAAO;EAClB,QAAQ,OAAO;EACf;EACA;EAEA,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,IAAI,OAAO;EAEX,MAAM,OAAO;EACb,SAAS,gBAAgB,OAAO,QAAQ;EACxC,eAAe,OAAO;EAEtB,YAAY,OAAO;EACnB,WAAW,OAAO;EAClB,cAAc,OAAO;EAErB,kBAAkB,mBAAmB,OAAO;EAE5C,MAAM,OAAO;EACb,UAAU,QAAQ,YAAY;EAC9B,QAAQ,QAAQ,UAAU;EAC1B,OAAO,QAAQ,SAAS;EACxB,aAAa,QAAQ;EACrB,eAAe,QAAQ;EACvB,qBAAqB,QAAQ;EAC7B,qBAAqB,QAAQ;EAE7B,yBAAyB,QAAQ,2BAA2B;EAC5D,mBAAmB,QAAQ,qBAAqB;EAChD,kBAAkB,QAAQ;EAC3B"}
|
|
1
|
+
{"version":3,"file":"player3DHelpers.js","names":[],"sources":["../../src/utils/player3DHelpers.ts"],"sourcesContent":["/**\n * Utility functions for Player3D component integration\n *\n * Converts PlayerState from combat system to Player3DUnifiedProps for rendering\n * with SkeletalPlayer3D (28-bone articulated body model).\n *\n * @module utils/player3DHelpers\n * @category Utilities\n * @korean 플레이어3D도우미\n */\n\nimport type { PlayerState } from \"../systems\";\nimport type { AnimationState } from \"../systems/animation/core/types\";\nimport type {\n BalanceState,\n Player3DUnifiedProps,\n PlayerAnimation,\n} from \"../types/player-visual\";\n\n/**\n * Static mapping from AnimationState to PlayerAnimation\n *\n * Stance guard animations map to \"idle\" since SkeletalPlayer3D\n * will handle the stance-specific guard rendering.\n * Tactical steps now use dedicated step animations with guard maintenance.\n *\n * @korean 애니메이션상태맵\n */\nconst ANIMATION_STATE_MAP: Record<AnimationState, PlayerAnimation> = {\n idle: \"idle\",\n walk: \"walk\",\n run: \"run\", // Now uses dedicated RUN_ANIMATION from BasicAnimations\n attack: \"attack\",\n defend: \"defend\",\n // Defensive animations (방어 애니메이션) - map to defend with variations handled by skeletal system\n defend_block_success: \"defend\",\n defend_parry: \"defend\",\n defend_guard_break: \"defend\",\n defend_recovery: \"defend\",\n hit: \"hit\",\n stance_change: \"stance_change\",\n stance_side_switch: \"stance_change\", // Reuse renderable stance-change visuals for foot-forward switch\n ko: \"death\", // Map ko to death\n // Stance guard animations map to stance-specific idle animations with proper biomechanics\n stance_guard_geon: \"stance_geon\",\n stance_guard_tae: \"stance_tae\",\n stance_guard_li: \"stance_li\",\n stance_guard_jin: \"stance_jin\",\n stance_guard_son: \"stance_son\",\n stance_guard_gam: \"stance_gam\",\n stance_guard_gan: \"stance_gan\",\n stance_guard_gon: \"stance_gon\",\n // Tactical step animations now map to dedicated step animations\n step_forward: \"step_forward\",\n step_back: \"step_back\",\n step_left: \"step_left\",\n step_right: \"step_right\",\n step_forward_left: \"step_forward_left\",\n step_forward_right: \"step_forward_right\",\n step_back_left: \"step_back_left\",\n step_back_right: \"step_back_right\",\n // Fall animations: Now using dedicated FALL_*_ANIMATION from BasicAnimations\n fall_forward: \"fall_forward\",\n fall_backward: \"fall_backward\",\n fall_side_left: \"fall_side_left\",\n fall_side_right: \"fall_side_right\",\n // Ground states map to idle with minimal movement\n ground_prone: \"idle\",\n ground_supine: \"idle\",\n ground_side_left: \"idle\",\n ground_side_right: \"idle\",\n // 180-degree turn animations map to stance_change (body pivot animation)\n turn_left: \"stance_change\",\n turn_right: \"stance_change\",\n // Footwork patterns (보법) - Korean martial arts specialized footwork\n footwork_circular_left: \"walk\", // Lateral movement\n footwork_circular_right: \"walk\",\n footwork_pivot_left: \"walk\", // Rotation movement\n footwork_pivot_right: \"walk\",\n footwork_slide_forward: \"walk\", // Sliding movement\n footwork_slide_back: \"walk\",\n footwork_slide_left: \"walk\",\n footwork_slide_right: \"walk\",\n footwork_shuffle: \"walk\", // Quick adjustment\n // Recovery animations (기상 애니메이션) - Getting up from ground states\n // Map to idle for now, custom 3D recovery animations will be added in future\n recovery_prone_standup: \"idle\",\n recovery_supine_standup: \"idle\",\n recovery_roll: \"walk\", // Rolling motion approximated by walk\n recovery_defensive: \"defend\", // Guarded getup approximated by defend\n // Grappling animations (잡기 애니메이션) - map to attack/defend based on action\n grapple_entry: \"attack\", // Initiating grab uses attack animation\n grapple_control: \"defend\", // Maintaining control uses defensive stance\n grapple_struggle: \"hit\", // Struggling escape uses hit reaction\n grapple_escape: \"attack\", // Successful escape uses attack burst\n};\n\n/**\n * Convert AnimationState to PlayerAnimation\n *\n * Maps the animation system's state types to SkeletalPlayer3D's animation types.\n *\n * @param animState - Animation state from the animation system\n * @returns Corresponding PlayerAnimation type\n * @korean 애니메이션상태변환\n */\nexport function animationStateToPlayerAnimation(\n animState: AnimationState\n): PlayerAnimation {\n return ANIMATION_STATE_MAP[animState];\n}\n\n/**\n * Convert balance number (0-100) to BalanceState enum\n *\n * @param balance - Balance value from PlayerState (0-100)\n * @returns BalanceState enum value\n * @korean 균형상태변환\n */\nexport function getBalanceState(balance: number): BalanceState {\n if (balance >= 80) return \"READY\";\n if (balance >= 50) return \"SHAKEN\";\n if (balance >= 20) return \"VULNERABLE\";\n return \"HELPLESS\";\n}\n\n/**\n * Get current animation state from PlayerState\n *\n * Returns stance-specific idle animations when player is in idle/recovering state.\n * This ensures each trigram stance displays the correct guard pose with proper\n * leg positioning and breathing animation.\n *\n * @param player - Current player state\n * @returns PlayerAnimation enum value (stance-specific for idle states)\n * @korean 애니메이션상태가져오기\n */\nexport function getPlayerAnimation(player: PlayerState): PlayerAnimation {\n if (player.isStunned) return \"hit\";\n if (player.isBlocking) return \"defend\";\n if (player.isCountering) return \"counter\";\n\n switch (player.combatState) {\n case \"attacking\":\n return \"attack\";\n case \"defending\":\n return \"defend\";\n case \"stunned\":\n return \"hit\";\n case \"recovering\":\n case \"idle\":\n default:\n return `stance_${player.currentStance}` as PlayerAnimation;\n }\n}\n\n/**\n * Converts PlayerState to Player3DUnifiedProps for visual rendering.\n *\n * Note: This function converts base PlayerState properties used in combat.\n * Training-specific stats (misses, accuracy, comboCount) are optional in PlayerState\n * and handled separately in training contexts.\n *\n * @param player - The player state to convert\n * @param position - 3D position [x, y, z]\n * @param rotation - Rotation in radians\n * @param options - Display and behavior options\n * @returns Props for SkeletalPlayer3D component (28-bone articulated body model)\n * @korean 플레이어상태변환\n *\n * @example\n * ```tsx\n * const playerProps = convertPlayerStateToProps(\n * playerState,\n * [-3, 0, 0],\n * 0,\n * { isMobile: false, showVitalPoints: false }\n * );\n *\n * <SkeletalPlayer3D {...playerProps} />\n * ```\n */\nexport function convertPlayerStateToProps(\n player: PlayerState,\n position: [number, number, number],\n rotation: number,\n options: {\n readonly isMobile?: boolean;\n readonly facing?: \"left\" | \"right\";\n readonly scale?: number;\n readonly showDetails?: boolean;\n readonly showHealthBar?: boolean;\n readonly showStanceIndicator?: boolean;\n readonly onAnimationComplete?: () => void;\n // Facial expression options - 얼굴 표정 옵션\n readonly enableFacialExpressions?: boolean;\n readonly enableEyeTracking?: boolean;\n readonly opponentPosition?: [number, number, number];\n } = {}\n): Player3DUnifiedProps {\n return {\n playerId: player.id,\n archetype: player.archetype,\n stance: player.currentStance,\n position,\n rotation,\n\n health: player.health,\n maxHealth: player.maxHealth,\n stamina: player.stamina,\n ki: player.ki,\n\n pain: player.pain,\n balance: getBalanceState(player.balance),\n consciousness: player.consciousness,\n\n isBlocking: player.isBlocking,\n isStunned: player.isStunned,\n isCountering: player.isCountering,\n\n currentAnimation: getPlayerAnimation(player),\n\n name: player.name,\n isMobile: options.isMobile ?? false,\n facing: options.facing ?? \"right\",\n scale: options.scale ?? 1,\n showDetails: options.showDetails,\n showHealthBar: options.showHealthBar,\n showStanceIndicator: options.showStanceIndicator,\n onAnimationComplete: options.onAnimationComplete,\n\n enableFacialExpressions: options.enableFacialExpressions ?? false,\n enableEyeTracking: options.enableEyeTracking ?? true,\n opponentPosition: options.opponentPosition,\n };\n}\n"],"mappings":";;;;;;;;;;AA4BA,IAAM,sBAA+D;CACnE,MAAM;CACN,MAAM;CACN,KAAK;CACL,QAAQ;CACR,QAAQ;CAER,sBAAsB;CACtB,cAAc;CACd,oBAAoB;CACpB,iBAAiB;CACjB,KAAK;CACL,eAAe;CACf,oBAAoB;CACpB,IAAI;CAEJ,mBAAmB;CACnB,kBAAkB;CAClB,iBAAiB;CACjB,kBAAkB;CAClB,kBAAkB;CAClB,kBAAkB;CAClB,kBAAkB;CAClB,kBAAkB;CAElB,cAAc;CACd,WAAW;CACX,WAAW;CACX,YAAY;CACZ,mBAAmB;CACnB,oBAAoB;CACpB,gBAAgB;CAChB,iBAAiB;CAEjB,cAAc;CACd,eAAe;CACf,gBAAgB;CAChB,iBAAiB;CAEjB,cAAc;CACd,eAAe;CACf,kBAAkB;CAClB,mBAAmB;CAEnB,WAAW;CACX,YAAY;CAEZ,wBAAwB;CACxB,yBAAyB;CACzB,qBAAqB;CACrB,sBAAsB;CACtB,wBAAwB;CACxB,qBAAqB;CACrB,qBAAqB;CACrB,sBAAsB;CACtB,kBAAkB;CAGlB,wBAAwB;CACxB,yBAAyB;CACzB,eAAe;CACf,oBAAoB;CAEpB,eAAe;CACf,iBAAiB;CACjB,kBAAkB;CAClB,gBAAgB;AAClB;;;;;;;;;;AAWA,SAAgB,gCACd,WACiB;CACjB,OAAO,oBAAoB;AAC7B;;;;;;;;AASA,SAAgB,gBAAgB,SAA+B;CAC7D,IAAI,WAAW,IAAI,OAAO;CAC1B,IAAI,WAAW,IAAI,OAAO;CAC1B,IAAI,WAAW,IAAI,OAAO;CAC1B,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,mBAAmB,QAAsC;CACvE,IAAI,OAAO,WAAW,OAAO;CAC7B,IAAI,OAAO,YAAY,OAAO;CAC9B,IAAI,OAAO,cAAc,OAAO;CAEhC,QAAQ,OAAO,aAAf;EACE,KAAK,aACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,WACH,OAAO;EAGT,SACE,OAAO,UAAU,OAAO;CAC5B;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,0BACd,QACA,UACA,UACA,UAYI,CAAC,GACiB;CACtB,OAAO;EACL,UAAU,OAAO;EACjB,WAAW,OAAO;EAClB,QAAQ,OAAO;EACf;EACA;EAEA,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,IAAI,OAAO;EAEX,MAAM,OAAO;EACb,SAAS,gBAAgB,OAAO,OAAO;EACvC,eAAe,OAAO;EAEtB,YAAY,OAAO;EACnB,WAAW,OAAO;EAClB,cAAc,OAAO;EAErB,kBAAkB,mBAAmB,MAAM;EAE3C,MAAM,OAAO;EACb,UAAU,QAAQ,YAAY;EAC9B,QAAQ,QAAQ,UAAU;EAC1B,OAAO,QAAQ,SAAS;EACxB,aAAa,QAAQ;EACrB,eAAe,QAAQ;EACvB,qBAAqB,QAAQ;EAC7B,qBAAqB,QAAQ;EAE7B,yBAAyB,QAAQ,2BAA2B;EAC5D,mBAAmB,QAAQ,qBAAqB;EAChD,kBAAkB,QAAQ;CAC5B;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playerUtils.js","names":[],"sources":["../../src/utils/playerUtils.ts"],"sourcesContent":["/**\n * Player state utilities and helper functions\n */\n\nimport { getArchetypePhysicalAttributes } from \"../data/archetypePhysicalAttributes\";\nimport { PLAYER_ARCHETYPES_DATA, PlayerState, StatusEffect } from \"../systems\";\nimport {\n calculateAngleToTarget,\n createDefaultBodyFacing,\n} from \"../systems/animation\";\nimport type { BodyFacing } from \"../systems/animation/core/types\";\nimport {\n BodyPart,\n BodyPartHealth,\n BodyPartMaxHealth,\n} from \"../systems/bodypart/types\";\nimport { PlayerArchetype, Position, TrigramStance } from \"../types\";\nimport { CombatState } from \"../types/common\";\nimport { ARCHETYPE_ASSETS } from \"../types/constants\";\n\n/**\n * Default body part health values (100 HP each)\n * @korean 기본 신체부위 체력\n */\nconst DEFAULT_BODY_PART_HEALTH: BodyPartHealth = {\n [BodyPart.HEAD]: 100,\n [BodyPart.NECK]: 100,\n [BodyPart.TORSO_UPPER]: 100,\n [BodyPart.TORSO_LOWER]: 100,\n [BodyPart.ARM_LEFT]: 100,\n [BodyPart.ARM_RIGHT]: 100,\n [BodyPart.LEG_LEFT]: 100,\n [BodyPart.LEG_RIGHT]: 100,\n};\n\n/**\n * Default body part max health values\n * @korean 기본 신체부위 최대체력\n */\nconst DEFAULT_BODY_PART_MAX_HEALTH: BodyPartMaxHealth = {\n [BodyPart.HEAD]: 100,\n [BodyPart.NECK]: 100,\n [BodyPart.TORSO_UPPER]: 100,\n [BodyPart.TORSO_LOWER]: 100,\n [BodyPart.ARM_LEFT]: 100,\n [BodyPart.ARM_RIGHT]: 100,\n [BodyPart.LEG_LEFT]: 100,\n [BodyPart.LEG_RIGHT]: 100,\n};\n\n/**\n * Create a complete PlayerState from archetype and player index\n */\nexport function createPlayerFromArchetype(\n archetype: PlayerArchetype,\n playerIndex: number,\n): PlayerState {\n const archetypeData = PLAYER_ARCHETYPES_DATA[archetype];\n\n const basePosition: Position = {\n x: playerIndex === 0 ? 300 : 500,\n y: 400,\n };\n\n return {\n id: `player_${playerIndex + 1}`,\n name: archetypeData.name,\n archetype,\n\n // Physical attributes loaded from archetype defaults\n physicalAttributes: getArchetypePhysicalAttributes(archetype),\n\n // Combat stats\n health: archetypeData.baseHealth,\n maxHealth: archetypeData.baseHealth,\n bodyPartHealth: { ...DEFAULT_BODY_PART_HEALTH },\n bodyPartMaxHealth: { ...DEFAULT_BODY_PART_MAX_HEALTH },\n ki: archetypeData.baseKi,\n maxKi: archetypeData.baseKi,\n stamina: archetypeData.baseStamina,\n maxStamina: archetypeData.baseStamina,\n energy: 100,\n maxEnergy: 100,\n\n // Combat attributes\n attackPower: archetypeData.stats.attackPower,\n defense: archetypeData.stats.defense,\n speed: archetypeData.stats.speed,\n technique: archetypeData.stats.technique,\n pain: 0,\n consciousness: 100,\n balance: 100,\n momentum: 0,\n\n // Combat state\n currentStance: archetypeData.coreStance,\n combatState: CombatState.IDLE,\n position: basePosition,\n // Default to orthodox/southpaw stance\n // Player 1 starts orthodox, Player 2 starts southpaw for facing each other\n stanceSide: playerIndex === 0 ? \"orthodox\" : \"southpaw\",\n isBlocking: false,\n isStunned: false,\n isCountering: false,\n lastActionTime: 0,\n recoveryTime: 0,\n lastStanceChangeTime: 0,\n\n // Status and effects\n statusEffects: [],\n activeEffects: [],\n\n // Vital points state\n vitalPoints: [],\n\n // Match statistics\n totalDamageReceived: 0,\n totalDamageDealt: 0,\n hitsTaken: 0,\n hitsLanded: 0,\n perfectStrikes: 0,\n vitalPointHits: 0,\n };\n}\n\n/**\n * Update player state with partial updates\n */\nexport function updatePlayerState(\n player: PlayerState,\n updates: Partial<PlayerState>,\n): PlayerState {\n return {\n ...player,\n ...updates,\n // Ensure vital constraints\n health: Math.max(\n 0,\n Math.min(updates.health ?? player.health, player.maxHealth),\n ),\n ki: Math.max(0, Math.min(updates.ki ?? player.ki, player.maxKi)),\n stamina: Math.max(\n 0,\n Math.min(updates.stamina ?? player.stamina, player.maxStamina),\n ),\n consciousness: Math.max(\n 0,\n Math.min(updates.consciousness ?? player.consciousness, 100),\n ),\n balance: Math.max(0, Math.min(updates.balance ?? player.balance, 100)),\n };\n}\n\n/**\n * Apply damage to player\n */\nexport function applyDamage(\n player: PlayerState,\n damage: number,\n _damageType?: string, // Fix: Add underscore for unused parameter\n): PlayerState {\n const newHealth = Math.max(0, player.health - damage);\n const isKnockedOut = newHealth <= 0;\n\n return updatePlayerState(player, {\n health: newHealth,\n totalDamageReceived: player.totalDamageReceived + damage,\n hitsTaken: player.hitsTaken + 1,\n combatState: isKnockedOut ? CombatState.STUNNED : player.combatState,\n consciousness: isKnockedOut ? 0 : player.consciousness,\n });\n}\n\n/**\n * Apply status effect to player\n */\nexport function applyStatusEffect(\n player: PlayerState,\n effect: StatusEffect,\n): PlayerState {\n const existingEffectIndex = player.statusEffects.findIndex(\n (e) => e.type === effect.type && !e.stackable,\n );\n\n let newEffects: StatusEffect[];\n if (existingEffectIndex >= 0 && !effect.stackable) {\n // Replace existing non-stackable effect\n newEffects = [...player.statusEffects];\n newEffects[existingEffectIndex] = effect;\n } else {\n // Add new effect\n newEffects = [...player.statusEffects, effect];\n }\n\n return updatePlayerState(player, {\n statusEffects: newEffects,\n activeEffects: [...player.activeEffects, effect.type],\n });\n}\n\n/**\n * Get vital point by ID (fix return type)\n */\nexport function getVitalPointByOnPlayerId(\n player: PlayerState,\n vitalPointId: string,\n): {\n readonly id: string;\n readonly isHit: boolean;\n readonly damage: number;\n readonly lastHitTime: number;\n} | null {\n return player.vitalPoints.find((vp) => vp.id === vitalPointId) ?? null;\n}\n\n/**\n * Check if player can act\n */\nexport function canPlayerAct(player: PlayerState): boolean {\n if (player.health <= 0) return false;\n if (player.consciousness <= 0) return false;\n if (player.combatState === CombatState.STUNNED) return false;\n if (player.isStunned) return false;\n return true;\n}\n\n/**\n * Get player's current stance effectiveness against opponent\n */\nexport function getStanceEffectiveness(\n _playerStance: TrigramStance, // Fix: Add underscore to unused parameter\n _opponentStance: TrigramStance, // Fix: Add underscore to unused parameter\n): number {\n // Basic implementation - could be enhanced with stance matrix\n return 1.0;\n}\n\n/**\n * Check if player has enough resources for action\n */\nexport function hasEnoughResources(\n player: PlayerState,\n kiCost: number,\n staminaCost: number,\n): boolean {\n return player.ki >= kiCost && player.stamina >= staminaCost;\n}\n\n/**\n * Get player archetype bonuses\n */\nexport function getArchetypeBonuses(archetype: PlayerArchetype): {\n attackBonus: number;\n defenseBonus: number;\n speedBonus: number;\n techniqueBonus: number;\n} {\n const data = PLAYER_ARCHETYPES_DATA[archetype];\n return {\n attackBonus: data.stats.attackPower * 0.1,\n defenseBonus: data.stats.defense * 0.1,\n speedBonus: data.stats.speed * 0.1,\n techniqueBonus: data.stats.technique * 0.1,\n };\n}\n\n/**\n * Calculate player's current combat effectiveness\n */\nexport function calculateCombatEffectiveness(player: PlayerState): number {\n const healthFactor = player.health / player.maxHealth;\n const staminaFactor = player.stamina / player.maxStamina;\n const consciousnessFactor = player.consciousness / 100;\n const balanceFactor = player.balance / 100;\n\n return (\n (healthFactor + staminaFactor + consciousnessFactor + balanceFactor) / 4\n );\n}\n\n/**\n * Remove expired status effects\n */\nexport function updateStatusEffects(\n player: PlayerState,\n currentTime: number,\n): PlayerState {\n const activeEffects = player.statusEffects.filter(\n (effect) => effect.endTime > currentTime,\n );\n\n return updatePlayerState(player, {\n statusEffects: activeEffects,\n activeEffects: activeEffects.map((e) => e.type),\n });\n}\n\n/**\n * Reset player to starting state\n */\nexport function resetPlayerState(\n archetype: PlayerArchetype,\n playerIndex: number,\n): PlayerState {\n return createPlayerFromArchetype(archetype, playerIndex);\n}\n\n/**\n * Get archetype asset paths (image, theme music, etc.)\n *\n * Note: PlayerArchetype enum values are already lowercase (e.g., MUSA = \"musa\"),\n * so the toLowerCase() call is defensive programming for type safety.\n */\nexport function getArchetypeAssets(archetype: PlayerArchetype): {\n readonly id: string;\n readonly image: string;\n readonly theme: string;\n readonly themeId: string;\n readonly name_korean: string;\n readonly name_english: string;\n readonly textureKey: string;\n} {\n const archetypeId = archetype.toLowerCase();\n const asset = ARCHETYPE_ASSETS[archetypeId as keyof typeof ARCHETYPE_ASSETS];\n\n if (!asset) {\n console.warn(`No assets found for archetype: ${archetype}`);\n return {\n id: \"unknown\",\n image: \"/assets/visual/logo/black-trigram-256.png\",\n theme: \"/assets/audio/music/intro_theme.mp3\",\n themeId: \"intro_theme\",\n name_korean: \"알 수 없음\",\n name_english: \"Unknown\",\n textureKey: archetype,\n };\n }\n\n return asset;\n}\n\n/**\n * Initialize body facing for a player based on opponent position\n *\n * Calculates initial facing angle to point toward opponent.\n *\n * @param playerPosition - Player's position\n * @param opponentPosition - Opponent's position\n * @returns Initial BodyFacing state\n * @korean 몸향하기초기화\n *\n * @example\n * ```typescript\n * const player = {\n * ...basePlayer,\n * bodyFacing: initializeBodyFacing(\n * { x: 300, y: 400 },\n * { x: 500, y: 400 }\n * ),\n * };\n * ```\n */\nexport function initializeBodyFacing(\n playerPosition: Position,\n opponentPosition: Position,\n): BodyFacing {\n // Calculate initial angle to face opponent\n const initialAngle = calculateAngleToTarget(playerPosition, opponentPosition);\n\n return createDefaultBodyFacing(initialAngle);\n}\n\n/**\n * Toggle the player's stance side (orthodox ↔ southpaw)\n *\n * Switches between orthodox (left foot forward) and southpaw (right foot forward).\n * This is used when executing stance switches or certain techniques.\n *\n * @param player - Current player state\n * @returns Updated player state with toggled stance side\n * @korean 자세측면전환\n *\n * @example\n * ```typescript\n * // Switch from orthodox to southpaw\n * const newState = toggleStanceSide(player);\n * // newState.stanceSide === 'southpaw'\n * ```\n */\nexport function toggleStanceSide(player: PlayerState): PlayerState {\n const newStanceSide = player.stanceSide === \"orthodox\" ? \"southpaw\" : \"orthodox\";\n\n return {\n ...player,\n stanceSide: newStanceSide,\n };\n}\n\n/**\n * Get the lead foot from stance side, defaulting to orthodox if not set\n *\n * @param player - Player state\n * @returns Lead foot ('left' for orthodox, 'right' for southpaw)\n * @korean 자세측면에서선발발가져오기\n */\nexport function getPlayerLeadFoot(player: PlayerState): \"left\" | \"right\" {\n return player.stanceSide === \"southpaw\" ? \"right\" : \"left\"; // Default to orthodox\n}\n\n/**\n * Get the rear (power) foot for a player\n *\n * @param player - Player state\n * @returns Rear foot ('right' for orthodox, 'left' for southpaw)\n * @korean 후발발가져오기\n */\nexport function getPlayerRearFoot(player: PlayerState): \"left\" | \"right\" {\n return player.stanceSide === \"southpaw\" ? \"left\" : \"right\";\n}\n"],"mappings":";;;;;;;;;;;;;;AAwBA,IAAM,2BAA2C;EAC9C,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,SAAS,cAAc;EACvB,SAAS,cAAc;EACvB,SAAS,WAAW;EACpB,SAAS,YAAY;EACrB,SAAS,WAAW;EACpB,SAAS,YAAY;CACvB;;;;;AAMD,IAAM,+BAAkD;EACrD,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,SAAS,cAAc;EACvB,SAAS,cAAc;EACvB,SAAS,WAAW;EACpB,SAAS,YAAY;EACrB,SAAS,WAAW;EACpB,SAAS,YAAY;CACvB;;;;AAKD,SAAgB,0BACd,WACA,aACa;CACb,MAAM,gBAAgB,uBAAuB;CAE7C,MAAM,eAAyB;EAC7B,GAAG,gBAAgB,IAAI,MAAM;EAC7B,GAAG;EACJ;CAED,OAAO;EACL,IAAI,UAAU,cAAc;EAC5B,MAAM,cAAc;EACpB;EAGA,oBAAoB,+BAA+B,UAAU;EAG7D,QAAQ,cAAc;EACtB,WAAW,cAAc;EACzB,gBAAgB,EAAE,GAAG,0BAA0B;EAC/C,mBAAmB,EAAE,GAAG,8BAA8B;EACtD,IAAI,cAAc;EAClB,OAAO,cAAc;EACrB,SAAS,cAAc;EACvB,YAAY,cAAc;EAC1B,QAAQ;EACR,WAAW;EAGX,aAAa,cAAc,MAAM;EACjC,SAAS,cAAc,MAAM;EAC7B,OAAO,cAAc,MAAM;EAC3B,WAAW,cAAc,MAAM;EAC/B,MAAM;EACN,eAAe;EACf,SAAS;EACT,UAAU;EAGV,eAAe,cAAc;EAC7B,aAAa,YAAY;EACzB,UAAU;EAGV,YAAY,gBAAgB,IAAI,aAAa;EAC7C,YAAY;EACZ,WAAW;EACX,cAAc;EACd,gBAAgB;EAChB,cAAc;EACd,sBAAsB;EAGtB,eAAe,EAAE;EACjB,eAAe,EAAE;EAGjB,aAAa,EAAE;EAGf,qBAAqB;EACrB,kBAAkB;EAClB,WAAW;EACX,YAAY;EACZ,gBAAgB;EAChB,gBAAgB;EACjB;;;;;AAMH,SAAgB,kBACd,QACA,SACa;CACb,OAAO;EACL,GAAG;EACH,GAAG;EAEH,QAAQ,KAAK,IACX,GACA,KAAK,IAAI,QAAQ,UAAU,OAAO,QAAQ,OAAO,UAAU,CAC5D;EACD,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,MAAM,OAAO,IAAI,OAAO,MAAM,CAAC;EAChE,SAAS,KAAK,IACZ,GACA,KAAK,IAAI,QAAQ,WAAW,OAAO,SAAS,OAAO,WAAW,CAC/D;EACD,eAAe,KAAK,IAClB,GACA,KAAK,IAAI,QAAQ,iBAAiB,OAAO,eAAe,IAAI,CAC7D;EACD,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,WAAW,OAAO,SAAS,IAAI,CAAC;EACvE;;;;;AAMH,SAAgB,YACd,QACA,QACA,aACa;CACb,MAAM,YAAY,KAAK,IAAI,GAAG,OAAO,SAAS,OAAO;CACrD,MAAM,eAAe,aAAa;CAElC,OAAO,kBAAkB,QAAQ;EAC/B,QAAQ;EACR,qBAAqB,OAAO,sBAAsB;EAClD,WAAW,OAAO,YAAY;EAC9B,aAAa,eAAe,YAAY,UAAU,OAAO;EACzD,eAAe,eAAe,IAAI,OAAO;EAC1C,CAAC;;;;;AAMJ,SAAgB,kBACd,QACA,QACa;CACb,MAAM,sBAAsB,OAAO,cAAc,WAC9C,MAAM,EAAE,SAAS,OAAO,QAAQ,CAAC,EAAE,UACrC;CAED,IAAI;CACJ,IAAI,uBAAuB,KAAK,CAAC,OAAO,WAAW;EAEjD,aAAa,CAAC,GAAG,OAAO,cAAc;EACtC,WAAW,uBAAuB;QAGlC,aAAa,CAAC,GAAG,OAAO,eAAe,OAAO;CAGhD,OAAO,kBAAkB,QAAQ;EAC/B,eAAe;EACf,eAAe,CAAC,GAAG,OAAO,eAAe,OAAO,KAAK;EACtD,CAAC;;;;;AAMJ,SAAgB,0BACd,QACA,cAMO;CACP,OAAO,OAAO,YAAY,MAAM,OAAO,GAAG,OAAO,aAAa,IAAI;;;;;AAMpE,SAAgB,aAAa,QAA8B;CACzD,IAAI,OAAO,UAAU,GAAG,OAAO;CAC/B,IAAI,OAAO,iBAAiB,GAAG,OAAO;CACtC,IAAI,OAAO,gBAAgB,YAAY,SAAS,OAAO;CACvD,IAAI,OAAO,WAAW,OAAO;CAC7B,OAAO;;;;;AAMT,SAAgB,uBACd,eACA,iBACQ;CAER,OAAO;;;;;AAMT,SAAgB,mBACd,QACA,QACA,aACS;CACT,OAAO,OAAO,MAAM,UAAU,OAAO,WAAW;;;;;AAMlD,SAAgB,oBAAoB,WAKlC;CACA,MAAM,OAAO,uBAAuB;CACpC,OAAO;EACL,aAAa,KAAK,MAAM,cAAc;EACtC,cAAc,KAAK,MAAM,UAAU;EACnC,YAAY,KAAK,MAAM,QAAQ;EAC/B,gBAAgB,KAAK,MAAM,YAAY;EACxC;;;;;AAMH,SAAgB,6BAA6B,QAA6B;CACxE,MAAM,eAAe,OAAO,SAAS,OAAO;CAC5C,MAAM,gBAAgB,OAAO,UAAU,OAAO;CAC9C,MAAM,sBAAsB,OAAO,gBAAgB;CACnD,MAAM,gBAAgB,OAAO,UAAU;CAEvC,QACG,eAAe,gBAAgB,sBAAsB,iBAAiB;;;;;AAO3E,SAAgB,oBACd,QACA,aACa;CACb,MAAM,gBAAgB,OAAO,cAAc,QACxC,WAAW,OAAO,UAAU,YAC9B;CAED,OAAO,kBAAkB,QAAQ;EAC/B,eAAe;EACf,eAAe,cAAc,KAAK,MAAM,EAAE,KAAK;EAChD,CAAC;;;;;AAMJ,SAAgB,iBACd,WACA,aACa;CACb,OAAO,0BAA0B,WAAW,YAAY;;;;;;;;AAS1D,SAAgB,mBAAmB,WAQjC;CAEA,MAAM,QAAQ,iBADM,UAAU,aACC;CAE/B,IAAI,CAAC,OAAO;EACV,QAAQ,KAAK,kCAAkC,YAAY;EAC3D,OAAO;GACL,IAAI;GACJ,OAAO;GACP,OAAO;GACP,SAAS;GACT,aAAa;GACb,cAAc;GACd,YAAY;GACb;;CAGH,OAAO;;;;;;;;;;;;;;;;;;;;;;;AAwBT,SAAgB,qBACd,gBACA,kBACY;CAIZ,OAAO,wBAFc,uBAAuB,gBAAgB,iBAE7B,CAAa;;;;;;;;;;;;;;;;;;;AAoB9C,SAAgB,iBAAiB,QAAkC;CACjE,MAAM,gBAAgB,OAAO,eAAe,aAAa,aAAa;CAEtE,OAAO;EACL,GAAG;EACH,YAAY;EACb;;;;;;;;;AAUH,SAAgB,kBAAkB,QAAuC;CACvE,OAAO,OAAO,eAAe,aAAa,UAAU;;;;;;;;;AAUtD,SAAgB,kBAAkB,QAAuC;CACvE,OAAO,OAAO,eAAe,aAAa,SAAS"}
|
|
1
|
+
{"version":3,"file":"playerUtils.js","names":[],"sources":["../../src/utils/playerUtils.ts"],"sourcesContent":["/**\n * Player state utilities and helper functions\n */\n\nimport { getArchetypePhysicalAttributes } from \"../data/archetypePhysicalAttributes\";\nimport { PLAYER_ARCHETYPES_DATA, PlayerState, StatusEffect } from \"../systems\";\nimport {\n calculateAngleToTarget,\n createDefaultBodyFacing,\n} from \"../systems/animation\";\nimport type { BodyFacing } from \"../systems/animation/core/types\";\nimport {\n BodyPart,\n BodyPartHealth,\n BodyPartMaxHealth,\n} from \"../systems/bodypart/types\";\nimport { PlayerArchetype, Position, TrigramStance } from \"../types\";\nimport { CombatState } from \"../types/common\";\nimport { ARCHETYPE_ASSETS } from \"../types/constants\";\n\n/**\n * Default body part health values (100 HP each)\n * @korean 기본 신체부위 체력\n */\nconst DEFAULT_BODY_PART_HEALTH: BodyPartHealth = {\n [BodyPart.HEAD]: 100,\n [BodyPart.NECK]: 100,\n [BodyPart.TORSO_UPPER]: 100,\n [BodyPart.TORSO_LOWER]: 100,\n [BodyPart.ARM_LEFT]: 100,\n [BodyPart.ARM_RIGHT]: 100,\n [BodyPart.LEG_LEFT]: 100,\n [BodyPart.LEG_RIGHT]: 100,\n};\n\n/**\n * Default body part max health values\n * @korean 기본 신체부위 최대체력\n */\nconst DEFAULT_BODY_PART_MAX_HEALTH: BodyPartMaxHealth = {\n [BodyPart.HEAD]: 100,\n [BodyPart.NECK]: 100,\n [BodyPart.TORSO_UPPER]: 100,\n [BodyPart.TORSO_LOWER]: 100,\n [BodyPart.ARM_LEFT]: 100,\n [BodyPart.ARM_RIGHT]: 100,\n [BodyPart.LEG_LEFT]: 100,\n [BodyPart.LEG_RIGHT]: 100,\n};\n\n/**\n * Create a complete PlayerState from archetype and player index\n */\nexport function createPlayerFromArchetype(\n archetype: PlayerArchetype,\n playerIndex: number,\n): PlayerState {\n const archetypeData = PLAYER_ARCHETYPES_DATA[archetype];\n\n const basePosition: Position = {\n x: playerIndex === 0 ? 300 : 500,\n y: 400,\n };\n\n return {\n id: `player_${playerIndex + 1}`,\n name: archetypeData.name,\n archetype,\n\n // Physical attributes loaded from archetype defaults\n physicalAttributes: getArchetypePhysicalAttributes(archetype),\n\n // Combat stats\n health: archetypeData.baseHealth,\n maxHealth: archetypeData.baseHealth,\n bodyPartHealth: { ...DEFAULT_BODY_PART_HEALTH },\n bodyPartMaxHealth: { ...DEFAULT_BODY_PART_MAX_HEALTH },\n ki: archetypeData.baseKi,\n maxKi: archetypeData.baseKi,\n stamina: archetypeData.baseStamina,\n maxStamina: archetypeData.baseStamina,\n energy: 100,\n maxEnergy: 100,\n\n // Combat attributes\n attackPower: archetypeData.stats.attackPower,\n defense: archetypeData.stats.defense,\n speed: archetypeData.stats.speed,\n technique: archetypeData.stats.technique,\n pain: 0,\n consciousness: 100,\n balance: 100,\n momentum: 0,\n\n // Combat state\n currentStance: archetypeData.coreStance,\n combatState: CombatState.IDLE,\n position: basePosition,\n // Default to orthodox/southpaw stance\n // Player 1 starts orthodox, Player 2 starts southpaw for facing each other\n stanceSide: playerIndex === 0 ? \"orthodox\" : \"southpaw\",\n isBlocking: false,\n isStunned: false,\n isCountering: false,\n lastActionTime: 0,\n recoveryTime: 0,\n lastStanceChangeTime: 0,\n\n // Status and effects\n statusEffects: [],\n activeEffects: [],\n\n // Vital points state\n vitalPoints: [],\n\n // Match statistics\n totalDamageReceived: 0,\n totalDamageDealt: 0,\n hitsTaken: 0,\n hitsLanded: 0,\n perfectStrikes: 0,\n vitalPointHits: 0,\n };\n}\n\n/**\n * Update player state with partial updates\n */\nexport function updatePlayerState(\n player: PlayerState,\n updates: Partial<PlayerState>,\n): PlayerState {\n return {\n ...player,\n ...updates,\n // Ensure vital constraints\n health: Math.max(\n 0,\n Math.min(updates.health ?? player.health, player.maxHealth),\n ),\n ki: Math.max(0, Math.min(updates.ki ?? player.ki, player.maxKi)),\n stamina: Math.max(\n 0,\n Math.min(updates.stamina ?? player.stamina, player.maxStamina),\n ),\n consciousness: Math.max(\n 0,\n Math.min(updates.consciousness ?? player.consciousness, 100),\n ),\n balance: Math.max(0, Math.min(updates.balance ?? player.balance, 100)),\n };\n}\n\n/**\n * Apply damage to player\n */\nexport function applyDamage(\n player: PlayerState,\n damage: number,\n _damageType?: string, // Fix: Add underscore for unused parameter\n): PlayerState {\n const newHealth = Math.max(0, player.health - damage);\n const isKnockedOut = newHealth <= 0;\n\n return updatePlayerState(player, {\n health: newHealth,\n totalDamageReceived: player.totalDamageReceived + damage,\n hitsTaken: player.hitsTaken + 1,\n combatState: isKnockedOut ? CombatState.STUNNED : player.combatState,\n consciousness: isKnockedOut ? 0 : player.consciousness,\n });\n}\n\n/**\n * Apply status effect to player\n */\nexport function applyStatusEffect(\n player: PlayerState,\n effect: StatusEffect,\n): PlayerState {\n const existingEffectIndex = player.statusEffects.findIndex(\n (e) => e.type === effect.type && !e.stackable,\n );\n\n let newEffects: StatusEffect[];\n if (existingEffectIndex >= 0 && !effect.stackable) {\n // Replace existing non-stackable effect\n newEffects = [...player.statusEffects];\n newEffects[existingEffectIndex] = effect;\n } else {\n // Add new effect\n newEffects = [...player.statusEffects, effect];\n }\n\n return updatePlayerState(player, {\n statusEffects: newEffects,\n activeEffects: [...player.activeEffects, effect.type],\n });\n}\n\n/**\n * Get vital point by ID (fix return type)\n */\nexport function getVitalPointByOnPlayerId(\n player: PlayerState,\n vitalPointId: string,\n): {\n readonly id: string;\n readonly isHit: boolean;\n readonly damage: number;\n readonly lastHitTime: number;\n} | null {\n return player.vitalPoints.find((vp) => vp.id === vitalPointId) ?? null;\n}\n\n/**\n * Check if player can act\n */\nexport function canPlayerAct(player: PlayerState): boolean {\n if (player.health <= 0) return false;\n if (player.consciousness <= 0) return false;\n if (player.combatState === CombatState.STUNNED) return false;\n if (player.isStunned) return false;\n return true;\n}\n\n/**\n * Get player's current stance effectiveness against opponent\n */\nexport function getStanceEffectiveness(\n _playerStance: TrigramStance, // Fix: Add underscore to unused parameter\n _opponentStance: TrigramStance, // Fix: Add underscore to unused parameter\n): number {\n // Basic implementation - could be enhanced with stance matrix\n return 1.0;\n}\n\n/**\n * Check if player has enough resources for action\n */\nexport function hasEnoughResources(\n player: PlayerState,\n kiCost: number,\n staminaCost: number,\n): boolean {\n return player.ki >= kiCost && player.stamina >= staminaCost;\n}\n\n/**\n * Get player archetype bonuses\n */\nexport function getArchetypeBonuses(archetype: PlayerArchetype): {\n attackBonus: number;\n defenseBonus: number;\n speedBonus: number;\n techniqueBonus: number;\n} {\n const data = PLAYER_ARCHETYPES_DATA[archetype];\n return {\n attackBonus: data.stats.attackPower * 0.1,\n defenseBonus: data.stats.defense * 0.1,\n speedBonus: data.stats.speed * 0.1,\n techniqueBonus: data.stats.technique * 0.1,\n };\n}\n\n/**\n * Calculate player's current combat effectiveness\n */\nexport function calculateCombatEffectiveness(player: PlayerState): number {\n const healthFactor = player.health / player.maxHealth;\n const staminaFactor = player.stamina / player.maxStamina;\n const consciousnessFactor = player.consciousness / 100;\n const balanceFactor = player.balance / 100;\n\n return (\n (healthFactor + staminaFactor + consciousnessFactor + balanceFactor) / 4\n );\n}\n\n/**\n * Remove expired status effects\n */\nexport function updateStatusEffects(\n player: PlayerState,\n currentTime: number,\n): PlayerState {\n const activeEffects = player.statusEffects.filter(\n (effect) => effect.endTime > currentTime,\n );\n\n return updatePlayerState(player, {\n statusEffects: activeEffects,\n activeEffects: activeEffects.map((e) => e.type),\n });\n}\n\n/**\n * Reset player to starting state\n */\nexport function resetPlayerState(\n archetype: PlayerArchetype,\n playerIndex: number,\n): PlayerState {\n return createPlayerFromArchetype(archetype, playerIndex);\n}\n\n/**\n * Get archetype asset paths (image, theme music, etc.)\n *\n * Note: PlayerArchetype enum values are already lowercase (e.g., MUSA = \"musa\"),\n * so the toLowerCase() call is defensive programming for type safety.\n */\nexport function getArchetypeAssets(archetype: PlayerArchetype): {\n readonly id: string;\n readonly image: string;\n readonly theme: string;\n readonly themeId: string;\n readonly name_korean: string;\n readonly name_english: string;\n readonly textureKey: string;\n} {\n const archetypeId = archetype.toLowerCase();\n const asset = ARCHETYPE_ASSETS[archetypeId as keyof typeof ARCHETYPE_ASSETS];\n\n if (!asset) {\n console.warn(`No assets found for archetype: ${archetype}`);\n return {\n id: \"unknown\",\n image: \"/assets/visual/logo/black-trigram-256.png\",\n theme: \"/assets/audio/music/intro_theme.mp3\",\n themeId: \"intro_theme\",\n name_korean: \"알 수 없음\",\n name_english: \"Unknown\",\n textureKey: archetype,\n };\n }\n\n return asset;\n}\n\n/**\n * Initialize body facing for a player based on opponent position\n *\n * Calculates initial facing angle to point toward opponent.\n *\n * @param playerPosition - Player's position\n * @param opponentPosition - Opponent's position\n * @returns Initial BodyFacing state\n * @korean 몸향하기초기화\n *\n * @example\n * ```typescript\n * const player = {\n * ...basePlayer,\n * bodyFacing: initializeBodyFacing(\n * { x: 300, y: 400 },\n * { x: 500, y: 400 }\n * ),\n * };\n * ```\n */\nexport function initializeBodyFacing(\n playerPosition: Position,\n opponentPosition: Position,\n): BodyFacing {\n // Calculate initial angle to face opponent\n const initialAngle = calculateAngleToTarget(playerPosition, opponentPosition);\n\n return createDefaultBodyFacing(initialAngle);\n}\n\n/**\n * Toggle the player's stance side (orthodox ↔ southpaw)\n *\n * Switches between orthodox (left foot forward) and southpaw (right foot forward).\n * This is used when executing stance switches or certain techniques.\n *\n * @param player - Current player state\n * @returns Updated player state with toggled stance side\n * @korean 자세측면전환\n *\n * @example\n * ```typescript\n * // Switch from orthodox to southpaw\n * const newState = toggleStanceSide(player);\n * // newState.stanceSide === 'southpaw'\n * ```\n */\nexport function toggleStanceSide(player: PlayerState): PlayerState {\n const newStanceSide = player.stanceSide === \"orthodox\" ? \"southpaw\" : \"orthodox\";\n\n return {\n ...player,\n stanceSide: newStanceSide,\n };\n}\n\n/**\n * Get the lead foot from stance side, defaulting to orthodox if not set\n *\n * @param player - Player state\n * @returns Lead foot ('left' for orthodox, 'right' for southpaw)\n * @korean 자세측면에서선발발가져오기\n */\nexport function getPlayerLeadFoot(player: PlayerState): \"left\" | \"right\" {\n return player.stanceSide === \"southpaw\" ? \"right\" : \"left\"; // Default to orthodox\n}\n\n/**\n * Get the rear (power) foot for a player\n *\n * @param player - Player state\n * @returns Rear foot ('right' for orthodox, 'left' for southpaw)\n * @korean 후발발가져오기\n */\nexport function getPlayerRearFoot(player: PlayerState): \"left\" | \"right\" {\n return player.stanceSide === \"southpaw\" ? \"left\" : \"right\";\n}\n"],"mappings":";;;;;;;;;;;;;;AAwBA,IAAM,2BAA2C;EAC9C,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,SAAS,cAAc;EACvB,SAAS,cAAc;EACvB,SAAS,WAAW;EACpB,SAAS,YAAY;EACrB,SAAS,WAAW;EACpB,SAAS,YAAY;AACxB;;;;;AAMA,IAAM,+BAAkD;EACrD,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,SAAS,cAAc;EACvB,SAAS,cAAc;EACvB,SAAS,WAAW;EACpB,SAAS,YAAY;EACrB,SAAS,WAAW;EACpB,SAAS,YAAY;AACxB;;;;AAKA,SAAgB,0BACd,WACA,aACa;CACb,MAAM,gBAAgB,uBAAuB;CAE7C,MAAM,eAAyB;EAC7B,GAAG,gBAAgB,IAAI,MAAM;EAC7B,GAAG;CACL;CAEA,OAAO;EACL,IAAI,UAAU,cAAc;EAC5B,MAAM,cAAc;EACpB;EAGA,oBAAoB,+BAA+B,SAAS;EAG5D,QAAQ,cAAc;EACtB,WAAW,cAAc;EACzB,gBAAgB,EAAE,GAAG,yBAAyB;EAC9C,mBAAmB,EAAE,GAAG,6BAA6B;EACrD,IAAI,cAAc;EAClB,OAAO,cAAc;EACrB,SAAS,cAAc;EACvB,YAAY,cAAc;EAC1B,QAAQ;EACR,WAAW;EAGX,aAAa,cAAc,MAAM;EACjC,SAAS,cAAc,MAAM;EAC7B,OAAO,cAAc,MAAM;EAC3B,WAAW,cAAc,MAAM;EAC/B,MAAM;EACN,eAAe;EACf,SAAS;EACT,UAAU;EAGV,eAAe,cAAc;EAC7B,aAAa,YAAY;EACzB,UAAU;EAGV,YAAY,gBAAgB,IAAI,aAAa;EAC7C,YAAY;EACZ,WAAW;EACX,cAAc;EACd,gBAAgB;EAChB,cAAc;EACd,sBAAsB;EAGtB,eAAe,CAAC;EAChB,eAAe,CAAC;EAGhB,aAAa,CAAC;EAGd,qBAAqB;EACrB,kBAAkB;EAClB,WAAW;EACX,YAAY;EACZ,gBAAgB;EAChB,gBAAgB;CAClB;AACF;;;;AAKA,SAAgB,kBACd,QACA,SACa;CACb,OAAO;EACL,GAAG;EACH,GAAG;EAEH,QAAQ,KAAK,IACX,GACA,KAAK,IAAI,QAAQ,UAAU,OAAO,QAAQ,OAAO,SAAS,CAC5D;EACA,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,MAAM,OAAO,IAAI,OAAO,KAAK,CAAC;EAC/D,SAAS,KAAK,IACZ,GACA,KAAK,IAAI,QAAQ,WAAW,OAAO,SAAS,OAAO,UAAU,CAC/D;EACA,eAAe,KAAK,IAClB,GACA,KAAK,IAAI,QAAQ,iBAAiB,OAAO,eAAe,GAAG,CAC7D;EACA,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,WAAW,OAAO,SAAS,GAAG,CAAC;CACvE;AACF;;;;AAKA,SAAgB,YACd,QACA,QACA,aACa;CACb,MAAM,YAAY,KAAK,IAAI,GAAG,OAAO,SAAS,MAAM;CACpD,MAAM,eAAe,aAAa;CAElC,OAAO,kBAAkB,QAAQ;EAC/B,QAAQ;EACR,qBAAqB,OAAO,sBAAsB;EAClD,WAAW,OAAO,YAAY;EAC9B,aAAa,eAAe,YAAY,UAAU,OAAO;EACzD,eAAe,eAAe,IAAI,OAAO;CAC3C,CAAC;AACH;;;;AAKA,SAAgB,kBACd,QACA,QACa;CACb,MAAM,sBAAsB,OAAO,cAAc,WAC9C,MAAM,EAAE,SAAS,OAAO,QAAQ,CAAC,EAAE,SACtC;CAEA,IAAI;CACJ,IAAI,uBAAuB,KAAK,CAAC,OAAO,WAAW;EAEjD,aAAa,CAAC,GAAG,OAAO,aAAa;EACrC,WAAW,uBAAuB;CACpC,OAEE,aAAa,CAAC,GAAG,OAAO,eAAe,MAAM;CAG/C,OAAO,kBAAkB,QAAQ;EAC/B,eAAe;EACf,eAAe,CAAC,GAAG,OAAO,eAAe,OAAO,IAAI;CACtD,CAAC;AACH;;;;AAKA,SAAgB,0BACd,QACA,cAMO;CACP,OAAO,OAAO,YAAY,MAAM,OAAO,GAAG,OAAO,YAAY,KAAK;AACpE;;;;AAKA,SAAgB,aAAa,QAA8B;CACzD,IAAI,OAAO,UAAU,GAAG,OAAO;CAC/B,IAAI,OAAO,iBAAiB,GAAG,OAAO;CACtC,IAAI,OAAO,gBAAgB,YAAY,SAAS,OAAO;CACvD,IAAI,OAAO,WAAW,OAAO;CAC7B,OAAO;AACT;;;;AAKA,SAAgB,uBACd,eACA,iBACQ;CAER,OAAO;AACT;;;;AAKA,SAAgB,mBACd,QACA,QACA,aACS;CACT,OAAO,OAAO,MAAM,UAAU,OAAO,WAAW;AAClD;;;;AAKA,SAAgB,oBAAoB,WAKlC;CACA,MAAM,OAAO,uBAAuB;CACpC,OAAO;EACL,aAAa,KAAK,MAAM,cAAc;EACtC,cAAc,KAAK,MAAM,UAAU;EACnC,YAAY,KAAK,MAAM,QAAQ;EAC/B,gBAAgB,KAAK,MAAM,YAAY;CACzC;AACF;;;;AAKA,SAAgB,6BAA6B,QAA6B;CACxE,MAAM,eAAe,OAAO,SAAS,OAAO;CAC5C,MAAM,gBAAgB,OAAO,UAAU,OAAO;CAC9C,MAAM,sBAAsB,OAAO,gBAAgB;CACnD,MAAM,gBAAgB,OAAO,UAAU;CAEvC,QACG,eAAe,gBAAgB,sBAAsB,iBAAiB;AAE3E;;;;AAKA,SAAgB,oBACd,QACA,aACa;CACb,MAAM,gBAAgB,OAAO,cAAc,QACxC,WAAW,OAAO,UAAU,WAC/B;CAEA,OAAO,kBAAkB,QAAQ;EAC/B,eAAe;EACf,eAAe,cAAc,KAAK,MAAM,EAAE,IAAI;CAChD,CAAC;AACH;;;;AAKA,SAAgB,iBACd,WACA,aACa;CACb,OAAO,0BAA0B,WAAW,WAAW;AACzD;;;;;;;AAQA,SAAgB,mBAAmB,WAQjC;CAEA,MAAM,QAAQ,iBADM,UAAU,YACC;CAE/B,IAAI,CAAC,OAAO;EACV,QAAQ,KAAK,kCAAkC,WAAW;EAC1D,OAAO;GACL,IAAI;GACJ,OAAO;GACP,OAAO;GACP,SAAS;GACT,aAAa;GACb,cAAc;GACd,YAAY;EACd;CACF;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,qBACd,gBACA,kBACY;CAIZ,OAAO,wBAFc,uBAAuB,gBAAgB,gBAE7B,CAAY;AAC7C;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,iBAAiB,QAAkC;CACjE,MAAM,gBAAgB,OAAO,eAAe,aAAa,aAAa;CAEtE,OAAO;EACL,GAAG;EACH,YAAY;CACd;AACF;;;;;;;;AASA,SAAgB,kBAAkB,QAAuC;CACvE,OAAO,OAAO,eAAe,aAAa,UAAU;AACtD;;;;;;;;AASA,SAAgB,kBAAkB,QAAuC;CACvE,OAAO,OAAO,eAAe,aAAa,SAAS;AACrD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responsiveLayout.js","names":[],"sources":["../../src/utils/responsiveLayout.ts"],"sourcesContent":["/**\n * Responsive Layout Utility Functions\n * \n * Helper functions for responsive layout calculations and touch target validation\n * \n * @module utils/responsiveLayout\n * @category Mobile UI\n * @korean 반응형레이아웃유틸\n */\n\n/**\n * Minimum touch target size according to iOS Human Interface Guidelines\n */\nexport const MIN_TOUCH_TARGET_SIZE = 44;\n\n/**\n * Convert a CSS percentage string (for example \"70%\") into a decimal ratio.\n *\n * Invalid or non-finite percentage values fall back to `1` so responsive\n * layout calculations preserve full-width behaviour instead of producing NaN.\n *\n * @param value - CSS percentage string\n * @returns Decimal ratio (e.g. \"70%\" -> 0.7)\n */\nexport function parsePercentageToRatio(value: string): number {\n const trimmedValue = value.trim();\n if (!trimmedValue.endsWith('%')) {\n return 1;\n }\n\n const percent = Number.parseFloat(trimmedValue.slice(0, -1));\n return Number.isFinite(percent) ? percent / 100 : 1;\n}\n\n/**\n * Validate if an element meets minimum touch target requirements\n * \n * @param width - Element width in pixels\n * @param height - Element height in pixels\n * @param minSize - Minimum size (default: 44px iOS guideline)\n * @returns True if element meets minimum requirements\n * \n * @example\n * ```typescript\n * const isValid = isValidTouchTarget(50, 50); // true\n * const isTooSmall = isValidTouchTarget(30, 30); // false\n * ```\n */\nexport function isValidTouchTarget(\n width: number,\n height: number,\n minSize: number = MIN_TOUCH_TARGET_SIZE\n): boolean {\n return width >= minSize && height >= minSize;\n}\n\n/**\n * Calculate optimal font size for given viewport width\n * Ensures minimum readable font sizes on mobile\n * \n * @param viewportWidth - Viewport width in pixels\n * @param baseSize - Base font size for desktop (default: 16)\n * @param minSize - Minimum font size for mobile (default: 14)\n * @returns Calculated font size in pixels\n * \n * @example\n * ```typescript\n * const fontSize = calculateFontSize(375, 16, 14); // 14px for mobile\n * const fontSize = calculateFontSize(1920, 16, 14); // 16px for desktop\n * ```\n */\nexport function calculateFontSize(\n viewportWidth: number,\n baseSize: number = 16,\n minSize: number = 14\n): number {\n if (viewportWidth < 768) {\n // Mobile: use minimum size or scale down slightly\n return Math.max(minSize, Math.floor(baseSize * 0.875));\n } else if (viewportWidth < 1024) {\n // Tablet: scale proportionally\n return Math.max(minSize, Math.floor(baseSize * 0.9375));\n }\n // Desktop: use base size\n return baseSize;\n}\n\n/**\n * Calculate element position within safe area\n * Ensures elements don't overlap notch or home indicator\n * \n * @param value - Position value in pixels\n * @param safeAreaInset - Safe area inset for that edge\n * @returns Adjusted position value\n * \n * @example\n * ```typescript\n * const top = calculateSafePosition(10, 44); // 54px (10 + 44)\n * const bottom = calculateSafePosition(20, 34); // 54px (20 + 34)\n * ```\n */\nexport function calculateSafePosition(\n value: number,\n safeAreaInset: number\n): number {\n return value + safeAreaInset;\n}\n\n/**\n * Calculate optimal HUD height for given viewport\n * Scales based on device type and orientation\n * \n * @param viewportWidth - Viewport width in pixels\n * @param isLandscape - Whether in landscape orientation\n * @returns HUD height in pixels\n * \n * @example\n * ```typescript\n * const hudHeight = calculateHUDHeight(375, false); // ~80px for mobile portrait\n * const hudHeight = calculateHUDHeight(667, true); // ~60px for mobile landscape\n * ```\n */\nexport function calculateHUDHeight(\n viewportWidth: number,\n isLandscape: boolean\n): number {\n const isMobile = viewportWidth < 768;\n const isSmallMobile = viewportWidth <= 375; // Changed to <= to match 375px iPhone SE\n\n if (isLandscape && isMobile) {\n // Landscape: minimize HUD to maximize gameplay area\n return isSmallMobile ? 60 : 70;\n }\n\n if (isMobile) {\n // Portrait mobile: compact but readable\n return isSmallMobile ? 80 : 95;\n }\n\n // Desktop/tablet: larger HUD with more information\n return 120;\n}\n\n/**\n * Calculate optimal control bar height for mobile\n * \n * @param isMobile - Whether on mobile device\n * @param isLandscape - Whether in landscape orientation\n * @returns Control bar height in pixels\n */\nexport function calculateControlsHeight(\n isMobile: boolean,\n isLandscape: boolean\n): number {\n if (!isMobile) {\n return 0; // Desktop uses keyboard\n }\n\n if (isLandscape) {\n return 100; // Minimal controls in landscape\n }\n\n return 130; // Full touch controls in portrait\n}\n\n/**\n * Calculate spacing between HUD elements\n * Ensures proper touch targets and visual hierarchy\n * \n * @param isMobile - Whether on mobile device\n * @param density - Spacing density ('compact' | 'normal' | 'spacious')\n * @returns Spacing value in pixels\n */\nexport function calculateSpacing(\n isMobile: boolean,\n density: 'compact' | 'normal' | 'spacious' = 'normal'\n): number {\n const baseSpacing = isMobile ? 8 : 12;\n\n switch (density) {\n case 'compact':\n return Math.floor(baseSpacing * 0.75);\n case 'spacious':\n return Math.ceil(baseSpacing * 1.5);\n case 'normal':\n default:\n return baseSpacing;\n }\n}\n\n/**\n * Calculate optimal progress bar dimensions\n * Ensures bars are touch-friendly on mobile\n * \n * @param isMobile - Whether on mobile device\n * @param type - Bar type ('health' | 'ki' | 'stamina')\n * @returns Bar dimensions { width, height }\n */\nexport function calculateProgressBarSize(\n isMobile: boolean,\n type: 'health' | 'ki' | 'stamina'\n): { width: number; height: number } {\n if (isMobile) {\n // Mobile: touch-friendly sizes\n const height = type === 'health' ? 48 : 40; // Health bar larger\n return {\n width: 160,\n height,\n };\n }\n\n // Desktop: more detailed display\n const height = type === 'health' ? 55 : 45;\n return {\n width: 220,\n height,\n };\n}\n\n/**\n * Get optimal grid layout for trigram stance selector\n * \n * @param isMobile - Whether on mobile device\n * @param isLandscape - Whether in landscape orientation\n * @returns Grid configuration { columns, rows, gap }\n */\nexport function getStanceSelectorLayout(\n isMobile: boolean,\n isLandscape: boolean\n): { columns: number; rows: number; gap: number } {\n if (isMobile) {\n if (isLandscape) {\n // Landscape: horizontal layout\n return { columns: 4, rows: 2, gap: 8 };\n }\n // Portrait: compact grid\n return { columns: 4, rows: 2, gap: 10 };\n }\n\n // Desktop: spacious layout\n return { columns: 4, rows: 2, gap: 15 };\n}\n\n/**\n * Resolution breakpoints for responsive design\n * @korean 반응형 중단점\n */\nexport interface ResolutionBreakpoints {\n readonly mobile: number; // 768px\n readonly tablet: number; // 1280px\n readonly desktop: number; // 1920px\n readonly ultrawide: number; // 2560px\n}\n\n/**\n * Standard breakpoints for responsive sizing\n */\nexport const BREAKPOINTS: ResolutionBreakpoints = Object.freeze({\n mobile: 768,\n tablet: 1280,\n desktop: 1920,\n ultrawide: 2560,\n});\n\n/**\n * Get responsive size based on screen width\n * Scales linearly between breakpoints\n * \n * @param width - Screen width in pixels\n * @param sizes - Size values at each breakpoint\n * @returns Interpolated size value\n * \n * @example\n * ```typescript\n * const fontSize = getResponsiveSize(1024, { mobile: 12, tablet: 14, desktop: 16 });\n * // Returns ~13.3 (interpolated between mobile and tablet)\n * ```\n */\nexport function getResponsiveSize(\n width: number,\n sizes: { mobile: number; tablet: number; desktop: number }\n): number {\n if (width < BREAKPOINTS.mobile) {\n return sizes.mobile;\n }\n if (width < BREAKPOINTS.tablet) {\n const ratio = (width - BREAKPOINTS.mobile) / (BREAKPOINTS.tablet - BREAKPOINTS.mobile);\n return sizes.mobile + (sizes.tablet - sizes.mobile) * ratio;\n }\n if (width < BREAKPOINTS.desktop) {\n const ratio = (width - BREAKPOINTS.tablet) / (BREAKPOINTS.desktop - BREAKPOINTS.tablet);\n return sizes.tablet + (sizes.desktop - sizes.tablet) * ratio;\n }\n return sizes.desktop;\n}\n\n/**\n * Calculate HUD height as percentage of screen height\n * Ensures minimum and maximum bounds for usability\n * \n * @param height - Screen height in pixels\n * @param percentage - Target percentage (0.0 - 1.0)\n * @returns Calculated HUD height with bounds applied\n * \n * @example\n * ```typescript\n * const hudHeight = getHUDHeight(1080, 0.08); // ~86px (8% of 1080)\n * const tooSmall = getHUDHeight(400, 0.08); // 40px (minimum applied)\n * const tooLarge = getHUDHeight(3000, 0.08); // 120px (maximum applied)\n * ```\n */\nexport function getHUDHeight(height: number, percentage: number): number {\n return Math.max(40, Math.min(120, height * percentage));\n}\n\n/**\n * Calculate padding based on resolution\n * \n * @param width - Screen width in pixels\n * @returns Padding value in pixels\n * \n * @example\n * ```typescript\n * const padding = getResponsivePadding(375); // 8px (mobile)\n * const padding = getResponsivePadding(1920); // 16px (desktop)\n * ```\n */\nexport function getResponsivePadding(width: number): number {\n return getResponsiveSize(width, { mobile: 8, tablet: 12, desktop: 16 });\n}\n\n/**\n * Calculate font size based on resolution\n * \n * @param width - Screen width in pixels\n * @returns Font size in pixels\n * \n * @example\n * ```typescript\n * const fontSize = getResponsiveFontSize(375); // 12px (mobile)\n * const fontSize = getResponsiveFontSize(1920); // 16px (desktop)\n * ```\n */\nexport function getResponsiveFontSize(width: number): number {\n return getResponsiveSize(width, { mobile: 12, tablet: 14, desktop: 16 });\n}\n\n/**\n * Determine if mobile controls should be shown\n * NOTE: This is the ONLY valid use of isMobile prop\n * \n * @param width - Screen width in pixels\n * @param isMobile - Optional mobile device flag\n * @returns Whether to show mobile controls\n * \n * @example\n * ```typescript\n * const showControls = shouldShowMobileControls(500, false); // true (narrow screen)\n * const showControls = shouldShowMobileControls(1920, false); // false (wide screen)\n * const showControls = shouldShowMobileControls(1920, true); // true (mobile device)\n * ```\n */\nexport function shouldShowMobileControls(width: number, isMobile: boolean = false): boolean {\n return isMobile || width < BREAKPOINTS.mobile;\n}\n"],"mappings":";;;;;;;;;;AAwBA,SAAgB,uBAAuB,OAAuB;CAC5D,MAAM,eAAe,MAAM,MAAM;CACjC,IAAI,CAAC,aAAa,SAAS,IAAI,EAC7B,OAAO;CAGT,MAAM,UAAU,OAAO,WAAW,aAAa,MAAM,GAAG,GAAG,CAAC;CAC5D,OAAO,OAAO,SAAS,QAAQ,GAAG,UAAU,MAAM;;;;;AAkOpD,IAAa,cAAqC,OAAO,OAAO;CAC9D,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,WAAW;CACZ,CAAC;;;;;;;;;;;;;;;AAgBF,SAAgB,kBACd,OACA,OACQ;CACR,IAAI,QAAQ,YAAY,QACtB,OAAO,MAAM;CAEf,IAAI,QAAQ,YAAY,QAAQ;EAC9B,MAAM,SAAS,QAAQ,YAAY,WAAW,YAAY,SAAS,YAAY;EAC/E,OAAO,MAAM,UAAU,MAAM,SAAS,MAAM,UAAU;;CAExD,IAAI,QAAQ,YAAY,SAAS;EAC/B,MAAM,SAAS,QAAQ,YAAY,WAAW,YAAY,UAAU,YAAY;EAChF,OAAO,MAAM,UAAU,MAAM,UAAU,MAAM,UAAU;;CAEzD,OAAO,MAAM;;;;;;;;;;;;;;;;;AAkBf,SAAgB,aAAa,QAAgB,YAA4B;CACvE,OAAO,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,SAAS,WAAW,CAAC;;;;;;;;;;;;;;AAezD,SAAgB,qBAAqB,OAAuB;CAC1D,OAAO,kBAAkB,OAAO;EAAE,QAAQ;EAAG,QAAQ;EAAI,SAAS;EAAI,CAAC;;;;;;;;;;;;;;AAezE,SAAgB,sBAAsB,OAAuB;CAC3D,OAAO,kBAAkB,OAAO;EAAE,QAAQ;EAAI,QAAQ;EAAI,SAAS;EAAI,CAAC;;;;;;;;;;;;;;;;;AAkB1E,SAAgB,yBAAyB,OAAe,WAAoB,OAAgB;CAC1F,OAAO,YAAY,QAAQ,YAAY"}
|
|
1
|
+
{"version":3,"file":"responsiveLayout.js","names":[],"sources":["../../src/utils/responsiveLayout.ts"],"sourcesContent":["/**\n * Responsive Layout Utility Functions\n * \n * Helper functions for responsive layout calculations and touch target validation\n * \n * @module utils/responsiveLayout\n * @category Mobile UI\n * @korean 반응형레이아웃유틸\n */\n\n/**\n * Minimum touch target size according to iOS Human Interface Guidelines\n */\nexport const MIN_TOUCH_TARGET_SIZE = 44;\n\n/**\n * Convert a CSS percentage string (for example \"70%\") into a decimal ratio.\n *\n * Invalid or non-finite percentage values fall back to `1` so responsive\n * layout calculations preserve full-width behaviour instead of producing NaN.\n *\n * @param value - CSS percentage string\n * @returns Decimal ratio (e.g. \"70%\" -> 0.7)\n */\nexport function parsePercentageToRatio(value: string): number {\n const trimmedValue = value.trim();\n if (!trimmedValue.endsWith('%')) {\n return 1;\n }\n\n const percent = Number.parseFloat(trimmedValue.slice(0, -1));\n return Number.isFinite(percent) ? percent / 100 : 1;\n}\n\n/**\n * Validate if an element meets minimum touch target requirements\n * \n * @param width - Element width in pixels\n * @param height - Element height in pixels\n * @param minSize - Minimum size (default: 44px iOS guideline)\n * @returns True if element meets minimum requirements\n * \n * @example\n * ```typescript\n * const isValid = isValidTouchTarget(50, 50); // true\n * const isTooSmall = isValidTouchTarget(30, 30); // false\n * ```\n */\nexport function isValidTouchTarget(\n width: number,\n height: number,\n minSize: number = MIN_TOUCH_TARGET_SIZE\n): boolean {\n return width >= minSize && height >= minSize;\n}\n\n/**\n * Calculate optimal font size for given viewport width\n * Ensures minimum readable font sizes on mobile\n * \n * @param viewportWidth - Viewport width in pixels\n * @param baseSize - Base font size for desktop (default: 16)\n * @param minSize - Minimum font size for mobile (default: 14)\n * @returns Calculated font size in pixels\n * \n * @example\n * ```typescript\n * const fontSize = calculateFontSize(375, 16, 14); // 14px for mobile\n * const fontSize = calculateFontSize(1920, 16, 14); // 16px for desktop\n * ```\n */\nexport function calculateFontSize(\n viewportWidth: number,\n baseSize: number = 16,\n minSize: number = 14\n): number {\n if (viewportWidth < 768) {\n // Mobile: use minimum size or scale down slightly\n return Math.max(minSize, Math.floor(baseSize * 0.875));\n } else if (viewportWidth < 1024) {\n // Tablet: scale proportionally\n return Math.max(minSize, Math.floor(baseSize * 0.9375));\n }\n // Desktop: use base size\n return baseSize;\n}\n\n/**\n * Calculate element position within safe area\n * Ensures elements don't overlap notch or home indicator\n * \n * @param value - Position value in pixels\n * @param safeAreaInset - Safe area inset for that edge\n * @returns Adjusted position value\n * \n * @example\n * ```typescript\n * const top = calculateSafePosition(10, 44); // 54px (10 + 44)\n * const bottom = calculateSafePosition(20, 34); // 54px (20 + 34)\n * ```\n */\nexport function calculateSafePosition(\n value: number,\n safeAreaInset: number\n): number {\n return value + safeAreaInset;\n}\n\n/**\n * Calculate optimal HUD height for given viewport\n * Scales based on device type and orientation\n * \n * @param viewportWidth - Viewport width in pixels\n * @param isLandscape - Whether in landscape orientation\n * @returns HUD height in pixels\n * \n * @example\n * ```typescript\n * const hudHeight = calculateHUDHeight(375, false); // ~80px for mobile portrait\n * const hudHeight = calculateHUDHeight(667, true); // ~60px for mobile landscape\n * ```\n */\nexport function calculateHUDHeight(\n viewportWidth: number,\n isLandscape: boolean\n): number {\n const isMobile = viewportWidth < 768;\n const isSmallMobile = viewportWidth <= 375; // Changed to <= to match 375px iPhone SE\n\n if (isLandscape && isMobile) {\n // Landscape: minimize HUD to maximize gameplay area\n return isSmallMobile ? 60 : 70;\n }\n\n if (isMobile) {\n // Portrait mobile: compact but readable\n return isSmallMobile ? 80 : 95;\n }\n\n // Desktop/tablet: larger HUD with more information\n return 120;\n}\n\n/**\n * Calculate optimal control bar height for mobile\n * \n * @param isMobile - Whether on mobile device\n * @param isLandscape - Whether in landscape orientation\n * @returns Control bar height in pixels\n */\nexport function calculateControlsHeight(\n isMobile: boolean,\n isLandscape: boolean\n): number {\n if (!isMobile) {\n return 0; // Desktop uses keyboard\n }\n\n if (isLandscape) {\n return 100; // Minimal controls in landscape\n }\n\n return 130; // Full touch controls in portrait\n}\n\n/**\n * Calculate spacing between HUD elements\n * Ensures proper touch targets and visual hierarchy\n * \n * @param isMobile - Whether on mobile device\n * @param density - Spacing density ('compact' | 'normal' | 'spacious')\n * @returns Spacing value in pixels\n */\nexport function calculateSpacing(\n isMobile: boolean,\n density: 'compact' | 'normal' | 'spacious' = 'normal'\n): number {\n const baseSpacing = isMobile ? 8 : 12;\n\n switch (density) {\n case 'compact':\n return Math.floor(baseSpacing * 0.75);\n case 'spacious':\n return Math.ceil(baseSpacing * 1.5);\n case 'normal':\n default:\n return baseSpacing;\n }\n}\n\n/**\n * Calculate optimal progress bar dimensions\n * Ensures bars are touch-friendly on mobile\n * \n * @param isMobile - Whether on mobile device\n * @param type - Bar type ('health' | 'ki' | 'stamina')\n * @returns Bar dimensions { width, height }\n */\nexport function calculateProgressBarSize(\n isMobile: boolean,\n type: 'health' | 'ki' | 'stamina'\n): { width: number; height: number } {\n if (isMobile) {\n // Mobile: touch-friendly sizes\n const height = type === 'health' ? 48 : 40; // Health bar larger\n return {\n width: 160,\n height,\n };\n }\n\n // Desktop: more detailed display\n const height = type === 'health' ? 55 : 45;\n return {\n width: 220,\n height,\n };\n}\n\n/**\n * Get optimal grid layout for trigram stance selector\n * \n * @param isMobile - Whether on mobile device\n * @param isLandscape - Whether in landscape orientation\n * @returns Grid configuration { columns, rows, gap }\n */\nexport function getStanceSelectorLayout(\n isMobile: boolean,\n isLandscape: boolean\n): { columns: number; rows: number; gap: number } {\n if (isMobile) {\n if (isLandscape) {\n // Landscape: horizontal layout\n return { columns: 4, rows: 2, gap: 8 };\n }\n // Portrait: compact grid\n return { columns: 4, rows: 2, gap: 10 };\n }\n\n // Desktop: spacious layout\n return { columns: 4, rows: 2, gap: 15 };\n}\n\n/**\n * Resolution breakpoints for responsive design\n * @korean 반응형 중단점\n */\nexport interface ResolutionBreakpoints {\n readonly mobile: number; // 768px\n readonly tablet: number; // 1280px\n readonly desktop: number; // 1920px\n readonly ultrawide: number; // 2560px\n}\n\n/**\n * Standard breakpoints for responsive sizing\n */\nexport const BREAKPOINTS: ResolutionBreakpoints = Object.freeze({\n mobile: 768,\n tablet: 1280,\n desktop: 1920,\n ultrawide: 2560,\n});\n\n/**\n * Get responsive size based on screen width\n * Scales linearly between breakpoints\n * \n * @param width - Screen width in pixels\n * @param sizes - Size values at each breakpoint\n * @returns Interpolated size value\n * \n * @example\n * ```typescript\n * const fontSize = getResponsiveSize(1024, { mobile: 12, tablet: 14, desktop: 16 });\n * // Returns ~13.3 (interpolated between mobile and tablet)\n * ```\n */\nexport function getResponsiveSize(\n width: number,\n sizes: { mobile: number; tablet: number; desktop: number }\n): number {\n if (width < BREAKPOINTS.mobile) {\n return sizes.mobile;\n }\n if (width < BREAKPOINTS.tablet) {\n const ratio = (width - BREAKPOINTS.mobile) / (BREAKPOINTS.tablet - BREAKPOINTS.mobile);\n return sizes.mobile + (sizes.tablet - sizes.mobile) * ratio;\n }\n if (width < BREAKPOINTS.desktop) {\n const ratio = (width - BREAKPOINTS.tablet) / (BREAKPOINTS.desktop - BREAKPOINTS.tablet);\n return sizes.tablet + (sizes.desktop - sizes.tablet) * ratio;\n }\n return sizes.desktop;\n}\n\n/**\n * Calculate HUD height as percentage of screen height\n * Ensures minimum and maximum bounds for usability\n * \n * @param height - Screen height in pixels\n * @param percentage - Target percentage (0.0 - 1.0)\n * @returns Calculated HUD height with bounds applied\n * \n * @example\n * ```typescript\n * const hudHeight = getHUDHeight(1080, 0.08); // ~86px (8% of 1080)\n * const tooSmall = getHUDHeight(400, 0.08); // 40px (minimum applied)\n * const tooLarge = getHUDHeight(3000, 0.08); // 120px (maximum applied)\n * ```\n */\nexport function getHUDHeight(height: number, percentage: number): number {\n return Math.max(40, Math.min(120, height * percentage));\n}\n\n/**\n * Calculate padding based on resolution\n * \n * @param width - Screen width in pixels\n * @returns Padding value in pixels\n * \n * @example\n * ```typescript\n * const padding = getResponsivePadding(375); // 8px (mobile)\n * const padding = getResponsivePadding(1920); // 16px (desktop)\n * ```\n */\nexport function getResponsivePadding(width: number): number {\n return getResponsiveSize(width, { mobile: 8, tablet: 12, desktop: 16 });\n}\n\n/**\n * Calculate font size based on resolution\n * \n * @param width - Screen width in pixels\n * @returns Font size in pixels\n * \n * @example\n * ```typescript\n * const fontSize = getResponsiveFontSize(375); // 12px (mobile)\n * const fontSize = getResponsiveFontSize(1920); // 16px (desktop)\n * ```\n */\nexport function getResponsiveFontSize(width: number): number {\n return getResponsiveSize(width, { mobile: 12, tablet: 14, desktop: 16 });\n}\n\n/**\n * Determine if mobile controls should be shown\n * NOTE: This is the ONLY valid use of isMobile prop\n * \n * @param width - Screen width in pixels\n * @param isMobile - Optional mobile device flag\n * @returns Whether to show mobile controls\n * \n * @example\n * ```typescript\n * const showControls = shouldShowMobileControls(500, false); // true (narrow screen)\n * const showControls = shouldShowMobileControls(1920, false); // false (wide screen)\n * const showControls = shouldShowMobileControls(1920, true); // true (mobile device)\n * ```\n */\nexport function shouldShowMobileControls(width: number, isMobile: boolean = false): boolean {\n return isMobile || width < BREAKPOINTS.mobile;\n}\n"],"mappings":";;;;;;;;;;AAwBA,SAAgB,uBAAuB,OAAuB;CAC5D,MAAM,eAAe,MAAM,KAAK;CAChC,IAAI,CAAC,aAAa,SAAS,GAAG,GAC5B,OAAO;CAGT,MAAM,UAAU,OAAO,WAAW,aAAa,MAAM,GAAG,EAAE,CAAC;CAC3D,OAAO,OAAO,SAAS,OAAO,IAAI,UAAU,MAAM;AACpD;;;;AAiOA,IAAa,cAAqC,OAAO,OAAO;CAC9D,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,WAAW;AACb,CAAC;;;;;;;;;;;;;;;AAgBD,SAAgB,kBACd,OACA,OACQ;CACR,IAAI,QAAQ,YAAY,QACtB,OAAO,MAAM;CAEf,IAAI,QAAQ,YAAY,QAAQ;EAC9B,MAAM,SAAS,QAAQ,YAAY,WAAW,YAAY,SAAS,YAAY;EAC/E,OAAO,MAAM,UAAU,MAAM,SAAS,MAAM,UAAU;CACxD;CACA,IAAI,QAAQ,YAAY,SAAS;EAC/B,MAAM,SAAS,QAAQ,YAAY,WAAW,YAAY,UAAU,YAAY;EAChF,OAAO,MAAM,UAAU,MAAM,UAAU,MAAM,UAAU;CACzD;CACA,OAAO,MAAM;AACf;;;;;;;;;;;;;;;;AAiBA,SAAgB,aAAa,QAAgB,YAA4B;CACvE,OAAO,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,SAAS,UAAU,CAAC;AACxD;;;;;;;;;;;;;AAcA,SAAgB,qBAAqB,OAAuB;CAC1D,OAAO,kBAAkB,OAAO;EAAE,QAAQ;EAAG,QAAQ;EAAI,SAAS;CAAG,CAAC;AACxE;;;;;;;;;;;;;AAcA,SAAgB,sBAAsB,OAAuB;CAC3D,OAAO,kBAAkB,OAAO;EAAE,QAAQ;EAAI,QAAQ;EAAI,SAAS;CAAG,CAAC;AACzE;;;;;;;;;;;;;;;;AAiBA,SAAgB,yBAAyB,OAAe,WAAoB,OAAgB;CAC1F,OAAO,YAAY,QAAQ,YAAY;AACzC"}
|
|
@@ -18,6 +18,13 @@ import type { ScreenSize } from '../systems/ResponsiveScaling';
|
|
|
18
18
|
*
|
|
19
19
|
*/
|
|
20
20
|
export declare function getDesktopArenaWidthBudget(width: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* Shared HUD position scale for combat and training screens.
|
|
23
|
+
*
|
|
24
|
+
* Keeps top/bottom HUDs, side HUD offsets, and arena reservations synchronized
|
|
25
|
+
* on large and 4K displays.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getHUDPositionScale(screenSize: ScreenSize, isMobile: boolean): number;
|
|
21
28
|
/**
|
|
22
29
|
* Calculate responsive padding value
|
|
23
30
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responsiveLayoutHelpers.d.ts","sourceRoot":"","sources":["../../src/utils/responsiveLayoutHelpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAa/D;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhE;AAiDD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAExE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAExE;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAE1E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEtE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM;;;;;;EAU/C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO;;;;;;;EA8DzE"}
|
|
1
|
+
{"version":3,"file":"responsiveLayoutHelpers.d.ts","sourceRoot":"","sources":["../../src/utils/responsiveLayoutHelpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAa/D;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,OAAO,GAChB,MAAM,CAgBR;AAiDD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAExE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAExE;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAE1E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEtE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM;;;;;;EAU/C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO;;;;;;;EA8DzE"}
|
|
@@ -12,7 +12,7 @@ import { getScreenSize } from "../systems/ResponsiveScaling.js";
|
|
|
12
12
|
* @korean 반응형레이아웃도우미
|
|
13
13
|
*/
|
|
14
14
|
/** Desktop arena width as a proportion of viewport width. */
|
|
15
|
-
var DESKTOP_ARENA_WIDTH_RATIO = .
|
|
15
|
+
var DESKTOP_ARENA_WIDTH_RATIO = .68;
|
|
16
16
|
/**
|
|
17
17
|
* Maximum desktop arena width in CSS pixels.
|
|
18
18
|
*
|
|
@@ -31,6 +31,20 @@ function getDesktopArenaWidthBudget(width) {
|
|
|
31
31
|
return Math.min(width * DESKTOP_ARENA_WIDTH_RATIO, DESKTOP_ARENA_MAX_WIDTH_PX);
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
+
* Shared HUD position scale for combat and training screens.
|
|
35
|
+
*
|
|
36
|
+
* Keeps top/bottom HUDs, side HUD offsets, and arena reservations synchronized
|
|
37
|
+
* on large and 4K displays.
|
|
38
|
+
*/
|
|
39
|
+
function getHUDPositionScale(screenSize, isMobile) {
|
|
40
|
+
if (isMobile) return 1;
|
|
41
|
+
switch (screenSize) {
|
|
42
|
+
case "large": return 1.25;
|
|
43
|
+
case "xlarge": return 1.5;
|
|
44
|
+
default: return 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
34
48
|
* Base layout values for different screen sizes
|
|
35
49
|
* These serve as reference values that scale proportionally
|
|
36
50
|
*/
|
|
@@ -203,6 +217,6 @@ function getCombatLayoutConstants(width, isMobile) {
|
|
|
203
217
|
};
|
|
204
218
|
}
|
|
205
219
|
//#endregion
|
|
206
|
-
export { getCombatLayoutConstants, getDesktopArenaWidthBudget, getLayoutConstants };
|
|
220
|
+
export { getCombatLayoutConstants, getDesktopArenaWidthBudget, getHUDPositionScale, getLayoutConstants };
|
|
207
221
|
|
|
208
222
|
//# sourceMappingURL=responsiveLayoutHelpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responsiveLayoutHelpers.js","names":[],"sources":["../../src/utils/responsiveLayoutHelpers.ts"],"sourcesContent":["/**\n * Responsive Layout Helpers\n * \n * Centralized utilities for calculating responsive layout constants\n * across different screen components. Uses the centralized ResponsiveScaling\n * system for consistent scaling patterns.\n * \n * @module utils/responsiveLayoutHelpers\n * @category Layout\n * @korean 반응형레이아웃도우미\n */\n\nimport { getScreenSize } from '../systems/ResponsiveScaling';\nimport type { ScreenSize } from '../systems/ResponsiveScaling';\n\n/** Desktop arena width as a proportion of viewport width. */\nconst DESKTOP_ARENA_WIDTH_RATIO = 0.
|
|
1
|
+
{"version":3,"file":"responsiveLayoutHelpers.js","names":[],"sources":["../../src/utils/responsiveLayoutHelpers.ts"],"sourcesContent":["/**\n * Responsive Layout Helpers\n * \n * Centralized utilities for calculating responsive layout constants\n * across different screen components. Uses the centralized ResponsiveScaling\n * system for consistent scaling patterns.\n * \n * @module utils/responsiveLayoutHelpers\n * @category Layout\n * @korean 반응형레이아웃도우미\n */\n\nimport { getScreenSize } from '../systems/ResponsiveScaling';\nimport type { ScreenSize } from '../systems/ResponsiveScaling';\n\n/** Desktop arena width as a proportion of viewport width. */\nconst DESKTOP_ARENA_WIDTH_RATIO = 0.68;\n\n/**\n * Maximum desktop arena width in CSS pixels.\n *\n * Caps ultra-wide/8K displays to protect WebGL fill-rate while preserving a\n * large, readable 4K desktop arena.\n */\nconst DESKTOP_ARENA_MAX_WIDTH_PX = 2560;\n\n/**\n * Calculate maximum desktop arena width for combat/training screens.\n *\n * @param width - Viewport width in CSS pixels\n * @returns Width budget for the 4:3 desktop arena\n *\n */\nexport function getDesktopArenaWidthBudget(width: number): number {\n return Math.min(width * DESKTOP_ARENA_WIDTH_RATIO, DESKTOP_ARENA_MAX_WIDTH_PX);\n}\n\n/**\n * Shared HUD position scale for combat and training screens.\n *\n * Keeps top/bottom HUDs, side HUD offsets, and arena reservations synchronized\n * on large and 4K displays.\n */\nexport function getHUDPositionScale(\n screenSize: ScreenSize,\n isMobile: boolean,\n): number {\n if (isMobile) {\n return 1.0;\n }\n\n switch (screenSize) {\n case \"large\":\n return 1.25;\n case \"xlarge\":\n return 1.5;\n case \"mobile\":\n case \"tablet\":\n case \"desktop\":\n default:\n return 1.0;\n }\n}\n\n/**\n * Base layout values for different screen sizes\n * These serve as reference values that scale proportionally\n */\nconst BASE_LAYOUT_VALUES = {\n // Base padding values (desktop reference)\n padding: {\n mobile: 20,\n tablet: 25,\n desktop: 30,\n large: 32,\n xlarge: 35,\n },\n // Base header height values\n headerHeight: {\n mobile: 90,\n tablet: 100,\n desktop: 110,\n large: 115,\n xlarge: 120,\n },\n // Base footer height values\n footerHeight: {\n mobile: 75,\n tablet: 85,\n desktop: 90,\n large: 95,\n xlarge: 100,\n },\n // Base section spacing values\n sectionSpacing: {\n mobile: 15,\n tablet: 18,\n desktop: 20,\n large: 22,\n xlarge: 25,\n },\n // Base button area values\n buttonArea: {\n mobile: 75,\n tablet: 85,\n desktop: 95,\n large: 102,\n xlarge: 110,\n },\n} as const;\n\n/**\n * Calculate responsive padding value\n * \n * @param screenSize - Current screen size category\n * @returns Calculated padding in pixels\n * \n */\nexport function getResponsivePadding(screenSize: ScreenSize): number {\n return BASE_LAYOUT_VALUES.padding[screenSize];\n}\n\n/**\n * Calculate responsive header height value\n * \n * @param screenSize - Current screen size category\n * @returns Calculated header height in pixels\n * \n */\nexport function getResponsiveHeaderHeight(screenSize: ScreenSize): number {\n return BASE_LAYOUT_VALUES.headerHeight[screenSize];\n}\n\n/**\n * Calculate responsive footer height value\n * \n * @param screenSize - Current screen size category\n * @returns Calculated footer height in pixels\n * \n */\nexport function getResponsiveFooterHeight(screenSize: ScreenSize): number {\n return BASE_LAYOUT_VALUES.footerHeight[screenSize];\n}\n\n/**\n * Calculate responsive section spacing value\n * \n * @param screenSize - Current screen size category\n * @returns Calculated section spacing in pixels\n * \n */\nexport function getResponsiveSectionSpacing(screenSize: ScreenSize): number {\n return BASE_LAYOUT_VALUES.sectionSpacing[screenSize];\n}\n\n/**\n * Calculate responsive button area value\n * \n * @param screenSize - Current screen size category\n * @returns Calculated button area in pixels\n * \n */\nexport function getResponsiveButtonArea(screenSize: ScreenSize): number {\n return BASE_LAYOUT_VALUES.buttonArea[screenSize];\n}\n\n/**\n * Get all layout constants for a given screen size\n * Convenient helper that returns all layout values at once\n * \n * @param width - Screen width in pixels\n * @returns Object with all layout constant values\n * \n */\nexport function getLayoutConstants(width: number) {\n const screenSize = getScreenSize(width);\n \n return {\n padding: getResponsivePadding(screenSize),\n headerHeight: getResponsiveHeaderHeight(screenSize),\n footerHeight: getResponsiveFooterHeight(screenSize),\n sectionSpacing: getResponsiveSectionSpacing(screenSize),\n buttonArea: getResponsiveButtonArea(screenSize),\n };\n}\n\n/**\n * Get combat-specific layout constants for a given screen size\n * \n * Optimized for narrow devices (<450px), with extra-small device support\n * explicitly tuned for ultra-small screens (<380px) like iPhone SE, old\n * Android phones, and budget smartphones.\n * \n * Now properly handles high-resolution mobile devices (2K+, Super HD) by\n * checking isMobile flag to ensure they get mobile-optimized layout values\n * regardless of pixel width.\n * \n * @param width - Screen width in pixels\n * @param isMobile - Optional: Whether device is mobile (from user-agent detection)\n * @returns Object with combat layout constant values\n * \n */\nexport function getCombatLayoutConstants(width: number, isMobile?: boolean) {\n // For mobile devices, force 'mobile' screen size regardless of pixel width\n // This ensures high-res mobile devices (2K+) get mobile-optimized layouts\n const screenSize = isMobile ? 'mobile' : getScreenSize(width);\n \n // Extra-small detection for low-end mobile devices (<380px)\n const isExtraSmall = isMobile && width < 380;\n \n // Combat screen uses different base values for compact HUD\n const hudHeightMap = {\n mobile: isExtraSmall ? 85 : 95,\n tablet: 100,\n desktop: 130,\n large: 135,\n xlarge: 140,\n };\n \n // Note: Tablet optimizations - controlsHeight and footerHeight are intentionally\n // smaller on tablets than mobile for better landscape orientation ergonomics.\n // Mobile (portrait) needs taller controls for thumb reach, while tablets\n // (often landscape) can use more compact controls with better screen utilization.\n const controlsHeightMap = {\n mobile: isExtraSmall ? 150 : 160, // Taller for portrait thumb reach\n tablet: 140, // Optimized for landscape - more compact\n desktop: 170,\n large: 175,\n xlarge: 180,\n };\n \n const footerHeightMap = {\n mobile: 34, // Adequate for portrait orientation\n tablet: 30, // Optimized for landscape - more compact\n desktop: 35,\n large: 37,\n xlarge: 40,\n };\n \n const healthBarHeightMap = {\n mobile: 48,\n tablet: 50,\n desktop: 65,\n large: 67,\n xlarge: 70,\n };\n \n // Touch target heights - WCAG AA compliance (minimum 44px)\n const buttonHeightMap = {\n mobile: isExtraSmall ? 48 : 55, // Minimum 48px for extra-small\n tablet: 55,\n desktop: 60,\n large: 60,\n xlarge: 60,\n };\n \n return {\n padding: isExtraSmall ? 8 : 10, // Reduced padding for extra-small\n hudHeight: hudHeightMap[screenSize],\n controlsHeight: controlsHeightMap[screenSize],\n footerHeight: footerHeightMap[screenSize],\n healthBarHeight: healthBarHeightMap[screenSize],\n buttonHeight: buttonHeightMap[screenSize],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAgBA,IAAM,4BAA4B;;;;;;;AAQlC,IAAM,6BAA6B;;;;;;;;AASnC,SAAgB,2BAA2B,OAAuB;CAChE,OAAO,KAAK,IAAI,QAAQ,2BAA2B,0BAA0B;AAC/E;;;;;;;AAQA,SAAgB,oBACd,YACA,UACQ;CACR,IAAI,UACF,OAAO;CAGT,QAAQ,YAAR;EACE,KAAK,SACH,OAAO;EACT,KAAK,UACH,OAAO;EAIT,SACE,OAAO;CACX;AACF;;;;;AAMA,IAAM,qBAAqB;CAEzB,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;CAEA,cAAc;EACZ,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;CAEA,cAAc;EACZ,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;CAEA,gBAAgB;EACd,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;CAEA,YAAY;EACV,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;AACF;;;;;;;;AASA,SAAgB,qBAAqB,YAAgC;CACnE,OAAO,mBAAmB,QAAQ;AACpC;;;;;;;;AASA,SAAgB,0BAA0B,YAAgC;CACxE,OAAO,mBAAmB,aAAa;AACzC;;;;;;;;AASA,SAAgB,0BAA0B,YAAgC;CACxE,OAAO,mBAAmB,aAAa;AACzC;;;;;;;;AASA,SAAgB,4BAA4B,YAAgC;CAC1E,OAAO,mBAAmB,eAAe;AAC3C;;;;;;;;AASA,SAAgB,wBAAwB,YAAgC;CACtE,OAAO,mBAAmB,WAAW;AACvC;;;;;;;;;AAUA,SAAgB,mBAAmB,OAAe;CAChD,MAAM,aAAa,cAAc,KAAK;CAEtC,OAAO;EACL,SAAS,qBAAqB,UAAU;EACxC,cAAc,0BAA0B,UAAU;EAClD,cAAc,0BAA0B,UAAU;EAClD,gBAAgB,4BAA4B,UAAU;EACtD,YAAY,wBAAwB,UAAU;CAChD;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,yBAAyB,OAAe,UAAoB;CAG1E,MAAM,aAAa,WAAW,WAAW,cAAc,KAAK;CAG5D,MAAM,eAAe,YAAY,QAAQ;CAGzC,MAAM,eAAe;EACnB,QAAQ,eAAe,KAAK;EAC5B,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;CAMA,MAAM,oBAAoB;EACxB,QAAQ,eAAe,MAAM;EAC7B,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;CAEA,MAAM,kBAAkB;EACtB,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;CAEA,MAAM,qBAAqB;EACzB,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;CAGA,MAAM,kBAAkB;EACtB,QAAQ,eAAe,KAAK;EAC5B,QAAQ;EACR,SAAS;EACT,OAAO;EACP,QAAQ;CACV;CAEA,OAAO;EACL,SAAS,eAAe,IAAI;EAC5B,WAAW,aAAa;EACxB,gBAAgB,kBAAkB;EAClC,cAAc,gBAAgB;EAC9B,iBAAiB,mBAAmB;EACpC,cAAc,gBAAgB;CAChC;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responsiveOrientationConstants.js","names":[],"sources":["../../src/utils/responsiveOrientationConstants.ts"],"sourcesContent":["/**\n * Shared constants for orientation-aware responsive layout.\n *\n * Extracted so that `useCombatLayout` and `useTrainingLayout` compute the\n * same portrait \"bottom band\" and share a single source of truth for the\n * portrait-forcing hysteresis and the reserved control-column heights.\n *\n * 반응형 레이아웃 상수\n *\n * @module utils/responsiveOrientationConstants\n * @category Layout\n */\n\n/**\n * Hysteresis factor for portrait detection.\n *\n * A viewport is treated as portrait when `height > width × FACTOR`. The\n * factor is < 1.0 so near-square viewports (e.g. `1024×1000`) settle into\n * one orientation and don't flap on every pixel of resize.\n *\n */\nexport const PORTRAIT_HYSTERESIS_FACTOR = 0.9;\n\n/**\n * Maximum width at which a portrait viewport is force-promoted to the mobile\n * layout branch even if the user-agent reports a desktop browser. Matches the\n * tablet breakpoint used elsewhere in the codebase (1024px).\n *\n * This makes devtools emulation of a rotated phone/tablet behave identically\n * to a real device.\n *\n */\nexport const PORTRAIT_FORCE_MAX_WIDTH_PX = 1024;\n\n/**\n * Height reserved at the bottom of a mobile portrait viewport for the\n * on-screen virtual controls (D-Pad + action buttons rendered by\n * `MobileControlsWrapper`). Used as a conservative upper bound so the\n * 3D arena never ends up drawn behind the controls.\n *\n * Two values are provided so that very small phones (iPhone SE class,\n * width < 380) can still fit a playable arena.\n *\n * Combat uses the larger 200/160 band because its Mobile controls stack\n * D-Pad + action buttons + the persistent technique bar. Training uses\n * the smaller 180/140 band because its on-screen controls are lighter.\n *\n */\nexport const MOBILE_CONTROLS_RESERVED_HEIGHT_PX = {\n /** D-Pad + action buttons + technique bar on combat (standard phones) */\n combatStandard: 200,\n /** Combat controls on extra-small phones (width < 380) */\n combatExtraSmall: 160,\n /** Training on-screen controls (standard phones) */\n trainingStandard: 180,\n /** Training on-screen controls (extra-small phones) */\n trainingExtraSmall: 140,\n} as const;\n\n/**\n * Height reserved at the bottom of mobile landscape viewports.\n *\n * Landscape devices have less vertical room than portrait phones, so these\n * compact values reserve only the visible bottom technique/control band while\n * still keeping the 3D arena above touch controls.\n *\n */\nexport const LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX = {\n /** Combat bottom band on landscape phones */\n combatStandard: 120,\n /** Combat bottom band on extra-small landscape phones */\n combatExtraSmall: 110,\n /** Training bottom band on landscape phones */\n trainingStandard: 120,\n /** Training bottom band on extra-small landscape phones */\n trainingExtraSmall: 110,\n} as const;\n\n/**\n * Total bottom clearance to reserve in portrait mode = control/technique\n * bar height + footer height + the on-screen virtual controls band.\n *\n * @param controlsHeight - layout constant for technique/control bar\n * @param footerHeight - layout constant for footer\n * @param isExtraSmall - true when the viewport is < 380px wide\n * @param variant - \"combat\" or \"training\" (differs in control band size)\n *\n */\nexport function portraitMobileControlsBottomBand(\n controlsHeight: number,\n footerHeight: number,\n isExtraSmall: boolean,\n variant: \"combat\" | \"training\",\n): number {\n const band =\n variant === \"combat\"\n ? isExtraSmall\n ? MOBILE_CONTROLS_RESERVED_HEIGHT_PX.combatExtraSmall\n : MOBILE_CONTROLS_RESERVED_HEIGHT_PX.combatStandard\n : isExtraSmall\n ? MOBILE_CONTROLS_RESERVED_HEIGHT_PX.trainingExtraSmall\n : MOBILE_CONTROLS_RESERVED_HEIGHT_PX.trainingStandard;\n\n return controlsHeight + footerHeight + band;\n}\n\n/**\n * Bottom clearance for mobile landscape viewports.\n *\n * @param isExtraSmall - true when the viewport is < 380px wide\n * @param variant - \"combat\" or \"training\"\n *\n */\nexport function landscapeMobileControlsBottomClearance(\n isExtraSmall: boolean,\n variant: \"combat\" | \"training\",\n): number {\n if (variant === \"combat\") {\n return isExtraSmall\n ? LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX.combatExtraSmall\n : LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX.combatStandard;\n }\n\n return isExtraSmall\n ? LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX.trainingExtraSmall\n : LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX.trainingStandard;\n}\n\n/**\n * Orientation-aware bottom clearance for mobile combat/training arenas.\n *\n * Keeps callers from duplicating portrait-vs-landscape clearance rules while\n * preserving the compact landscape band and the full portrait touch-control\n * reservation.\n *\n * @param controlsHeight - layout constant for technique/control bar\n * @param footerHeight - layout constant for footer\n * @param isExtraSmall - true when the viewport is < 380px wide\n * @param isPortrait - true for portrait orientation\n * @param variant - \"combat\" or \"training\"\n *\n */\nexport function mobileControlsBottomClearance(\n controlsHeight: number,\n footerHeight: number,\n isExtraSmall: boolean,\n isPortrait: boolean,\n variant: \"combat\" | \"training\",\n): number {\n return isPortrait\n ? portraitMobileControlsBottomBand(\n controlsHeight,\n footerHeight,\n isExtraSmall,\n variant,\n )\n : landscapeMobileControlsBottomClearance(isExtraSmall, variant);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,IAAa,6BAA6B;;;;;;;;;;AAW1C,IAAa,8BAA8B;;;;;;;;;;;;;;;AAgB3C,IAAa,qCAAqC;;CAEhD,gBAAgB;;CAEhB,kBAAkB;;CAElB,kBAAkB;;CAElB,oBAAoB;
|
|
1
|
+
{"version":3,"file":"responsiveOrientationConstants.js","names":[],"sources":["../../src/utils/responsiveOrientationConstants.ts"],"sourcesContent":["/**\n * Shared constants for orientation-aware responsive layout.\n *\n * Extracted so that `useCombatLayout` and `useTrainingLayout` compute the\n * same portrait \"bottom band\" and share a single source of truth for the\n * portrait-forcing hysteresis and the reserved control-column heights.\n *\n * 반응형 레이아웃 상수\n *\n * @module utils/responsiveOrientationConstants\n * @category Layout\n */\n\n/**\n * Hysteresis factor for portrait detection.\n *\n * A viewport is treated as portrait when `height > width × FACTOR`. The\n * factor is < 1.0 so near-square viewports (e.g. `1024×1000`) settle into\n * one orientation and don't flap on every pixel of resize.\n *\n */\nexport const PORTRAIT_HYSTERESIS_FACTOR = 0.9;\n\n/**\n * Maximum width at which a portrait viewport is force-promoted to the mobile\n * layout branch even if the user-agent reports a desktop browser. Matches the\n * tablet breakpoint used elsewhere in the codebase (1024px).\n *\n * This makes devtools emulation of a rotated phone/tablet behave identically\n * to a real device.\n *\n */\nexport const PORTRAIT_FORCE_MAX_WIDTH_PX = 1024;\n\n/**\n * Height reserved at the bottom of a mobile portrait viewport for the\n * on-screen virtual controls (D-Pad + action buttons rendered by\n * `MobileControlsWrapper`). Used as a conservative upper bound so the\n * 3D arena never ends up drawn behind the controls.\n *\n * Two values are provided so that very small phones (iPhone SE class,\n * width < 380) can still fit a playable arena.\n *\n * Combat uses the larger 200/160 band because its Mobile controls stack\n * D-Pad + action buttons + the persistent technique bar. Training uses\n * the smaller 180/140 band because its on-screen controls are lighter.\n *\n */\nexport const MOBILE_CONTROLS_RESERVED_HEIGHT_PX = {\n /** D-Pad + action buttons + technique bar on combat (standard phones) */\n combatStandard: 200,\n /** Combat controls on extra-small phones (width < 380) */\n combatExtraSmall: 160,\n /** Training on-screen controls (standard phones) */\n trainingStandard: 180,\n /** Training on-screen controls (extra-small phones) */\n trainingExtraSmall: 140,\n} as const;\n\n/**\n * Height reserved at the bottom of mobile landscape viewports.\n *\n * Landscape devices have less vertical room than portrait phones, so these\n * compact values reserve only the visible bottom technique/control band while\n * still keeping the 3D arena above touch controls.\n *\n */\nexport const LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX = {\n /** Combat bottom band on landscape phones */\n combatStandard: 120,\n /** Combat bottom band on extra-small landscape phones */\n combatExtraSmall: 110,\n /** Training bottom band on landscape phones */\n trainingStandard: 120,\n /** Training bottom band on extra-small landscape phones */\n trainingExtraSmall: 110,\n} as const;\n\n/**\n * Total bottom clearance to reserve in portrait mode = control/technique\n * bar height + footer height + the on-screen virtual controls band.\n *\n * @param controlsHeight - layout constant for technique/control bar\n * @param footerHeight - layout constant for footer\n * @param isExtraSmall - true when the viewport is < 380px wide\n * @param variant - \"combat\" or \"training\" (differs in control band size)\n *\n */\nexport function portraitMobileControlsBottomBand(\n controlsHeight: number,\n footerHeight: number,\n isExtraSmall: boolean,\n variant: \"combat\" | \"training\",\n): number {\n const band =\n variant === \"combat\"\n ? isExtraSmall\n ? MOBILE_CONTROLS_RESERVED_HEIGHT_PX.combatExtraSmall\n : MOBILE_CONTROLS_RESERVED_HEIGHT_PX.combatStandard\n : isExtraSmall\n ? MOBILE_CONTROLS_RESERVED_HEIGHT_PX.trainingExtraSmall\n : MOBILE_CONTROLS_RESERVED_HEIGHT_PX.trainingStandard;\n\n return controlsHeight + footerHeight + band;\n}\n\n/**\n * Bottom clearance for mobile landscape viewports.\n *\n * @param isExtraSmall - true when the viewport is < 380px wide\n * @param variant - \"combat\" or \"training\"\n *\n */\nexport function landscapeMobileControlsBottomClearance(\n isExtraSmall: boolean,\n variant: \"combat\" | \"training\",\n): number {\n if (variant === \"combat\") {\n return isExtraSmall\n ? LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX.combatExtraSmall\n : LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX.combatStandard;\n }\n\n return isExtraSmall\n ? LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX.trainingExtraSmall\n : LANDSCAPE_MOBILE_CONTROLS_BOTTOM_CLEARANCE_PX.trainingStandard;\n}\n\n/**\n * Orientation-aware bottom clearance for mobile combat/training arenas.\n *\n * Keeps callers from duplicating portrait-vs-landscape clearance rules while\n * preserving the compact landscape band and the full portrait touch-control\n * reservation.\n *\n * @param controlsHeight - layout constant for technique/control bar\n * @param footerHeight - layout constant for footer\n * @param isExtraSmall - true when the viewport is < 380px wide\n * @param isPortrait - true for portrait orientation\n * @param variant - \"combat\" or \"training\"\n *\n */\nexport function mobileControlsBottomClearance(\n controlsHeight: number,\n footerHeight: number,\n isExtraSmall: boolean,\n isPortrait: boolean,\n variant: \"combat\" | \"training\",\n): number {\n return isPortrait\n ? portraitMobileControlsBottomBand(\n controlsHeight,\n footerHeight,\n isExtraSmall,\n variant,\n )\n : landscapeMobileControlsBottomClearance(isExtraSmall, variant);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,IAAa,6BAA6B;;;;;;;;;;AAW1C,IAAa,8BAA8B;;;;;;;;;;;;;;;AAgB3C,IAAa,qCAAqC;;CAEhD,gBAAgB;;CAEhB,kBAAkB;;CAElB,kBAAkB;;CAElB,oBAAoB;AACtB;;;;;;;;;AAUA,IAAa,gDAAgD;;CAE3D,gBAAgB;;CAEhB,kBAAkB;;CAElB,kBAAkB;;CAElB,oBAAoB;AACtB;;;;;;;;;;;AAYA,SAAgB,iCACd,gBACA,cACA,cACA,SACQ;CACR,MAAM,OACJ,YAAY,WACR,eACE,mCAAmC,mBACnC,mCAAmC,iBACrC,eACE,mCAAmC,qBACnC,mCAAmC;CAE3C,OAAO,iBAAiB,eAAe;AACzC;;;;;;;;AASA,SAAgB,uCACd,cACA,SACQ;CACR,IAAI,YAAY,UACd,OAAO,eACH,8CAA8C,mBAC9C,8CAA8C;CAGpD,OAAO,eACH,8CAA8C,qBAC9C,8CAA8C;AACpD;;;;;;;;;;;;;;;AAgBA,SAAgB,8BACd,gBACA,cACA,cACA,YACA,SACQ;CACR,OAAO,aACH,iCACE,gBACA,cACA,cACA,OACF,IACA,uCAAuC,cAAc,OAAO;AAClE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safeAreaUtils.js","names":[],"sources":["../../src/utils/safeAreaUtils.ts"],"sourcesContent":["/**\n * Safe area utilities for handling notched devices\n * \n * Provides CSS safe area inset helpers for iOS and Android devices\n * with notches, home indicators, and other screen intrusions.\n * \n * @module utils/safeAreaUtils\n * @category UI Utilities\n * @korean 안전영역유틸리티\n */\n\nimport { SAFE_AREA_INSETS } from \"../types/constants/ui\";\n\n/**\n * Safe area edge types\n * \n * @category Safe Area\n * @korean 안전영역가장자리타입\n */\nexport type SafeAreaEdge = \"top\" | \"bottom\" | \"left\" | \"right\";\n\n/**\n * Get CSS safe area inset with fallback\n * Returns CSS `env()` with fallback to constant values\n * \n * @param edge - Which edge to get inset for\n * @param fallback - Optional custom fallback value in pixels\n * @returns CSS string with env() and fallback\n * \n * @example\n * ```typescript\n * getSafeAreaInset('top'); // \"env(safe-area-inset-top, 44px)\"\n * getSafeAreaInset('bottom', 20); // \"env(safe-area-inset-bottom, 20px)\"\n * ```\n * \n * @korean 안전영역삽입얻기\n */\nexport function getSafeAreaInset(\n edge: SafeAreaEdge,\n fallback?: number\n): string {\n const defaultFallback =\n edge === \"top\"\n ? SAFE_AREA_INSETS.TOP\n : edge === \"bottom\"\n ? SAFE_AREA_INSETS.BOTTOM\n : edge === \"left\"\n ? SAFE_AREA_INSETS.LEFT\n : SAFE_AREA_INSETS.RIGHT;\n\n const fallbackValue = fallback ?? defaultFallback;\n return `env(safe-area-inset-${edge}, ${fallbackValue}px)`;\n}\n\n/**\n * Get safe area padding for a container\n * Returns CSS padding object with safe area support\n * \n * @param edges - Which edges to apply safe area padding\n * @param additionalPadding - Additional padding to add (in pixels)\n * @returns CSS padding object\n * \n * @example\n * ```typescript\n * getSafeAreaPadding(['top', 'bottom'], 16);\n * // {\n * // paddingTop: \"max(16px, env(safe-area-inset-top))\",\n * // paddingBottom: \"max(16px, env(safe-area-inset-bottom))\"\n * // }\n * ```\n * \n * @korean 안전영역패딩얻기\n */\nexport function getSafeAreaPadding(\n edges: readonly SafeAreaEdge[],\n additionalPadding = 0\n): Partial<Record<`padding${Capitalize<SafeAreaEdge>}`, string>> {\n const result: Partial<Record<`padding${Capitalize<SafeAreaEdge>}`, string>> =\n {};\n\n for (const edge of edges) {\n const capitalizedEdge =\n (edge.charAt(0).toUpperCase() + edge.slice(1)) as Capitalize<SafeAreaEdge>;\n const key = `padding${capitalizedEdge}` as const;\n\n result[key] = `max(${additionalPadding}px, ${getSafeAreaInset(edge)})`;\n }\n\n return result;\n}\n\n/**\n * Get safe area aware positioning styles\n * Useful for fixed/absolute positioned elements\n * \n * @param edge - Which edge to position from\n * @param baseOffset - Base offset in pixels\n * @returns CSS positioning string\n * \n * @example\n * ```typescript\n * getSafeAreaPosition('bottom', 20);\n * // \"calc(20px + env(safe-area-inset-bottom, 34px))\"\n * ```\n * \n * @korean 안전영역위치얻기\n */\nexport function getSafeAreaPosition(\n edge: SafeAreaEdge,\n baseOffset = 0\n): string {\n return `calc(${baseOffset}px + ${getSafeAreaInset(edge)})`;\n}\n\n/**\n * Get safe area aware height calculation\n * Useful for full-height containers\n * \n * @param excludeEdges - Which edges to exclude from height\n * @returns CSS calc() string for height\n * \n * @example\n * ```typescript\n * getSafeAreaHeight(['top', 'bottom']);\n * // \"calc(100vh - env(safe-area-inset-top, 44px) - env(safe-area-inset-bottom, 34px))\"\n * ```\n * \n * @korean 안전영역높이얻기\n */\nexport function getSafeAreaHeight(\n excludeEdges: readonly SafeAreaEdge[]\n): string {\n const insets = excludeEdges.map((edge) => getSafeAreaInset(edge)).join(\" - \");\n return `calc(100vh - ${insets})`;\n}\n\n/**\n * Check if device likely has safe area insets\n * Uses user agent detection (not perfect but useful)\n * \n * @returns Whether device likely has safe area insets\n * \n * @example\n * ```typescript\n * if (hasSafeAreaInsets()) {\n * // Apply safe area styles\n * }\n * ```\n * \n * @korean 안전영역삽입여부\n */\nexport function hasSafeAreaInsets(): boolean {\n // Check if CSS env() is supported\n if (typeof CSS !== \"undefined\" && CSS.supports) {\n return CSS.supports(\"padding-top\", \"env(safe-area-inset-top)\");\n }\n\n // Fallback: Check for iPhone X+ user agents\n const ua = navigator.userAgent;\n return (\n /iPhone/.test(ua) &&\n ((screen.width === 375 && screen.height === 812) || // iPhone X, XS, 11 Pro\n (screen.width === 414 && screen.height === 896) || // iPhone XR, XS Max, 11, 11 Pro Max\n (screen.width === 390 && screen.height === 844) || // iPhone 12, 12 Pro, 13, 13 Pro, 14\n (screen.width === 393 && screen.height === 852) || // iPhone 14 Pro\n (screen.width === 428 && screen.height === 926)) // iPhone 12/13/14 Pro Max\n );\n}\n\n/**\n * Get comprehensive safe area styles for a container\n * Combines padding and positioning for complete safe area support\n * \n * @param options - Configuration options\n * @returns React CSSProperties object\n * \n * @example\n * ```typescript\n * const styles = getSafeAreaStyles({\n * applyPadding: ['top', 'bottom'],\n * additionalPadding: 16,\n * position: 'fixed'\n * });\n * ```\n * \n * @korean 안전영역스타일얻기\n */\nexport function getSafeAreaStyles(options: {\n readonly applyPadding?: readonly SafeAreaEdge[];\n readonly additionalPadding?: number;\n readonly position?: \"fixed\" | \"absolute\" | \"sticky\";\n}): React.CSSProperties {\n const { applyPadding = [], additionalPadding = 0, position } = options;\n\n const paddingStyles = getSafeAreaPadding(applyPadding, additionalPadding);\n\n return {\n ...paddingStyles,\n ...(position && { position }),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,iBACd,MACA,UACQ;CACR,MAAM,kBACJ,SAAS,QACL,iBAAiB,MACjB,SAAS,WACT,iBAAiB,SACjB,SAAS,SACT,iBAAiB,OACjB,iBAAiB;CAGvB,OAAO,uBAAuB,KAAK,IADb,YAAY,gBACmB
|
|
1
|
+
{"version":3,"file":"safeAreaUtils.js","names":[],"sources":["../../src/utils/safeAreaUtils.ts"],"sourcesContent":["/**\n * Safe area utilities for handling notched devices\n * \n * Provides CSS safe area inset helpers for iOS and Android devices\n * with notches, home indicators, and other screen intrusions.\n * \n * @module utils/safeAreaUtils\n * @category UI Utilities\n * @korean 안전영역유틸리티\n */\n\nimport { SAFE_AREA_INSETS } from \"../types/constants/ui\";\n\n/**\n * Safe area edge types\n * \n * @category Safe Area\n * @korean 안전영역가장자리타입\n */\nexport type SafeAreaEdge = \"top\" | \"bottom\" | \"left\" | \"right\";\n\n/**\n * Get CSS safe area inset with fallback\n * Returns CSS `env()` with fallback to constant values\n * \n * @param edge - Which edge to get inset for\n * @param fallback - Optional custom fallback value in pixels\n * @returns CSS string with env() and fallback\n * \n * @example\n * ```typescript\n * getSafeAreaInset('top'); // \"env(safe-area-inset-top, 44px)\"\n * getSafeAreaInset('bottom', 20); // \"env(safe-area-inset-bottom, 20px)\"\n * ```\n * \n * @korean 안전영역삽입얻기\n */\nexport function getSafeAreaInset(\n edge: SafeAreaEdge,\n fallback?: number\n): string {\n const defaultFallback =\n edge === \"top\"\n ? SAFE_AREA_INSETS.TOP\n : edge === \"bottom\"\n ? SAFE_AREA_INSETS.BOTTOM\n : edge === \"left\"\n ? SAFE_AREA_INSETS.LEFT\n : SAFE_AREA_INSETS.RIGHT;\n\n const fallbackValue = fallback ?? defaultFallback;\n return `env(safe-area-inset-${edge}, ${fallbackValue}px)`;\n}\n\n/**\n * Get safe area padding for a container\n * Returns CSS padding object with safe area support\n * \n * @param edges - Which edges to apply safe area padding\n * @param additionalPadding - Additional padding to add (in pixels)\n * @returns CSS padding object\n * \n * @example\n * ```typescript\n * getSafeAreaPadding(['top', 'bottom'], 16);\n * // {\n * // paddingTop: \"max(16px, env(safe-area-inset-top))\",\n * // paddingBottom: \"max(16px, env(safe-area-inset-bottom))\"\n * // }\n * ```\n * \n * @korean 안전영역패딩얻기\n */\nexport function getSafeAreaPadding(\n edges: readonly SafeAreaEdge[],\n additionalPadding = 0\n): Partial<Record<`padding${Capitalize<SafeAreaEdge>}`, string>> {\n const result: Partial<Record<`padding${Capitalize<SafeAreaEdge>}`, string>> =\n {};\n\n for (const edge of edges) {\n const capitalizedEdge =\n (edge.charAt(0).toUpperCase() + edge.slice(1)) as Capitalize<SafeAreaEdge>;\n const key = `padding${capitalizedEdge}` as const;\n\n result[key] = `max(${additionalPadding}px, ${getSafeAreaInset(edge)})`;\n }\n\n return result;\n}\n\n/**\n * Get safe area aware positioning styles\n * Useful for fixed/absolute positioned elements\n * \n * @param edge - Which edge to position from\n * @param baseOffset - Base offset in pixels\n * @returns CSS positioning string\n * \n * @example\n * ```typescript\n * getSafeAreaPosition('bottom', 20);\n * // \"calc(20px + env(safe-area-inset-bottom, 34px))\"\n * ```\n * \n * @korean 안전영역위치얻기\n */\nexport function getSafeAreaPosition(\n edge: SafeAreaEdge,\n baseOffset = 0\n): string {\n return `calc(${baseOffset}px + ${getSafeAreaInset(edge)})`;\n}\n\n/**\n * Get safe area aware height calculation\n * Useful for full-height containers\n * \n * @param excludeEdges - Which edges to exclude from height\n * @returns CSS calc() string for height\n * \n * @example\n * ```typescript\n * getSafeAreaHeight(['top', 'bottom']);\n * // \"calc(100vh - env(safe-area-inset-top, 44px) - env(safe-area-inset-bottom, 34px))\"\n * ```\n * \n * @korean 안전영역높이얻기\n */\nexport function getSafeAreaHeight(\n excludeEdges: readonly SafeAreaEdge[]\n): string {\n const insets = excludeEdges.map((edge) => getSafeAreaInset(edge)).join(\" - \");\n return `calc(100vh - ${insets})`;\n}\n\n/**\n * Check if device likely has safe area insets\n * Uses user agent detection (not perfect but useful)\n * \n * @returns Whether device likely has safe area insets\n * \n * @example\n * ```typescript\n * if (hasSafeAreaInsets()) {\n * // Apply safe area styles\n * }\n * ```\n * \n * @korean 안전영역삽입여부\n */\nexport function hasSafeAreaInsets(): boolean {\n // Check if CSS env() is supported\n if (typeof CSS !== \"undefined\" && CSS.supports) {\n return CSS.supports(\"padding-top\", \"env(safe-area-inset-top)\");\n }\n\n // Fallback: Check for iPhone X+ user agents\n const ua = navigator.userAgent;\n return (\n /iPhone/.test(ua) &&\n ((screen.width === 375 && screen.height === 812) || // iPhone X, XS, 11 Pro\n (screen.width === 414 && screen.height === 896) || // iPhone XR, XS Max, 11, 11 Pro Max\n (screen.width === 390 && screen.height === 844) || // iPhone 12, 12 Pro, 13, 13 Pro, 14\n (screen.width === 393 && screen.height === 852) || // iPhone 14 Pro\n (screen.width === 428 && screen.height === 926)) // iPhone 12/13/14 Pro Max\n );\n}\n\n/**\n * Get comprehensive safe area styles for a container\n * Combines padding and positioning for complete safe area support\n * \n * @param options - Configuration options\n * @returns React CSSProperties object\n * \n * @example\n * ```typescript\n * const styles = getSafeAreaStyles({\n * applyPadding: ['top', 'bottom'],\n * additionalPadding: 16,\n * position: 'fixed'\n * });\n * ```\n * \n * @korean 안전영역스타일얻기\n */\nexport function getSafeAreaStyles(options: {\n readonly applyPadding?: readonly SafeAreaEdge[];\n readonly additionalPadding?: number;\n readonly position?: \"fixed\" | \"absolute\" | \"sticky\";\n}): React.CSSProperties {\n const { applyPadding = [], additionalPadding = 0, position } = options;\n\n const paddingStyles = getSafeAreaPadding(applyPadding, additionalPadding);\n\n return {\n ...paddingStyles,\n ...(position && { position }),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,iBACd,MACA,UACQ;CACR,MAAM,kBACJ,SAAS,QACL,iBAAiB,MACjB,SAAS,WACT,iBAAiB,SACjB,SAAS,SACT,iBAAiB,OACjB,iBAAiB;CAGvB,OAAO,uBAAuB,KAAK,IADb,YAAY,gBACmB;AACvD;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,mBACd,OACA,oBAAoB,GAC2C;CAC/D,MAAM,SACJ,CAAC;CAEH,KAAK,MAAM,QAAQ,OAAO;EAGxB,MAAM,MAAM,UADT,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC;EAG9C,OAAO,OAAO,OAAO,kBAAkB,MAAM,iBAAiB,IAAI,EAAE;CACtE;CAEA,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedPhysicsConfig.js","names":[],"sources":["../../src/utils/sharedPhysicsConfig.ts"],"sourcesContent":["/**\n * Shared physics configuration for Black Trigram screens.\n *\n * **Korean**: 공유 물리 설정\n *\n * This module provides shared physics configuration utilities to ensure\n * consistent physics behavior, coordinate systems, and camera setup across\n * TrainingScreen3D and CombatScreen3D.\n *\n * ## Synchronization Guarantees\n *\n * All screens using these utilities will have:\n * - Identical camera FOV and positioning for same device types\n * - Consistent arena bounds calculations\n * - Same physics constants (acceleration, stamina regen, etc.)\n * - Unified coordinate system (meter-based)\n * - Matching movement speed and range calculations\n *\n * @module utils/sharedPhysicsConfig\n * @category Physics\n * @korean 공유물리설정\n */\n\nimport {\n BASE_STAMINA_REGEN_RATE,\n BASE_MOVEMENT_ACCELERATION,\n COMBAT_RANGES_METERS,\n} from \"@/types/physicsConstants\";\nimport {\n calculateArenaConfiguration,\n type ArenaConfiguration,\n} from \"./arenaWorldDimensions\";\n\n/**\n * Camera configuration for 3D rendering.\n *\n * **Korean**: 카메라 설정\n *\n */\nexport interface CameraConfiguration {\n /** Field of view in degrees */\n readonly fov: number;\n /** Camera position [x, y, z] in meters */\n readonly position: readonly [number, number, number];\n /** Near clipping plane */\n readonly near: number;\n /** Far clipping plane */\n readonly far: number;\n}\n\n/**\n * Complete physics configuration for a game screen.\n *\n * **Korean**: 물리 설정\n *\n * Includes all parameters needed to ensure consistent physics\n * behavior across different screens and device types.\n *\n */\nexport interface PhysicsConfiguration {\n /** Arena configuration with pixel and meter dimensions */\n readonly arenaConfig: ArenaConfiguration;\n /** Camera configuration for consistent perspective */\n readonly cameraConfig: CameraConfiguration;\n /** Base stamina regeneration rate (stamina/second) */\n readonly staminaRegenRate: number;\n /** Base movement acceleration (m/s²) */\n readonly movementAcceleration: number;\n /** Combat ranges in meters */\n readonly combatRanges: typeof COMBAT_RANGES_METERS;\n /** Pixels per meter ratio for this configuration */\n readonly pixelsPerMeter: number;\n}\n\n/**\n * Create camera configuration based on device type.\n *\n * Mobile devices get a tighter FOV and closer camera position\n * for better framing of the smaller arena. Desktop gets a wider\n * FOV and further camera for full arena view.\n *\n * **Korean**: 카메라 설정 생성\n *\n * @param isMobile - Whether the device is mobile\n * @returns Camera configuration for device type\n *\n * @example\n * ```typescript\n * const mobile = createCameraConfig(true);\n * // { fov: 55, position: [0, 6, 10], near: 0.1, far: 1000 }\n *\n * const desktop = createCameraConfig(false);\n * // { fov: 60, position: [0, 8, 12], near: 0.1, far: 1000 }\n * ```\n *\n */\nexport function createCameraConfig(isMobile: boolean): CameraConfiguration {\n if (isMobile) {\n return {\n fov: 55, // Tighter FOV for smaller mobile arena\n position: [0, 6, 10], // Closer camera\n near: 0.1,\n far: 1000,\n };\n }\n\n return {\n fov: 60, // Standard FOV for desktop\n position: [0, 8, 12], // Further back for full view\n near: 0.1,\n far: 1000,\n };\n}\n\n/**\n * Create complete physics configuration for a screen.\n *\n * This is the primary function for setting up physics in both\n * TrainingScreen3D and CombatScreen3D. It ensures consistent\n * physics behavior by using the same calculations and constants.\n *\n * **Korean**: 물리 설정 생성\n *\n * @param screenWidth - Screen width in pixels\n * @param screenHeight - Screen height in pixels\n * @param topOffset - Pixels reserved at top (HUD, headers)\n * @param bottomOffset - Pixels reserved at bottom (controls, footer)\n * @param isMobile - Whether the device is mobile\n * @returns Complete physics configuration\n *\n * @example\n * ```typescript\n * // Training screen\n * const trainingPhysics = createPhysicsConfig(\n * 1920, 1080, 60, 100, false\n * );\n *\n * // Combat screen\n * const combatPhysics = createPhysicsConfig(\n * 1920, 1080, 60, 100, false\n * );\n *\n * // Both have identical physics configuration\n * trainingPhysics.pixelsPerMeter === combatPhysics.pixelsPerMeter; // true\n * ```\n *\n */\nexport function createPhysicsConfig(\n screenWidth: number,\n screenHeight: number,\n topOffset: number,\n bottomOffset: number,\n isMobile: boolean,\n): PhysicsConfiguration {\n // Calculate arena dimensions (same for both screens)\n const arenaConfig = calculateArenaConfiguration(\n screenWidth,\n screenHeight,\n topOffset,\n bottomOffset,\n );\n\n // Create camera config based on device type\n const cameraConfig = createCameraConfig(isMobile);\n\n return {\n arenaConfig,\n cameraConfig,\n staminaRegenRate: BASE_STAMINA_REGEN_RATE,\n movementAcceleration: BASE_MOVEMENT_ACCELERATION,\n combatRanges: COMBAT_RANGES_METERS,\n pixelsPerMeter: arenaConfig.pixelsPerMeter,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGA,SAAgB,mBAAmB,UAAwC;CACzE,IAAI,UACF,OAAO;EACL,KAAK;EACL,UAAU;GAAC;GAAG;GAAG;
|
|
1
|
+
{"version":3,"file":"sharedPhysicsConfig.js","names":[],"sources":["../../src/utils/sharedPhysicsConfig.ts"],"sourcesContent":["/**\n * Shared physics configuration for Black Trigram screens.\n *\n * **Korean**: 공유 물리 설정\n *\n * This module provides shared physics configuration utilities to ensure\n * consistent physics behavior, coordinate systems, and camera setup across\n * TrainingScreen3D and CombatScreen3D.\n *\n * ## Synchronization Guarantees\n *\n * All screens using these utilities will have:\n * - Identical camera FOV and positioning for same device types\n * - Consistent arena bounds calculations\n * - Same physics constants (acceleration, stamina regen, etc.)\n * - Unified coordinate system (meter-based)\n * - Matching movement speed and range calculations\n *\n * @module utils/sharedPhysicsConfig\n * @category Physics\n * @korean 공유물리설정\n */\n\nimport {\n BASE_STAMINA_REGEN_RATE,\n BASE_MOVEMENT_ACCELERATION,\n COMBAT_RANGES_METERS,\n} from \"@/types/physicsConstants\";\nimport {\n calculateArenaConfiguration,\n type ArenaConfiguration,\n} from \"./arenaWorldDimensions\";\n\n/**\n * Camera configuration for 3D rendering.\n *\n * **Korean**: 카메라 설정\n *\n */\nexport interface CameraConfiguration {\n /** Field of view in degrees */\n readonly fov: number;\n /** Camera position [x, y, z] in meters */\n readonly position: readonly [number, number, number];\n /** Near clipping plane */\n readonly near: number;\n /** Far clipping plane */\n readonly far: number;\n}\n\n/**\n * Complete physics configuration for a game screen.\n *\n * **Korean**: 물리 설정\n *\n * Includes all parameters needed to ensure consistent physics\n * behavior across different screens and device types.\n *\n */\nexport interface PhysicsConfiguration {\n /** Arena configuration with pixel and meter dimensions */\n readonly arenaConfig: ArenaConfiguration;\n /** Camera configuration for consistent perspective */\n readonly cameraConfig: CameraConfiguration;\n /** Base stamina regeneration rate (stamina/second) */\n readonly staminaRegenRate: number;\n /** Base movement acceleration (m/s²) */\n readonly movementAcceleration: number;\n /** Combat ranges in meters */\n readonly combatRanges: typeof COMBAT_RANGES_METERS;\n /** Pixels per meter ratio for this configuration */\n readonly pixelsPerMeter: number;\n}\n\n/**\n * Create camera configuration based on device type.\n *\n * Mobile devices get a tighter FOV and closer camera position\n * for better framing of the smaller arena. Desktop gets a wider\n * FOV and further camera for full arena view.\n *\n * **Korean**: 카메라 설정 생성\n *\n * @param isMobile - Whether the device is mobile\n * @returns Camera configuration for device type\n *\n * @example\n * ```typescript\n * const mobile = createCameraConfig(true);\n * // { fov: 55, position: [0, 6, 10], near: 0.1, far: 1000 }\n *\n * const desktop = createCameraConfig(false);\n * // { fov: 60, position: [0, 8, 12], near: 0.1, far: 1000 }\n * ```\n *\n */\nexport function createCameraConfig(isMobile: boolean): CameraConfiguration {\n if (isMobile) {\n return {\n fov: 55, // Tighter FOV for smaller mobile arena\n position: [0, 6, 10], // Closer camera\n near: 0.1,\n far: 1000,\n };\n }\n\n return {\n fov: 60, // Standard FOV for desktop\n position: [0, 8, 12], // Further back for full view\n near: 0.1,\n far: 1000,\n };\n}\n\n/**\n * Create complete physics configuration for a screen.\n *\n * This is the primary function for setting up physics in both\n * TrainingScreen3D and CombatScreen3D. It ensures consistent\n * physics behavior by using the same calculations and constants.\n *\n * **Korean**: 물리 설정 생성\n *\n * @param screenWidth - Screen width in pixels\n * @param screenHeight - Screen height in pixels\n * @param topOffset - Pixels reserved at top (HUD, headers)\n * @param bottomOffset - Pixels reserved at bottom (controls, footer)\n * @param isMobile - Whether the device is mobile\n * @returns Complete physics configuration\n *\n * @example\n * ```typescript\n * // Training screen\n * const trainingPhysics = createPhysicsConfig(\n * 1920, 1080, 60, 100, false\n * );\n *\n * // Combat screen\n * const combatPhysics = createPhysicsConfig(\n * 1920, 1080, 60, 100, false\n * );\n *\n * // Both have identical physics configuration\n * trainingPhysics.pixelsPerMeter === combatPhysics.pixelsPerMeter; // true\n * ```\n *\n */\nexport function createPhysicsConfig(\n screenWidth: number,\n screenHeight: number,\n topOffset: number,\n bottomOffset: number,\n isMobile: boolean,\n): PhysicsConfiguration {\n // Calculate arena dimensions (same for both screens)\n const arenaConfig = calculateArenaConfiguration(\n screenWidth,\n screenHeight,\n topOffset,\n bottomOffset,\n );\n\n // Create camera config based on device type\n const cameraConfig = createCameraConfig(isMobile);\n\n return {\n arenaConfig,\n cameraConfig,\n staminaRegenRate: BASE_STAMINA_REGEN_RATE,\n movementAcceleration: BASE_MOVEMENT_ACCELERATION,\n combatRanges: COMBAT_RANGES_METERS,\n pixelsPerMeter: arenaConfig.pixelsPerMeter,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGA,SAAgB,mBAAmB,UAAwC;CACzE,IAAI,UACF,OAAO;EACL,KAAK;EACL,UAAU;GAAC;GAAG;GAAG;EAAE;EACnB,MAAM;EACN,KAAK;CACP;CAGF,OAAO;EACL,KAAK;EACL,UAAU;GAAC;GAAG;GAAG;EAAE;EACnB,MAAM;EACN,KAAK;CACP;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,oBACd,aACA,cACA,WACA,cACA,UACsB;CAEtB,MAAM,cAAc,4BAClB,aACA,cACA,WACA,YACF;CAKA,OAAO;EACL;EACA,cAJmB,mBAAmB,QAItC;EACA,kBAAA;EACA,sBAAA;EACA,cAAc;EACd,gBAAgB,YAAY;CAC9B;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skeletonScaling.js","names":[],"sources":["../../src/utils/skeletonScaling.ts"],"sourcesContent":["/**\n * Skeleton rig scaling based on physical attributes.\n *\n * **Korean**: 골격 크기 조정 (Skeleton Scaling)\n *\n * This module provides functions to scale the skeleton rig based on player\n * physical attributes, allowing each archetype to have anatomically accurate\n * body proportions that affect combat hitboxes, vital point positioning, and\n * visual representation.\n *\n * ## Visual Amplification System\n *\n * Raw physical attribute differences between archetypes are subtle (2-12%).\n * To create recognizable visual silhouettes in 3D, this system applies\n * amplification factors that enhance differences while maintaining realistic\n * proportions:\n *\n * - **Limb Scaling**: 2.5x amplification (subtle 5% difference → 12.5% visual)\n * - **Shoulder Width**: 1.15x additional amplification for silhouette distinction\n * - **Overall Height**: 1.5x amplification (kept subtle for realism)\n *\n * ### Expected Visual Silhouettes\n *\n * Each archetype has a distinct, recognizable body shape:\n *\n * - **해커 (Hacker)**: Compact, narrow shoulders (43cm), shortest limbs - SMALLEST\n * - **암살자 (Amsalja)**: Tall (186cm), lean, long limbs (102cm legs) - TALLEST\n * - **정보요원 (Jeongbo)**: Balanced, average proportions - BASELINE\n * - **무사 (Musa)**: Athletic military build (46cm shoulders) - TRADITIONAL\n * - **조직폭력배 (Jojik)**: Massive, widest shoulders (54cm), imposing - LARGEST\n *\n * ## Integration with Korean Anatomy\n *\n * The scaling system respects Korean martial arts anatomy principles:\n * - **Head vital points** (두부 급소) scale with head size\n * - **Neck vulnerability** (경부 취약성) affected by neck length\n * - **Torso vital points** (몸통 급소) distributed across torso length\n * - **Limb reach** (팔다리 거리) determined by arm/leg length\n * - **Shoulder defense** (어깨 방어) coverage from shoulder width\n *\n * @module utils/skeletonScaling\n * @category Combat System\n * @korean 골격조정\n */\n\nimport { PhysicalAttributes } from \"@/types\";\nimport { BoneName } from \"@/types/skeletal\";\nimport {\n amplifyScaling,\n REFERENCE_ATTRIBUTES as BODY_REFERENCE_ATTRIBUTES,\n} from \"../constants/bodyRenderingConstants\";\n\n/**\n * Bone scaling factors for each body region.\n *\n * **Korean**: 뼈 크기 비율 (Bone Scaling Factors)\n *\n * Defines how physical attributes map to skeleton bone scaling.\n * Each factor is a multiplier applied to the base bone length.\n *\n * @korean 뼈크기비율\n */\nexport interface BoneScalingFactors {\n /** Head bone scaling (based on headSize) */\n readonly head: number;\n /** Neck bone scaling (based on neckLength) */\n readonly neck: number;\n /** Spine bones scaling (based on torsoLength) */\n readonly spine: number;\n /** Upper arm bones scaling (based on armLength) */\n readonly upperArm: number;\n /** Forearm bones scaling (based on armLength) */\n readonly forearm: number;\n /** Thigh bones scaling (based on legLength) */\n readonly thigh: number;\n /** Shin bones scaling (based on legLength) */\n readonly shin: number;\n /** Shoulder bones scaling (based on shoulderWidth) */\n readonly shoulder: number;\n /** Overall skeleton scaling (based on totalHeight) */\n readonly overall: number;\n}\n\n/**\n * Anatomically correct bone dimensions in centimeters.\n *\n * **Korean**: 해부학적 뼈 치수 (Anatomical Bone Dimensions)\n *\n * These are anatomically accurate bone lengths based on human proportions.\n * For a 178cm reference height, these values create a realistic skeleton.\n *\n * Reference: Human body proportions (8-head canon)\n * - Head: ~12.5% of height (22cm)\n * - Neck: ~5.5% of height (10cm)\n * - Torso: ~33% of height (59cm / 3 segments = 20cm each)\n * - Legs: ~48% of height (85cm total)\n * - Thigh: ~55% of leg length (47cm)\n * - Shin: ~45% of leg length (38cm)\n *\n * @internal\n * @korean 기본뼈치수\n */\nconst BASE_BONE_DIMENSIONS = {\n // Head region\n head: 22, // Head bone length in cm (12.5% of 178cm)\n neck: 10, // Neck bone length in cm (5.5% of 178cm)\n\n // Torso region - Total torso ~59cm for 178cm person (33%)\n spineLower: 20, // Lower spine segment (lumbar)\n spineMiddle: 20, // Middle spine segment (thoracic)\n spineUpper: 19, // Upper spine segment (upper thoracic)\n pelvis: 8, // Pelvis/root bone height (~4.5% of height)\n\n // Arm region (one side) - Total arm ~75cm for 178cm (42% of height)\n shoulder: 12, // Shoulder/clavicle bone (~6.5% of height)\n upperArm: 32, // Upper arm (shoulder to elbow) - ~55% of arm\n forearm: 27, // Forearm (elbow to wrist) - ~45% of arm (including wrist)\n hand: 19, // Hand length (~10.5% of height)\n\n // Leg region (one side) - Total leg ~95cm for 178cm (53% of height)\n hip: 10, // Hip joint height\n thigh: 48, // Thigh (hip to knee) - ~55% of leg length\n shin: 40, // Shin (knee to ankle) - ~45% of leg length\n foot: 5, // Foot/ankle height (ground to ankle)\n\n // Width measurements\n shoulderWidth: 43, // Total shoulder width (half = 21.5cm offset)\n hipWidth: 30, // Total hip width (half = 15cm offset)\n} as const;\n\n/**\n * Reference physical attributes for baseline scaling.\n *\n * **Korean**: 기준 신체 속성 (Reference Physical Attributes)\n *\n * These represent the \"average\" Korean male fighter that the base\n * skeleton rig is designed for. Scaling is calculated relative to\n * these reference values.\n *\n * Uses centralized REFERENCE_ATTRIBUTES from bodyRenderingConstants.\n *\n * @internal\n * @korean 기준신체속성\n */\nconst REFERENCE_ATTRIBUTES: PhysicalAttributes = {\n ...BODY_REFERENCE_ATTRIBUTES,\n};\n\n// VISUAL_AMPLIFICATION_FACTOR and amplifyScaling imported from bodyRenderingConstants\n\n/**\n * Calculate bone scaling factors from physical attributes.\n *\n * **Korean**: 뼈 크기 비율 계산 (Calculate Bone Scaling Factors)\n *\n * Computes scaling multipliers for each bone region based on the\n * player's physical attributes compared to reference values.\n * Differences are amplified for more noticeable visual distinction\n * between archetypes.\n *\n * @param attributes - Player's physical attributes\n * @returns Bone scaling factors for skeleton rig\n *\n * @example\n * ```typescript\n * const factors = calculateBoneScalingFactors(AMSALJA_PHYSICAL);\n * // Amsalja has longer legs: factors.thigh ~= 1.06 (amplified from ~1.03)\n * // Amsalja has smaller head: factors.head ~= 0.90 (amplified from ~0.95)\n * ```\n *\n * @korean 뼈크기비율계산\n */\nexport function calculateBoneScalingFactors(\n attributes: PhysicalAttributes,\n): BoneScalingFactors {\n // Calculate raw scaling ratios from physical attributes\n const rawOverall = attributes.totalHeight / REFERENCE_ATTRIBUTES.totalHeight;\n const rawHead = attributes.headSize / REFERENCE_ATTRIBUTES.headSize;\n const rawNeck = attributes.neckLength / REFERENCE_ATTRIBUTES.neckLength;\n const rawSpine = attributes.torsoLength / REFERENCE_ATTRIBUTES.torsoLength;\n const rawArm = attributes.armLength / REFERENCE_ATTRIBUTES.armLength;\n const rawLeg = attributes.legLength / REFERENCE_ATTRIBUTES.legLength;\n const rawShoulder =\n attributes.shoulderWidth / REFERENCE_ATTRIBUTES.shoulderWidth;\n\n // Apply visual amplification for more noticeable archetype differences\n // Overall height scaling is kept subtle (1.5x) for realism\n const overall = 1.0 + (rawOverall - 1.0) * 1.5;\n\n // Head, neck, and body proportions are amplified for visual distinction\n const head = amplifyScaling(rawHead);\n const neck = amplifyScaling(rawNeck);\n const spine = amplifyScaling(rawSpine);\n\n // Limb proportions amplified for noticeable reach differences\n const upperArm = amplifyScaling(rawArm);\n const forearm = amplifyScaling(rawArm);\n const thigh = amplifyScaling(rawLeg);\n const shin = amplifyScaling(rawLeg);\n\n // Shoulder width amplified for body silhouette distinction\n const shoulder = amplifyScaling(rawShoulder);\n\n return {\n head,\n neck,\n spine,\n upperArm,\n forearm,\n thigh,\n shin,\n shoulder,\n overall,\n };\n}\n\n/**\n * Get scaled bone length for a specific bone.\n *\n * **Korean**: 크기 조정된 뼈 길이 (Scaled Bone Length)\n *\n * Returns the scaled length for a specific bone based on the player's\n * physical attributes. Used when creating or updating skeleton rigs.\n *\n * @param boneName - Name of the bone to scale\n * @param attributes - Player's physical attributes\n * @returns Scaled bone length in centimeters\n *\n * @example\n * ```typescript\n * const headLength = getScaledBoneLength(BoneName.HEAD, JOJIK_PHYSICAL);\n * // Jojik has larger head: returns ~23cm (base 20cm * 1.15)\n * ```\n *\n * @korean 크기조정된뼈길이\n */\nexport function getScaledBoneLength(\n boneName: string,\n attributes: PhysicalAttributes,\n): number {\n const factors = calculateBoneScalingFactors(attributes);\n\n // Map bone names to their base dimensions and scaling factors\n switch (boneName) {\n // Head region\n case BoneName.HEAD:\n return BASE_BONE_DIMENSIONS.head * factors.head;\n case BoneName.NECK:\n return BASE_BONE_DIMENSIONS.neck * factors.neck;\n\n // Spine region\n case BoneName.SPINE_LOWER:\n return BASE_BONE_DIMENSIONS.spineLower * factors.spine;\n case BoneName.SPINE_MIDDLE:\n return BASE_BONE_DIMENSIONS.spineMiddle * factors.spine;\n case BoneName.SPINE_UPPER:\n return BASE_BONE_DIMENSIONS.spineUpper * factors.spine;\n case BoneName.PELVIS:\n return BASE_BONE_DIMENSIONS.pelvis * factors.overall;\n\n // Left arm\n case BoneName.SHOULDER_L:\n case BoneName.SHOULDER_R:\n return BASE_BONE_DIMENSIONS.shoulder * factors.shoulder;\n case BoneName.UPPER_ARM_L:\n case BoneName.UPPER_ARM_R:\n return BASE_BONE_DIMENSIONS.upperArm * factors.upperArm;\n case BoneName.FOREARM_L:\n case BoneName.FOREARM_R:\n return BASE_BONE_DIMENSIONS.forearm * factors.forearm;\n case BoneName.HAND_L:\n case BoneName.HAND_R:\n return BASE_BONE_DIMENSIONS.hand * factors.overall;\n\n // Left leg\n case BoneName.THIGH_L:\n case BoneName.THIGH_R:\n return BASE_BONE_DIMENSIONS.thigh * factors.thigh;\n case BoneName.SHIN_L:\n case BoneName.SHIN_R:\n return BASE_BONE_DIMENSIONS.shin * factors.shin;\n case BoneName.FOOT_L:\n case BoneName.FOOT_R:\n return BASE_BONE_DIMENSIONS.foot * factors.overall;\n\n // Default: use overall scaling\n default:\n return 10 * factors.overall; // Default bone length\n }\n}\n\n/**\n * Calculate scaled shoulder offset for skeleton positioning.\n *\n * **Korean**: 어깨 오프셋 계산 (Shoulder Offset Calculation)\n *\n * Determines the horizontal offset for shoulder bones based on\n * shoulder width. Used to position arms correctly on the skeleton.\n *\n * Applies amplification to shoulder width for more noticeable silhouette\n * differences between archetypes:\n * - Jojik (54cm): Offset = 31.05cm (54/2 * 1.15) - WIDE, imposing\n * - Hacker (43cm): Offset = 24.73cm (43/2 * 1.15) - NARROW, compact\n * - Difference: 26% wider shoulder span for visual distinction\n *\n * @param attributes - Player's physical attributes\n * @returns Shoulder offset in centimeters (half of total shoulder width, amplified)\n *\n * @example\n * ```typescript\n * const offset = calculateShoulderOffset(JOJIK_PHYSICAL);\n * // Jojik has wide shoulders: returns ~31cm (54cm width / 2 * 1.15)\n * // Left shoulder at -31cm, right shoulder at +31cm\n * ```\n *\n * @korean 어깨오프셋계산\n */\nexport function calculateShoulderOffset(\n attributes: PhysicalAttributes,\n): number {\n // Shoulder width is the total span, so divide by 2 for offset from center\n // Apply amplification factor for more visible width differences\n const SHOULDER_AMPLIFICATION = 1.15;\n return (attributes.shoulderWidth / 2) * SHOULDER_AMPLIFICATION;\n}\n\n/**\n * Calculate hitbox dimensions based on physical attributes.\n *\n * **Korean**: 히트박스 크기 계산 (Hitbox Dimension Calculation)\n *\n * Determines the bounding box dimensions for collision detection\n * and hit registration based on body proportions.\n *\n * @param attributes - Player's physical attributes\n * @returns Hitbox dimensions {width, height, depth} in centimeters\n *\n * @example\n * ```typescript\n * const hitbox = calculateHitboxDimensions(AMSALJA_PHYSICAL);\n * // Amsalja is tall and lean: {width: 40, height: 182, depth: 25}\n * ```\n *\n * @korean 히트박스크기계산\n */\nexport function calculateHitboxDimensions(attributes: PhysicalAttributes): {\n width: number;\n height: number;\n depth: number;\n} {\n return {\n width: attributes.shoulderWidth,\n height: attributes.totalHeight,\n depth: attributes.shoulderWidth * 0.5, // Approximate depth from shoulder width\n };\n}\n\n/**\n * Calculate vital point offset adjustments based on body proportions.\n *\n * **Korean**: 급소 위치 조정 (Vital Point Position Adjustment)\n *\n * Adjusts vital point positions based on scaled skeleton dimensions.\n * Ensures vital points remain anatomically accurate for different\n * body types while maintaining Korean anatomy principles.\n *\n * @param vitalPointId - ID of the vital point to adjust\n * @param attributes - Player's physical attributes\n * @returns Position adjustment {x, y} offset in centimeters\n *\n * @example\n * ```typescript\n * const adjustment = calculateVitalPointAdjustment(\"head_temple\", MUSA_PHYSICAL);\n * // Returns offset based on head size and torso height\n * ```\n *\n * @korean 급소위치조정\n */\nexport function calculateVitalPointAdjustment(\n vitalPointId: string,\n attributes: PhysicalAttributes,\n): { x: number; y: number } {\n const factors = calculateBoneScalingFactors(attributes);\n\n // Determine which body region this vital point belongs to\n // Use exact prefix matching to avoid ambiguity\n if (vitalPointId.startsWith(\"head_\")) {\n // Head vital points scale with head size and torso height\n return {\n x: 0,\n y: (factors.head - 1.0) * 20 + (factors.spine - 1.0) * 60,\n };\n } else if (vitalPointId.startsWith(\"neck_\")) {\n // Neck vital points scale with neck length and torso height\n return {\n x: 0,\n y: (factors.neck - 1.0) * 10 + (factors.spine - 1.0) * 60,\n };\n } else if (vitalPointId.startsWith(\"torso_\")) {\n // Torso vital points scale with torso length\n return {\n x: 0,\n y: (factors.spine - 1.0) * 30,\n };\n } else if (\n vitalPointId.startsWith(\"shoulder_\") ||\n vitalPointId.startsWith(\"arm_\")\n ) {\n // Arm vital points scale with arm length and shoulder width\n return {\n x: (factors.shoulder - 1.0) * 15,\n y: (factors.upperArm - 1.0) * 12,\n };\n } else if (\n vitalPointId.startsWith(\"leg_\") ||\n vitalPointId.startsWith(\"knee_\")\n ) {\n // Leg vital points scale with leg length\n return {\n x: 0,\n y: -(factors.thigh - 1.0) * 15,\n };\n }\n\n // Default: no adjustment\n return { x: 0, y: 0 };\n}\n\n/**\n * Calculate choke effectiveness modifier based on neck dimensions.\n *\n * **Korean**: 목 조르기 효과 계산 (Choke Effectiveness Calculation)\n *\n * Determines how effective chokes and strangles are based on neck\n * length and thickness. Longer, thinner necks are more vulnerable.\n *\n * @param attributes - Player's physical attributes\n * @returns Choke effectiveness multiplier (1.0 = baseline)\n *\n * @example\n * ```typescript\n * const chokeEffectiveness = calculateChokeEffectiveness(AMSALJA_PHYSICAL);\n * // Amsalja has longer neck: returns ~1.1 (10% more vulnerable)\n *\n * const jojikChoke = calculateChokeEffectiveness(JOJIK_PHYSICAL);\n * // Jojik has shorter, thicker neck: returns ~0.9 (10% more resistant)\n * ```\n *\n * @korean 목조르기효과계산\n */\nexport function calculateChokeEffectiveness(\n attributes: PhysicalAttributes,\n): number {\n // Longer necks are more vulnerable to chokes\n const lengthFactor = attributes.neckLength / REFERENCE_ATTRIBUTES.neckLength;\n\n // Thicker necks (from muscle/weight) resist chokes better\n const thicknessFactor = REFERENCE_ATTRIBUTES.weight / attributes.weight;\n\n // Combine factors: longer neck + lighter weight = more vulnerable\n return lengthFactor * thicknessFactor;\n}\n\n/**\n * Calculate head strike vulnerability based on head size.\n *\n * **Korean**: 머리 타격 취약성 계산 (Head Strike Vulnerability)\n *\n * Determines vulnerability to head strikes based on head size.\n * Larger heads have more mass and resistance, smaller heads are\n * more vulnerable to concussive force.\n *\n * @param attributes - Player's physical attributes\n * @returns Head strike vulnerability multiplier (1.0 = baseline)\n *\n * @example\n * ```typescript\n * const headVuln = calculateHeadStrikeVulnerability(JOJIK_PHYSICAL);\n * // Jojik has larger head: returns ~0.95 (5% more resistant)\n * ```\n *\n * @korean 머리타격취약성계산\n */\nexport function calculateHeadStrikeVulnerability(\n attributes: PhysicalAttributes,\n): number {\n // Larger heads have more mass, providing some protection\n const sizeFactor = REFERENCE_ATTRIBUTES.headSize / attributes.headSize;\n\n // But larger heads are also bigger targets (handled by hitbox size)\n // Here we only calculate the mass-based resistance\n return sizeFactor;\n}\n\n/**\n * Calculate body radius for hit distance calculation.\n *\n * **Korean**: 몸체 반경 계산 (Body Radius Calculation)\n *\n * When calculating hit distance, we measure center-to-center, but attacks\n * land on the target's body surface. This function calculates the effective\n * \"depth\" of a fighter's body from their center point based on their\n * physical attributes.\n *\n * The calculation uses torso depth derived from shoulder width, as broader\n * fighters have proportionally deeper torsos. This is more anatomically\n * accurate than using a fixed constant.\n *\n * Formula: shoulderWidth * 0.5 (torso depth ratio) / 100 (cm to meters)\n *\n * Example body radii:\n * - Hacker (43cm shoulders): 0.215m depth → 0.215m radius\n * - Musa (46cm shoulders): 0.23m depth → 0.23m radius\n * - Jojik (54cm shoulders): 0.27m depth → 0.27m radius\n *\n * @param attributes - Player's physical attributes\n * @returns Body radius in meters (distance from center to body surface)\n *\n * @example\n * ```typescript\n * const radius = calculateBodyRadius(JOJIK_PHYSICAL);\n * // Jojik has wide shoulders (54cm): returns 0.27m\n *\n * const hackerRadius = calculateBodyRadius(HACKER_PHYSICAL);\n * // Hacker is lean (43cm shoulders): returns 0.215m\n * ```\n *\n * @korean 몸체반경계산\n */\nexport function calculateBodyRadius(attributes: PhysicalAttributes): number {\n // Body depth is approximately half of shoulder width (anatomical ratio)\n // This matches calculateHitboxDimensions which uses shoulderWidth * 0.5\n const bodyDepthCm = attributes.shoulderWidth * 0.5;\n\n // Convert from centimeters to meters\n // The radius is the distance from center to body surface (half of total depth)\n // Since bodyDepthCm represents front-to-back depth, we use it directly as\n // the distance from center to front surface\n return bodyDepthCm / 100;\n}\n"],"mappings":";CAgJiD,EAC/C,GAAG,wBACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+XD,SAAgB,oBAAoB,YAAwC;CAS1E,OANoB,WAAW,gBAAgB,KAM1B"}
|
|
1
|
+
{"version":3,"file":"skeletonScaling.js","names":[],"sources":["../../src/utils/skeletonScaling.ts"],"sourcesContent":["/**\n * Skeleton rig scaling based on physical attributes.\n *\n * **Korean**: 골격 크기 조정 (Skeleton Scaling)\n *\n * This module provides functions to scale the skeleton rig based on player\n * physical attributes, allowing each archetype to have anatomically accurate\n * body proportions that affect combat hitboxes, vital point positioning, and\n * visual representation.\n *\n * ## Visual Amplification System\n *\n * Raw physical attribute differences between archetypes are subtle (2-12%).\n * To create recognizable visual silhouettes in 3D, this system applies\n * amplification factors that enhance differences while maintaining realistic\n * proportions:\n *\n * - **Limb Scaling**: 2.5x amplification (subtle 5% difference → 12.5% visual)\n * - **Shoulder Width**: 1.15x additional amplification for silhouette distinction\n * - **Overall Height**: 1.5x amplification (kept subtle for realism)\n *\n * ### Expected Visual Silhouettes\n *\n * Each archetype has a distinct, recognizable body shape:\n *\n * - **해커 (Hacker)**: Compact, narrow shoulders (43cm), shortest limbs - SMALLEST\n * - **암살자 (Amsalja)**: Tall (186cm), lean, long limbs (102cm legs) - TALLEST\n * - **정보요원 (Jeongbo)**: Balanced, average proportions - BASELINE\n * - **무사 (Musa)**: Athletic military build (46cm shoulders) - TRADITIONAL\n * - **조직폭력배 (Jojik)**: Massive, widest shoulders (54cm), imposing - LARGEST\n *\n * ## Integration with Korean Anatomy\n *\n * The scaling system respects Korean martial arts anatomy principles:\n * - **Head vital points** (두부 급소) scale with head size\n * - **Neck vulnerability** (경부 취약성) affected by neck length\n * - **Torso vital points** (몸통 급소) distributed across torso length\n * - **Limb reach** (팔다리 거리) determined by arm/leg length\n * - **Shoulder defense** (어깨 방어) coverage from shoulder width\n *\n * @module utils/skeletonScaling\n * @category Combat System\n * @korean 골격조정\n */\n\nimport { PhysicalAttributes } from \"@/types\";\nimport { BoneName } from \"@/types/skeletal\";\nimport {\n amplifyScaling,\n REFERENCE_ATTRIBUTES as BODY_REFERENCE_ATTRIBUTES,\n} from \"../constants/bodyRenderingConstants\";\n\n/**\n * Bone scaling factors for each body region.\n *\n * **Korean**: 뼈 크기 비율 (Bone Scaling Factors)\n *\n * Defines how physical attributes map to skeleton bone scaling.\n * Each factor is a multiplier applied to the base bone length.\n *\n * @korean 뼈크기비율\n */\nexport interface BoneScalingFactors {\n /** Head bone scaling (based on headSize) */\n readonly head: number;\n /** Neck bone scaling (based on neckLength) */\n readonly neck: number;\n /** Spine bones scaling (based on torsoLength) */\n readonly spine: number;\n /** Upper arm bones scaling (based on armLength) */\n readonly upperArm: number;\n /** Forearm bones scaling (based on armLength) */\n readonly forearm: number;\n /** Thigh bones scaling (based on legLength) */\n readonly thigh: number;\n /** Shin bones scaling (based on legLength) */\n readonly shin: number;\n /** Shoulder bones scaling (based on shoulderWidth) */\n readonly shoulder: number;\n /** Overall skeleton scaling (based on totalHeight) */\n readonly overall: number;\n}\n\n/**\n * Anatomically correct bone dimensions in centimeters.\n *\n * **Korean**: 해부학적 뼈 치수 (Anatomical Bone Dimensions)\n *\n * These are anatomically accurate bone lengths based on human proportions.\n * For a 178cm reference height, these values create a realistic skeleton.\n *\n * Reference: Human body proportions (8-head canon)\n * - Head: ~12.5% of height (22cm)\n * - Neck: ~5.5% of height (10cm)\n * - Torso: ~33% of height (59cm / 3 segments = 20cm each)\n * - Legs: ~48% of height (85cm total)\n * - Thigh: ~55% of leg length (47cm)\n * - Shin: ~45% of leg length (38cm)\n *\n * @internal\n * @korean 기본뼈치수\n */\nconst BASE_BONE_DIMENSIONS = {\n // Head region\n head: 22, // Head bone length in cm (12.5% of 178cm)\n neck: 10, // Neck bone length in cm (5.5% of 178cm)\n\n // Torso region - Total torso ~59cm for 178cm person (33%)\n spineLower: 20, // Lower spine segment (lumbar)\n spineMiddle: 20, // Middle spine segment (thoracic)\n spineUpper: 19, // Upper spine segment (upper thoracic)\n pelvis: 8, // Pelvis/root bone height (~4.5% of height)\n\n // Arm region (one side) - Total arm ~75cm for 178cm (42% of height)\n shoulder: 12, // Shoulder/clavicle bone (~6.5% of height)\n upperArm: 32, // Upper arm (shoulder to elbow) - ~55% of arm\n forearm: 27, // Forearm (elbow to wrist) - ~45% of arm (including wrist)\n hand: 19, // Hand length (~10.5% of height)\n\n // Leg region (one side) - Total leg ~95cm for 178cm (53% of height)\n hip: 10, // Hip joint height\n thigh: 48, // Thigh (hip to knee) - ~55% of leg length\n shin: 40, // Shin (knee to ankle) - ~45% of leg length\n foot: 5, // Foot/ankle height (ground to ankle)\n\n // Width measurements\n shoulderWidth: 43, // Total shoulder width (half = 21.5cm offset)\n hipWidth: 30, // Total hip width (half = 15cm offset)\n} as const;\n\n/**\n * Reference physical attributes for baseline scaling.\n *\n * **Korean**: 기준 신체 속성 (Reference Physical Attributes)\n *\n * These represent the \"average\" Korean male fighter that the base\n * skeleton rig is designed for. Scaling is calculated relative to\n * these reference values.\n *\n * Uses centralized REFERENCE_ATTRIBUTES from bodyRenderingConstants.\n *\n * @internal\n * @korean 기준신체속성\n */\nconst REFERENCE_ATTRIBUTES: PhysicalAttributes = {\n ...BODY_REFERENCE_ATTRIBUTES,\n};\n\n// VISUAL_AMPLIFICATION_FACTOR and amplifyScaling imported from bodyRenderingConstants\n\n/**\n * Calculate bone scaling factors from physical attributes.\n *\n * **Korean**: 뼈 크기 비율 계산 (Calculate Bone Scaling Factors)\n *\n * Computes scaling multipliers for each bone region based on the\n * player's physical attributes compared to reference values.\n * Differences are amplified for more noticeable visual distinction\n * between archetypes.\n *\n * @param attributes - Player's physical attributes\n * @returns Bone scaling factors for skeleton rig\n *\n * @example\n * ```typescript\n * const factors = calculateBoneScalingFactors(AMSALJA_PHYSICAL);\n * // Amsalja has longer legs: factors.thigh ~= 1.06 (amplified from ~1.03)\n * // Amsalja has smaller head: factors.head ~= 0.90 (amplified from ~0.95)\n * ```\n *\n * @korean 뼈크기비율계산\n */\nexport function calculateBoneScalingFactors(\n attributes: PhysicalAttributes,\n): BoneScalingFactors {\n // Calculate raw scaling ratios from physical attributes\n const rawOverall = attributes.totalHeight / REFERENCE_ATTRIBUTES.totalHeight;\n const rawHead = attributes.headSize / REFERENCE_ATTRIBUTES.headSize;\n const rawNeck = attributes.neckLength / REFERENCE_ATTRIBUTES.neckLength;\n const rawSpine = attributes.torsoLength / REFERENCE_ATTRIBUTES.torsoLength;\n const rawArm = attributes.armLength / REFERENCE_ATTRIBUTES.armLength;\n const rawLeg = attributes.legLength / REFERENCE_ATTRIBUTES.legLength;\n const rawShoulder =\n attributes.shoulderWidth / REFERENCE_ATTRIBUTES.shoulderWidth;\n\n // Apply visual amplification for more noticeable archetype differences\n // Overall height scaling is kept subtle (1.5x) for realism\n const overall = 1.0 + (rawOverall - 1.0) * 1.5;\n\n // Head, neck, and body proportions are amplified for visual distinction\n const head = amplifyScaling(rawHead);\n const neck = amplifyScaling(rawNeck);\n const spine = amplifyScaling(rawSpine);\n\n // Limb proportions amplified for noticeable reach differences\n const upperArm = amplifyScaling(rawArm);\n const forearm = amplifyScaling(rawArm);\n const thigh = amplifyScaling(rawLeg);\n const shin = amplifyScaling(rawLeg);\n\n // Shoulder width amplified for body silhouette distinction\n const shoulder = amplifyScaling(rawShoulder);\n\n return {\n head,\n neck,\n spine,\n upperArm,\n forearm,\n thigh,\n shin,\n shoulder,\n overall,\n };\n}\n\n/**\n * Get scaled bone length for a specific bone.\n *\n * **Korean**: 크기 조정된 뼈 길이 (Scaled Bone Length)\n *\n * Returns the scaled length for a specific bone based on the player's\n * physical attributes. Used when creating or updating skeleton rigs.\n *\n * @param boneName - Name of the bone to scale\n * @param attributes - Player's physical attributes\n * @returns Scaled bone length in centimeters\n *\n * @example\n * ```typescript\n * const headLength = getScaledBoneLength(BoneName.HEAD, JOJIK_PHYSICAL);\n * // Jojik has larger head: returns ~23cm (base 20cm * 1.15)\n * ```\n *\n * @korean 크기조정된뼈길이\n */\nexport function getScaledBoneLength(\n boneName: string,\n attributes: PhysicalAttributes,\n): number {\n const factors = calculateBoneScalingFactors(attributes);\n\n // Map bone names to their base dimensions and scaling factors\n switch (boneName) {\n // Head region\n case BoneName.HEAD:\n return BASE_BONE_DIMENSIONS.head * factors.head;\n case BoneName.NECK:\n return BASE_BONE_DIMENSIONS.neck * factors.neck;\n\n // Spine region\n case BoneName.SPINE_LOWER:\n return BASE_BONE_DIMENSIONS.spineLower * factors.spine;\n case BoneName.SPINE_MIDDLE:\n return BASE_BONE_DIMENSIONS.spineMiddle * factors.spine;\n case BoneName.SPINE_UPPER:\n return BASE_BONE_DIMENSIONS.spineUpper * factors.spine;\n case BoneName.PELVIS:\n return BASE_BONE_DIMENSIONS.pelvis * factors.overall;\n\n // Left arm\n case BoneName.SHOULDER_L:\n case BoneName.SHOULDER_R:\n return BASE_BONE_DIMENSIONS.shoulder * factors.shoulder;\n case BoneName.UPPER_ARM_L:\n case BoneName.UPPER_ARM_R:\n return BASE_BONE_DIMENSIONS.upperArm * factors.upperArm;\n case BoneName.FOREARM_L:\n case BoneName.FOREARM_R:\n return BASE_BONE_DIMENSIONS.forearm * factors.forearm;\n case BoneName.HAND_L:\n case BoneName.HAND_R:\n return BASE_BONE_DIMENSIONS.hand * factors.overall;\n\n // Left leg\n case BoneName.THIGH_L:\n case BoneName.THIGH_R:\n return BASE_BONE_DIMENSIONS.thigh * factors.thigh;\n case BoneName.SHIN_L:\n case BoneName.SHIN_R:\n return BASE_BONE_DIMENSIONS.shin * factors.shin;\n case BoneName.FOOT_L:\n case BoneName.FOOT_R:\n return BASE_BONE_DIMENSIONS.foot * factors.overall;\n\n // Default: use overall scaling\n default:\n return 10 * factors.overall; // Default bone length\n }\n}\n\n/**\n * Calculate scaled shoulder offset for skeleton positioning.\n *\n * **Korean**: 어깨 오프셋 계산 (Shoulder Offset Calculation)\n *\n * Determines the horizontal offset for shoulder bones based on\n * shoulder width. Used to position arms correctly on the skeleton.\n *\n * Applies amplification to shoulder width for more noticeable silhouette\n * differences between archetypes:\n * - Jojik (54cm): Offset = 31.05cm (54/2 * 1.15) - WIDE, imposing\n * - Hacker (43cm): Offset = 24.73cm (43/2 * 1.15) - NARROW, compact\n * - Difference: 26% wider shoulder span for visual distinction\n *\n * @param attributes - Player's physical attributes\n * @returns Shoulder offset in centimeters (half of total shoulder width, amplified)\n *\n * @example\n * ```typescript\n * const offset = calculateShoulderOffset(JOJIK_PHYSICAL);\n * // Jojik has wide shoulders: returns ~31cm (54cm width / 2 * 1.15)\n * // Left shoulder at -31cm, right shoulder at +31cm\n * ```\n *\n * @korean 어깨오프셋계산\n */\nexport function calculateShoulderOffset(\n attributes: PhysicalAttributes,\n): number {\n // Shoulder width is the total span, so divide by 2 for offset from center\n // Apply amplification factor for more visible width differences\n const SHOULDER_AMPLIFICATION = 1.15;\n return (attributes.shoulderWidth / 2) * SHOULDER_AMPLIFICATION;\n}\n\n/**\n * Calculate hitbox dimensions based on physical attributes.\n *\n * **Korean**: 히트박스 크기 계산 (Hitbox Dimension Calculation)\n *\n * Determines the bounding box dimensions for collision detection\n * and hit registration based on body proportions.\n *\n * @param attributes - Player's physical attributes\n * @returns Hitbox dimensions {width, height, depth} in centimeters\n *\n * @example\n * ```typescript\n * const hitbox = calculateHitboxDimensions(AMSALJA_PHYSICAL);\n * // Amsalja is tall and lean: {width: 40, height: 182, depth: 25}\n * ```\n *\n * @korean 히트박스크기계산\n */\nexport function calculateHitboxDimensions(attributes: PhysicalAttributes): {\n width: number;\n height: number;\n depth: number;\n} {\n return {\n width: attributes.shoulderWidth,\n height: attributes.totalHeight,\n depth: attributes.shoulderWidth * 0.5, // Approximate depth from shoulder width\n };\n}\n\n/**\n * Calculate vital point offset adjustments based on body proportions.\n *\n * **Korean**: 급소 위치 조정 (Vital Point Position Adjustment)\n *\n * Adjusts vital point positions based on scaled skeleton dimensions.\n * Ensures vital points remain anatomically accurate for different\n * body types while maintaining Korean anatomy principles.\n *\n * @param vitalPointId - ID of the vital point to adjust\n * @param attributes - Player's physical attributes\n * @returns Position adjustment {x, y} offset in centimeters\n *\n * @example\n * ```typescript\n * const adjustment = calculateVitalPointAdjustment(\"head_temple\", MUSA_PHYSICAL);\n * // Returns offset based on head size and torso height\n * ```\n *\n * @korean 급소위치조정\n */\nexport function calculateVitalPointAdjustment(\n vitalPointId: string,\n attributes: PhysicalAttributes,\n): { x: number; y: number } {\n const factors = calculateBoneScalingFactors(attributes);\n\n // Determine which body region this vital point belongs to\n // Use exact prefix matching to avoid ambiguity\n if (vitalPointId.startsWith(\"head_\")) {\n // Head vital points scale with head size and torso height\n return {\n x: 0,\n y: (factors.head - 1.0) * 20 + (factors.spine - 1.0) * 60,\n };\n } else if (vitalPointId.startsWith(\"neck_\")) {\n // Neck vital points scale with neck length and torso height\n return {\n x: 0,\n y: (factors.neck - 1.0) * 10 + (factors.spine - 1.0) * 60,\n };\n } else if (vitalPointId.startsWith(\"torso_\")) {\n // Torso vital points scale with torso length\n return {\n x: 0,\n y: (factors.spine - 1.0) * 30,\n };\n } else if (\n vitalPointId.startsWith(\"shoulder_\") ||\n vitalPointId.startsWith(\"arm_\")\n ) {\n // Arm vital points scale with arm length and shoulder width\n return {\n x: (factors.shoulder - 1.0) * 15,\n y: (factors.upperArm - 1.0) * 12,\n };\n } else if (\n vitalPointId.startsWith(\"leg_\") ||\n vitalPointId.startsWith(\"knee_\")\n ) {\n // Leg vital points scale with leg length\n return {\n x: 0,\n y: -(factors.thigh - 1.0) * 15,\n };\n }\n\n // Default: no adjustment\n return { x: 0, y: 0 };\n}\n\n/**\n * Calculate choke effectiveness modifier based on neck dimensions.\n *\n * **Korean**: 목 조르기 효과 계산 (Choke Effectiveness Calculation)\n *\n * Determines how effective chokes and strangles are based on neck\n * length and thickness. Longer, thinner necks are more vulnerable.\n *\n * @param attributes - Player's physical attributes\n * @returns Choke effectiveness multiplier (1.0 = baseline)\n *\n * @example\n * ```typescript\n * const chokeEffectiveness = calculateChokeEffectiveness(AMSALJA_PHYSICAL);\n * // Amsalja has longer neck: returns ~1.1 (10% more vulnerable)\n *\n * const jojikChoke = calculateChokeEffectiveness(JOJIK_PHYSICAL);\n * // Jojik has shorter, thicker neck: returns ~0.9 (10% more resistant)\n * ```\n *\n * @korean 목조르기효과계산\n */\nexport function calculateChokeEffectiveness(\n attributes: PhysicalAttributes,\n): number {\n // Longer necks are more vulnerable to chokes\n const lengthFactor = attributes.neckLength / REFERENCE_ATTRIBUTES.neckLength;\n\n // Thicker necks (from muscle/weight) resist chokes better\n const thicknessFactor = REFERENCE_ATTRIBUTES.weight / attributes.weight;\n\n // Combine factors: longer neck + lighter weight = more vulnerable\n return lengthFactor * thicknessFactor;\n}\n\n/**\n * Calculate head strike vulnerability based on head size.\n *\n * **Korean**: 머리 타격 취약성 계산 (Head Strike Vulnerability)\n *\n * Determines vulnerability to head strikes based on head size.\n * Larger heads have more mass and resistance, smaller heads are\n * more vulnerable to concussive force.\n *\n * @param attributes - Player's physical attributes\n * @returns Head strike vulnerability multiplier (1.0 = baseline)\n *\n * @example\n * ```typescript\n * const headVuln = calculateHeadStrikeVulnerability(JOJIK_PHYSICAL);\n * // Jojik has larger head: returns ~0.95 (5% more resistant)\n * ```\n *\n * @korean 머리타격취약성계산\n */\nexport function calculateHeadStrikeVulnerability(\n attributes: PhysicalAttributes,\n): number {\n // Larger heads have more mass, providing some protection\n const sizeFactor = REFERENCE_ATTRIBUTES.headSize / attributes.headSize;\n\n // But larger heads are also bigger targets (handled by hitbox size)\n // Here we only calculate the mass-based resistance\n return sizeFactor;\n}\n\n/**\n * Calculate body radius for hit distance calculation.\n *\n * **Korean**: 몸체 반경 계산 (Body Radius Calculation)\n *\n * When calculating hit distance, we measure center-to-center, but attacks\n * land on the target's body surface. This function calculates the effective\n * \"depth\" of a fighter's body from their center point based on their\n * physical attributes.\n *\n * The calculation uses torso depth derived from shoulder width, as broader\n * fighters have proportionally deeper torsos. This is more anatomically\n * accurate than using a fixed constant.\n *\n * Formula: shoulderWidth * 0.5 (torso depth ratio) / 100 (cm to meters)\n *\n * Example body radii:\n * - Hacker (43cm shoulders): 0.215m depth → 0.215m radius\n * - Musa (46cm shoulders): 0.23m depth → 0.23m radius\n * - Jojik (54cm shoulders): 0.27m depth → 0.27m radius\n *\n * @param attributes - Player's physical attributes\n * @returns Body radius in meters (distance from center to body surface)\n *\n * @example\n * ```typescript\n * const radius = calculateBodyRadius(JOJIK_PHYSICAL);\n * // Jojik has wide shoulders (54cm): returns 0.27m\n *\n * const hackerRadius = calculateBodyRadius(HACKER_PHYSICAL);\n * // Hacker is lean (43cm shoulders): returns 0.215m\n * ```\n *\n * @korean 몸체반경계산\n */\nexport function calculateBodyRadius(attributes: PhysicalAttributes): number {\n // Body depth is approximately half of shoulder width (anatomical ratio)\n // This matches calculateHitboxDimensions which uses shoulderWidth * 0.5\n const bodyDepthCm = attributes.shoulderWidth * 0.5;\n\n // Convert from centimeters to meters\n // The radius is the distance from center to body surface (half of total depth)\n // Since bodyDepthCm represents front-to-back depth, we use it directly as\n // the distance from center to front surface\n return bodyDepthCm / 100;\n}\n"],"mappings":";CAgJiD,EAC/C,GAAG,uBACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+XA,SAAgB,oBAAoB,YAAwC;CAS1E,OANoB,WAAW,gBAAgB,KAM1B;AACvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stanceHelpers.js","names":[],"sources":["../../src/utils/stanceHelpers.ts"],"sourcesContent":["/**\n * Shared utility functions for stance-related operations\n *\n * Provides common helpers for stance colors, names, and symbols\n * to avoid duplication across components.\n *\n * @module utils/stanceHelpers\n * @category Utilities\n * @korean 자세도우미\n */\n\nimport { TrigramStance } from \"../types/common\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\n\n/**\n * Get color for each trigram stance\n * Maps 8 trigrams to Korean cyberpunk color palette\n *\n * @param stance - Current trigram stance\n * @returns Hex color number\n * @korean 자세색상가져오기\n */\nexport const getStanceColor = (stance: TrigramStance): number => {\n const stanceColors = {\n [TrigramStance.GEON]: KOREAN_COLORS.TRIGRAM_GEON_PRIMARY, // Heaven - Gold\n [TrigramStance.TAE]: KOREAN_COLORS.TRIGRAM_TAE_PRIMARY, // Lake - Sky Blue\n [TrigramStance.LI]: KOREAN_COLORS.TRIGRAM_LI_PRIMARY, // Fire - Orange Red\n [TrigramStance.JIN]: KOREAN_COLORS.TRIGRAM_JIN_PRIMARY, // Thunder - Purple\n [TrigramStance.SON]: KOREAN_COLORS.TRIGRAM_SON_PRIMARY, // Wind - Light Green\n [TrigramStance.GAM]: KOREAN_COLORS.TRIGRAM_GAM_PRIMARY, // Water - Blue\n [TrigramStance.GAN]: KOREAN_COLORS.TRIGRAM_GAN_PRIMARY, // Mountain - Brown\n [TrigramStance.GON]: KOREAN_COLORS.TRIGRAM_GON_PRIMARY, // Earth - Dark Khaki\n };\n return stanceColors[stance] ?? KOREAN_COLORS.PRIMARY_CYAN;\n};\n\n/**\n * Get stance display names (Korean + English)\n *\n * @param stance - Current trigram stance\n * @returns Object with korean, english, and romanized names\n * @korean 자세이름가져오기\n */\nexport const getStanceNames = (stance: TrigramStance) => {\n const names = {\n [TrigramStance.GEON]: {\n korean: \"건\",\n english: \"Heaven\",\n romanized: \"Geon\",\n },\n [TrigramStance.TAE]: { korean: \"태\", english: \"Lake\", romanized: \"Tae\" },\n [TrigramStance.LI]: { korean: \"리\", english: \"Fire\", romanized: \"Li\" },\n [TrigramStance.JIN]: { korean: \"진\", english: \"Thunder\", romanized: \"Jin\" },\n [TrigramStance.SON]: { korean: \"손\", english: \"Wind\", romanized: \"Son\" },\n [TrigramStance.GAM]: { korean: \"감\", english: \"Water\", romanized: \"Gam\" },\n [TrigramStance.GAN]: {\n korean: \"간\",\n english: \"Mountain\",\n romanized: \"Gan\",\n },\n [TrigramStance.GON]: { korean: \"곤\", english: \"Earth\", romanized: \"Gon\" },\n };\n return (\n names[stance] ?? { korean: \"건\", english: \"Heaven\", romanized: \"Geon\" }\n );\n};\n\n/**\n * Get trigram Unicode symbol for each stance\n *\n * @param stance - Current trigram stance\n * @returns Unicode trigram symbol\n * @korean 팔괘기호가져오기\n */\nexport const getTrigramSymbol = (stance: TrigramStance): string => {\n const symbols = {\n [TrigramStance.GEON]: \"☰\", // Heaven\n [TrigramStance.TAE]: \"☱\", // Lake\n [TrigramStance.LI]: \"☲\", // Fire\n [TrigramStance.JIN]: \"☳\", // Thunder\n [TrigramStance.SON]: \"☴\", // Wind\n [TrigramStance.GAM]: \"☵\", // Water\n [TrigramStance.GAN]: \"☶\", // Mountain\n [TrigramStance.GON]: \"☷\", // Earth\n };\n return symbols[stance] ?? \"☰\";\n};\n\n/**\n * Get Korean name for each stance\n *\n * @param stance - Current trigram stance\n * @returns Korean name (Hangul)\n * @korean 자세한글이름가져오기\n */\nexport const getStanceKoreanName = (stance: TrigramStance): string => {\n return getStanceNames(stance).korean;\n};\n\n/**\n * Get color as hex string for CSS usage\n *\n * @param stance - Current trigram stance\n * @returns Hex color string (e.g., \"#FFD700\")\n * @korean 자세CSS색상가져오기\n */\nexport const getStanceColorHex = (stance: TrigramStance): string => {\n const color = getStanceColor(stance);\n return `#${color.toString(16).padStart(6, \"0\").toLowerCase()}`;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,IAAa,kBAAkB,WAAkC;CAW/D,OAAO;GATJ,cAAc,OAAO,cAAc;GACnC,cAAc,MAAM,cAAc;GAClC,cAAc,KAAK,cAAc;GACjC,cAAc,MAAM,cAAc;GAClC,cAAc,MAAM,cAAc;GAClC,cAAc,MAAM,cAAc;GAClC,cAAc,MAAM,cAAc;GAClC,cAAc,MAAM,cAAc;
|
|
1
|
+
{"version":3,"file":"stanceHelpers.js","names":[],"sources":["../../src/utils/stanceHelpers.ts"],"sourcesContent":["/**\n * Shared utility functions for stance-related operations\n *\n * Provides common helpers for stance colors, names, and symbols\n * to avoid duplication across components.\n *\n * @module utils/stanceHelpers\n * @category Utilities\n * @korean 자세도우미\n */\n\nimport { TrigramStance } from \"../types/common\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\n\n/**\n * Get color for each trigram stance\n * Maps 8 trigrams to Korean cyberpunk color palette\n *\n * @param stance - Current trigram stance\n * @returns Hex color number\n * @korean 자세색상가져오기\n */\nexport const getStanceColor = (stance: TrigramStance): number => {\n const stanceColors = {\n [TrigramStance.GEON]: KOREAN_COLORS.TRIGRAM_GEON_PRIMARY, // Heaven - Gold\n [TrigramStance.TAE]: KOREAN_COLORS.TRIGRAM_TAE_PRIMARY, // Lake - Sky Blue\n [TrigramStance.LI]: KOREAN_COLORS.TRIGRAM_LI_PRIMARY, // Fire - Orange Red\n [TrigramStance.JIN]: KOREAN_COLORS.TRIGRAM_JIN_PRIMARY, // Thunder - Purple\n [TrigramStance.SON]: KOREAN_COLORS.TRIGRAM_SON_PRIMARY, // Wind - Light Green\n [TrigramStance.GAM]: KOREAN_COLORS.TRIGRAM_GAM_PRIMARY, // Water - Blue\n [TrigramStance.GAN]: KOREAN_COLORS.TRIGRAM_GAN_PRIMARY, // Mountain - Brown\n [TrigramStance.GON]: KOREAN_COLORS.TRIGRAM_GON_PRIMARY, // Earth - Dark Khaki\n };\n return stanceColors[stance] ?? KOREAN_COLORS.PRIMARY_CYAN;\n};\n\n/**\n * Get stance display names (Korean + English)\n *\n * @param stance - Current trigram stance\n * @returns Object with korean, english, and romanized names\n * @korean 자세이름가져오기\n */\nexport const getStanceNames = (stance: TrigramStance) => {\n const names = {\n [TrigramStance.GEON]: {\n korean: \"건\",\n english: \"Heaven\",\n romanized: \"Geon\",\n },\n [TrigramStance.TAE]: { korean: \"태\", english: \"Lake\", romanized: \"Tae\" },\n [TrigramStance.LI]: { korean: \"리\", english: \"Fire\", romanized: \"Li\" },\n [TrigramStance.JIN]: { korean: \"진\", english: \"Thunder\", romanized: \"Jin\" },\n [TrigramStance.SON]: { korean: \"손\", english: \"Wind\", romanized: \"Son\" },\n [TrigramStance.GAM]: { korean: \"감\", english: \"Water\", romanized: \"Gam\" },\n [TrigramStance.GAN]: {\n korean: \"간\",\n english: \"Mountain\",\n romanized: \"Gan\",\n },\n [TrigramStance.GON]: { korean: \"곤\", english: \"Earth\", romanized: \"Gon\" },\n };\n return (\n names[stance] ?? { korean: \"건\", english: \"Heaven\", romanized: \"Geon\" }\n );\n};\n\n/**\n * Get trigram Unicode symbol for each stance\n *\n * @param stance - Current trigram stance\n * @returns Unicode trigram symbol\n * @korean 팔괘기호가져오기\n */\nexport const getTrigramSymbol = (stance: TrigramStance): string => {\n const symbols = {\n [TrigramStance.GEON]: \"☰\", // Heaven\n [TrigramStance.TAE]: \"☱\", // Lake\n [TrigramStance.LI]: \"☲\", // Fire\n [TrigramStance.JIN]: \"☳\", // Thunder\n [TrigramStance.SON]: \"☴\", // Wind\n [TrigramStance.GAM]: \"☵\", // Water\n [TrigramStance.GAN]: \"☶\", // Mountain\n [TrigramStance.GON]: \"☷\", // Earth\n };\n return symbols[stance] ?? \"☰\";\n};\n\n/**\n * Get Korean name for each stance\n *\n * @param stance - Current trigram stance\n * @returns Korean name (Hangul)\n * @korean 자세한글이름가져오기\n */\nexport const getStanceKoreanName = (stance: TrigramStance): string => {\n return getStanceNames(stance).korean;\n};\n\n/**\n * Get color as hex string for CSS usage\n *\n * @param stance - Current trigram stance\n * @returns Hex color string (e.g., \"#FFD700\")\n * @korean 자세CSS색상가져오기\n */\nexport const getStanceColorHex = (stance: TrigramStance): string => {\n const color = getStanceColor(stance);\n return `#${color.toString(16).padStart(6, \"0\").toLowerCase()}`;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,IAAa,kBAAkB,WAAkC;CAW/D,OAAO;GATJ,cAAc,OAAO,cAAc;GACnC,cAAc,MAAM,cAAc;GAClC,cAAc,KAAK,cAAc;GACjC,cAAc,MAAM,cAAc;GAClC,cAAc,MAAM,cAAc;GAClC,cAAc,MAAM,cAAc;GAClC,cAAc,MAAM,cAAc;GAClC,cAAc,MAAM,cAAc;CAE9B,EAAa,WAAW,cAAc;AAC/C;;;;;;;;AASA,IAAa,kBAAkB,WAA0B;CAmBvD,OACE;GAlBC,cAAc,OAAO;GACpB,QAAQ;GACR,SAAS;GACT,WAAW;EACb;GACC,cAAc,MAAM;GAAE,QAAQ;GAAK,SAAS;GAAQ,WAAW;EAAM;GACrE,cAAc,KAAK;GAAE,QAAQ;GAAK,SAAS;GAAQ,WAAW;EAAK;GACnE,cAAc,MAAM;GAAE,QAAQ;GAAK,SAAS;GAAW,WAAW;EAAM;GACxE,cAAc,MAAM;GAAE,QAAQ;GAAK,SAAS;GAAQ,WAAW;EAAM;GACrE,cAAc,MAAM;GAAE,QAAQ;GAAK,SAAS;GAAS,WAAW;EAAM;GACtE,cAAc,MAAM;GACnB,QAAQ;GACR,SAAS;GACT,WAAW;EACb;GACC,cAAc,MAAM;GAAE,QAAQ;GAAK,SAAS;GAAS,WAAW;EAAM;CAGvE,EAAM,WAAW;EAAE,QAAQ;EAAK,SAAS;EAAU,WAAW;CAAO;AAEzE;;;;;;;;AASA,IAAa,oBAAoB,WAAkC;CAWjE,OAAO;GATJ,cAAc,OAAO;GACrB,cAAc,MAAM;GACpB,cAAc,KAAK;GACnB,cAAc,MAAM;GACpB,cAAc,MAAM;GACpB,cAAc,MAAM;GACpB,cAAc,MAAM;GACpB,cAAc,MAAM;CAEhB,EAAQ,WAAW;AAC5B;;;;;;;;AASA,IAAa,uBAAuB,WAAkC;CACpE,OAAO,eAAe,MAAM,EAAE;AAChC;;;;;;;;AASA,IAAa,qBAAqB,WAAkC;CAElE,OAAO,IADO,eAAe,MAClB,EAAM,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,EAAE,YAAY;AAC7D"}
|