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":"skeletal.js","names":[],"sources":["../../src/types/skeletal.ts"],"sourcesContent":["/**\n * Skeletal rigging types for articulated body model\n *\n * Defines bone hierarchy, skeletal rig structure, and animation keyframes\n * for realistic human-like fighter animations with independent limb movement.\n *\n * @module types/skeletal\n * @category Type Definitions\n * @korean 골격타입\n */\n\nimport * as THREE from \"three\";\n\n/**\n * Bone in skeletal rig hierarchy\n *\n * Represents a single bone with position, rotation, scale, and parent-child relationships.\n * Bones form a tree structure for realistic articulated body movement.\n *\n * @public\n * @category Skeletal System\n * @korean 뼈\n */\nexport interface Bone {\n /**\n * Unique identifier for the bone\n * @korean 이름\n */\n readonly name: string;\n\n /**\n * Parent bone (null for root bone)\n * @korean 부모뼈\n */\n parent: Bone | null;\n\n /**\n * Local position relative to parent\n * @korean 위치\n */\n position: THREE.Vector3;\n\n /**\n * Local rotation in Euler angles\n * @korean 회전\n */\n rotation: THREE.Euler;\n\n /**\n * Local scale\n * @korean 크기\n */\n scale: THREE.Vector3;\n\n /**\n * Child bones\n * @korean 자식뼈들\n */\n children: Bone[];\n\n /**\n * Length of the bone (for rendering)\n * @korean 길이\n */\n readonly length: number;\n\n /**\n * Rest pose position (default pose)\n * @korean 기본위치\n */\n readonly restPosition: THREE.Vector3;\n\n /**\n * Rest pose rotation (default pose)\n * @korean 기본회전\n */\n readonly restRotation: THREE.Euler;\n}\n\n/**\n * Skeletal rig with complete bone hierarchy\n *\n * Contains root bone and map of all bones for efficient lookup.\n * Maximum 30 bones for 60fps performance.\n *\n * @public\n * @category Skeletal System\n * @korean 골격\n */\nexport interface SkeletalRig {\n /**\n * Root bone (pelvis/center)\n * @korean 뿌리뼈\n */\n readonly root: Bone;\n\n /**\n * Map of bone name to bone for fast lookup\n * @korean 뼈맵\n */\n readonly bones: Map<string, Bone>;\n\n /**\n * Total number of bones in rig\n * @korean 뼈개수\n */\n readonly boneCount: number;\n}\n\n/**\n * Animation keyframe for skeletal animation\n *\n * Defines bone transformations at a specific time in the animation.\n * Keyframes are interpolated for smooth animation between poses.\n * Now includes integrated anatomy state for hands, feet, and facial expressions.\n *\n * @public\n * @category Animation\n * @korean 애니메이션키프레임\n */\nexport interface AnimationKeyframe {\n /**\n * Time in seconds from animation start\n * @korean 시간\n */\n readonly time: number;\n\n /**\n * Bone rotations at this keyframe\n * Map of bone name to rotation\n * @korean 뼈회전들\n */\n readonly boneRotations: Map<string, THREE.Euler>;\n\n /**\n * Bone positions at this keyframe (optional, for IK or special moves)\n * Map of bone name to position offset from rest pose\n * @korean 뼈위치들\n */\n readonly bonePositions: Map<string, THREE.Vector3>;\n\n /**\n * Optional easing function name for interpolation\n * @korean 이징함수\n */\n readonly easing?: \"linear\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\";\n\n // ═══════════════════════════════════════════════════════════════════════════\n // ANATOMY STATE (해부학 상태) - Integrated hand, foot, and facial animation\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Left hand pose type at this keyframe\n * @korean 왼손자세\n */\n readonly leftHandPose?: string;\n\n /**\n * Right hand pose type at this keyframe\n * @korean 오른손자세\n */\n readonly rightHandPose?: string;\n\n /**\n * Whether left foot is highlighted (e.g., during kicks)\n * @korean 왼발강조\n */\n readonly leftFootHighlight?: boolean;\n\n /**\n * Whether right foot is highlighted (e.g., during kicks)\n * @korean 오른발강조\n */\n readonly rightFootHighlight?: boolean;\n\n /**\n * Left hand highlight mode for striking surface\n * @korean 왼손강조모드\n */\n readonly leftHandHighlightMode?:\n | \"none\"\n | \"knuckles\"\n | \"palm\"\n | \"knife_edge\"\n | \"fingertips\";\n\n /**\n * Right hand highlight mode for striking surface\n * @korean 오른손강조모드\n */\n readonly rightHandHighlightMode?:\n | \"none\"\n | \"knuckles\"\n | \"palm\"\n | \"knife_edge\"\n | \"fingertips\";\n\n /**\n * Facial expression at this keyframe\n * @korean 얼굴표정\n */\n readonly facialExpression?: string;\n\n /**\n * Muscle activation targets at this keyframe\n * Map of muscle group name to tension level (0-1)\n * @korean 근육활성화\n */\n readonly muscleActivations?: Map<string, number>;\n}\n\n/**\n * Attack animation type categories\n *\n * Defines the 5 base categories of attack animations with variants.\n * Each technique maps to one of these animation types.\n *\n * @public\n * @category Animation\n * @korean 공격애니메이션타입\n */\nexport enum AttackAnimationType {\n // Punch category (주먹 타격)\n PUNCH_HIGH = \"punch_high\",\n PUNCH_MID = \"punch_mid\",\n PUNCH_LOW = \"punch_low\",\n\n // Kick category (발차기)\n KICK_FRONT = \"kick_front\",\n KICK_SIDE = \"kick_side\",\n KICK_ROUNDHOUSE = \"kick_round\",\n\n // Elbow category (팔꿈치 타격)\n ELBOW_STRIKE = \"elbow_strike\",\n ELBOW_UPPERCUT = \"elbow_uppercut\",\n\n // Knee category (무릎 타격)\n KNEE_STRIKE = \"knee_strike\",\n KNEE_CLINCH = \"knee_clinch\",\n\n // Pressure point category (급소 타격)\n PRESSURE_POINT = \"pressure_point\",\n PRESSURE_POINT_RAPID = \"pressure_point_rapid\",\n}\n\n/**\n * Animation configuration for a technique\n *\n * Links a technique to its specific attack animation with speed modifier.\n *\n * @public\n * @category Animation\n * @korean 기술애니메이션설정\n */\nexport interface TechniqueAnimationConfig {\n /**\n * Type of attack animation to play\n * @korean 애니메이션타입\n */\n readonly type: AttackAnimationType;\n\n /**\n * Speed modifier (0.8-1.2)\n * - Light techniques: 1.2x speed\n * - Normal techniques: 1.0x speed\n * - Heavy techniques: 0.8x speed\n * @korean 속도배율\n */\n readonly speedModifier: number;\n}\n\n/**\n * Complete skeletal animation sequence\n *\n * Sequence of keyframes defining a complete animation\n * (e.g., jab, cross, roundhouse kick).\n *\n * @public\n * @category Animation\n * @korean 골격애니메이션\n */\nexport interface SkeletalAnimation {\n /**\n * Animation identifier\n * @korean 이름\n */\n readonly name: string;\n\n /**\n * Korean name for animation\n * @korean 한글이름\n */\n readonly koreanName: string;\n\n /**\n * Animation keyframes in chronological order\n * @korean 키프레임들\n */\n readonly keyframes: AnimationKeyframe[];\n\n /**\n * Total duration in seconds\n * @korean 지속시간\n */\n readonly duration: number;\n\n /**\n * Whether animation loops\n * @korean 반복여부\n */\n readonly loop: boolean;\n\n /**\n * Animation type (attack, defense, stance change, movement, etc.)\n * @korean 애니메이션타입\n */\n readonly type: \"attack\" | \"defense\" | \"stance\" | \"walk\" | \"idle\" | \"movement\";\n}\n\n/**\n * Bone chain definition for IK (Inverse Kinematics)\n *\n * Defines a chain of bones for IK solving (e.g., arm chain, leg chain).\n * Used for realistic limb positioning and movement.\n *\n * @public\n * @category Skeletal System\n * @korean 뼈체인\n */\nexport interface BoneChain {\n /**\n * Chain identifier\n * @korean 이름\n */\n readonly name: string;\n\n /**\n * Start bone (e.g., shoulder for arm chain)\n * @korean 시작뼈\n */\n readonly startBone: string;\n\n /**\n * End bone (e.g., hand for arm chain)\n * @korean 끝뼈\n */\n readonly endBone: string;\n\n /**\n * Bones in chain from start to end\n * @korean 뼈들\n */\n readonly bones: string[];\n\n /**\n * IK target position (for end effector)\n * @korean IK목표\n */\n ikTarget?: THREE.Vector3;\n}\n\n/**\n * Hand bone structure with 5 fingers\n *\n * Simplified hand model with palm and 5 fingers.\n * Each finger has 3 segments (proximal, middle, distal).\n *\n * @public\n * @category Skeletal System\n * @korean 손뼈\n */\nexport interface HandBones {\n /**\n * Palm bone\n * @korean 손바닥\n */\n readonly palm: Bone;\n\n /**\n * Thumb bones (3 segments)\n * @korean 엄지손가락\n */\n readonly thumb: [Bone, Bone, Bone];\n\n /**\n * Index finger bones (3 segments)\n * @korean 검지손가락\n */\n readonly index: [Bone, Bone, Bone];\n\n /**\n * Middle finger bones (3 segments)\n * @korean 중지손가락\n */\n readonly middle: [Bone, Bone, Bone];\n\n /**\n * Ring finger bones (3 segments)\n * @korean 약지손가락\n */\n readonly ring: [Bone, Bone, Bone];\n\n /**\n * Pinky finger bones (3 segments)\n * @korean 새끼손가락\n */\n readonly pinky: [Bone, Bone, Bone];\n}\n\n/**\n * Joint constraint for realistic movement\n *\n * Defines rotation limits for joints (e.g., elbow can't bend backward).\n * Ensures anatomically correct movement.\n *\n * @public\n * @category Skeletal System\n * @korean 관절제약\n */\nexport interface JointConstraint {\n /**\n * Bone name this constraint applies to\n * @korean 뼈이름\n */\n readonly boneName: string;\n\n /**\n * Minimum rotation angles (X, Y, Z) in radians\n * @korean 최소회전\n */\n readonly minRotation: THREE.Vector3;\n\n /**\n * Maximum rotation angles (X, Y, Z) in radians\n * @korean 최대회전\n */\n readonly maxRotation: THREE.Vector3;\n\n /**\n * Whether this joint can twist (rotate around its length)\n * @korean 비틀기가능\n */\n readonly canTwist: boolean;\n}\n\n/**\n * Bone names for humanoid rig\n *\n * Standard bone naming convention for humanoid skeleton.\n * Total: 28 bones base + optional 38 hand bones (19 per hand) = 66 bones max.\n *\n * Hand bones are optional and can be excluded for performance (LOD system).\n *\n * @public\n * @category Skeletal System\n * @korean 뼈이름들\n */\nexport enum BoneName {\n // Core (1 bone)\n PELVIS = \"pelvis\",\n\n // Spine (3 bones)\n SPINE_LOWER = \"spine_lower\",\n SPINE_MIDDLE = \"spine_middle\",\n SPINE_UPPER = \"spine_upper\",\n\n // Head (2 bones)\n NECK = \"neck\",\n HEAD = \"head\",\n\n // Left Arm (6 bones)\n SHOULDER_L = \"shoulder_L\",\n UPPER_ARM_L = \"upper_arm_L\",\n ELBOW_L = \"elbow_L\",\n FOREARM_L = \"forearm_L\",\n WRIST_L = \"wrist_L\",\n HAND_L = \"hand_L\",\n\n // Right Arm (6 bones)\n SHOULDER_R = \"shoulder_R\",\n UPPER_ARM_R = \"upper_arm_R\",\n ELBOW_R = \"elbow_R\",\n FOREARM_R = \"forearm_R\",\n WRIST_R = \"wrist_R\",\n HAND_R = \"hand_R\",\n\n // Left Leg (5 bones)\n HIP_L = \"hip_L\",\n THIGH_L = \"thigh_L\",\n KNEE_L = \"knee_L\",\n SHIN_L = \"shin_L\",\n FOOT_L = \"foot_L\",\n\n // Right Leg (5 bones)\n HIP_R = \"hip_R\",\n THIGH_R = \"thigh_R\",\n KNEE_R = \"knee_R\",\n SHIN_R = \"shin_R\",\n FOOT_R = \"foot_R\",\n\n // Left Hand Fingers (19 bones - optional for LOD)\n THUMB_META_L = \"thumb_meta_L\",\n THUMB_PROX_L = \"thumb_prox_L\",\n THUMB_DIST_L = \"thumb_dist_L\",\n INDEX_META_L = \"index_meta_L\",\n INDEX_PROX_L = \"index_prox_L\",\n INDEX_INTER_L = \"index_inter_L\",\n INDEX_DIST_L = \"index_dist_L\",\n MIDDLE_META_L = \"middle_meta_L\",\n MIDDLE_PROX_L = \"middle_prox_L\",\n MIDDLE_INTER_L = \"middle_inter_L\",\n MIDDLE_DIST_L = \"middle_dist_L\",\n RING_META_L = \"ring_meta_L\",\n RING_PROX_L = \"ring_prox_L\",\n RING_INTER_L = \"ring_inter_L\",\n RING_DIST_L = \"ring_dist_L\",\n PINKY_META_L = \"pinky_meta_L\",\n PINKY_PROX_L = \"pinky_prox_L\",\n PINKY_INTER_L = \"pinky_inter_L\",\n PINKY_DIST_L = \"pinky_dist_L\",\n\n // Right Hand Fingers (19 bones - optional for LOD)\n THUMB_META_R = \"thumb_meta_R\",\n THUMB_PROX_R = \"thumb_prox_R\",\n THUMB_DIST_R = \"thumb_dist_R\",\n INDEX_META_R = \"index_meta_R\",\n INDEX_PROX_R = \"index_prox_R\",\n INDEX_INTER_R = \"index_inter_R\",\n INDEX_DIST_R = \"index_dist_R\",\n MIDDLE_META_R = \"middle_meta_R\",\n MIDDLE_PROX_R = \"middle_prox_R\",\n MIDDLE_INTER_R = \"middle_inter_R\",\n MIDDLE_DIST_R = \"middle_dist_R\",\n RING_META_R = \"ring_meta_R\",\n RING_PROX_R = \"ring_prox_R\",\n RING_INTER_R = \"ring_inter_R\",\n RING_DIST_R = \"ring_dist_R\",\n PINKY_META_R = \"pinky_meta_R\",\n PINKY_PROX_R = \"pinky_prox_R\",\n PINKY_INTER_R = \"pinky_inter_R\",\n PINKY_DIST_R = \"pinky_dist_R\",\n}\n\n/**\n * Animation state for skeletal player\n *\n * Tracks current animation playback state for skeletal animations.\n *\n * @public\n * @category Animation\n * @korean 애니메이션상태\n */\nexport interface SkeletalAnimationState {\n /**\n * Current animation being played\n * @korean 현재애니메이션\n */\n currentAnimation: SkeletalAnimation | null;\n\n /**\n * Current time in animation (seconds)\n * @korean 현재시간\n */\n currentTime: number;\n\n /**\n * Whether animation is playing\n * @korean 재생중\n */\n isPlaying: boolean;\n\n /**\n * Animation playback speed multiplier\n * @korean 재생속도\n */\n playbackSpeed: number;\n\n /**\n * Previous keyframe index\n * @korean 이전키프레임\n */\n previousKeyframeIndex: number;\n\n /**\n * Next keyframe index\n * @korean 다음키프레임\n */\n nextKeyframeIndex: number;\n}\n\n/**\n * Fighting stance guard pose configuration\n *\n * Defines complete body positioning for authentic Korean martial arts stances.\n * Includes arms, torso, legs, and pelvis rotations based on traditional Taekwondo/Hapkido.\n *\n * Each stance corresponds to a real martial arts position with unique leg positioning.\n * Used for stance-specific idle animations at 60fps.\n *\n * @public\n * @category Animation\n * @korean 자세방어포즈\n */\nexport interface StanceGuardPose {\n /**\n * Left arm bone rotations (shoulder, elbow, wrist)\n * @korean 왼팔\n */\n readonly leftArm: {\n readonly shoulder: THREE.Euler;\n readonly elbow: THREE.Euler;\n readonly wrist: THREE.Euler;\n };\n\n /**\n * Right arm bone rotations (shoulder, elbow, wrist)\n * @korean 오른팔\n */\n readonly rightArm: {\n readonly shoulder: THREE.Euler;\n readonly elbow: THREE.Euler;\n readonly wrist: THREE.Euler;\n };\n\n /**\n * Torso rotation (spine upper bone)\n * @korean 몸통회전\n */\n readonly torso: THREE.Euler;\n\n /**\n * Left leg bone rotations (hip, knee, ankle)\n * NEW: For authentic Taekwondo stance positioning\n * @korean 왼다리\n */\n readonly leftLeg: {\n readonly hip: THREE.Euler; // Hip rotation/abduction\n readonly knee: THREE.Euler; // Knee bend angle\n readonly ankle: THREE.Euler; // Ankle dorsiflexion/plantarflexion\n };\n\n /**\n * Right leg bone rotations (hip, knee, ankle)\n * NEW: For authentic Taekwondo stance positioning\n * @korean 오른다리\n */\n readonly rightLeg: {\n readonly hip: THREE.Euler; // Hip rotation/abduction\n readonly knee: THREE.Euler; // Knee bend angle\n readonly ankle: THREE.Euler; // Ankle dorsiflexion/plantarflexion\n };\n\n /**\n * Pelvis rotation (hip tilt and rotation)\n * NEW: For proper stance base\n * @korean 골반회전\n */\n readonly pelvis: THREE.Euler;\n\n /**\n * Stance width (foot spacing in meters)\n * NEW: Defines lateral distance between feet\n * Range: 0.3 (narrow) to 1.5 (wide horse stance)\n *\n * This value describes the intended lateral spacing between the feet for this\n * stance configuration. Consumers (e.g. animation systems or positioning\n * overlays) may use it to drive foot placement, validation, or visualization\n * as needed.\n *\n * @korean 자세너비\n */\n readonly stanceWidth: number;\n\n /**\n * Stance depth (front-to-back foot spacing in meters)\n * Optional: Defaults to 0 for parallel stances (Juchum Seogi, Narani Seogi)\n *\n * For forward stances (Ap Koobi Seogi): positive value (~0.6-0.9m)\n * For back stances (Dwi Seogi): negative value (~-0.3m with back foot forward)\n *\n * Left foot gets -depth/2, right foot gets +depth/2 on Z-axis.\n * Combined with stanceWidth, this defines the full 2D foot placement.\n *\n * @korean 자세깊이\n */\n readonly stanceDepth?: number;\n\n /**\n * Pelvis height offset (vertical drop in meters)\n * Optional: Defaults to 0 for normal standing height\n *\n * For deep stances (Juchum Seogi, Joong Ha Seogi): negative value (~-0.15 to -0.3m)\n * This lowers the center of gravity for power and stability.\n *\n * @korean 골반높이\n */\n readonly pelvisHeight?: number;\n\n /**\n * Weight distribution (forward/neutral/back)\n * @korean 무게중심\n */\n readonly weight: \"forward\" | \"neutral\" | \"back\";\n\n /**\n * Breathing animation range (min/max for chest movement)\n * @korean 호흡범위\n */\n readonly breathingRange: {\n readonly min: number;\n readonly max: number;\n };\n}\n\n/**\n * Stance guard animation configuration\n *\n * Extends base AnimationConfig with stance-specific guard pose data.\n * Includes 4-6 frame breathing animation for realistic idle behavior.\n *\n * @public\n * @category Animation\n * @korean 자세방어애니메이션설정\n */\nexport interface StanceGuardAnimationConfig {\n /**\n * Trigram stance identifier\n * @korean 괘\n */\n readonly stance: string;\n\n /**\n * Korean name of stance\n * @korean 한글이름\n */\n readonly koreanName: string;\n\n /**\n * English name of stance\n * @korean 영어이름\n */\n readonly englishName: string;\n\n /**\n * Guard pose keyframe (default position)\n * @korean 방어포즈\n */\n readonly guardPose: StanceGuardPose;\n\n /**\n * Breathing animation frames (4-6 frames)\n * @korean 호흡프레임\n */\n readonly breathingFrames: number;\n\n /**\n * Target frames per second (60fps)\n * @korean 초당프레임\n */\n readonly fps: number;\n\n /**\n * Breathing cycle loop enabled\n * @korean 반복여부\n */\n readonly loop: boolean;\n\n /**\n * Animation priority (0 for idle guards)\n * @korean 우선순위\n */\n readonly priority: number;\n}\n\n/**\n * Mirror a guard pose for left/right stance laterality.\n *\n * **Korean**: 자세 좌우 대칭\n *\n * Creates a mirror-image guard pose by swapping left and right limb positions\n * and negating lateral (Y-axis and Z-axis) rotations. This enables authentic\n * left/right stance differentiation in Korean martial arts.\n *\n * Key transformations:\n * - Swap leftArm ↔ rightArm bone rotations\n * - Negate Y rotation (lateral twist)\n * - Negate Z rotation (roll)\n * - Preserve X rotation (forward/back bend)\n * - Keep weight distribution and breathing range unchanged\n *\n * @param pose - Original guard pose to mirror\n * @returns Mirrored guard pose with swapped and negated rotations\n *\n * @example\n * ```typescript\n * // Create right-handed version of a left-handed guard\n * const leftGeonGuard = GEON_HIGH_GUARD_POSE;\n * const rightGeonGuard = mirrorGuardPose(leftGeonGuard);\n *\n * // leftGeonGuard has left hand forward\n * // rightGeonGuard has right hand forward (mirrored)\n * ```\n *\n * @public\n * @category Animation\n * @korean 방어포즈대칭\n */\nexport function mirrorGuardPose(pose: StanceGuardPose): StanceGuardPose {\n // Helper to negate Y and Z rotations while preserving X\n const mirrorEuler = (euler: THREE.Euler): THREE.Euler => {\n return new THREE.Euler(\n euler.x, // Preserve forward/back bend\n -euler.y, // Negate lateral twist\n -euler.z // Negate roll\n );\n };\n\n return {\n // Swap left and right arms with mirrored rotations\n leftArm: {\n shoulder: mirrorEuler(pose.rightArm.shoulder),\n elbow: mirrorEuler(pose.rightArm.elbow),\n wrist: mirrorEuler(pose.rightArm.wrist),\n },\n rightArm: {\n shoulder: mirrorEuler(pose.leftArm.shoulder),\n elbow: mirrorEuler(pose.leftArm.elbow),\n wrist: mirrorEuler(pose.leftArm.wrist),\n },\n // Mirror torso rotation\n torso: mirrorEuler(pose.torso),\n // NEW: Swap and mirror leg positions\n leftLeg: {\n hip: mirrorEuler(pose.rightLeg.hip),\n knee: mirrorEuler(pose.rightLeg.knee),\n ankle: mirrorEuler(pose.rightLeg.ankle),\n },\n rightLeg: {\n hip: mirrorEuler(pose.leftLeg.hip),\n knee: mirrorEuler(pose.leftLeg.knee),\n ankle: mirrorEuler(pose.leftLeg.ankle),\n },\n // Mirror pelvis rotation\n pelvis: mirrorEuler(pose.pelvis),\n // Stance width remains the same\n stanceWidth: pose.stanceWidth,\n // Stance depth: negate for mirrored stance (front foot becomes back foot)\n stanceDepth: pose.stanceDepth !== undefined ? -pose.stanceDepth : undefined,\n // Pelvis height remains unchanged (not affected by laterality)\n pelvisHeight: pose.pelvisHeight,\n // Weight distribution remains the same\n weight: pose.weight,\n // Breathing range unchanged (not affected by laterality, reuse original object)\n breathingRange: pose.breathingRange,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"skeletal.js","names":[],"sources":["../../src/types/skeletal.ts"],"sourcesContent":["/**\n * Skeletal rigging types for articulated body model\n *\n * Defines bone hierarchy, skeletal rig structure, and animation keyframes\n * for realistic human-like fighter animations with independent limb movement.\n *\n * @module types/skeletal\n * @category Type Definitions\n * @korean 골격타입\n */\n\nimport * as THREE from \"three\";\n\n/**\n * Bone in skeletal rig hierarchy\n *\n * Represents a single bone with position, rotation, scale, and parent-child relationships.\n * Bones form a tree structure for realistic articulated body movement.\n *\n * @public\n * @category Skeletal System\n * @korean 뼈\n */\nexport interface Bone {\n /**\n * Unique identifier for the bone\n * @korean 이름\n */\n readonly name: string;\n\n /**\n * Parent bone (null for root bone)\n * @korean 부모뼈\n */\n parent: Bone | null;\n\n /**\n * Local position relative to parent\n * @korean 위치\n */\n position: THREE.Vector3;\n\n /**\n * Local rotation in Euler angles\n * @korean 회전\n */\n rotation: THREE.Euler;\n\n /**\n * Local scale\n * @korean 크기\n */\n scale: THREE.Vector3;\n\n /**\n * Child bones\n * @korean 자식뼈들\n */\n children: Bone[];\n\n /**\n * Length of the bone (for rendering)\n * @korean 길이\n */\n readonly length: number;\n\n /**\n * Rest pose position (default pose)\n * @korean 기본위치\n */\n readonly restPosition: THREE.Vector3;\n\n /**\n * Rest pose rotation (default pose)\n * @korean 기본회전\n */\n readonly restRotation: THREE.Euler;\n}\n\n/**\n * Skeletal rig with complete bone hierarchy\n *\n * Contains root bone and map of all bones for efficient lookup.\n * Maximum 30 bones for 60fps performance.\n *\n * @public\n * @category Skeletal System\n * @korean 골격\n */\nexport interface SkeletalRig {\n /**\n * Root bone (pelvis/center)\n * @korean 뿌리뼈\n */\n readonly root: Bone;\n\n /**\n * Map of bone name to bone for fast lookup\n * @korean 뼈맵\n */\n readonly bones: Map<string, Bone>;\n\n /**\n * Total number of bones in rig\n * @korean 뼈개수\n */\n readonly boneCount: number;\n}\n\n/**\n * Animation keyframe for skeletal animation\n *\n * Defines bone transformations at a specific time in the animation.\n * Keyframes are interpolated for smooth animation between poses.\n * Now includes integrated anatomy state for hands, feet, and facial expressions.\n *\n * @public\n * @category Animation\n * @korean 애니메이션키프레임\n */\nexport interface AnimationKeyframe {\n /**\n * Time in seconds from animation start\n * @korean 시간\n */\n readonly time: number;\n\n /**\n * Bone rotations at this keyframe\n * Map of bone name to rotation\n * @korean 뼈회전들\n */\n readonly boneRotations: Map<string, THREE.Euler>;\n\n /**\n * Bone positions at this keyframe (optional, for IK or special moves)\n * Map of bone name to position offset from rest pose\n * @korean 뼈위치들\n */\n readonly bonePositions: Map<string, THREE.Vector3>;\n\n /**\n * Optional easing function name for interpolation\n * @korean 이징함수\n */\n readonly easing?: \"linear\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\";\n\n // ═══════════════════════════════════════════════════════════════════════════\n // ANATOMY STATE (해부학 상태) - Integrated hand, foot, and facial animation\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Left hand pose type at this keyframe\n * @korean 왼손자세\n */\n readonly leftHandPose?: string;\n\n /**\n * Right hand pose type at this keyframe\n * @korean 오른손자세\n */\n readonly rightHandPose?: string;\n\n /**\n * Whether left foot is highlighted (e.g., during kicks)\n * @korean 왼발강조\n */\n readonly leftFootHighlight?: boolean;\n\n /**\n * Whether right foot is highlighted (e.g., during kicks)\n * @korean 오른발강조\n */\n readonly rightFootHighlight?: boolean;\n\n /**\n * Left hand highlight mode for striking surface\n * @korean 왼손강조모드\n */\n readonly leftHandHighlightMode?:\n | \"none\"\n | \"knuckles\"\n | \"palm\"\n | \"knife_edge\"\n | \"fingertips\";\n\n /**\n * Right hand highlight mode for striking surface\n * @korean 오른손강조모드\n */\n readonly rightHandHighlightMode?:\n | \"none\"\n | \"knuckles\"\n | \"palm\"\n | \"knife_edge\"\n | \"fingertips\";\n\n /**\n * Facial expression at this keyframe\n * @korean 얼굴표정\n */\n readonly facialExpression?: string;\n\n /**\n * Muscle activation targets at this keyframe\n * Map of muscle group name to tension level (0-1)\n * @korean 근육활성화\n */\n readonly muscleActivations?: Map<string, number>;\n}\n\n/**\n * Attack animation type categories\n *\n * Defines the 5 base categories of attack animations with variants.\n * Each technique maps to one of these animation types.\n *\n * @public\n * @category Animation\n * @korean 공격애니메이션타입\n */\nexport enum AttackAnimationType {\n // Punch category (주먹 타격)\n PUNCH_HIGH = \"punch_high\",\n PUNCH_MID = \"punch_mid\",\n PUNCH_LOW = \"punch_low\",\n\n // Kick category (발차기)\n KICK_FRONT = \"kick_front\",\n KICK_SIDE = \"kick_side\",\n KICK_ROUNDHOUSE = \"kick_round\",\n\n // Elbow category (팔꿈치 타격)\n ELBOW_STRIKE = \"elbow_strike\",\n ELBOW_UPPERCUT = \"elbow_uppercut\",\n\n // Knee category (무릎 타격)\n KNEE_STRIKE = \"knee_strike\",\n KNEE_CLINCH = \"knee_clinch\",\n\n // Pressure point category (급소 타격)\n PRESSURE_POINT = \"pressure_point\",\n PRESSURE_POINT_RAPID = \"pressure_point_rapid\",\n}\n\n/**\n * Animation configuration for a technique\n *\n * Links a technique to its specific attack animation with speed modifier.\n *\n * @public\n * @category Animation\n * @korean 기술애니메이션설정\n */\nexport interface TechniqueAnimationConfig {\n /**\n * Type of attack animation to play\n * @korean 애니메이션타입\n */\n readonly type: AttackAnimationType;\n\n /**\n * Speed modifier (0.8-1.2)\n * - Light techniques: 1.2x speed\n * - Normal techniques: 1.0x speed\n * - Heavy techniques: 0.8x speed\n * @korean 속도배율\n */\n readonly speedModifier: number;\n}\n\n/**\n * Complete skeletal animation sequence\n *\n * Sequence of keyframes defining a complete animation\n * (e.g., jab, cross, roundhouse kick).\n *\n * @public\n * @category Animation\n * @korean 골격애니메이션\n */\nexport interface SkeletalAnimation {\n /**\n * Animation identifier\n * @korean 이름\n */\n readonly name: string;\n\n /**\n * Korean name for animation\n * @korean 한글이름\n */\n readonly koreanName: string;\n\n /**\n * Animation keyframes in chronological order\n * @korean 키프레임들\n */\n readonly keyframes: AnimationKeyframe[];\n\n /**\n * Total duration in seconds\n * @korean 지속시간\n */\n readonly duration: number;\n\n /**\n * Whether animation loops\n * @korean 반복여부\n */\n readonly loop: boolean;\n\n /**\n * Animation type (attack, defense, stance change, movement, etc.)\n * @korean 애니메이션타입\n */\n readonly type: \"attack\" | \"defense\" | \"stance\" | \"walk\" | \"idle\" | \"movement\";\n}\n\n/**\n * Bone chain definition for IK (Inverse Kinematics)\n *\n * Defines a chain of bones for IK solving (e.g., arm chain, leg chain).\n * Used for realistic limb positioning and movement.\n *\n * @public\n * @category Skeletal System\n * @korean 뼈체인\n */\nexport interface BoneChain {\n /**\n * Chain identifier\n * @korean 이름\n */\n readonly name: string;\n\n /**\n * Start bone (e.g., shoulder for arm chain)\n * @korean 시작뼈\n */\n readonly startBone: string;\n\n /**\n * End bone (e.g., hand for arm chain)\n * @korean 끝뼈\n */\n readonly endBone: string;\n\n /**\n * Bones in chain from start to end\n * @korean 뼈들\n */\n readonly bones: string[];\n\n /**\n * IK target position (for end effector)\n * @korean IK목표\n */\n ikTarget?: THREE.Vector3;\n}\n\n/**\n * Hand bone structure with 5 fingers\n *\n * Simplified hand model with palm and 5 fingers.\n * Each finger has 3 segments (proximal, middle, distal).\n *\n * @public\n * @category Skeletal System\n * @korean 손뼈\n */\nexport interface HandBones {\n /**\n * Palm bone\n * @korean 손바닥\n */\n readonly palm: Bone;\n\n /**\n * Thumb bones (3 segments)\n * @korean 엄지손가락\n */\n readonly thumb: [Bone, Bone, Bone];\n\n /**\n * Index finger bones (3 segments)\n * @korean 검지손가락\n */\n readonly index: [Bone, Bone, Bone];\n\n /**\n * Middle finger bones (3 segments)\n * @korean 중지손가락\n */\n readonly middle: [Bone, Bone, Bone];\n\n /**\n * Ring finger bones (3 segments)\n * @korean 약지손가락\n */\n readonly ring: [Bone, Bone, Bone];\n\n /**\n * Pinky finger bones (3 segments)\n * @korean 새끼손가락\n */\n readonly pinky: [Bone, Bone, Bone];\n}\n\n/**\n * Joint constraint for realistic movement\n *\n * Defines rotation limits for joints (e.g., elbow can't bend backward).\n * Ensures anatomically correct movement.\n *\n * @public\n * @category Skeletal System\n * @korean 관절제약\n */\nexport interface JointConstraint {\n /**\n * Bone name this constraint applies to\n * @korean 뼈이름\n */\n readonly boneName: string;\n\n /**\n * Minimum rotation angles (X, Y, Z) in radians\n * @korean 최소회전\n */\n readonly minRotation: THREE.Vector3;\n\n /**\n * Maximum rotation angles (X, Y, Z) in radians\n * @korean 최대회전\n */\n readonly maxRotation: THREE.Vector3;\n\n /**\n * Whether this joint can twist (rotate around its length)\n * @korean 비틀기가능\n */\n readonly canTwist: boolean;\n}\n\n/**\n * Bone names for humanoid rig\n *\n * Standard bone naming convention for humanoid skeleton.\n * Total: 28 bones base + optional 38 hand bones (19 per hand) = 66 bones max.\n *\n * Hand bones are optional and can be excluded for performance (LOD system).\n *\n * @public\n * @category Skeletal System\n * @korean 뼈이름들\n */\nexport enum BoneName {\n // Core (1 bone)\n PELVIS = \"pelvis\",\n\n // Spine (3 bones)\n SPINE_LOWER = \"spine_lower\",\n SPINE_MIDDLE = \"spine_middle\",\n SPINE_UPPER = \"spine_upper\",\n\n // Head (2 bones)\n NECK = \"neck\",\n HEAD = \"head\",\n\n // Left Arm (6 bones)\n SHOULDER_L = \"shoulder_L\",\n UPPER_ARM_L = \"upper_arm_L\",\n ELBOW_L = \"elbow_L\",\n FOREARM_L = \"forearm_L\",\n WRIST_L = \"wrist_L\",\n HAND_L = \"hand_L\",\n\n // Right Arm (6 bones)\n SHOULDER_R = \"shoulder_R\",\n UPPER_ARM_R = \"upper_arm_R\",\n ELBOW_R = \"elbow_R\",\n FOREARM_R = \"forearm_R\",\n WRIST_R = \"wrist_R\",\n HAND_R = \"hand_R\",\n\n // Left Leg (5 bones)\n HIP_L = \"hip_L\",\n THIGH_L = \"thigh_L\",\n KNEE_L = \"knee_L\",\n SHIN_L = \"shin_L\",\n FOOT_L = \"foot_L\",\n\n // Right Leg (5 bones)\n HIP_R = \"hip_R\",\n THIGH_R = \"thigh_R\",\n KNEE_R = \"knee_R\",\n SHIN_R = \"shin_R\",\n FOOT_R = \"foot_R\",\n\n // Left Hand Fingers (19 bones - optional for LOD)\n THUMB_META_L = \"thumb_meta_L\",\n THUMB_PROX_L = \"thumb_prox_L\",\n THUMB_DIST_L = \"thumb_dist_L\",\n INDEX_META_L = \"index_meta_L\",\n INDEX_PROX_L = \"index_prox_L\",\n INDEX_INTER_L = \"index_inter_L\",\n INDEX_DIST_L = \"index_dist_L\",\n MIDDLE_META_L = \"middle_meta_L\",\n MIDDLE_PROX_L = \"middle_prox_L\",\n MIDDLE_INTER_L = \"middle_inter_L\",\n MIDDLE_DIST_L = \"middle_dist_L\",\n RING_META_L = \"ring_meta_L\",\n RING_PROX_L = \"ring_prox_L\",\n RING_INTER_L = \"ring_inter_L\",\n RING_DIST_L = \"ring_dist_L\",\n PINKY_META_L = \"pinky_meta_L\",\n PINKY_PROX_L = \"pinky_prox_L\",\n PINKY_INTER_L = \"pinky_inter_L\",\n PINKY_DIST_L = \"pinky_dist_L\",\n\n // Right Hand Fingers (19 bones - optional for LOD)\n THUMB_META_R = \"thumb_meta_R\",\n THUMB_PROX_R = \"thumb_prox_R\",\n THUMB_DIST_R = \"thumb_dist_R\",\n INDEX_META_R = \"index_meta_R\",\n INDEX_PROX_R = \"index_prox_R\",\n INDEX_INTER_R = \"index_inter_R\",\n INDEX_DIST_R = \"index_dist_R\",\n MIDDLE_META_R = \"middle_meta_R\",\n MIDDLE_PROX_R = \"middle_prox_R\",\n MIDDLE_INTER_R = \"middle_inter_R\",\n MIDDLE_DIST_R = \"middle_dist_R\",\n RING_META_R = \"ring_meta_R\",\n RING_PROX_R = \"ring_prox_R\",\n RING_INTER_R = \"ring_inter_R\",\n RING_DIST_R = \"ring_dist_R\",\n PINKY_META_R = \"pinky_meta_R\",\n PINKY_PROX_R = \"pinky_prox_R\",\n PINKY_INTER_R = \"pinky_inter_R\",\n PINKY_DIST_R = \"pinky_dist_R\",\n}\n\n/**\n * Animation state for skeletal player\n *\n * Tracks current animation playback state for skeletal animations.\n *\n * @public\n * @category Animation\n * @korean 애니메이션상태\n */\nexport interface SkeletalAnimationState {\n /**\n * Current animation being played\n * @korean 현재애니메이션\n */\n currentAnimation: SkeletalAnimation | null;\n\n /**\n * Current time in animation (seconds)\n * @korean 현재시간\n */\n currentTime: number;\n\n /**\n * Whether animation is playing\n * @korean 재생중\n */\n isPlaying: boolean;\n\n /**\n * Animation playback speed multiplier\n * @korean 재생속도\n */\n playbackSpeed: number;\n\n /**\n * Previous keyframe index\n * @korean 이전키프레임\n */\n previousKeyframeIndex: number;\n\n /**\n * Next keyframe index\n * @korean 다음키프레임\n */\n nextKeyframeIndex: number;\n}\n\n/**\n * Fighting stance guard pose configuration\n *\n * Defines complete body positioning for authentic Korean martial arts stances.\n * Includes arms, torso, legs, and pelvis rotations based on traditional Taekwondo/Hapkido.\n *\n * Each stance corresponds to a real martial arts position with unique leg positioning.\n * Used for stance-specific idle animations at 60fps.\n *\n * @public\n * @category Animation\n * @korean 자세방어포즈\n */\nexport interface StanceGuardPose {\n /**\n * Left arm bone rotations (shoulder, elbow, wrist)\n * @korean 왼팔\n */\n readonly leftArm: {\n readonly shoulder: THREE.Euler;\n readonly elbow: THREE.Euler;\n readonly wrist: THREE.Euler;\n };\n\n /**\n * Right arm bone rotations (shoulder, elbow, wrist)\n * @korean 오른팔\n */\n readonly rightArm: {\n readonly shoulder: THREE.Euler;\n readonly elbow: THREE.Euler;\n readonly wrist: THREE.Euler;\n };\n\n /**\n * Torso rotation (spine upper bone)\n * @korean 몸통회전\n */\n readonly torso: THREE.Euler;\n\n /**\n * Left leg bone rotations (hip, knee, ankle)\n * NEW: For authentic Taekwondo stance positioning\n * @korean 왼다리\n */\n readonly leftLeg: {\n readonly hip: THREE.Euler; // Hip rotation/abduction\n readonly knee: THREE.Euler; // Knee bend angle\n readonly ankle: THREE.Euler; // Ankle dorsiflexion/plantarflexion\n };\n\n /**\n * Right leg bone rotations (hip, knee, ankle)\n * NEW: For authentic Taekwondo stance positioning\n * @korean 오른다리\n */\n readonly rightLeg: {\n readonly hip: THREE.Euler; // Hip rotation/abduction\n readonly knee: THREE.Euler; // Knee bend angle\n readonly ankle: THREE.Euler; // Ankle dorsiflexion/plantarflexion\n };\n\n /**\n * Pelvis rotation (hip tilt and rotation)\n * NEW: For proper stance base\n * @korean 골반회전\n */\n readonly pelvis: THREE.Euler;\n\n /**\n * Stance width (foot spacing in meters)\n * NEW: Defines lateral distance between feet\n * Range: 0.3 (narrow) to 1.5 (wide horse stance)\n *\n * This value describes the intended lateral spacing between the feet for this\n * stance configuration. Consumers (e.g. animation systems or positioning\n * overlays) may use it to drive foot placement, validation, or visualization\n * as needed.\n *\n * @korean 자세너비\n */\n readonly stanceWidth: number;\n\n /**\n * Stance depth (front-to-back foot spacing in meters)\n * Optional: Defaults to 0 for parallel stances (Juchum Seogi, Narani Seogi)\n *\n * For forward stances (Ap Koobi Seogi): positive value (~0.6-0.9m)\n * For back stances (Dwi Seogi): negative value (~-0.3m with back foot forward)\n *\n * Left foot gets -depth/2, right foot gets +depth/2 on Z-axis.\n * Combined with stanceWidth, this defines the full 2D foot placement.\n *\n * @korean 자세깊이\n */\n readonly stanceDepth?: number;\n\n /**\n * Pelvis height offset (vertical drop in meters)\n * Optional: Defaults to 0 for normal standing height\n *\n * For deep stances (Juchum Seogi, Joong Ha Seogi): negative value (~-0.15 to -0.3m)\n * This lowers the center of gravity for power and stability.\n *\n * @korean 골반높이\n */\n readonly pelvisHeight?: number;\n\n /**\n * Weight distribution (forward/neutral/back)\n * @korean 무게중심\n */\n readonly weight: \"forward\" | \"neutral\" | \"back\";\n\n /**\n * Breathing animation range (min/max for chest movement)\n * @korean 호흡범위\n */\n readonly breathingRange: {\n readonly min: number;\n readonly max: number;\n };\n}\n\n/**\n * Stance guard animation configuration\n *\n * Extends base AnimationConfig with stance-specific guard pose data.\n * Includes 4-6 frame breathing animation for realistic idle behavior.\n *\n * @public\n * @category Animation\n * @korean 자세방어애니메이션설정\n */\nexport interface StanceGuardAnimationConfig {\n /**\n * Trigram stance identifier\n * @korean 괘\n */\n readonly stance: string;\n\n /**\n * Korean name of stance\n * @korean 한글이름\n */\n readonly koreanName: string;\n\n /**\n * English name of stance\n * @korean 영어이름\n */\n readonly englishName: string;\n\n /**\n * Guard pose keyframe (default position)\n * @korean 방어포즈\n */\n readonly guardPose: StanceGuardPose;\n\n /**\n * Breathing animation frames (4-6 frames)\n * @korean 호흡프레임\n */\n readonly breathingFrames: number;\n\n /**\n * Target frames per second (60fps)\n * @korean 초당프레임\n */\n readonly fps: number;\n\n /**\n * Breathing cycle loop enabled\n * @korean 반복여부\n */\n readonly loop: boolean;\n\n /**\n * Animation priority (0 for idle guards)\n * @korean 우선순위\n */\n readonly priority: number;\n}\n\n/**\n * Mirror a guard pose for left/right stance laterality.\n *\n * **Korean**: 자세 좌우 대칭\n *\n * Creates a mirror-image guard pose by swapping left and right limb positions\n * and negating lateral (Y-axis and Z-axis) rotations. This enables authentic\n * left/right stance differentiation in Korean martial arts.\n *\n * Key transformations:\n * - Swap leftArm ↔ rightArm bone rotations\n * - Negate Y rotation (lateral twist)\n * - Negate Z rotation (roll)\n * - Preserve X rotation (forward/back bend)\n * - Keep weight distribution and breathing range unchanged\n *\n * @param pose - Original guard pose to mirror\n * @returns Mirrored guard pose with swapped and negated rotations\n *\n * @example\n * ```typescript\n * // Create right-handed version of a left-handed guard\n * const leftGeonGuard = GEON_HIGH_GUARD_POSE;\n * const rightGeonGuard = mirrorGuardPose(leftGeonGuard);\n *\n * // leftGeonGuard has left hand forward\n * // rightGeonGuard has right hand forward (mirrored)\n * ```\n *\n * @public\n * @category Animation\n * @korean 방어포즈대칭\n */\nexport function mirrorGuardPose(pose: StanceGuardPose): StanceGuardPose {\n // Helper to negate Y and Z rotations while preserving X\n const mirrorEuler = (euler: THREE.Euler): THREE.Euler => {\n return new THREE.Euler(\n euler.x, // Preserve forward/back bend\n -euler.y, // Negate lateral twist\n -euler.z // Negate roll\n );\n };\n\n return {\n // Swap left and right arms with mirrored rotations\n leftArm: {\n shoulder: mirrorEuler(pose.rightArm.shoulder),\n elbow: mirrorEuler(pose.rightArm.elbow),\n wrist: mirrorEuler(pose.rightArm.wrist),\n },\n rightArm: {\n shoulder: mirrorEuler(pose.leftArm.shoulder),\n elbow: mirrorEuler(pose.leftArm.elbow),\n wrist: mirrorEuler(pose.leftArm.wrist),\n },\n // Mirror torso rotation\n torso: mirrorEuler(pose.torso),\n // NEW: Swap and mirror leg positions\n leftLeg: {\n hip: mirrorEuler(pose.rightLeg.hip),\n knee: mirrorEuler(pose.rightLeg.knee),\n ankle: mirrorEuler(pose.rightLeg.ankle),\n },\n rightLeg: {\n hip: mirrorEuler(pose.leftLeg.hip),\n knee: mirrorEuler(pose.leftLeg.knee),\n ankle: mirrorEuler(pose.leftLeg.ankle),\n },\n // Mirror pelvis rotation\n pelvis: mirrorEuler(pose.pelvis),\n // Stance width remains the same\n stanceWidth: pose.stanceWidth,\n // Stance depth: negate for mirrored stance (front foot becomes back foot)\n stanceDepth: pose.stanceDepth !== undefined ? -pose.stanceDepth : undefined,\n // Pelvis height remains unchanged (not affected by laterality)\n pelvisHeight: pose.pelvisHeight,\n // Weight distribution remains the same\n weight: pose.weight,\n // Breathing range unchanged (not affected by laterality, reuse original object)\n breathingRange: pose.breathingRange,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"techniqueId.js","names":[],"sources":["../../src/types/techniqueId.ts"],"sourcesContent":["/**\n * Technique ID enum for type-safe technique references\n *\n * **Korean**: 기술 ID 열거형\n *\n * Provides compile-time safety for all technique references across the codebase.\n * Every technique in the game has a unique ID defined here.\n *\n * @module types/techniqueId\n * @category Combat System\n * @korean 기술ID\n */\n\n/**\n * All technique IDs in the game system\n *\n * Each archetype has unique techniques:\n * - 무사 (Musa) - 4 techniques: musa_thunder_strike, musa_iron_defense, musa_dragon_fist, musa_mountain_breaker\n * - 암살자 (Amsalja) - 4 techniques: amsalja_shadow_strike, amsalja_nerve_strike, amsalja_deadly_precision, amsalja_silent_death\n * - 해커 (Hacker) - 4 techniques: hacker_electric_shock, hacker_data_strike, hacker_cyber_overdrive, hacker_system_crash\n * - 정보요원 (Jeongbo) - 5 techniques: jeongbo_tactical_strike, jeongbo_counter_intelligence, jeongbo_psychological_warfare, jeongbo_precision_takedown, jeongbo_intelligence_strike\n * - 조직폭력배 (Jojik) - 4 techniques: jojik_street_brawl, jojik_improvised_weapon, jojik_ruthless_assault, jojik_brutal_takedown\n *\n * @category Combat System\n * @korean 기술ID열거형\n */\nexport enum TechniqueId {\n // 무사 (Musa) - Traditional Warrior\n MUSA_THUNDER_STRIKE = \"musa_thunder_strike\",\n MUSA_IRON_DEFENSE = \"musa_iron_defense\",\n MUSA_DRAGON_FIST = \"musa_dragon_fist\",\n MUSA_MOUNTAIN_BREAKER = \"musa_mountain_breaker\",\n\n // 암살자 (Amsalja) - Shadow Assassin\n AMSALJA_SHADOW_STRIKE = \"amsalja_shadow_strike\",\n AMSALJA_NERVE_STRIKE = \"amsalja_nerve_strike\",\n AMSALJA_DEADLY_PRECISION = \"amsalja_deadly_precision\",\n AMSALJA_SILENT_DEATH = \"amsalja_silent_death\",\n\n // 해커 (Hacker) - Cyber Warrior\n HACKER_ELECTRIC_SHOCK = \"hacker_electric_shock\",\n HACKER_DATA_STRIKE = \"hacker_data_strike\",\n HACKER_CYBER_OVERDRIVE = \"hacker_cyber_overdrive\",\n HACKER_SYSTEM_CRASH = \"hacker_system_crash\",\n\n // 정보요원 (Jeongbo Yowon) - Intelligence Operative\n JEONGBO_TACTICAL_STRIKE = \"jeongbo_tactical_strike\",\n JEONGBO_COUNTER_INTELLIGENCE = \"jeongbo_counter_intelligence\",\n JEONGBO_PSYCHOLOGICAL_WARFARE = \"jeongbo_psychological_warfare\",\n JEONGBO_PRECISION_TAKEDOWN = \"jeongbo_precision_takedown\",\n JEONGBO_INTELLIGENCE_STRIKE = \"jeongbo_intelligence_strike\",\n\n // 조직폭력배 (Jojik Pokryeokbae) - Organized Crime\n JOJIK_STREET_BRAWL = \"jojik_street_brawl\",\n JOJIK_IMPROVISED_WEAPON = \"jojik_improvised_weapon\",\n JOJIK_RUTHLESS_ASSAULT = \"jojik_ruthless_assault\",\n JOJIK_BRUTAL_TAKEDOWN = \"jojik_brutal_takedown\",\n}\n\nexport default TechniqueId;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,IAAY,cAAL,yBAAA,aAAA;CAEL,YAAA,
|
|
1
|
+
{"version":3,"file":"techniqueId.js","names":[],"sources":["../../src/types/techniqueId.ts"],"sourcesContent":["/**\n * Technique ID enum for type-safe technique references\n *\n * **Korean**: 기술 ID 열거형\n *\n * Provides compile-time safety for all technique references across the codebase.\n * Every technique in the game has a unique ID defined here.\n *\n * @module types/techniqueId\n * @category Combat System\n * @korean 기술ID\n */\n\n/**\n * All technique IDs in the game system\n *\n * Each archetype has unique techniques:\n * - 무사 (Musa) - 4 techniques: musa_thunder_strike, musa_iron_defense, musa_dragon_fist, musa_mountain_breaker\n * - 암살자 (Amsalja) - 4 techniques: amsalja_shadow_strike, amsalja_nerve_strike, amsalja_deadly_precision, amsalja_silent_death\n * - 해커 (Hacker) - 4 techniques: hacker_electric_shock, hacker_data_strike, hacker_cyber_overdrive, hacker_system_crash\n * - 정보요원 (Jeongbo) - 5 techniques: jeongbo_tactical_strike, jeongbo_counter_intelligence, jeongbo_psychological_warfare, jeongbo_precision_takedown, jeongbo_intelligence_strike\n * - 조직폭력배 (Jojik) - 4 techniques: jojik_street_brawl, jojik_improvised_weapon, jojik_ruthless_assault, jojik_brutal_takedown\n *\n * @category Combat System\n * @korean 기술ID열거형\n */\nexport enum TechniqueId {\n // 무사 (Musa) - Traditional Warrior\n MUSA_THUNDER_STRIKE = \"musa_thunder_strike\",\n MUSA_IRON_DEFENSE = \"musa_iron_defense\",\n MUSA_DRAGON_FIST = \"musa_dragon_fist\",\n MUSA_MOUNTAIN_BREAKER = \"musa_mountain_breaker\",\n\n // 암살자 (Amsalja) - Shadow Assassin\n AMSALJA_SHADOW_STRIKE = \"amsalja_shadow_strike\",\n AMSALJA_NERVE_STRIKE = \"amsalja_nerve_strike\",\n AMSALJA_DEADLY_PRECISION = \"amsalja_deadly_precision\",\n AMSALJA_SILENT_DEATH = \"amsalja_silent_death\",\n\n // 해커 (Hacker) - Cyber Warrior\n HACKER_ELECTRIC_SHOCK = \"hacker_electric_shock\",\n HACKER_DATA_STRIKE = \"hacker_data_strike\",\n HACKER_CYBER_OVERDRIVE = \"hacker_cyber_overdrive\",\n HACKER_SYSTEM_CRASH = \"hacker_system_crash\",\n\n // 정보요원 (Jeongbo Yowon) - Intelligence Operative\n JEONGBO_TACTICAL_STRIKE = \"jeongbo_tactical_strike\",\n JEONGBO_COUNTER_INTELLIGENCE = \"jeongbo_counter_intelligence\",\n JEONGBO_PSYCHOLOGICAL_WARFARE = \"jeongbo_psychological_warfare\",\n JEONGBO_PRECISION_TAKEDOWN = \"jeongbo_precision_takedown\",\n JEONGBO_INTELLIGENCE_STRIKE = \"jeongbo_intelligence_strike\",\n\n // 조직폭력배 (Jojik Pokryeokbae) - Organized Crime\n JOJIK_STREET_BRAWL = \"jojik_street_brawl\",\n JOJIK_IMPROVISED_WEAPON = \"jojik_improvised_weapon\",\n JOJIK_RUTHLESS_ASSAULT = \"jojik_ruthless_assault\",\n JOJIK_BRUTAL_TAKEDOWN = \"jojik_brutal_takedown\",\n}\n\nexport default TechniqueId;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,IAAY,cAAL,yBAAA,aAAA;CAEL,YAAA,yBAAA;CACA,YAAA,uBAAA;CACA,YAAA,sBAAA;CACA,YAAA,2BAAA;CAGA,YAAA,2BAAA;CACA,YAAA,0BAAA;CACA,YAAA,8BAAA;CACA,YAAA,0BAAA;CAGA,YAAA,2BAAA;CACA,YAAA,wBAAA;CACA,YAAA,4BAAA;CACA,YAAA,yBAAA;CAGA,YAAA,6BAAA;CACA,YAAA,kCAAA;CACA,YAAA,mCAAA;CACA,YAAA,gCAAA;CACA,YAAA,iCAAA;CAGA,YAAA,wBAAA;CACA,YAAA,6BAAA;CACA,YAAA,4BAAA;CACA,YAAA,2BAAA;;AACF,EAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessibility.js","names":[],"sources":["../../src/utils/accessibility.ts"],"sourcesContent":["/**\n * Accessibility utility functions for WCAG 2.1 Level AA compliance\n * Provides keyboard navigation, focus management, color contrast checking,\n * and screen reader support utilities\n *\n * @module utils/accessibility\n * @category Accessibility\n * @korean 접근성 유틸리티\n */\n\nimport React from \"react\";\nimport {\n AriaAttributes,\n ColorContrastConfig,\n createBilingualLabel,\n FocusIndicatorStyle,\n KeyboardActions,\n ScreenReaderAnnouncement,\n WCAGComplianceResult,\n WCAGLevel,\n} from \"../types/AccessibilityTypes\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { EventManager } from \"./EventManager\";\n\n/**\n * Handle keyboard navigation events with WCAG compliance\n * Supports Tab, Enter, Space, Escape, Arrow keys, Home, End\n *\n * @param event - Keyboard event\n * @param actions - Actions to perform for different keys\n *\n * @example\n * ```tsx\n * <div onKeyDown={(e) => handleKeyboardNav(e, {\n * onActivate: () => handleClick(),\n * onCancel: () => handleClose(),\n * onNavigate: (dir) => handleMove(dir),\n * })}>\n * ```\n */\nexport function handleKeyboardNav(\n event: KeyboardEvent,\n actions: KeyboardActions,\n): void {\n const { key, shiftKey } = event;\n\n switch (key) {\n case \"Enter\":\n case \" \":\n // Activate element (button, link, etc.)\n event.preventDefault();\n actions.onActivate?.();\n break;\n\n case \"Escape\":\n // Cancel or close action\n event.preventDefault();\n actions.onCancel?.();\n break;\n\n case \"Tab\":\n // Tab navigation (forward or backward with Shift)\n actions.onTab?.(shiftKey);\n break;\n\n case \"ArrowUp\":\n // Navigate up\n event.preventDefault();\n actions.onNavigate?.(\"up\");\n break;\n\n case \"ArrowDown\":\n // Navigate down\n event.preventDefault();\n actions.onNavigate?.(\"down\");\n break;\n\n case \"ArrowLeft\":\n // Navigate left\n event.preventDefault();\n actions.onNavigate?.(\"left\");\n break;\n\n case \"ArrowRight\":\n // Navigate right\n event.preventDefault();\n actions.onNavigate?.(\"right\");\n break;\n\n case \"Home\":\n // Jump to start\n event.preventDefault();\n actions.onJump?.(\"start\");\n break;\n\n case \"End\":\n // Jump to end\n event.preventDefault();\n actions.onJump?.(\"end\");\n break;\n }\n}\n\n/**\n * Get default focus indicator style (WCAG 2.1 Level AA compliant)\n * Uses 2px solid outline with high contrast cyan color\n * Computed lazily to avoid initialization order dependencies\n */\nfunction getDefaultFocusStyle(): FocusIndicatorStyle {\n const rgb = hexToRgb(KOREAN_COLORS.PRIMARY_CYAN);\n return {\n outlineWidth: 2,\n outlineColor: KOREAN_COLORS.PRIMARY_CYAN,\n outlineOffset: 2,\n outlineStyle: \"solid\",\n boxShadow: `0 0 0 2px rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.5)`,\n transitionDuration: 0.2,\n };\n}\n\n/**\n * Get focus indicator CSS style object\n *\n * @param isFocused - Whether element is currently focused\n * @param customStyle - Optional custom focus style overrides\n * @returns CSS style object for focus indicator\n *\n * @example\n * ```tsx\n * const [isFocused, setIsFocused] = useState(false);\n * <button\n * style={getFocusStyle(isFocused)}\n * onFocus={() => setIsFocused(true)}\n * onBlur={() => setIsFocused(false)}\n * >\n * ```\n */\nexport function getFocusStyle(\n isFocused: boolean,\n customStyle?: Partial<FocusIndicatorStyle>,\n): React.CSSProperties {\n const defaultStyle = getDefaultFocusStyle();\n\n if (!isFocused) {\n return {\n // Preserve browser default focus indicator instead of 'none'\n transition: `all ${defaultStyle.transitionDuration}s ease`,\n };\n }\n\n const style = { ...defaultStyle, ...customStyle };\n const rgb = hexToRgb(style.outlineColor ?? KOREAN_COLORS.PRIMARY_CYAN);\n\n return {\n outline: `${style.outlineWidth}px ${style.outlineStyle} rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,\n outlineOffset: `${style.outlineOffset}px`,\n boxShadow:\n style.boxShadow ?? `0 0 0 2px rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.5)`,\n transition: `all ${style.transitionDuration}s ease`,\n };\n}\n\n/**\n * Convert hex color to RGB components\n *\n * @param hex - Hex color code (0xRRGGBB)\n * @returns RGB components\n */\nexport function hexToRgb(hex: number): { r: number; g: number; b: number } {\n return {\n r: (hex >> 16) & 255,\n g: (hex >> 8) & 255,\n b: hex & 255,\n };\n}\n\n/**\n * Calculate contrast ratio between two colors (WCAG 2.1)\n *\n * @param foreground - Foreground color (hex)\n * @param background - Background color (hex)\n * @returns Contrast ratio (1-21)\n *\n * @example\n * ```typescript\n * const ratio = getContrastRatio(KOREAN_COLORS.TEXT_PRIMARY, KOREAN_COLORS.UI_BACKGROUND_DARK);\n * console.log(`Contrast ratio: ${ratio.toFixed(2)}:1`); // Should be >= 4.5:1 for WCAG AA\n * ```\n */\nexport function getContrastRatio(\n foreground: number,\n background: number,\n): number {\n const fg = hexToRgb(foreground);\n const bg = hexToRgb(background);\n\n // Calculate relative luminance (WCAG formula)\n const getLuminance = (r: number, g: number, b: number): number => {\n const [rs, gs, bs] = [r, g, b].map((c) => {\n const sRGB = c / 255;\n return sRGB <= 0.03928\n ? sRGB / 12.92\n : Math.pow((sRGB + 0.055) / 1.055, 2.4);\n });\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n };\n\n const l1 = getLuminance(fg.r, fg.g, fg.b);\n const l2 = getLuminance(bg.r, bg.g, bg.b);\n\n // Contrast ratio formula\n const lighter = Math.max(l1, l2);\n const darker = Math.min(l1, l2);\n return (lighter + 0.05) / (darker + 0.05);\n}\n\n/**\n * Check if color combination meets WCAG contrast requirements\n *\n * @param config - Color contrast configuration\n * @returns Whether contrast meets WCAG requirements\n *\n * @example\n * ```typescript\n * const meetsWCAG = meetsContrastRequirement({\n * foreground: KOREAN_COLORS.TEXT_PRIMARY,\n * background: KOREAN_COLORS.UI_BACKGROUND_DARK,\n * targetRatio: 4.5,\n * });\n * ```\n */\nexport function meetsContrastRequirement(config: ColorContrastConfig): boolean {\n const ratio = getContrastRatio(config.foreground, config.background);\n return ratio >= config.targetRatio;\n}\n\n/**\n * Get WCAG-compliant foreground color for given background\n * Returns white or black depending on which provides better contrast\n *\n * @param background - Background color (hex)\n * @returns Foreground color (hex) that meets WCAG AA\n */\nexport function getAccessibleForeground(background: number): number {\n const whiteRatio = getContrastRatio(KOREAN_COLORS.TEXT_PRIMARY, background);\n const blackRatio = getContrastRatio(KOREAN_COLORS.BLACK_SOLID, background);\n\n // Return color with better contrast\n return whiteRatio >= blackRatio\n ? KOREAN_COLORS.TEXT_PRIMARY\n : KOREAN_COLORS.BLACK_SOLID;\n}\n\n/**\n * Announce message to screen readers\n * Creates a live region announcement with configurable politeness\n *\n * @param announcement - Screen reader announcement configuration\n *\n * @example\n * ```typescript\n * announceToScreenReader({\n * message: '공격 성공 | Attack successful',\n * politeness: 'polite',\n * delay: 100,\n * });\n * ```\n */\nlet liveRegionInstance: HTMLElement | null = null;\n\nexport function announceToScreenReader(\n announcement: ScreenReaderAnnouncement,\n): void {\n const { message, politeness = \"polite\", delay = 0 } = announcement;\n\n setTimeout(() => {\n // Create or get existing live region\n if (!liveRegionInstance) {\n liveRegionInstance = document.createElement(\"div\");\n liveRegionInstance.id = \"sr-live-region\";\n liveRegionInstance.setAttribute(\"aria-live\", politeness);\n liveRegionInstance.setAttribute(\"aria-atomic\", \"true\");\n liveRegionInstance.style.cssText = `\n position: absolute;\n left: -10000px;\n width: 1px;\n height: 1px;\n overflow: hidden;\n `;\n document.body.appendChild(liveRegionInstance);\n }\n\n // Update politeness level if changed\n if (liveRegionInstance.getAttribute(\"aria-live\") !== politeness) {\n liveRegionInstance.setAttribute(\"aria-live\", politeness);\n }\n\n // Update message\n liveRegionInstance.textContent = message;\n\n // Clear message after 3 seconds\n setTimeout(() => {\n if (liveRegionInstance) {\n liveRegionInstance.textContent = \"\";\n }\n }, 3000);\n }, delay);\n}\n\n/**\n * Clean up screen reader live region (for testing or app unmount)\n */\nexport function cleanupScreenReaderRegion(): void {\n if (liveRegionInstance?.parentNode) {\n liveRegionInstance.parentNode.removeChild(liveRegionInstance);\n liveRegionInstance = null;\n }\n}\n\n/**\n * Trap focus within a container element\n * Prevents focus from leaving the container (e.g., for modals)\n *\n * @param container - Container element to trap focus within\n * @returns Cleanup function to remove focus trap\n *\n * @example\n * ```typescript\n * useEffect(() => {\n * const cleanup = trapFocus(modalRef.current);\n * return cleanup;\n * }, []);\n * ```\n */\nexport function trapFocus(container: HTMLElement | null): () => void {\n if (!container) return () => {};\n\n const focusableElements = getFocusableElements(container);\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Tab\") return;\n\n if (event.shiftKey) {\n // Shift + Tab: Move focus backward\n if (document.activeElement === firstElement) {\n event.preventDefault();\n lastElement?.focus();\n }\n } else {\n // Tab: Move focus forward\n if (document.activeElement === lastElement) {\n event.preventDefault();\n firstElement?.focus();\n }\n }\n };\n\n container.addEventListener(\"keydown\", handleKeyDown);\n\n // Set initial focus\n firstElement?.focus();\n\n // Return cleanup function\n return () => {\n container.removeEventListener(\"keydown\", handleKeyDown);\n };\n}\n\n/**\n * Get all focusable elements within a container\n *\n * @param container - Container element\n * @returns Array of focusable elements\n */\nexport function getFocusableElements(container: HTMLElement): HTMLElement[] {\n const selector = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n ].join(\", \");\n\n return Array.from(container.querySelectorAll<HTMLElement>(selector));\n}\n\n/**\n * Validate WCAG 2.1 Level AA compliance for a component\n *\n * Note: Color contrast checking requires access to computed styles,\n * which is not available in all testing environments. This validation\n * focuses on structural accessibility attributes.\n *\n * @param element - Element to validate\n * @param config - Validation configuration\n * @returns Compliance validation result\n */\nexport function validateWCAGCompliance(\n element: HTMLElement | null,\n config?: {\n checkKeyboard?: boolean;\n checkFocusVisible?: boolean;\n checkAria?: boolean;\n },\n): WCAGComplianceResult {\n const {\n checkKeyboard = true,\n checkFocusVisible = true,\n checkAria = true,\n } = config ?? {};\n\n const issues: string[] = [];\n let keyboardAccessible = true;\n const colorContrast = true; // Note: Requires computed styles - use getContrastRatio() directly for color validation\n let focusVisible = true;\n let ariaLabels = true;\n\n if (!element) {\n issues.push(\"Element not found\");\n return {\n level: WCAGLevel.A,\n compliant: false,\n criteria: {\n keyboardAccessible: false,\n colorContrast: false,\n focusVisible: false,\n ariaLabels: false,\n semanticHTML: false,\n errorIdentification: false,\n },\n issues,\n };\n }\n\n // Check keyboard accessibility\n if (checkKeyboard) {\n const isInteractive =\n element.tagName === \"BUTTON\" ||\n element.tagName === \"A\" ||\n element.tagName === \"INPUT\" ||\n element.hasAttribute(\"onclick\");\n\n if (isInteractive && element.getAttribute(\"tabindex\") === \"-1\") {\n keyboardAccessible = false;\n issues.push(\"Interactive element not keyboard accessible\");\n }\n }\n\n // Check ARIA labels\n if (checkAria) {\n const hasAriaLabel =\n element.hasAttribute(\"aria-label\") ||\n element.hasAttribute(\"aria-labelledby\");\n\n const hasTextContent = element.textContent?.trim();\n\n if (!hasAriaLabel && !hasTextContent) {\n ariaLabels = false;\n issues.push(\"Missing ARIA label or text content\");\n }\n }\n\n // Check focus indicator\n if (checkFocusVisible) {\n const computedStyle = window.getComputedStyle(element);\n const outline = computedStyle.outline;\n\n if (outline === \"none\" || outline === \"\") {\n focusVisible = false;\n issues.push(\"Missing visible focus indicator\");\n }\n }\n\n const allPass =\n keyboardAccessible && colorContrast && focusVisible && ariaLabels;\n\n return {\n level: allPass ? WCAGLevel.AA : WCAGLevel.A,\n compliant: allPass,\n criteria: {\n keyboardAccessible,\n colorContrast, // Note: Always true - use getContrastRatio() for actual color validation\n focusVisible,\n ariaLabels,\n semanticHTML: true, // Requires manual verification\n errorIdentification: true, // Requires manual verification\n },\n issues,\n };\n}\n\n/**\n * Create comprehensive ARIA attributes for a component\n *\n * @param label - Bilingual label (Korean | English)\n * @param role - ARIA role\n * @param additionalAttrs - Additional ARIA attributes\n * @returns Complete ARIA attributes object\n *\n * @example\n * ```tsx\n * const ariaProps = createAriaAttributes(\n * createBilingualLabel('공격', 'Attack'),\n * 'button',\n * { 'aria-pressed': isPressed }\n * );\n * <button {...ariaProps}>\n * ```\n */\nexport function createAriaAttributes(\n label: string | { korean: string; english: string },\n role?: AriaAttributes[\"role\"],\n additionalAttrs?: Partial<AriaAttributes>,\n): AriaAttributes {\n const bilingualLabel =\n typeof label === \"string\"\n ? label\n : createBilingualLabel(label.korean, label.english).label;\n\n return {\n role,\n \"aria-label\": bilingualLabel,\n ...additionalAttrs,\n };\n}\n\n/**\n * Check if element is currently visible\n * Useful for skip-to-content and focus management\n *\n * @param element - Element to check\n * @returns Whether element is visible\n */\nexport function isElementVisible(element: HTMLElement | null): boolean {\n if (!element) return false;\n\n const style = window.getComputedStyle(element);\n return (\n style.display !== \"none\" &&\n style.visibility !== \"hidden\" &&\n style.opacity !== \"0\" &&\n element.offsetParent !== null\n );\n}\n\n/**\n * Focus first error element in a form\n * Useful for form validation accessibility\n *\n * @param container - Form container\n */\nexport function focusFirstError(container: HTMLElement): void {\n const errorElements = container.querySelectorAll<HTMLElement>(\n '[aria-invalid=\"true\"]',\n );\n\n if (errorElements.length > 0) {\n errorElements[0].focus();\n announceToScreenReader({\n message: \"오류가 발견되었습니다 | Errors found\",\n politeness: \"assertive\",\n });\n }\n}\n\n/**\n * Create skip to content link for keyboard navigation\n * Returns a link that allows users to skip to main content\n *\n * Returns an object with the link element and a cleanup function to properly\n * remove event listeners when the component is no longer needed.\n *\n * **Breaking Change**: This function now returns `{ element, cleanup }` instead of\n * just `HTMLAnchorElement`. Update usage from:\n * ```typescript\n * const link = createSkipLink(id);\n * ```\n * to:\n * ```typescript\n * const { element, cleanup } = createSkipLink(id);\n * ```\n *\n * 컴포넌트가 더 이상 필요하지 않을 때 이벤트 리스너를 적절히 제거하기 위한\n * 링크 요소와 정리 함수가 포함된 객체를 반환합니다.\n *\n * @param targetId - ID of main content element\n * @returns Object with skip link element and cleanup function\n *\n * @example\n * ```typescript\n * const { element, cleanup } = createSkipLink(\"main-content\");\n * document.body.prepend(element);\n * // Later, when component unmounts\n * cleanup();\n * element.remove();\n * ```\n */\nexport function createSkipLink(targetId: string): {\n element: HTMLAnchorElement;\n cleanup: () => void;\n} {\n const skipLink = document.createElement(\"a\");\n skipLink.href = `#${targetId}`;\n skipLink.textContent = \"본문으로 건너뛰기 | Skip to content\";\n skipLink.className = \"skip-link\";\n skipLink.style.cssText = `\n position: absolute;\n top: -40px;\n left: 0;\n background: #00e6e6;\n color: #000;\n padding: 8px;\n text-decoration: none;\n z-index: 10000;\n font-weight: bold;\n `;\n\n // Use EventManager for consistent event handling across the codebase\n const eventManager = new EventManager();\n\n // Event handlers\n const handleFocus = () => {\n skipLink.style.top = \"0\";\n };\n\n const handleBlur = () => {\n skipLink.style.top = \"-40px\";\n };\n\n // Add event listeners via EventManager\n eventManager.add(skipLink, \"focus\", handleFocus);\n eventManager.add(skipLink, \"blur\", handleBlur);\n\n // Cleanup function to remove all event listeners\n const cleanup = () => {\n eventManager.cleanup();\n };\n\n return { element: skipLink, cleanup };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwCA,SAAgB,kBACd,OACA,SACM;CACN,MAAM,EAAE,KAAK,aAAa;CAE1B,QAAQ,KAAR;EACE,KAAK;EACL,KAAK;GAEH,MAAM,gBAAgB;GACtB,QAAQ,cAAc;GACtB;EAEF,KAAK;GAEH,MAAM,gBAAgB;GACtB,QAAQ,YAAY;GACpB;EAEF,KAAK;GAEH,QAAQ,QAAQ,SAAS;GACzB;EAEF,KAAK;GAEH,MAAM,gBAAgB;GACtB,QAAQ,aAAa,KAAK;GAC1B;EAEF,KAAK;GAEH,MAAM,gBAAgB;GACtB,QAAQ,aAAa,OAAO;GAC5B;EAEF,KAAK;GAEH,MAAM,gBAAgB;GACtB,QAAQ,aAAa,OAAO;GAC5B;EAEF,KAAK;GAEH,MAAM,gBAAgB;GACtB,QAAQ,aAAa,QAAQ;GAC7B;EAEF,KAAK;GAEH,MAAM,gBAAgB;GACtB,QAAQ,SAAS,QAAQ;GACzB;EAEF,KAAK;GAEH,MAAM,gBAAgB;GACtB,QAAQ,SAAS,MAAM;GACvB;;;;;;;;AASN,SAAS,uBAA4C;CACnD,MAAM,MAAM,SAAS,cAAc,aAAa;CAChD,OAAO;EACL,cAAc;EACd,cAAc,cAAc;EAC5B,eAAe;EACf,cAAc;EACd,WAAW,kBAAkB,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE;EACvD,oBAAoB;EACrB;;;;;;;;;;;;;;;;;;;AAoBH,SAAgB,cACd,WACA,aACqB;CACrB,MAAM,eAAe,sBAAsB;CAE3C,IAAI,CAAC,WACH,OAAO,EAEL,YAAY,OAAO,aAAa,mBAAmB,SACpD;CAGH,MAAM,QAAQ;EAAE,GAAG;EAAc,GAAG;EAAa;CACjD,MAAM,MAAM,SAAS,MAAM,gBAAgB,cAAc,aAAa;CAEtE,OAAO;EACL,SAAS,GAAG,MAAM,aAAa,KAAK,MAAM,aAAa,OAAO,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE;EACxF,eAAe,GAAG,MAAM,cAAc;EACtC,WACE,MAAM,aAAa,kBAAkB,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE;EACjE,YAAY,OAAO,MAAM,mBAAmB;EAC7C;;;;;;;;AASH,SAAgB,SAAS,KAAkD;CACzE,OAAO;EACL,GAAI,OAAO,KAAM;EACjB,GAAI,OAAO,IAAK;EAChB,GAAG,MAAM;EACV;;;;;;;;;;;;;;;;;AA+FH,IAAI,qBAAyC;AAE7C,SAAgB,uBACd,cACM;CACN,MAAM,EAAE,SAAS,aAAa,UAAU,QAAQ,MAAM;CAEtD,iBAAiB;EAEf,IAAI,CAAC,oBAAoB;GACvB,qBAAqB,SAAS,cAAc,MAAM;GAClD,mBAAmB,KAAK;GACxB,mBAAmB,aAAa,aAAa,WAAW;GACxD,mBAAmB,aAAa,eAAe,OAAO;GACtD,mBAAmB,MAAM,UAAU;;;;;;;GAOnC,SAAS,KAAK,YAAY,mBAAmB;;EAI/C,IAAI,mBAAmB,aAAa,YAAY,KAAK,YACnD,mBAAmB,aAAa,aAAa,WAAW;EAI1D,mBAAmB,cAAc;EAGjC,iBAAiB;GACf,IAAI,oBACF,mBAAmB,cAAc;KAElC,IAAK;IACP,MAAM"}
|
|
1
|
+
{"version":3,"file":"accessibility.js","names":[],"sources":["../../src/utils/accessibility.ts"],"sourcesContent":["/**\n * Accessibility utility functions for WCAG 2.1 Level AA compliance\n * Provides keyboard navigation, focus management, color contrast checking,\n * and screen reader support utilities\n *\n * @module utils/accessibility\n * @category Accessibility\n * @korean 접근성 유틸리티\n */\n\nimport React from \"react\";\nimport {\n AriaAttributes,\n ColorContrastConfig,\n createBilingualLabel,\n FocusIndicatorStyle,\n KeyboardActions,\n ScreenReaderAnnouncement,\n WCAGComplianceResult,\n WCAGLevel,\n} from \"../types/AccessibilityTypes\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { EventManager } from \"./EventManager\";\n\n/**\n * Handle keyboard navigation events with WCAG compliance\n * Supports Tab, Enter, Space, Escape, Arrow keys, Home, End\n *\n * @param event - Keyboard event\n * @param actions - Actions to perform for different keys\n *\n * @example\n * ```tsx\n * <div onKeyDown={(e) => handleKeyboardNav(e, {\n * onActivate: () => handleClick(),\n * onCancel: () => handleClose(),\n * onNavigate: (dir) => handleMove(dir),\n * })}>\n * ```\n */\nexport function handleKeyboardNav(\n event: KeyboardEvent,\n actions: KeyboardActions,\n): void {\n const { key, shiftKey } = event;\n\n switch (key) {\n case \"Enter\":\n case \" \":\n // Activate element (button, link, etc.)\n event.preventDefault();\n actions.onActivate?.();\n break;\n\n case \"Escape\":\n // Cancel or close action\n event.preventDefault();\n actions.onCancel?.();\n break;\n\n case \"Tab\":\n // Tab navigation (forward or backward with Shift)\n actions.onTab?.(shiftKey);\n break;\n\n case \"ArrowUp\":\n // Navigate up\n event.preventDefault();\n actions.onNavigate?.(\"up\");\n break;\n\n case \"ArrowDown\":\n // Navigate down\n event.preventDefault();\n actions.onNavigate?.(\"down\");\n break;\n\n case \"ArrowLeft\":\n // Navigate left\n event.preventDefault();\n actions.onNavigate?.(\"left\");\n break;\n\n case \"ArrowRight\":\n // Navigate right\n event.preventDefault();\n actions.onNavigate?.(\"right\");\n break;\n\n case \"Home\":\n // Jump to start\n event.preventDefault();\n actions.onJump?.(\"start\");\n break;\n\n case \"End\":\n // Jump to end\n event.preventDefault();\n actions.onJump?.(\"end\");\n break;\n }\n}\n\n/**\n * Get default focus indicator style (WCAG 2.1 Level AA compliant)\n * Uses 2px solid outline with high contrast cyan color\n * Computed lazily to avoid initialization order dependencies\n */\nfunction getDefaultFocusStyle(): FocusIndicatorStyle {\n const rgb = hexToRgb(KOREAN_COLORS.PRIMARY_CYAN);\n return {\n outlineWidth: 2,\n outlineColor: KOREAN_COLORS.PRIMARY_CYAN,\n outlineOffset: 2,\n outlineStyle: \"solid\",\n boxShadow: `0 0 0 2px rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.5)`,\n transitionDuration: 0.2,\n };\n}\n\n/**\n * Get focus indicator CSS style object\n *\n * @param isFocused - Whether element is currently focused\n * @param customStyle - Optional custom focus style overrides\n * @returns CSS style object for focus indicator\n *\n * @example\n * ```tsx\n * const [isFocused, setIsFocused] = useState(false);\n * <button\n * style={getFocusStyle(isFocused)}\n * onFocus={() => setIsFocused(true)}\n * onBlur={() => setIsFocused(false)}\n * >\n * ```\n */\nexport function getFocusStyle(\n isFocused: boolean,\n customStyle?: Partial<FocusIndicatorStyle>,\n): React.CSSProperties {\n const defaultStyle = getDefaultFocusStyle();\n\n if (!isFocused) {\n return {\n // Preserve browser default focus indicator instead of 'none'\n transition: `all ${defaultStyle.transitionDuration}s ease`,\n };\n }\n\n const style = { ...defaultStyle, ...customStyle };\n const rgb = hexToRgb(style.outlineColor ?? KOREAN_COLORS.PRIMARY_CYAN);\n\n return {\n outline: `${style.outlineWidth}px ${style.outlineStyle} rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,\n outlineOffset: `${style.outlineOffset}px`,\n boxShadow:\n style.boxShadow ?? `0 0 0 2px rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.5)`,\n transition: `all ${style.transitionDuration}s ease`,\n };\n}\n\n/**\n * Convert hex color to RGB components\n *\n * @param hex - Hex color code (0xRRGGBB)\n * @returns RGB components\n */\nexport function hexToRgb(hex: number): { r: number; g: number; b: number } {\n return {\n r: (hex >> 16) & 255,\n g: (hex >> 8) & 255,\n b: hex & 255,\n };\n}\n\n/**\n * Calculate contrast ratio between two colors (WCAG 2.1)\n *\n * @param foreground - Foreground color (hex)\n * @param background - Background color (hex)\n * @returns Contrast ratio (1-21)\n *\n * @example\n * ```typescript\n * const ratio = getContrastRatio(KOREAN_COLORS.TEXT_PRIMARY, KOREAN_COLORS.UI_BACKGROUND_DARK);\n * console.log(`Contrast ratio: ${ratio.toFixed(2)}:1`); // Should be >= 4.5:1 for WCAG AA\n * ```\n */\nexport function getContrastRatio(\n foreground: number,\n background: number,\n): number {\n const fg = hexToRgb(foreground);\n const bg = hexToRgb(background);\n\n // Calculate relative luminance (WCAG formula)\n const getLuminance = (r: number, g: number, b: number): number => {\n const [rs, gs, bs] = [r, g, b].map((c) => {\n const sRGB = c / 255;\n return sRGB <= 0.03928\n ? sRGB / 12.92\n : Math.pow((sRGB + 0.055) / 1.055, 2.4);\n });\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n };\n\n const l1 = getLuminance(fg.r, fg.g, fg.b);\n const l2 = getLuminance(bg.r, bg.g, bg.b);\n\n // Contrast ratio formula\n const lighter = Math.max(l1, l2);\n const darker = Math.min(l1, l2);\n return (lighter + 0.05) / (darker + 0.05);\n}\n\n/**\n * Check if color combination meets WCAG contrast requirements\n *\n * @param config - Color contrast configuration\n * @returns Whether contrast meets WCAG requirements\n *\n * @example\n * ```typescript\n * const meetsWCAG = meetsContrastRequirement({\n * foreground: KOREAN_COLORS.TEXT_PRIMARY,\n * background: KOREAN_COLORS.UI_BACKGROUND_DARK,\n * targetRatio: 4.5,\n * });\n * ```\n */\nexport function meetsContrastRequirement(config: ColorContrastConfig): boolean {\n const ratio = getContrastRatio(config.foreground, config.background);\n return ratio >= config.targetRatio;\n}\n\n/**\n * Get WCAG-compliant foreground color for given background\n * Returns white or black depending on which provides better contrast\n *\n * @param background - Background color (hex)\n * @returns Foreground color (hex) that meets WCAG AA\n */\nexport function getAccessibleForeground(background: number): number {\n const whiteRatio = getContrastRatio(KOREAN_COLORS.TEXT_PRIMARY, background);\n const blackRatio = getContrastRatio(KOREAN_COLORS.BLACK_SOLID, background);\n\n // Return color with better contrast\n return whiteRatio >= blackRatio\n ? KOREAN_COLORS.TEXT_PRIMARY\n : KOREAN_COLORS.BLACK_SOLID;\n}\n\n/**\n * Announce message to screen readers\n * Creates a live region announcement with configurable politeness\n *\n * @param announcement - Screen reader announcement configuration\n *\n * @example\n * ```typescript\n * announceToScreenReader({\n * message: '공격 성공 | Attack successful',\n * politeness: 'polite',\n * delay: 100,\n * });\n * ```\n */\nlet liveRegionInstance: HTMLElement | null = null;\n\nexport function announceToScreenReader(\n announcement: ScreenReaderAnnouncement,\n): void {\n const { message, politeness = \"polite\", delay = 0 } = announcement;\n\n setTimeout(() => {\n // Create or get existing live region\n if (!liveRegionInstance) {\n liveRegionInstance = document.createElement(\"div\");\n liveRegionInstance.id = \"sr-live-region\";\n liveRegionInstance.setAttribute(\"aria-live\", politeness);\n liveRegionInstance.setAttribute(\"aria-atomic\", \"true\");\n liveRegionInstance.style.cssText = `\n position: absolute;\n left: -10000px;\n width: 1px;\n height: 1px;\n overflow: hidden;\n `;\n document.body.appendChild(liveRegionInstance);\n }\n\n // Update politeness level if changed\n if (liveRegionInstance.getAttribute(\"aria-live\") !== politeness) {\n liveRegionInstance.setAttribute(\"aria-live\", politeness);\n }\n\n // Update message\n liveRegionInstance.textContent = message;\n\n // Clear message after 3 seconds\n setTimeout(() => {\n if (liveRegionInstance) {\n liveRegionInstance.textContent = \"\";\n }\n }, 3000);\n }, delay);\n}\n\n/**\n * Clean up screen reader live region (for testing or app unmount)\n */\nexport function cleanupScreenReaderRegion(): void {\n if (liveRegionInstance?.parentNode) {\n liveRegionInstance.parentNode.removeChild(liveRegionInstance);\n liveRegionInstance = null;\n }\n}\n\n/**\n * Trap focus within a container element\n * Prevents focus from leaving the container (e.g., for modals)\n *\n * @param container - Container element to trap focus within\n * @returns Cleanup function to remove focus trap\n *\n * @example\n * ```typescript\n * useEffect(() => {\n * const cleanup = trapFocus(modalRef.current);\n * return cleanup;\n * }, []);\n * ```\n */\nexport function trapFocus(container: HTMLElement | null): () => void {\n if (!container) return () => {};\n\n const focusableElements = getFocusableElements(container);\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Tab\") return;\n\n if (event.shiftKey) {\n // Shift + Tab: Move focus backward\n if (document.activeElement === firstElement) {\n event.preventDefault();\n lastElement?.focus();\n }\n } else {\n // Tab: Move focus forward\n if (document.activeElement === lastElement) {\n event.preventDefault();\n firstElement?.focus();\n }\n }\n };\n\n container.addEventListener(\"keydown\", handleKeyDown);\n\n // Set initial focus\n firstElement?.focus();\n\n // Return cleanup function\n return () => {\n container.removeEventListener(\"keydown\", handleKeyDown);\n };\n}\n\n/**\n * Get all focusable elements within a container\n *\n * @param container - Container element\n * @returns Array of focusable elements\n */\nexport function getFocusableElements(container: HTMLElement): HTMLElement[] {\n const selector = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n ].join(\", \");\n\n return Array.from(container.querySelectorAll<HTMLElement>(selector));\n}\n\n/**\n * Validate WCAG 2.1 Level AA compliance for a component\n *\n * Note: Color contrast checking requires access to computed styles,\n * which is not available in all testing environments. This validation\n * focuses on structural accessibility attributes.\n *\n * @param element - Element to validate\n * @param config - Validation configuration\n * @returns Compliance validation result\n */\nexport function validateWCAGCompliance(\n element: HTMLElement | null,\n config?: {\n checkKeyboard?: boolean;\n checkFocusVisible?: boolean;\n checkAria?: boolean;\n },\n): WCAGComplianceResult {\n const {\n checkKeyboard = true,\n checkFocusVisible = true,\n checkAria = true,\n } = config ?? {};\n\n const issues: string[] = [];\n let keyboardAccessible = true;\n const colorContrast = true; // Note: Requires computed styles - use getContrastRatio() directly for color validation\n let focusVisible = true;\n let ariaLabels = true;\n\n if (!element) {\n issues.push(\"Element not found\");\n return {\n level: WCAGLevel.A,\n compliant: false,\n criteria: {\n keyboardAccessible: false,\n colorContrast: false,\n focusVisible: false,\n ariaLabels: false,\n semanticHTML: false,\n errorIdentification: false,\n },\n issues,\n };\n }\n\n // Check keyboard accessibility\n if (checkKeyboard) {\n const isInteractive =\n element.tagName === \"BUTTON\" ||\n element.tagName === \"A\" ||\n element.tagName === \"INPUT\" ||\n element.hasAttribute(\"onclick\");\n\n if (isInteractive && element.getAttribute(\"tabindex\") === \"-1\") {\n keyboardAccessible = false;\n issues.push(\"Interactive element not keyboard accessible\");\n }\n }\n\n // Check ARIA labels\n if (checkAria) {\n const hasAriaLabel =\n element.hasAttribute(\"aria-label\") ||\n element.hasAttribute(\"aria-labelledby\");\n\n const hasTextContent = element.textContent?.trim();\n\n if (!hasAriaLabel && !hasTextContent) {\n ariaLabels = false;\n issues.push(\"Missing ARIA label or text content\");\n }\n }\n\n // Check focus indicator\n if (checkFocusVisible) {\n const computedStyle = window.getComputedStyle(element);\n const outline = computedStyle.outline;\n\n if (outline === \"none\" || outline === \"\") {\n focusVisible = false;\n issues.push(\"Missing visible focus indicator\");\n }\n }\n\n const allPass =\n keyboardAccessible && colorContrast && focusVisible && ariaLabels;\n\n return {\n level: allPass ? WCAGLevel.AA : WCAGLevel.A,\n compliant: allPass,\n criteria: {\n keyboardAccessible,\n colorContrast, // Note: Always true - use getContrastRatio() for actual color validation\n focusVisible,\n ariaLabels,\n semanticHTML: true, // Requires manual verification\n errorIdentification: true, // Requires manual verification\n },\n issues,\n };\n}\n\n/**\n * Create comprehensive ARIA attributes for a component\n *\n * @param label - Bilingual label (Korean | English)\n * @param role - ARIA role\n * @param additionalAttrs - Additional ARIA attributes\n * @returns Complete ARIA attributes object\n *\n * @example\n * ```tsx\n * const ariaProps = createAriaAttributes(\n * createBilingualLabel('공격', 'Attack'),\n * 'button',\n * { 'aria-pressed': isPressed }\n * );\n * <button {...ariaProps}>\n * ```\n */\nexport function createAriaAttributes(\n label: string | { korean: string; english: string },\n role?: AriaAttributes[\"role\"],\n additionalAttrs?: Partial<AriaAttributes>,\n): AriaAttributes {\n const bilingualLabel =\n typeof label === \"string\"\n ? label\n : createBilingualLabel(label.korean, label.english).label;\n\n return {\n role,\n \"aria-label\": bilingualLabel,\n ...additionalAttrs,\n };\n}\n\n/**\n * Check if element is currently visible\n * Useful for skip-to-content and focus management\n *\n * @param element - Element to check\n * @returns Whether element is visible\n */\nexport function isElementVisible(element: HTMLElement | null): boolean {\n if (!element) return false;\n\n const style = window.getComputedStyle(element);\n return (\n style.display !== \"none\" &&\n style.visibility !== \"hidden\" &&\n style.opacity !== \"0\" &&\n element.offsetParent !== null\n );\n}\n\n/**\n * Focus first error element in a form\n * Useful for form validation accessibility\n *\n * @param container - Form container\n */\nexport function focusFirstError(container: HTMLElement): void {\n const errorElements = container.querySelectorAll<HTMLElement>(\n '[aria-invalid=\"true\"]',\n );\n\n if (errorElements.length > 0) {\n errorElements[0].focus();\n announceToScreenReader({\n message: \"오류가 발견되었습니다 | Errors found\",\n politeness: \"assertive\",\n });\n }\n}\n\n/**\n * Create skip to content link for keyboard navigation\n * Returns a link that allows users to skip to main content\n *\n * Returns an object with the link element and a cleanup function to properly\n * remove event listeners when the component is no longer needed.\n *\n * **Breaking Change**: This function now returns `{ element, cleanup }` instead of\n * just `HTMLAnchorElement`. Update usage from:\n * ```typescript\n * const link = createSkipLink(id);\n * ```\n * to:\n * ```typescript\n * const { element, cleanup } = createSkipLink(id);\n * ```\n *\n * 컴포넌트가 더 이상 필요하지 않을 때 이벤트 리스너를 적절히 제거하기 위한\n * 링크 요소와 정리 함수가 포함된 객체를 반환합니다.\n *\n * @param targetId - ID of main content element\n * @returns Object with skip link element and cleanup function\n *\n * @example\n * ```typescript\n * const { element, cleanup } = createSkipLink(\"main-content\");\n * document.body.prepend(element);\n * // Later, when component unmounts\n * cleanup();\n * element.remove();\n * ```\n */\nexport function createSkipLink(targetId: string): {\n element: HTMLAnchorElement;\n cleanup: () => void;\n} {\n const skipLink = document.createElement(\"a\");\n skipLink.href = `#${targetId}`;\n skipLink.textContent = \"본문으로 건너뛰기 | Skip to content\";\n skipLink.className = \"skip-link\";\n skipLink.style.cssText = `\n position: absolute;\n top: -40px;\n left: 0;\n background: #00e6e6;\n color: #000;\n padding: 8px;\n text-decoration: none;\n z-index: 10000;\n font-weight: bold;\n `;\n\n // Use EventManager for consistent event handling across the codebase\n const eventManager = new EventManager();\n\n // Event handlers\n const handleFocus = () => {\n skipLink.style.top = \"0\";\n };\n\n const handleBlur = () => {\n skipLink.style.top = \"-40px\";\n };\n\n // Add event listeners via EventManager\n eventManager.add(skipLink, \"focus\", handleFocus);\n eventManager.add(skipLink, \"blur\", handleBlur);\n\n // Cleanup function to remove all event listeners\n const cleanup = () => {\n eventManager.cleanup();\n };\n\n return { element: skipLink, cleanup };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwCA,SAAgB,kBACd,OACA,SACM;CACN,MAAM,EAAE,KAAK,aAAa;CAE1B,QAAQ,KAAR;EACE,KAAK;EACL,KAAK;GAEH,MAAM,eAAe;GACrB,QAAQ,aAAa;GACrB;EAEF,KAAK;GAEH,MAAM,eAAe;GACrB,QAAQ,WAAW;GACnB;EAEF,KAAK;GAEH,QAAQ,QAAQ,QAAQ;GACxB;EAEF,KAAK;GAEH,MAAM,eAAe;GACrB,QAAQ,aAAa,IAAI;GACzB;EAEF,KAAK;GAEH,MAAM,eAAe;GACrB,QAAQ,aAAa,MAAM;GAC3B;EAEF,KAAK;GAEH,MAAM,eAAe;GACrB,QAAQ,aAAa,MAAM;GAC3B;EAEF,KAAK;GAEH,MAAM,eAAe;GACrB,QAAQ,aAAa,OAAO;GAC5B;EAEF,KAAK;GAEH,MAAM,eAAe;GACrB,QAAQ,SAAS,OAAO;GACxB;EAEF,KAAK;GAEH,MAAM,eAAe;GACrB,QAAQ,SAAS,KAAK;GACtB;CACJ;AACF;;;;;;AAOA,SAAS,uBAA4C;CACnD,MAAM,MAAM,SAAS,cAAc,YAAY;CAC/C,OAAO;EACL,cAAc;EACd,cAAc,cAAc;EAC5B,eAAe;EACf,cAAc;EACd,WAAW,kBAAkB,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE;EACvD,oBAAoB;CACtB;AACF;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,cACd,WACA,aACqB;CACrB,MAAM,eAAe,qBAAqB;CAE1C,IAAI,CAAC,WACH,OAAO,EAEL,YAAY,OAAO,aAAa,mBAAmB,QACrD;CAGF,MAAM,QAAQ;EAAE,GAAG;EAAc,GAAG;CAAY;CAChD,MAAM,MAAM,SAAS,MAAM,gBAAgB,cAAc,YAAY;CAErE,OAAO;EACL,SAAS,GAAG,MAAM,aAAa,KAAK,MAAM,aAAa,OAAO,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE;EACxF,eAAe,GAAG,MAAM,cAAc;EACtC,WACE,MAAM,aAAa,kBAAkB,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE;EACjE,YAAY,OAAO,MAAM,mBAAmB;CAC9C;AACF;;;;;;;AAQA,SAAgB,SAAS,KAAkD;CACzE,OAAO;EACL,GAAI,OAAO,KAAM;EACjB,GAAI,OAAO,IAAK;EAChB,GAAG,MAAM;CACX;AACF;;;;;;;;;;;;;;;;AA8FA,IAAI,qBAAyC;AAE7C,SAAgB,uBACd,cACM;CACN,MAAM,EAAE,SAAS,aAAa,UAAU,QAAQ,MAAM;CAEtD,iBAAiB;EAEf,IAAI,CAAC,oBAAoB;GACvB,qBAAqB,SAAS,cAAc,KAAK;GACjD,mBAAmB,KAAK;GACxB,mBAAmB,aAAa,aAAa,UAAU;GACvD,mBAAmB,aAAa,eAAe,MAAM;GACrD,mBAAmB,MAAM,UAAU;;;;;;;GAOnC,SAAS,KAAK,YAAY,kBAAkB;EAC9C;EAGA,IAAI,mBAAmB,aAAa,WAAW,MAAM,YACnD,mBAAmB,aAAa,aAAa,UAAU;EAIzD,mBAAmB,cAAc;EAGjC,iBAAiB;GACf,IAAI,oBACF,mBAAmB,cAAc;EAErC,GAAG,GAAI;CACT,GAAG,KAAK;AACV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arenaWorldDimensions.js","names":[],"sources":["../../src/utils/arenaWorldDimensions.ts"],"sourcesContent":["/**\n * Arena world dimension calculations for physics-first coordinate system.\n *\n * Determines the physical size of the combat arena in meters based on\n * screen **resolution** (not device type), ensuring appropriate gameplay\n * space for different display capabilities.\n *\n * **Important**: Arena size is determined by RESOLUTION, not device type.\n * A mobile device with a 4K screen gets a larger arena than a desktop with\n * 1080p resolution, because arena size should match display capability.\n *\n * **4:3 Aspect Ratio Philosophy**:\n * All arenas use 4:3 aspect ratio (width > height) for optimal screen usage.\n * Width sizes: 6m, 8m, 10m, 12m, 14m based on resolution.\n * Height is calculated as width × 0.75 (3/4).\n * This ensures:\n * - Efficient use of horizontal screen space\n * - Consistent physics across all devices\n * - Traditional fighting game proportions\n *\n * **Korean**: 경기장세계크기 (Arena World Size)\n *\n * @module utils/arenaWorldDimensions\n * @category Physics\n */\n\n/**\n * Screen size category based on RESOLUTION (width in pixels).\n *\n * **Korean**: 화면크기범주 (Screen Size Category)\n *\n */\nexport type ScreenSizeCategory =\n | \"small\"\n | \"medium\"\n | \"large\"\n | \"xlarge\"\n | \"ultra\";\n\n/**\n * Standard arena widths in meters.\n * Arenas use 4:3 aspect ratio (height = width × 0.75).\n *\n */\nexport const ARENA_SIZES = {\n /** Small arena for compact screens (6m × 4.5m) */\n SMALL: 6,\n /** Medium arena for tablets and small laptops (8m × 6m) */\n MEDIUM: 8,\n /** Large arena for standard desktops (10m × 7.5m) */\n LARGE: 10,\n /** Extra-large arena for high-res displays (12m × 9m) */\n XLARGE: 12,\n /** Ultra arena for 4K+ displays (14m × 10.5m) */\n ULTRA: 14,\n} as const;\n\n/**\n * Resolution breakpoints for arena size determination.\n * Based on common display widths.\n *\n */\nexport const RESOLUTION_BREAKPOINTS = {\n /** Small screens: < 768px (phones, small tablets) */\n SMALL_MAX: 768,\n /** Medium screens: 768-1199px (tablets, small laptops) */\n MEDIUM_MAX: 1200,\n /** Large screens: 1200-1919px (laptops, standard monitors) */\n LARGE_MAX: 1920,\n /** XLarge screens: 1920-2559px (large monitors, some 4K) */\n XLARGE_MAX: 2560,\n /** Ultra screens: ≥ 2560px (4K, ultrawide) */\n} as const;\n\n/**\n * World dimensions for an arena in meters.\n *\n * **Korean**: 세계크기 (World Dimensions)\n *\n */\nexport interface WorldDimensions {\n /** Arena width in meters */\n readonly widthMeters: number;\n /** Arena depth in meters (height = width × 0.75 for 4:3 ratio) */\n readonly depthMeters: number;\n /** Arena width in meters (same as widthMeters, for convenience) */\n readonly sizeMeters: number;\n /** Screen category used to determine arena size */\n readonly screenCategory: ScreenSizeCategory;\n}\n\n/**\n * Complete arena configuration including pixel and meter dimensions.\n *\n */\nexport interface ArenaConfiguration {\n /** Arena position X in pixels */\n readonly x: number;\n /** Arena position Y in pixels */\n readonly y: number;\n /** Arena width in pixels */\n readonly width: number;\n /** Arena height in pixels */\n readonly height: number;\n /** Arena world width in meters */\n readonly worldWidthMeters: number;\n /** Arena world depth in meters */\n readonly worldDepthMeters: number;\n /** Pixels per meter ratio for this arena */\n readonly pixelsPerMeter: number;\n /** Scale factor (1.0 = reference desktop, <1.0 = smaller screens) */\n readonly scale: number;\n}\n\n/**\n * Categorize screen based on resolution (width in pixels).\n *\n * This is used for arena sizing. Device type (mobile/desktop) is determined\n * separately for UI controls only.\n *\n * **Korean**: 화면크기범주결정 (Determine Screen Size Category)\n *\n * @param screenWidth - Screen width in pixels\n * @returns Screen size category for arena dimensions\n *\n * @example\n * ```typescript\n * getScreenSizeCategory(640); // \"small\" - small phone\n * getScreenSizeCategory(1200); // \"large\" - standard laptop\n * getScreenSizeCategory(3840); // \"ultra\" - 4K display\n * ```\n *\n */\nexport function getScreenSizeCategory(screenWidth: number): ScreenSizeCategory {\n if (screenWidth < RESOLUTION_BREAKPOINTS.SMALL_MAX) return \"small\";\n if (screenWidth < RESOLUTION_BREAKPOINTS.MEDIUM_MAX) return \"medium\";\n if (screenWidth < RESOLUTION_BREAKPOINTS.LARGE_MAX) return \"large\";\n if (screenWidth < RESOLUTION_BREAKPOINTS.XLARGE_MAX) return \"xlarge\";\n return \"ultra\";\n}\n\n/**\n * Get arena size in meters for a given screen category.\n *\n * @param category - Screen size category\n * @returns Arena size in meters (square dimension)\n *\n */\nexport function getArenaSizeForCategory(category: ScreenSizeCategory): number {\n switch (category) {\n case \"small\":\n return ARENA_SIZES.SMALL;\n case \"medium\":\n return ARENA_SIZES.MEDIUM;\n case \"large\":\n return ARENA_SIZES.LARGE;\n case \"xlarge\":\n return ARENA_SIZES.XLARGE;\n case \"ultra\":\n return ARENA_SIZES.ULTRA;\n default:\n return ARENA_SIZES.LARGE;\n }\n}\n\n/**\n * Calculate appropriate arena world dimensions based on screen resolution.\n *\n * Larger resolution displays get larger arenas for better gameplay, while\n * maintaining the same physical combat mechanics. Movement speed in m/s\n * remains constant, but larger arenas provide more tactical space.\n *\n * **Arenas use 4:3 aspect ratio** (width > depth):\n * - < 768px: 6m × 4.5m (compact, fast-paced combat)\n * - 768-1199px: 8m × 6m (balanced gameplay)\n * - 1200-1919px: 10m × 7.5m (tactical combat)\n * - 1920-2559px: 12m × 9m (advanced positioning)\n * - ≥ 2560px: 14m × 10.5m (maximum tactical space)\n *\n * **Korean**: 경기장크기계산 (Calculate Arena Size)\n *\n * @param screenWidth - Screen width in pixels (resolution, not device type)\n * @returns World dimensions in meters (4:3 aspect ratio)\n *\n * @example\n * ```typescript\n * // Mobile phone 640px width\n * const smallPhone = calculateArenaWorldDimensions(640);\n * // Result: { widthMeters: 6, depthMeters: 4.5, sizeMeters: 6, screenCategory: \"small\" }\n *\n * // Desktop 1920×1080\n * const desktop = calculateArenaWorldDimensions(1920);\n * // Result: { widthMeters: 12, depthMeters: 9, sizeMeters: 12, screenCategory: \"xlarge\" }\n * ```\n *\n */\nexport function calculateArenaWorldDimensions(\n screenWidth: number,\n): WorldDimensions {\n const category = getScreenSizeCategory(screenWidth);\n const sizeMeters = getArenaSizeForCategory(category);\n // 4:3 aspect ratio: depth = width × 0.75\n const depthMeters = sizeMeters * 0.75;\n\n return {\n widthMeters: sizeMeters,\n depthMeters,\n sizeMeters,\n screenCategory: category,\n };\n}\n\n/**\n * Calculate complete arena configuration including pixel and meter dimensions.\n *\n * This is the primary function for setting up arena bounds. It calculates:\n * - Pixel dimensions based on available screen space\n * - Meter dimensions based on screen resolution category\n * - Pixels-per-meter ratio for physics conversion\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 horizontalMargin - Pixels to leave on sides (as ratio 0-1)\n * @returns Complete arena configuration\n *\n */\nexport function calculateArenaConfiguration(\n screenWidth: number,\n screenHeight: number,\n topOffset: number,\n bottomOffset: number,\n horizontalMargin: number = 0.1,\n): ArenaConfiguration {\n // Get world dimensions based on resolution\n const worldDimensions = calculateArenaWorldDimensions(screenWidth);\n\n // Calculate available pixel space\n const availableWidth = screenWidth * (1 - 2 * horizontalMargin);\n const availableHeight = screenHeight - topOffset - bottomOffset;\n\n // Use the smaller dimension to ensure square arena fits\n const arenaSizePixels = Math.min(availableWidth, availableHeight);\n\n // Calculate arena position (centered horizontally)\n const arenaX = (screenWidth - arenaSizePixels) / 2;\n const arenaY = topOffset;\n\n // Calculate pixels per meter for this configuration\n const pixelsPerMeter = arenaSizePixels / worldDimensions.sizeMeters;\n\n // Calculate scale relative to reference (1000px / 10m = 100 px/m)\n const referencePixelsPerMeter = 100;\n const scale = pixelsPerMeter / referencePixelsPerMeter;\n\n return {\n x: arenaX,\n y: arenaY,\n width: arenaSizePixels,\n height: arenaSizePixels,\n worldWidthMeters: worldDimensions.widthMeters,\n worldDepthMeters: worldDimensions.depthMeters,\n pixelsPerMeter,\n scale,\n };\n}\n\n/**\n * Calculate pixels-per-meter ratio from arena dimensions.\n *\n * @param arenaWidthPixels - Arena width in pixels\n * @param arenaWidthMeters - Arena width in meters\n * @returns Pixels per meter ratio\n *\n */\nexport function calculatePixelsPerMeter(\n arenaWidthPixels: number,\n arenaWidthMeters: number,\n): number {\n if (arenaWidthMeters <= 0) {\n throw new Error(`arenaWidthMeters must be positive: ${arenaWidthMeters}`);\n }\n return arenaWidthPixels / arenaWidthMeters;\n}\n\n/**\n * Get player height in meters from archetype physical attributes.\n *\n * Converts archetype totalHeight (in centimeters) to meters for physics calculations.\n *\n * **Korean**: 플레이어키 (Player Height)\n *\n * @param archetypeHeightCm - Player height in centimeters from PhysicalAttributes\n * @returns Height in meters\n *\n * @example\n * ```typescript\n * // Musa archetype: 180cm\n * const height = getPlayerHeightMeters(180);\n * // Result: 1.8 meters\n * ```\n *\n */\nexport function getPlayerHeightMeters(archetypeHeightCm: number): number {\n return archetypeHeightCm / 100;\n}\n"],"mappings":";;;;;;AA4CA,IAAa,cAAc;;CAEzB,OAAO;;CAEP,QAAQ;;CAER,OAAO;;CAEP,QAAQ;;CAER,OAAO;CACR;;;;;;AAOD,IAAa,yBAAyB;;CAEpC,WAAW;;CAEX,YAAY;;CAEZ,WAAW;;CAEX,YAAY;CAEb;;;;;;;;;;;;;;;;;;;;AA6DD,SAAgB,sBAAsB,aAAyC;CAC7E,IAAI,cAAc,uBAAuB,WAAW,OAAO;CAC3D,IAAI,cAAc,uBAAuB,YAAY,OAAO;CAC5D,IAAI,cAAc,uBAAuB,WAAW,OAAO;CAC3D,IAAI,cAAc,uBAAuB,YAAY,OAAO;CAC5D,OAAO;;;;;;;;;AAUT,SAAgB,wBAAwB,UAAsC;CAC5E,QAAQ,UAAR;EACE,KAAK,SACH,OAAO,YAAY;EACrB,KAAK,UACH,OAAO,YAAY;EACrB,KAAK,SACH,OAAO,YAAY;EACrB,KAAK,UACH,OAAO,YAAY;EACrB,KAAK,SACH,OAAO,YAAY;EACrB,SACE,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCzB,SAAgB,8BACd,aACiB;CACjB,MAAM,WAAW,sBAAsB,YAAY;CACnD,MAAM,aAAa,wBAAwB,SAAS;CAIpD,OAAO;EACL,aAAa;EACb,aAJkB,aAAa;EAK/B;EACA,gBAAgB;EACjB;;;;;;;;;;;;;;;;;;AAmBH,SAAgB,4BACd,aACA,cACA,WACA,cACA,mBAA2B,IACP;CAEpB,MAAM,kBAAkB,8BAA8B,YAAY;CAGlE,MAAM,iBAAiB,eAAe,IAAI,IAAI;CAC9C,MAAM,kBAAkB,eAAe,YAAY;CAGnD,MAAM,kBAAkB,KAAK,IAAI,gBAAgB,gBAAgB;CAGjE,MAAM,UAAU,cAAc,mBAAmB;CACjD,MAAM,SAAS;CAGf,MAAM,iBAAiB,kBAAkB,gBAAgB;CAIzD,MAAM,QAAQ,iBAAiB;CAE/B,OAAO;EACL,GAAG;EACH,GAAG;EACH,OAAO;EACP,QAAQ;EACR,kBAAkB,gBAAgB;EAClC,kBAAkB,gBAAgB;EAClC;EACA;EACD"}
|
|
1
|
+
{"version":3,"file":"arenaWorldDimensions.js","names":[],"sources":["../../src/utils/arenaWorldDimensions.ts"],"sourcesContent":["/**\n * Arena world dimension calculations for physics-first coordinate system.\n *\n * Determines the physical size of the combat arena in meters based on\n * screen **resolution** (not device type), ensuring appropriate gameplay\n * space for different display capabilities.\n *\n * **Important**: Arena size is determined by RESOLUTION, not device type.\n * A mobile device with a 4K screen gets a larger arena than a desktop with\n * 1080p resolution, because arena size should match display capability.\n *\n * **4:3 Aspect Ratio Philosophy**:\n * All arenas use 4:3 aspect ratio (width > height) for optimal screen usage.\n * Width sizes: 6m, 8m, 10m, 12m, 14m based on resolution.\n * Height is calculated as width × 0.75 (3/4).\n * This ensures:\n * - Efficient use of horizontal screen space\n * - Consistent physics across all devices\n * - Traditional fighting game proportions\n *\n * **Korean**: 경기장세계크기 (Arena World Size)\n *\n * @module utils/arenaWorldDimensions\n * @category Physics\n */\n\n/**\n * Screen size category based on RESOLUTION (width in pixels).\n *\n * **Korean**: 화면크기범주 (Screen Size Category)\n *\n */\nexport type ScreenSizeCategory =\n | \"small\"\n | \"medium\"\n | \"large\"\n | \"xlarge\"\n | \"ultra\";\n\n/**\n * Standard arena widths in meters.\n * Arenas use 4:3 aspect ratio (height = width × 0.75).\n *\n */\nexport const ARENA_SIZES = {\n /** Small arena for compact screens (6m × 4.5m) */\n SMALL: 6,\n /** Medium arena for tablets and small laptops (8m × 6m) */\n MEDIUM: 8,\n /** Large arena for standard desktops (10m × 7.5m) */\n LARGE: 10,\n /** Extra-large arena for high-res displays (12m × 9m) */\n XLARGE: 12,\n /** Ultra arena for 4K+ displays (14m × 10.5m) */\n ULTRA: 14,\n} as const;\n\n/**\n * Resolution breakpoints for arena size determination.\n * Based on common display widths.\n *\n */\nexport const RESOLUTION_BREAKPOINTS = {\n /** Small screens: < 768px (phones, small tablets) */\n SMALL_MAX: 768,\n /** Medium screens: 768-1199px (tablets, small laptops) */\n MEDIUM_MAX: 1200,\n /** Large screens: 1200-1919px (laptops, standard monitors) */\n LARGE_MAX: 1920,\n /** XLarge screens: 1920-2559px (large monitors, some 4K) */\n XLARGE_MAX: 2560,\n /** Ultra screens: ≥ 2560px (4K, ultrawide) */\n} as const;\n\n/**\n * World dimensions for an arena in meters.\n *\n * **Korean**: 세계크기 (World Dimensions)\n *\n */\nexport interface WorldDimensions {\n /** Arena width in meters */\n readonly widthMeters: number;\n /** Arena depth in meters (height = width × 0.75 for 4:3 ratio) */\n readonly depthMeters: number;\n /** Arena width in meters (same as widthMeters, for convenience) */\n readonly sizeMeters: number;\n /** Screen category used to determine arena size */\n readonly screenCategory: ScreenSizeCategory;\n}\n\n/**\n * Complete arena configuration including pixel and meter dimensions.\n *\n */\nexport interface ArenaConfiguration {\n /** Arena position X in pixels */\n readonly x: number;\n /** Arena position Y in pixels */\n readonly y: number;\n /** Arena width in pixels */\n readonly width: number;\n /** Arena height in pixels */\n readonly height: number;\n /** Arena world width in meters */\n readonly worldWidthMeters: number;\n /** Arena world depth in meters */\n readonly worldDepthMeters: number;\n /** Pixels per meter ratio for this arena */\n readonly pixelsPerMeter: number;\n /** Scale factor (1.0 = reference desktop, <1.0 = smaller screens) */\n readonly scale: number;\n}\n\n/**\n * Categorize screen based on resolution (width in pixels).\n *\n * This is used for arena sizing. Device type (mobile/desktop) is determined\n * separately for UI controls only.\n *\n * **Korean**: 화면크기범주결정 (Determine Screen Size Category)\n *\n * @param screenWidth - Screen width in pixels\n * @returns Screen size category for arena dimensions\n *\n * @example\n * ```typescript\n * getScreenSizeCategory(640); // \"small\" - small phone\n * getScreenSizeCategory(1200); // \"large\" - standard laptop\n * getScreenSizeCategory(3840); // \"ultra\" - 4K display\n * ```\n *\n */\nexport function getScreenSizeCategory(screenWidth: number): ScreenSizeCategory {\n if (screenWidth < RESOLUTION_BREAKPOINTS.SMALL_MAX) return \"small\";\n if (screenWidth < RESOLUTION_BREAKPOINTS.MEDIUM_MAX) return \"medium\";\n if (screenWidth < RESOLUTION_BREAKPOINTS.LARGE_MAX) return \"large\";\n if (screenWidth < RESOLUTION_BREAKPOINTS.XLARGE_MAX) return \"xlarge\";\n return \"ultra\";\n}\n\n/**\n * Get arena size in meters for a given screen category.\n *\n * @param category - Screen size category\n * @returns Arena size in meters (square dimension)\n *\n */\nexport function getArenaSizeForCategory(category: ScreenSizeCategory): number {\n switch (category) {\n case \"small\":\n return ARENA_SIZES.SMALL;\n case \"medium\":\n return ARENA_SIZES.MEDIUM;\n case \"large\":\n return ARENA_SIZES.LARGE;\n case \"xlarge\":\n return ARENA_SIZES.XLARGE;\n case \"ultra\":\n return ARENA_SIZES.ULTRA;\n default:\n return ARENA_SIZES.LARGE;\n }\n}\n\n/**\n * Calculate appropriate arena world dimensions based on screen resolution.\n *\n * Larger resolution displays get larger arenas for better gameplay, while\n * maintaining the same physical combat mechanics. Movement speed in m/s\n * remains constant, but larger arenas provide more tactical space.\n *\n * **Arenas use 4:3 aspect ratio** (width > depth):\n * - < 768px: 6m × 4.5m (compact, fast-paced combat)\n * - 768-1199px: 8m × 6m (balanced gameplay)\n * - 1200-1919px: 10m × 7.5m (tactical combat)\n * - 1920-2559px: 12m × 9m (advanced positioning)\n * - ≥ 2560px: 14m × 10.5m (maximum tactical space)\n *\n * **Korean**: 경기장크기계산 (Calculate Arena Size)\n *\n * @param screenWidth - Screen width in pixels (resolution, not device type)\n * @returns World dimensions in meters (4:3 aspect ratio)\n *\n * @example\n * ```typescript\n * // Mobile phone 640px width\n * const smallPhone = calculateArenaWorldDimensions(640);\n * // Result: { widthMeters: 6, depthMeters: 4.5, sizeMeters: 6, screenCategory: \"small\" }\n *\n * // Desktop 1920×1080\n * const desktop = calculateArenaWorldDimensions(1920);\n * // Result: { widthMeters: 12, depthMeters: 9, sizeMeters: 12, screenCategory: \"xlarge\" }\n * ```\n *\n */\nexport function calculateArenaWorldDimensions(\n screenWidth: number,\n): WorldDimensions {\n const category = getScreenSizeCategory(screenWidth);\n const sizeMeters = getArenaSizeForCategory(category);\n // 4:3 aspect ratio: depth = width × 0.75\n const depthMeters = sizeMeters * 0.75;\n\n return {\n widthMeters: sizeMeters,\n depthMeters,\n sizeMeters,\n screenCategory: category,\n };\n}\n\n/**\n * Calculate complete arena configuration including pixel and meter dimensions.\n *\n * This is the primary function for setting up arena bounds. It calculates:\n * - Pixel dimensions based on available screen space\n * - Meter dimensions based on screen resolution category\n * - Pixels-per-meter ratio for physics conversion\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 horizontalMargin - Pixels to leave on sides (as ratio 0-1)\n * @returns Complete arena configuration\n *\n */\nexport function calculateArenaConfiguration(\n screenWidth: number,\n screenHeight: number,\n topOffset: number,\n bottomOffset: number,\n horizontalMargin: number = 0.1,\n): ArenaConfiguration {\n // Get world dimensions based on resolution\n const worldDimensions = calculateArenaWorldDimensions(screenWidth);\n\n // Calculate available pixel space\n const availableWidth = screenWidth * (1 - 2 * horizontalMargin);\n const availableHeight = screenHeight - topOffset - bottomOffset;\n\n // Use the smaller dimension to ensure square arena fits\n const arenaSizePixels = Math.min(availableWidth, availableHeight);\n\n // Calculate arena position (centered horizontally)\n const arenaX = (screenWidth - arenaSizePixels) / 2;\n const arenaY = topOffset;\n\n // Calculate pixels per meter for this configuration\n const pixelsPerMeter = arenaSizePixels / worldDimensions.sizeMeters;\n\n // Calculate scale relative to reference (1000px / 10m = 100 px/m)\n const referencePixelsPerMeter = 100;\n const scale = pixelsPerMeter / referencePixelsPerMeter;\n\n return {\n x: arenaX,\n y: arenaY,\n width: arenaSizePixels,\n height: arenaSizePixels,\n worldWidthMeters: worldDimensions.widthMeters,\n worldDepthMeters: worldDimensions.depthMeters,\n pixelsPerMeter,\n scale,\n };\n}\n\n/**\n * Calculate pixels-per-meter ratio from arena dimensions.\n *\n * @param arenaWidthPixels - Arena width in pixels\n * @param arenaWidthMeters - Arena width in meters\n * @returns Pixels per meter ratio\n *\n */\nexport function calculatePixelsPerMeter(\n arenaWidthPixels: number,\n arenaWidthMeters: number,\n): number {\n if (arenaWidthMeters <= 0) {\n throw new Error(`arenaWidthMeters must be positive: ${arenaWidthMeters}`);\n }\n return arenaWidthPixels / arenaWidthMeters;\n}\n\n/**\n * Get player height in meters from archetype physical attributes.\n *\n * Converts archetype totalHeight (in centimeters) to meters for physics calculations.\n *\n * **Korean**: 플레이어키 (Player Height)\n *\n * @param archetypeHeightCm - Player height in centimeters from PhysicalAttributes\n * @returns Height in meters\n *\n * @example\n * ```typescript\n * // Musa archetype: 180cm\n * const height = getPlayerHeightMeters(180);\n * // Result: 1.8 meters\n * ```\n *\n */\nexport function getPlayerHeightMeters(archetypeHeightCm: number): number {\n return archetypeHeightCm / 100;\n}\n"],"mappings":";;;;;;AA4CA,IAAa,cAAc;;CAEzB,OAAO;;CAEP,QAAQ;;CAER,OAAO;;CAEP,QAAQ;;CAER,OAAO;AACT;;;;;;AAOA,IAAa,yBAAyB;;CAEpC,WAAW;;CAEX,YAAY;;CAEZ,WAAW;;CAEX,YAAY;AAEd;;;;;;;;;;;;;;;;;;;;AA6DA,SAAgB,sBAAsB,aAAyC;CAC7E,IAAI,cAAc,uBAAuB,WAAW,OAAO;CAC3D,IAAI,cAAc,uBAAuB,YAAY,OAAO;CAC5D,IAAI,cAAc,uBAAuB,WAAW,OAAO;CAC3D,IAAI,cAAc,uBAAuB,YAAY,OAAO;CAC5D,OAAO;AACT;;;;;;;;AASA,SAAgB,wBAAwB,UAAsC;CAC5E,QAAQ,UAAR;EACE,KAAK,SACH,OAAO,YAAY;EACrB,KAAK,UACH,OAAO,YAAY;EACrB,KAAK,SACH,OAAO,YAAY;EACrB,KAAK,UACH,OAAO,YAAY;EACrB,KAAK,SACH,OAAO,YAAY;EACrB,SACE,OAAO,YAAY;CACvB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,8BACd,aACiB;CACjB,MAAM,WAAW,sBAAsB,WAAW;CAClD,MAAM,aAAa,wBAAwB,QAAQ;CAInD,OAAO;EACL,aAAa;EACb,aAJkB,aAAa;EAK/B;EACA,gBAAgB;CAClB;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,4BACd,aACA,cACA,WACA,cACA,mBAA2B,IACP;CAEpB,MAAM,kBAAkB,8BAA8B,WAAW;CAGjE,MAAM,iBAAiB,eAAe,IAAI,IAAI;CAC9C,MAAM,kBAAkB,eAAe,YAAY;CAGnD,MAAM,kBAAkB,KAAK,IAAI,gBAAgB,eAAe;CAGhE,MAAM,UAAU,cAAc,mBAAmB;CACjD,MAAM,SAAS;CAGf,MAAM,iBAAiB,kBAAkB,gBAAgB;CAIzD,MAAM,QAAQ,iBAAiB;CAE/B,OAAO;EACL,GAAG;EACH,GAAG;EACH,OAAO;EACP,QAAQ;EACR,kBAAkB,gBAAgB;EAClC,kBAAkB,gBAAgB;EAClC;EACA;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assetConfig.js","names":[],"sources":["../../src/utils/assetConfig.ts"],"sourcesContent":["/**\n * Asset path configuration for Black Trigram library consumers.\n *\n * By default, all asset URLs are root-relative (e.g. /assets/audio/*).\n * Library consumers who host assets at a different path can call\n * {@link setAssetBasePath} to prefix all asset URLs.\n *\n * @example\n * ```ts\n * import { setAssetBasePath } from 'blacktrigram';\n * // Serve assets from a CDN\n * setAssetBasePath('https://cdn.example.com');\n * ```\n *\n * @module utils\n * @category Utilities\n */\n\nlet assetBasePath = \"\";\n\n/**\n * Set the base path for all game assets.\n * Call this before initializing any game components.\n *\n * @param basePath - The base URL/path prefix (e.g. `\"https://cdn.example.com\"` or `\"/my-app\"`)\n */\nexport function setAssetBasePath(basePath: string): void {\n // Remove trailing slash for consistency\n assetBasePath = basePath.replace(/\\/+$/, \"\");\n}\n\n/**\n * Get the current asset base path.\n */\nexport function getAssetBasePath(): string {\n return assetBasePath;\n}\n\n/**\n * Resolve an asset path by prepending the configured base path.\n *\n * Accepts paths with or without a leading slash — both are normalized\n * to root-relative before the base path (if any) is prepended.\n *\n * @param path - Asset path, e.g. /assets/audio/music/intro_theme.mp3 or assets/audio/music/intro_theme.mp3\n * @returns The resolved URL with the configured base path prepended\n */\nexport function resolveAssetPath(path: string): string {\n // Ensure path starts with / for correct concatenation\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n\n if (!assetBasePath) {\n return normalizedPath;\n }\n return `${assetBasePath}${normalizedPath}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,IAAI,gBAAgB;;;;;;;AAQpB,SAAgB,iBAAiB,UAAwB;CAEvD,gBAAgB,SAAS,QAAQ,QAAQ,
|
|
1
|
+
{"version":3,"file":"assetConfig.js","names":[],"sources":["../../src/utils/assetConfig.ts"],"sourcesContent":["/**\n * Asset path configuration for Black Trigram library consumers.\n *\n * By default, all asset URLs are root-relative (e.g. /assets/audio/*).\n * Library consumers who host assets at a different path can call\n * {@link setAssetBasePath} to prefix all asset URLs.\n *\n * @example\n * ```ts\n * import { setAssetBasePath } from 'blacktrigram';\n * // Serve assets from a CDN\n * setAssetBasePath('https://cdn.example.com');\n * ```\n *\n * @module utils\n * @category Utilities\n */\n\nlet assetBasePath = \"\";\n\n/**\n * Set the base path for all game assets.\n * Call this before initializing any game components.\n *\n * @param basePath - The base URL/path prefix (e.g. `\"https://cdn.example.com\"` or `\"/my-app\"`)\n */\nexport function setAssetBasePath(basePath: string): void {\n // Remove trailing slash for consistency\n assetBasePath = basePath.replace(/\\/+$/, \"\");\n}\n\n/**\n * Get the current asset base path.\n */\nexport function getAssetBasePath(): string {\n return assetBasePath;\n}\n\n/**\n * Resolve an asset path by prepending the configured base path.\n *\n * Accepts paths with or without a leading slash — both are normalized\n * to root-relative before the base path (if any) is prepended.\n *\n * @param path - Asset path, e.g. /assets/audio/music/intro_theme.mp3 or assets/audio/music/intro_theme.mp3\n * @returns The resolved URL with the configured base path prepended\n */\nexport function resolveAssetPath(path: string): string {\n // Ensure path starts with / for correct concatenation\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n\n if (!assetBasePath) {\n return normalizedPath;\n }\n return `${assetBasePath}${normalizedPath}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,IAAI,gBAAgB;;;;;;;AAQpB,SAAgB,iBAAiB,UAAwB;CAEvD,gBAAgB,SAAS,QAAQ,QAAQ,EAAE;AAC7C;;;;AAKA,SAAgB,mBAA2B;CACzC,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,iBAAiB,MAAsB;CAErD,MAAM,iBAAiB,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI;CAEzD,IAAI,CAAC,eACH,OAAO;CAET,OAAO,GAAG,gBAAgB;AAC5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"characterScaling.js","names":[],"sources":["../../src/utils/characterScaling.ts"],"sourcesContent":["/**\n * Unified character scaling system for Black Trigram.\n *\n * **Korean**: 통합 캐릭터 스케일링 시스템\n *\n * This module provides a physics-first approach to character scaling where:\n * - All measurements are in METERS for consistency with arena and physics\n * - Physical attributes (in cm) are converted to meters\n * - All body part dimensions derive from physical attributes\n * - Scale factors are based on anatomical proportions\n *\n * ## Design Philosophy\n *\n * The scaling system ensures that:\n * 1. Characters appear correctly sized in arenas (6-14 meters wide)\n * 2. Reach calculations (arm/leg length) match visual representation\n * 3. Muscle and clothing scale proportionally with body dimensions\n * 4. Combat animations use realistic distances\n *\n * ## Unit Conventions\n * - Physical attributes: centimeters (totalHeight: 180 = 180cm)\n * - World space: meters (arena width: 10 = 10m)\n * - Character model: meters (height: 1.80 = 1.80m)\n *\n * @module utils/characterScaling\n * @category Character Rendering\n * @korean 캐릭터스케일링\n */\n\nimport type { PhysicalAttributes } from \"@/types\";\n\n// ============================================================================\n// REFERENCE ANATOMY (Based on average Korean adult male proportions)\n// ============================================================================\n\n/**\n * Reference body proportions as fractions of total height.\n * Based on canonical human anatomy (8-head proportion system).\n *\n * @korean 기준신체비율\n */\nexport const BODY_PROPORTIONS = {\n /** Head is ~1/8 of total height */\n HEAD_HEIGHT_RATIO: 0.125,\n /** Neck is ~0.055 of total height */\n NECK_HEIGHT_RATIO: 0.055,\n /** Torso (spine) is ~0.33 of total height */\n TORSO_HEIGHT_RATIO: 0.33,\n /** Leg (hip to floor) is ~0.48 of total height */\n LEG_HEIGHT_RATIO: 0.48,\n /** Upper leg (thigh) is ~55% of total leg length */\n THIGH_LEG_RATIO: 0.55,\n /** Lower leg (shin) is ~45% of total leg length */\n SHIN_LEG_RATIO: 0.45,\n /** Upper arm is ~55% of total arm length */\n UPPER_ARM_RATIO: 0.55,\n /** Forearm is ~45% of total arm length */\n FOREARM_RATIO: 0.45,\n /** Hand is ~0.11 of total height (or ~10.5% of total height) */\n HAND_HEIGHT_RATIO: 0.105,\n /** Foot length is ~15% of total height */\n FOOT_LENGTH_RATIO: 0.15,\n /** Shoulder width is typically 23-25% of height for males */\n SHOULDER_WIDTH_RATIO: 0.255,\n /** Hip width is typically 16-18% of height */\n HIP_WIDTH_RATIO: 0.17,\n /** Torso depth is typically 10-12% of height */\n TORSO_DEPTH_RATIO: 0.11,\n} as const;\n\n/**\n * Bone length as fraction of total height.\n * These are anatomically accurate proportions for skeletal rig creation.\n *\n * @korean 뼈길이비율\n */\nexport const BONE_HEIGHT_RATIOS = {\n // Head & Neck\n head: 0.125, // 22.5cm for 180cm person\n neck: 0.055, // 9.9cm for 180cm person\n\n // Spine (split into 3 segments)\n spineUpper: 0.11, // Upper thoracic\n spineMiddle: 0.11, // Middle thoracic\n spineLower: 0.11, // Lumbar\n\n // Shoulders (clavicle + shoulder joint)\n shoulder: 0.055, // Clavicle length\n\n // Arms (based on arm length from physical attributes)\n // Actual values calculated from physicalAttributes.armLength\n upperArm: 0.21, // ~38cm for 180cm (55% of arm)\n elbow: 0.025, // Elbow joint\n forearm: 0.17, // ~31cm for 180cm (45% of arm)\n wrist: 0.025, // Wrist joint\n hand: 0.105, // ~19cm for 180cm\n\n // Pelvis & Hips\n pelvis: 0.08, // Pelvis height\n hip: 0.05, // Hip joint\n\n // Legs (based on leg length from physical attributes)\n // Actual values calculated from physicalAttributes.legLength\n thigh: 0.265, // ~48cm for 180cm (55% of leg)\n knee: 0.025, // Knee joint\n shin: 0.22, // ~40cm for 180cm (45% of leg)\n foot: 0.05, // Ankle to ground\n} as const;\n\n// ============================================================================\n// CONVERSION UTILITIES\n// ============================================================================\n\n/**\n * Convert centimeters to meters.\n *\n * @param cm - Value in centimeters\n * @returns Value in meters\n * @korean cm를미터로\n */\nexport function cmToMeters(cm: number): number {\n return cm / 100;\n}\n\n/**\n * Convert physical attributes from centimeters to meters.\n *\n * @param attrs - Physical attributes in centimeters\n * @returns Object with key measurements in meters\n * @korean 신체속성미터변환\n */\nexport function getPhysicalMeters(attrs: PhysicalAttributes): {\n readonly totalHeight: number;\n readonly armLength: number;\n readonly legLength: number;\n readonly shoulderWidth: number;\n readonly torsoLength: number;\n readonly headSize: number;\n readonly neckLength: number;\n} {\n return {\n totalHeight: cmToMeters(attrs.totalHeight),\n armLength: cmToMeters(attrs.armLength),\n legLength: cmToMeters(attrs.legLength),\n shoulderWidth: cmToMeters(attrs.shoulderWidth),\n torsoLength: cmToMeters(attrs.torsoLength),\n headSize: cmToMeters(attrs.headSize),\n neckLength: cmToMeters(attrs.neckLength),\n };\n}\n\n// ============================================================================\n// SKELETON DIMENSIONS\n// ============================================================================\n\n/**\n * Calculate skeleton bone dimensions in meters based on physical attributes.\n *\n * Returns actual bone lengths that should be used in SkeletonRig creation.\n * All values are in meters and anatomically proportioned.\n *\n * @param attrs - Physical attributes (in centimeters)\n * @returns Bone dimensions in meters\n * @korean 골격치수계산\n */\nexport function calculateSkeletonDimensions(attrs: PhysicalAttributes): {\n readonly pelvisHeight: number;\n readonly spineLower: number;\n readonly spineMiddle: number;\n readonly spineUpper: number;\n readonly neck: number;\n readonly head: number;\n readonly shoulder: number;\n readonly upperArm: number;\n readonly elbow: number;\n readonly forearm: number;\n readonly wrist: number;\n readonly hand: number;\n readonly hip: number;\n readonly thigh: number;\n readonly knee: number;\n readonly shin: number;\n readonly foot: number;\n readonly totalHeight: number;\n} {\n const totalHeightM = cmToMeters(attrs.totalHeight);\n const armLengthM = cmToMeters(attrs.armLength);\n const legLengthM = cmToMeters(attrs.legLength);\n const shoulderWidthM = cmToMeters(attrs.shoulderWidth);\n const neckLengthM = cmToMeters(attrs.neckLength);\n const headSizeM = cmToMeters(attrs.headSize);\n const torsoLengthM = cmToMeters(attrs.torsoLength);\n\n // Calculate arm segments based on actual arm length\n const upperArmLength = armLengthM * BODY_PROPORTIONS.UPPER_ARM_RATIO;\n const forearmLength = armLengthM * BODY_PROPORTIONS.FOREARM_RATIO;\n\n // Calculate leg segments based on actual leg length\n const thighLength = legLengthM * BODY_PROPORTIONS.THIGH_LEG_RATIO;\n const shinLength = legLengthM * BODY_PROPORTIONS.SHIN_LEG_RATIO;\n\n // Spine segments (divide torso into 3 parts)\n const spineSegment = torsoLengthM / 3;\n\n return {\n // Pelvis height = leg length + foot height (to have feet at Y=0)\n pelvisHeight: legLengthM + 0.05, // 0.05m for foot/ankle height\n\n // Spine segments\n spineLower: spineSegment,\n spineMiddle: spineSegment,\n spineUpper: spineSegment,\n\n // Head & Neck (use actual values if available, or proportional)\n neck: neckLengthM || totalHeightM * BONE_HEIGHT_RATIOS.neck,\n head: headSizeM || totalHeightM * BONE_HEIGHT_RATIOS.head,\n\n // Shoulders\n shoulder: shoulderWidthM * 0.25, // Clavicle is ~25% of shoulder width\n\n // Arms\n upperArm: upperArmLength,\n elbow: totalHeightM * BONE_HEIGHT_RATIOS.elbow,\n forearm: forearmLength,\n wrist: totalHeightM * BONE_HEIGHT_RATIOS.wrist,\n hand: totalHeightM * BONE_HEIGHT_RATIOS.hand,\n\n // Pelvis & Hip\n hip: totalHeightM * BONE_HEIGHT_RATIOS.hip,\n\n // Legs\n thigh: thighLength,\n knee: totalHeightM * BONE_HEIGHT_RATIOS.knee,\n shin: shinLength,\n foot: 0.05, // Ankle height\n\n // Total for validation\n totalHeight: totalHeightM,\n };\n}\n\n// ============================================================================\n// MUSCLE DIMENSIONS\n// ============================================================================\n\n/**\n * Base muscle dimensions as fractions of associated bone length.\n * These are proportional values that scale with bone size.\n *\n * @korean 근육비율\n */\nexport const MUSCLE_BONE_RATIOS = {\n // Shoulder muscles (deltoid)\n shoulder: {\n radiusFactor: 0.4, // 40% of shoulder bone length\n lengthFactor: 0.8, // 80% of shoulder bone length\n },\n\n // Bicep (front of upper arm)\n bicep: {\n radiusFactor: 0.35, // 35% of upper arm length\n lengthFactor: 0.7, // 70% of upper arm length\n },\n\n // Tricep (back of upper arm)\n tricep: {\n radiusFactor: 0.3, // 30% of upper arm length\n lengthFactor: 0.65, // 65% of upper arm length\n },\n\n // Forearm\n forearm: {\n radiusFactor: 0.3, // 30% of forearm length\n lengthFactor: 0.8, // 80% of forearm length\n },\n\n // Pectorals (chest)\n pectorals: {\n radiusFactor: 0.5, // 50% of torso segment\n lengthFactor: 0.7, // 70% of torso segment\n },\n\n // Core/Abs\n core: {\n radiusFactor: 0.45, // 45% of torso segment\n lengthFactor: 0.9, // 90% of torso segment\n },\n\n // Abs (lower core)\n abs: {\n radiusFactor: 0.4, // 40% of torso segment\n lengthFactor: 0.8, // 80% of torso segment\n },\n\n // Obliques (side core)\n obliques: {\n radiusFactor: 0.35, // 35% of torso segment\n lengthFactor: 0.75, // 75% of torso segment\n },\n\n // Glutes\n glute: {\n radiusFactor: 0.5, // 50% of hip/pelvis width\n lengthFactor: 0.6, // 60% of hip area\n },\n\n // Quadriceps (front of thigh)\n quad: {\n radiusFactor: 0.35, // 35% of thigh length\n lengthFactor: 0.8, // 80% of thigh length\n },\n\n // Hamstring (back of thigh)\n hamstring: {\n radiusFactor: 0.3, // 30% of thigh length\n lengthFactor: 0.75, // 75% of thigh length\n },\n\n // Calf\n calf: {\n radiusFactor: 0.28, // 28% of shin length\n lengthFactor: 0.6, // 60% of shin length\n },\n} as const;\n\n/**\n * Calculate muscle dimensions in meters based on skeleton dimensions.\n *\n * @param skeletonDims - Skeleton dimensions from calculateSkeletonDimensions\n * @param muscleMass - Muscle mass in kg for scaling\n * @param referenceMass - Reference muscle mass (default 35kg)\n * @returns Muscle dimensions in meters\n * @korean 근육치수계산\n */\nexport function calculateMuscleDimensions(\n skeletonDims: ReturnType<typeof calculateSkeletonDimensions>,\n muscleMass: number,\n referenceMass: number = 35,\n): Record<\n string,\n {\n readonly radius: number;\n readonly length: number;\n readonly scaleFactor: number;\n }\n> {\n // Muscle scale factor based on muscle mass\n // Uses square root for more natural visual scaling\n const massRatio = muscleMass / referenceMass;\n const scaleFactor = Math.sqrt(massRatio);\n\n return {\n shoulder: {\n radius:\n skeletonDims.shoulder *\n MUSCLE_BONE_RATIOS.shoulder.radiusFactor *\n scaleFactor,\n length: skeletonDims.shoulder * MUSCLE_BONE_RATIOS.shoulder.lengthFactor,\n scaleFactor,\n },\n bicep: {\n radius:\n skeletonDims.upperArm *\n MUSCLE_BONE_RATIOS.bicep.radiusFactor *\n scaleFactor,\n length: skeletonDims.upperArm * MUSCLE_BONE_RATIOS.bicep.lengthFactor,\n scaleFactor,\n },\n tricep: {\n radius:\n skeletonDims.upperArm *\n MUSCLE_BONE_RATIOS.tricep.radiusFactor *\n scaleFactor,\n length: skeletonDims.upperArm * MUSCLE_BONE_RATIOS.tricep.lengthFactor,\n scaleFactor,\n },\n forearm: {\n radius:\n skeletonDims.forearm *\n MUSCLE_BONE_RATIOS.forearm.radiusFactor *\n scaleFactor,\n length: skeletonDims.forearm * MUSCLE_BONE_RATIOS.forearm.lengthFactor,\n scaleFactor,\n },\n pectorals: {\n radius:\n skeletonDims.spineMiddle *\n MUSCLE_BONE_RATIOS.pectorals.radiusFactor *\n scaleFactor,\n length:\n skeletonDims.spineMiddle * MUSCLE_BONE_RATIOS.pectorals.lengthFactor,\n scaleFactor,\n },\n core: {\n radius:\n skeletonDims.spineMiddle *\n MUSCLE_BONE_RATIOS.core.radiusFactor *\n scaleFactor,\n length: skeletonDims.spineMiddle * MUSCLE_BONE_RATIOS.core.lengthFactor,\n scaleFactor,\n },\n abs: {\n radius:\n skeletonDims.spineLower *\n MUSCLE_BONE_RATIOS.abs.radiusFactor *\n scaleFactor,\n length: skeletonDims.spineLower * MUSCLE_BONE_RATIOS.abs.lengthFactor,\n scaleFactor,\n },\n obliques: {\n radius:\n skeletonDims.spineLower *\n MUSCLE_BONE_RATIOS.obliques.radiusFactor *\n scaleFactor,\n length:\n skeletonDims.spineLower * MUSCLE_BONE_RATIOS.obliques.lengthFactor,\n scaleFactor,\n },\n glute: {\n radius:\n skeletonDims.hip * MUSCLE_BONE_RATIOS.glute.radiusFactor * scaleFactor,\n length: skeletonDims.hip * MUSCLE_BONE_RATIOS.glute.lengthFactor,\n scaleFactor,\n },\n quad: {\n radius:\n skeletonDims.thigh * MUSCLE_BONE_RATIOS.quad.radiusFactor * scaleFactor,\n length: skeletonDims.thigh * MUSCLE_BONE_RATIOS.quad.lengthFactor,\n scaleFactor,\n },\n hamstring: {\n radius:\n skeletonDims.thigh *\n MUSCLE_BONE_RATIOS.hamstring.radiusFactor *\n scaleFactor,\n length: skeletonDims.thigh * MUSCLE_BONE_RATIOS.hamstring.lengthFactor,\n scaleFactor,\n },\n calf: {\n radius:\n skeletonDims.shin * MUSCLE_BONE_RATIOS.calf.radiusFactor * scaleFactor,\n length: skeletonDims.shin * MUSCLE_BONE_RATIOS.calf.lengthFactor,\n scaleFactor,\n },\n };\n}\n\n// ============================================================================\n// HAND & FOOT DIMENSIONS\n// ============================================================================\n\n/**\n * Calculate hand dimensions in meters based on physical attributes.\n *\n * @param attrs - Physical attributes\n * @returns Hand dimensions in meters\n * @korean 손치수계산\n */\nexport function calculateHandDimensions(attrs: PhysicalAttributes): {\n readonly palmLength: number;\n readonly palmWidth: number;\n readonly palmDepth: number;\n readonly fingerLengths: {\n readonly thumb: number;\n readonly index: number;\n readonly middle: number;\n readonly ring: number;\n readonly pinky: number;\n };\n readonly fingerWidths: {\n readonly thumb: number;\n readonly index: number;\n readonly middle: number;\n readonly ring: number;\n readonly pinky: number;\n };\n} {\n const totalHeightM = cmToMeters(attrs.totalHeight);\n\n // Hand is ~10.5% of total height, palm is ~55% of hand\n const handLength = totalHeightM * BODY_PROPORTIONS.HAND_HEIGHT_RATIO;\n const palmLength = handLength * 0.55;\n const fingerBaseLength = handLength * 0.45; // Fingers are 45% of hand\n\n // Palm width is ~85% of palm length\n const palmWidth = palmLength * 0.85;\n const palmDepth = palmLength * 0.25;\n\n // Finger lengths relative to middle finger (longest)\n const middleFingerLength = fingerBaseLength;\n\n return {\n palmLength,\n palmWidth,\n palmDepth,\n fingerLengths: {\n thumb: middleFingerLength * 0.65, // Thumb is ~65% of middle finger\n index: middleFingerLength * 0.95, // Index is ~95% of middle finger\n middle: middleFingerLength, // Middle is longest\n ring: middleFingerLength * 0.94, // Ring is ~94% of middle finger\n pinky: middleFingerLength * 0.75, // Pinky is ~75% of middle finger\n },\n fingerWidths: {\n thumb: palmWidth * 0.2, // Thumb is thickest\n index: palmWidth * 0.16,\n middle: palmWidth * 0.16,\n ring: palmWidth * 0.15,\n pinky: palmWidth * 0.13, // Pinky is thinnest\n },\n };\n}\n\n/**\n * Calculate foot dimensions in meters based on physical attributes.\n *\n * @param attrs - Physical attributes\n * @returns Foot dimensions in meters\n * @korean 발치수계산\n */\nexport function calculateFootDimensions(attrs: PhysicalAttributes): {\n readonly footLength: number;\n readonly footWidth: number;\n readonly footHeight: number;\n readonly toeLength: number;\n readonly heelLength: number;\n readonly ankleRadius: number;\n} {\n const totalHeightM = cmToMeters(attrs.totalHeight);\n\n // Foot length is ~15% of total height\n const footLength = totalHeightM * BODY_PROPORTIONS.FOOT_LENGTH_RATIO;\n\n return {\n footLength,\n footWidth: footLength * 0.38, // Foot width is ~38% of length\n footHeight: footLength * 0.3, // Foot height at ankle is ~30% of length\n toeLength: footLength * 0.3, // Toes are ~30% of foot length\n heelLength: footLength * 0.7, // Heel area is ~70% of foot length\n ankleRadius: footLength * 0.12, // Ankle joint radius\n };\n}\n\n// ============================================================================\n// CLOTHING DIMENSIONS\n// ============================================================================\n\n/**\n * Clothing fit scale multipliers.\n * Applied on top of body dimensions.\n *\n * @korean 의류핏배율\n */\nexport const CLOTHING_FIT_SCALES = {\n tight: 1.02, // 2% larger than body\n fitted: 1.06, // 6% larger than body\n regular: 1.1, // 10% larger than body\n loose: 1.18, // 18% larger than body\n oversized: 1.3, // 30% larger than body\n} as const;\n\nexport type ClothingFit = keyof typeof CLOTHING_FIT_SCALES;\n\n/**\n * Calculate clothing dimensions in meters based on physical attributes.\n *\n * @param attrs - Physical attributes\n * @param fit - Clothing fit type\n * @returns Clothing dimensions in meters\n * @korean 의류치수계산\n */\nexport function calculateClothingDimensions(\n attrs: PhysicalAttributes,\n fit: ClothingFit = \"fitted\",\n): {\n readonly torsoWidth: number;\n readonly torsoDepth: number;\n readonly torsoLength: number;\n readonly armThickness: number;\n readonly legThickness: number;\n readonly fitScale: number;\n} {\n const phys = getPhysicalMeters(attrs);\n const fitScale = CLOTHING_FIT_SCALES[fit];\n\n // Calculate body thickness factor based on mass distribution\n const bmi = attrs.weight / Math.pow(phys.totalHeight, 2);\n const thicknessFactor = Math.min(1.3, Math.max(0.85, bmi / 22)); // Normalize around BMI 22\n\n return {\n torsoWidth: phys.shoulderWidth * fitScale,\n torsoDepth: phys.shoulderWidth * 0.45 * fitScale * thicknessFactor,\n torsoLength: phys.torsoLength * fitScale,\n armThickness: phys.armLength * 0.12 * fitScale * thicknessFactor,\n legThickness: phys.legLength * 0.14 * fitScale * thicknessFactor,\n fitScale,\n };\n}\n\n// ============================================================================\n// VISUAL SCALING (for render-time adjustments)\n// ============================================================================\n\n/**\n * Visual amplification factor for better screen readability.\n * This is applied uniformly to make characters more visible in the arena.\n * Does NOT affect physics or reach calculations.\n *\n * Value of 1.0 = realistic scale (1.8m character in 10m arena)\n * Value of 1.15 = 15% larger for better visibility\n *\n * @korean 시각적증폭계수\n */\nexport const VISUAL_SCALE_FACTOR = 1.0;\n\n/**\n * Calculate the uniform scale factor to apply to a character group.\n * This ensures the character's total height matches their physical height.\n *\n * @param attrs - Physical attributes\n * @param baseSkeletonHeight - Height of the base skeleton rig (before scaling)\n * @returns Scale factor to apply to character group\n * @korean 캐릭터스케일계산\n */\nexport function calculateCharacterScale(\n attrs: PhysicalAttributes,\n baseSkeletonHeight: number = 1.8, // Default skeleton is ~1.8m\n): number {\n const targetHeight = cmToMeters(attrs.totalHeight);\n const scale = targetHeight / baseSkeletonHeight;\n return scale * VISUAL_SCALE_FACTOR;\n}\n"],"mappings":";;;;;;;AAyCA,IAAa,mBAAmB;;CAE9B,mBAAmB;;CAEnB,mBAAmB;;CAEnB,oBAAoB;;CAEpB,kBAAkB;;CAElB,iBAAiB;;CAEjB,gBAAgB;;CAEhB,iBAAiB;;CAEjB,eAAe;;CAEf,mBAAmB;;CAEnB,mBAAmB;;CAEnB,sBAAsB;;CAEtB,iBAAiB;;CAEjB,mBAAmB;CACpB;;;;;;;AAQD,IAAa,qBAAqB;CAEhC,MAAM;CACN,MAAM;CAGN,YAAY;CACZ,aAAa;CACb,YAAY;CAGZ,UAAU;CAIV,UAAU;CACV,OAAO;CACP,SAAS;CACT,OAAO;CACP,MAAM;CAGN,QAAQ;CACR,KAAK;CAIL,OAAO;CACP,MAAM;CACN,MAAM;CACN,MAAM;CACP;;;;;;;;AAaD,SAAgB,WAAW,IAAoB;CAC7C,OAAO,KAAK;;;;;;;;;;;;AA4Cd,SAAgB,4BAA4B,OAmB1C;CACA,MAAM,eAAe,WAAW,MAAM,YAAY;CAClD,MAAM,aAAa,WAAW,MAAM,UAAU;CAC9C,MAAM,aAAa,WAAW,MAAM,UAAU;CAC9C,MAAM,iBAAiB,WAAW,MAAM,cAAc;CACtD,MAAM,cAAc,WAAW,MAAM,WAAW;CAChD,MAAM,YAAY,WAAW,MAAM,SAAS;CAC5C,MAAM,eAAe,WAAW,MAAM,YAAY;CAGlD,MAAM,iBAAiB,aAAa,iBAAiB;CACrD,MAAM,gBAAgB,aAAa,iBAAiB;CAGpD,MAAM,cAAc,aAAa,iBAAiB;CAClD,MAAM,aAAa,aAAa,iBAAiB;CAGjD,MAAM,eAAe,eAAe;CAEpC,OAAO;EAEL,cAAc,aAAa;EAG3B,YAAY;EACZ,aAAa;EACb,YAAY;EAGZ,MAAM,eAAe,eAAe,mBAAmB;EACvD,MAAM,aAAa,eAAe,mBAAmB;EAGrD,UAAU,iBAAiB;EAG3B,UAAU;EACV,OAAO,eAAe,mBAAmB;EACzC,SAAS;EACT,OAAO,eAAe,mBAAmB;EACzC,MAAM,eAAe,mBAAmB;EAGxC,KAAK,eAAe,mBAAmB;EAGvC,OAAO;EACP,MAAM,eAAe,mBAAmB;EACxC,MAAM;EACN,MAAM;EAGN,aAAa;EACd"}
|
|
1
|
+
{"version":3,"file":"characterScaling.js","names":[],"sources":["../../src/utils/characterScaling.ts"],"sourcesContent":["/**\n * Unified character scaling system for Black Trigram.\n *\n * **Korean**: 통합 캐릭터 스케일링 시스템\n *\n * This module provides a physics-first approach to character scaling where:\n * - All measurements are in METERS for consistency with arena and physics\n * - Physical attributes (in cm) are converted to meters\n * - All body part dimensions derive from physical attributes\n * - Scale factors are based on anatomical proportions\n *\n * ## Design Philosophy\n *\n * The scaling system ensures that:\n * 1. Characters appear correctly sized in arenas (6-14 meters wide)\n * 2. Reach calculations (arm/leg length) match visual representation\n * 3. Muscle and clothing scale proportionally with body dimensions\n * 4. Combat animations use realistic distances\n *\n * ## Unit Conventions\n * - Physical attributes: centimeters (totalHeight: 180 = 180cm)\n * - World space: meters (arena width: 10 = 10m)\n * - Character model: meters (height: 1.80 = 1.80m)\n *\n * @module utils/characterScaling\n * @category Character Rendering\n * @korean 캐릭터스케일링\n */\n\nimport type { PhysicalAttributes } from \"@/types\";\n\n// ============================================================================\n// REFERENCE ANATOMY (Based on average Korean adult male proportions)\n// ============================================================================\n\n/**\n * Reference body proportions as fractions of total height.\n * Based on canonical human anatomy (8-head proportion system).\n *\n * @korean 기준신체비율\n */\nexport const BODY_PROPORTIONS = {\n /** Head is ~1/8 of total height */\n HEAD_HEIGHT_RATIO: 0.125,\n /** Neck is ~0.055 of total height */\n NECK_HEIGHT_RATIO: 0.055,\n /** Torso (spine) is ~0.33 of total height */\n TORSO_HEIGHT_RATIO: 0.33,\n /** Leg (hip to floor) is ~0.48 of total height */\n LEG_HEIGHT_RATIO: 0.48,\n /** Upper leg (thigh) is ~55% of total leg length */\n THIGH_LEG_RATIO: 0.55,\n /** Lower leg (shin) is ~45% of total leg length */\n SHIN_LEG_RATIO: 0.45,\n /** Upper arm is ~55% of total arm length */\n UPPER_ARM_RATIO: 0.55,\n /** Forearm is ~45% of total arm length */\n FOREARM_RATIO: 0.45,\n /** Hand is ~0.11 of total height (or ~10.5% of total height) */\n HAND_HEIGHT_RATIO: 0.105,\n /** Foot length is ~15% of total height */\n FOOT_LENGTH_RATIO: 0.15,\n /** Shoulder width is typically 23-25% of height for males */\n SHOULDER_WIDTH_RATIO: 0.255,\n /** Hip width is typically 16-18% of height */\n HIP_WIDTH_RATIO: 0.17,\n /** Torso depth is typically 10-12% of height */\n TORSO_DEPTH_RATIO: 0.11,\n} as const;\n\n/**\n * Bone length as fraction of total height.\n * These are anatomically accurate proportions for skeletal rig creation.\n *\n * @korean 뼈길이비율\n */\nexport const BONE_HEIGHT_RATIOS = {\n // Head & Neck\n head: 0.125, // 22.5cm for 180cm person\n neck: 0.055, // 9.9cm for 180cm person\n\n // Spine (split into 3 segments)\n spineUpper: 0.11, // Upper thoracic\n spineMiddle: 0.11, // Middle thoracic\n spineLower: 0.11, // Lumbar\n\n // Shoulders (clavicle + shoulder joint)\n shoulder: 0.055, // Clavicle length\n\n // Arms (based on arm length from physical attributes)\n // Actual values calculated from physicalAttributes.armLength\n upperArm: 0.21, // ~38cm for 180cm (55% of arm)\n elbow: 0.025, // Elbow joint\n forearm: 0.17, // ~31cm for 180cm (45% of arm)\n wrist: 0.025, // Wrist joint\n hand: 0.105, // ~19cm for 180cm\n\n // Pelvis & Hips\n pelvis: 0.08, // Pelvis height\n hip: 0.05, // Hip joint\n\n // Legs (based on leg length from physical attributes)\n // Actual values calculated from physicalAttributes.legLength\n thigh: 0.265, // ~48cm for 180cm (55% of leg)\n knee: 0.025, // Knee joint\n shin: 0.22, // ~40cm for 180cm (45% of leg)\n foot: 0.05, // Ankle to ground\n} as const;\n\n// ============================================================================\n// CONVERSION UTILITIES\n// ============================================================================\n\n/**\n * Convert centimeters to meters.\n *\n * @param cm - Value in centimeters\n * @returns Value in meters\n * @korean cm를미터로\n */\nexport function cmToMeters(cm: number): number {\n return cm / 100;\n}\n\n/**\n * Convert physical attributes from centimeters to meters.\n *\n * @param attrs - Physical attributes in centimeters\n * @returns Object with key measurements in meters\n * @korean 신체속성미터변환\n */\nexport function getPhysicalMeters(attrs: PhysicalAttributes): {\n readonly totalHeight: number;\n readonly armLength: number;\n readonly legLength: number;\n readonly shoulderWidth: number;\n readonly torsoLength: number;\n readonly headSize: number;\n readonly neckLength: number;\n} {\n return {\n totalHeight: cmToMeters(attrs.totalHeight),\n armLength: cmToMeters(attrs.armLength),\n legLength: cmToMeters(attrs.legLength),\n shoulderWidth: cmToMeters(attrs.shoulderWidth),\n torsoLength: cmToMeters(attrs.torsoLength),\n headSize: cmToMeters(attrs.headSize),\n neckLength: cmToMeters(attrs.neckLength),\n };\n}\n\n// ============================================================================\n// SKELETON DIMENSIONS\n// ============================================================================\n\n/**\n * Calculate skeleton bone dimensions in meters based on physical attributes.\n *\n * Returns actual bone lengths that should be used in SkeletonRig creation.\n * All values are in meters and anatomically proportioned.\n *\n * @param attrs - Physical attributes (in centimeters)\n * @returns Bone dimensions in meters\n * @korean 골격치수계산\n */\nexport function calculateSkeletonDimensions(attrs: PhysicalAttributes): {\n readonly pelvisHeight: number;\n readonly spineLower: number;\n readonly spineMiddle: number;\n readonly spineUpper: number;\n readonly neck: number;\n readonly head: number;\n readonly shoulder: number;\n readonly upperArm: number;\n readonly elbow: number;\n readonly forearm: number;\n readonly wrist: number;\n readonly hand: number;\n readonly hip: number;\n readonly thigh: number;\n readonly knee: number;\n readonly shin: number;\n readonly foot: number;\n readonly totalHeight: number;\n} {\n const totalHeightM = cmToMeters(attrs.totalHeight);\n const armLengthM = cmToMeters(attrs.armLength);\n const legLengthM = cmToMeters(attrs.legLength);\n const shoulderWidthM = cmToMeters(attrs.shoulderWidth);\n const neckLengthM = cmToMeters(attrs.neckLength);\n const headSizeM = cmToMeters(attrs.headSize);\n const torsoLengthM = cmToMeters(attrs.torsoLength);\n\n // Calculate arm segments based on actual arm length\n const upperArmLength = armLengthM * BODY_PROPORTIONS.UPPER_ARM_RATIO;\n const forearmLength = armLengthM * BODY_PROPORTIONS.FOREARM_RATIO;\n\n // Calculate leg segments based on actual leg length\n const thighLength = legLengthM * BODY_PROPORTIONS.THIGH_LEG_RATIO;\n const shinLength = legLengthM * BODY_PROPORTIONS.SHIN_LEG_RATIO;\n\n // Spine segments (divide torso into 3 parts)\n const spineSegment = torsoLengthM / 3;\n\n return {\n // Pelvis height = leg length + foot height (to have feet at Y=0)\n pelvisHeight: legLengthM + 0.05, // 0.05m for foot/ankle height\n\n // Spine segments\n spineLower: spineSegment,\n spineMiddle: spineSegment,\n spineUpper: spineSegment,\n\n // Head & Neck (use actual values if available, or proportional)\n neck: neckLengthM || totalHeightM * BONE_HEIGHT_RATIOS.neck,\n head: headSizeM || totalHeightM * BONE_HEIGHT_RATIOS.head,\n\n // Shoulders\n shoulder: shoulderWidthM * 0.25, // Clavicle is ~25% of shoulder width\n\n // Arms\n upperArm: upperArmLength,\n elbow: totalHeightM * BONE_HEIGHT_RATIOS.elbow,\n forearm: forearmLength,\n wrist: totalHeightM * BONE_HEIGHT_RATIOS.wrist,\n hand: totalHeightM * BONE_HEIGHT_RATIOS.hand,\n\n // Pelvis & Hip\n hip: totalHeightM * BONE_HEIGHT_RATIOS.hip,\n\n // Legs\n thigh: thighLength,\n knee: totalHeightM * BONE_HEIGHT_RATIOS.knee,\n shin: shinLength,\n foot: 0.05, // Ankle height\n\n // Total for validation\n totalHeight: totalHeightM,\n };\n}\n\n// ============================================================================\n// MUSCLE DIMENSIONS\n// ============================================================================\n\n/**\n * Base muscle dimensions as fractions of associated bone length.\n * These are proportional values that scale with bone size.\n *\n * @korean 근육비율\n */\nexport const MUSCLE_BONE_RATIOS = {\n // Shoulder muscles (deltoid)\n shoulder: {\n radiusFactor: 0.4, // 40% of shoulder bone length\n lengthFactor: 0.8, // 80% of shoulder bone length\n },\n\n // Bicep (front of upper arm)\n bicep: {\n radiusFactor: 0.35, // 35% of upper arm length\n lengthFactor: 0.7, // 70% of upper arm length\n },\n\n // Tricep (back of upper arm)\n tricep: {\n radiusFactor: 0.3, // 30% of upper arm length\n lengthFactor: 0.65, // 65% of upper arm length\n },\n\n // Forearm\n forearm: {\n radiusFactor: 0.3, // 30% of forearm length\n lengthFactor: 0.8, // 80% of forearm length\n },\n\n // Pectorals (chest)\n pectorals: {\n radiusFactor: 0.5, // 50% of torso segment\n lengthFactor: 0.7, // 70% of torso segment\n },\n\n // Core/Abs\n core: {\n radiusFactor: 0.45, // 45% of torso segment\n lengthFactor: 0.9, // 90% of torso segment\n },\n\n // Abs (lower core)\n abs: {\n radiusFactor: 0.4, // 40% of torso segment\n lengthFactor: 0.8, // 80% of torso segment\n },\n\n // Obliques (side core)\n obliques: {\n radiusFactor: 0.35, // 35% of torso segment\n lengthFactor: 0.75, // 75% of torso segment\n },\n\n // Glutes\n glute: {\n radiusFactor: 0.5, // 50% of hip/pelvis width\n lengthFactor: 0.6, // 60% of hip area\n },\n\n // Quadriceps (front of thigh)\n quad: {\n radiusFactor: 0.35, // 35% of thigh length\n lengthFactor: 0.8, // 80% of thigh length\n },\n\n // Hamstring (back of thigh)\n hamstring: {\n radiusFactor: 0.3, // 30% of thigh length\n lengthFactor: 0.75, // 75% of thigh length\n },\n\n // Calf\n calf: {\n radiusFactor: 0.28, // 28% of shin length\n lengthFactor: 0.6, // 60% of shin length\n },\n} as const;\n\n/**\n * Calculate muscle dimensions in meters based on skeleton dimensions.\n *\n * @param skeletonDims - Skeleton dimensions from calculateSkeletonDimensions\n * @param muscleMass - Muscle mass in kg for scaling\n * @param referenceMass - Reference muscle mass (default 35kg)\n * @returns Muscle dimensions in meters\n * @korean 근육치수계산\n */\nexport function calculateMuscleDimensions(\n skeletonDims: ReturnType<typeof calculateSkeletonDimensions>,\n muscleMass: number,\n referenceMass: number = 35,\n): Record<\n string,\n {\n readonly radius: number;\n readonly length: number;\n readonly scaleFactor: number;\n }\n> {\n // Muscle scale factor based on muscle mass\n // Uses square root for more natural visual scaling\n const massRatio = muscleMass / referenceMass;\n const scaleFactor = Math.sqrt(massRatio);\n\n return {\n shoulder: {\n radius:\n skeletonDims.shoulder *\n MUSCLE_BONE_RATIOS.shoulder.radiusFactor *\n scaleFactor,\n length: skeletonDims.shoulder * MUSCLE_BONE_RATIOS.shoulder.lengthFactor,\n scaleFactor,\n },\n bicep: {\n radius:\n skeletonDims.upperArm *\n MUSCLE_BONE_RATIOS.bicep.radiusFactor *\n scaleFactor,\n length: skeletonDims.upperArm * MUSCLE_BONE_RATIOS.bicep.lengthFactor,\n scaleFactor,\n },\n tricep: {\n radius:\n skeletonDims.upperArm *\n MUSCLE_BONE_RATIOS.tricep.radiusFactor *\n scaleFactor,\n length: skeletonDims.upperArm * MUSCLE_BONE_RATIOS.tricep.lengthFactor,\n scaleFactor,\n },\n forearm: {\n radius:\n skeletonDims.forearm *\n MUSCLE_BONE_RATIOS.forearm.radiusFactor *\n scaleFactor,\n length: skeletonDims.forearm * MUSCLE_BONE_RATIOS.forearm.lengthFactor,\n scaleFactor,\n },\n pectorals: {\n radius:\n skeletonDims.spineMiddle *\n MUSCLE_BONE_RATIOS.pectorals.radiusFactor *\n scaleFactor,\n length:\n skeletonDims.spineMiddle * MUSCLE_BONE_RATIOS.pectorals.lengthFactor,\n scaleFactor,\n },\n core: {\n radius:\n skeletonDims.spineMiddle *\n MUSCLE_BONE_RATIOS.core.radiusFactor *\n scaleFactor,\n length: skeletonDims.spineMiddle * MUSCLE_BONE_RATIOS.core.lengthFactor,\n scaleFactor,\n },\n abs: {\n radius:\n skeletonDims.spineLower *\n MUSCLE_BONE_RATIOS.abs.radiusFactor *\n scaleFactor,\n length: skeletonDims.spineLower * MUSCLE_BONE_RATIOS.abs.lengthFactor,\n scaleFactor,\n },\n obliques: {\n radius:\n skeletonDims.spineLower *\n MUSCLE_BONE_RATIOS.obliques.radiusFactor *\n scaleFactor,\n length:\n skeletonDims.spineLower * MUSCLE_BONE_RATIOS.obliques.lengthFactor,\n scaleFactor,\n },\n glute: {\n radius:\n skeletonDims.hip * MUSCLE_BONE_RATIOS.glute.radiusFactor * scaleFactor,\n length: skeletonDims.hip * MUSCLE_BONE_RATIOS.glute.lengthFactor,\n scaleFactor,\n },\n quad: {\n radius:\n skeletonDims.thigh * MUSCLE_BONE_RATIOS.quad.radiusFactor * scaleFactor,\n length: skeletonDims.thigh * MUSCLE_BONE_RATIOS.quad.lengthFactor,\n scaleFactor,\n },\n hamstring: {\n radius:\n skeletonDims.thigh *\n MUSCLE_BONE_RATIOS.hamstring.radiusFactor *\n scaleFactor,\n length: skeletonDims.thigh * MUSCLE_BONE_RATIOS.hamstring.lengthFactor,\n scaleFactor,\n },\n calf: {\n radius:\n skeletonDims.shin * MUSCLE_BONE_RATIOS.calf.radiusFactor * scaleFactor,\n length: skeletonDims.shin * MUSCLE_BONE_RATIOS.calf.lengthFactor,\n scaleFactor,\n },\n };\n}\n\n// ============================================================================\n// HAND & FOOT DIMENSIONS\n// ============================================================================\n\n/**\n * Calculate hand dimensions in meters based on physical attributes.\n *\n * @param attrs - Physical attributes\n * @returns Hand dimensions in meters\n * @korean 손치수계산\n */\nexport function calculateHandDimensions(attrs: PhysicalAttributes): {\n readonly palmLength: number;\n readonly palmWidth: number;\n readonly palmDepth: number;\n readonly fingerLengths: {\n readonly thumb: number;\n readonly index: number;\n readonly middle: number;\n readonly ring: number;\n readonly pinky: number;\n };\n readonly fingerWidths: {\n readonly thumb: number;\n readonly index: number;\n readonly middle: number;\n readonly ring: number;\n readonly pinky: number;\n };\n} {\n const totalHeightM = cmToMeters(attrs.totalHeight);\n\n // Hand is ~10.5% of total height, palm is ~55% of hand\n const handLength = totalHeightM * BODY_PROPORTIONS.HAND_HEIGHT_RATIO;\n const palmLength = handLength * 0.55;\n const fingerBaseLength = handLength * 0.45; // Fingers are 45% of hand\n\n // Palm width is ~85% of palm length\n const palmWidth = palmLength * 0.85;\n const palmDepth = palmLength * 0.25;\n\n // Finger lengths relative to middle finger (longest)\n const middleFingerLength = fingerBaseLength;\n\n return {\n palmLength,\n palmWidth,\n palmDepth,\n fingerLengths: {\n thumb: middleFingerLength * 0.65, // Thumb is ~65% of middle finger\n index: middleFingerLength * 0.95, // Index is ~95% of middle finger\n middle: middleFingerLength, // Middle is longest\n ring: middleFingerLength * 0.94, // Ring is ~94% of middle finger\n pinky: middleFingerLength * 0.75, // Pinky is ~75% of middle finger\n },\n fingerWidths: {\n thumb: palmWidth * 0.2, // Thumb is thickest\n index: palmWidth * 0.16,\n middle: palmWidth * 0.16,\n ring: palmWidth * 0.15,\n pinky: palmWidth * 0.13, // Pinky is thinnest\n },\n };\n}\n\n/**\n * Calculate foot dimensions in meters based on physical attributes.\n *\n * @param attrs - Physical attributes\n * @returns Foot dimensions in meters\n * @korean 발치수계산\n */\nexport function calculateFootDimensions(attrs: PhysicalAttributes): {\n readonly footLength: number;\n readonly footWidth: number;\n readonly footHeight: number;\n readonly toeLength: number;\n readonly heelLength: number;\n readonly ankleRadius: number;\n} {\n const totalHeightM = cmToMeters(attrs.totalHeight);\n\n // Foot length is ~15% of total height\n const footLength = totalHeightM * BODY_PROPORTIONS.FOOT_LENGTH_RATIO;\n\n return {\n footLength,\n footWidth: footLength * 0.38, // Foot width is ~38% of length\n footHeight: footLength * 0.3, // Foot height at ankle is ~30% of length\n toeLength: footLength * 0.3, // Toes are ~30% of foot length\n heelLength: footLength * 0.7, // Heel area is ~70% of foot length\n ankleRadius: footLength * 0.12, // Ankle joint radius\n };\n}\n\n// ============================================================================\n// CLOTHING DIMENSIONS\n// ============================================================================\n\n/**\n * Clothing fit scale multipliers.\n * Applied on top of body dimensions.\n *\n * @korean 의류핏배율\n */\nexport const CLOTHING_FIT_SCALES = {\n tight: 1.02, // 2% larger than body\n fitted: 1.06, // 6% larger than body\n regular: 1.1, // 10% larger than body\n loose: 1.18, // 18% larger than body\n oversized: 1.3, // 30% larger than body\n} as const;\n\nexport type ClothingFit = keyof typeof CLOTHING_FIT_SCALES;\n\n/**\n * Calculate clothing dimensions in meters based on physical attributes.\n *\n * @param attrs - Physical attributes\n * @param fit - Clothing fit type\n * @returns Clothing dimensions in meters\n * @korean 의류치수계산\n */\nexport function calculateClothingDimensions(\n attrs: PhysicalAttributes,\n fit: ClothingFit = \"fitted\",\n): {\n readonly torsoWidth: number;\n readonly torsoDepth: number;\n readonly torsoLength: number;\n readonly armThickness: number;\n readonly legThickness: number;\n readonly fitScale: number;\n} {\n const phys = getPhysicalMeters(attrs);\n const fitScale = CLOTHING_FIT_SCALES[fit];\n\n // Calculate body thickness factor based on mass distribution\n const bmi = attrs.weight / Math.pow(phys.totalHeight, 2);\n const thicknessFactor = Math.min(1.3, Math.max(0.85, bmi / 22)); // Normalize around BMI 22\n\n return {\n torsoWidth: phys.shoulderWidth * fitScale,\n torsoDepth: phys.shoulderWidth * 0.45 * fitScale * thicknessFactor,\n torsoLength: phys.torsoLength * fitScale,\n armThickness: phys.armLength * 0.12 * fitScale * thicknessFactor,\n legThickness: phys.legLength * 0.14 * fitScale * thicknessFactor,\n fitScale,\n };\n}\n\n// ============================================================================\n// VISUAL SCALING (for render-time adjustments)\n// ============================================================================\n\n/**\n * Visual amplification factor for better screen readability.\n * This is applied uniformly to make characters more visible in the arena.\n * Does NOT affect physics or reach calculations.\n *\n * Value of 1.0 = realistic scale (1.8m character in 10m arena)\n * Value of 1.15 = 15% larger for better visibility\n *\n * @korean 시각적증폭계수\n */\nexport const VISUAL_SCALE_FACTOR = 1.0;\n\n/**\n * Calculate the uniform scale factor to apply to a character group.\n * This ensures the character's total height matches their physical height.\n *\n * @param attrs - Physical attributes\n * @param baseSkeletonHeight - Height of the base skeleton rig (before scaling)\n * @returns Scale factor to apply to character group\n * @korean 캐릭터스케일계산\n */\nexport function calculateCharacterScale(\n attrs: PhysicalAttributes,\n baseSkeletonHeight: number = 1.8, // Default skeleton is ~1.8m\n): number {\n const targetHeight = cmToMeters(attrs.totalHeight);\n const scale = targetHeight / baseSkeletonHeight;\n return scale * VISUAL_SCALE_FACTOR;\n}\n"],"mappings":";;;;;;;AAyCA,IAAa,mBAAmB;;CAE9B,mBAAmB;;CAEnB,mBAAmB;;CAEnB,oBAAoB;;CAEpB,kBAAkB;;CAElB,iBAAiB;;CAEjB,gBAAgB;;CAEhB,iBAAiB;;CAEjB,eAAe;;CAEf,mBAAmB;;CAEnB,mBAAmB;;CAEnB,sBAAsB;;CAEtB,iBAAiB;;CAEjB,mBAAmB;AACrB;;;;;;;AAQA,IAAa,qBAAqB;CAEhC,MAAM;CACN,MAAM;CAGN,YAAY;CACZ,aAAa;CACb,YAAY;CAGZ,UAAU;CAIV,UAAU;CACV,OAAO;CACP,SAAS;CACT,OAAO;CACP,MAAM;CAGN,QAAQ;CACR,KAAK;CAIL,OAAO;CACP,MAAM;CACN,MAAM;CACN,MAAM;AACR;;;;;;;;AAaA,SAAgB,WAAW,IAAoB;CAC7C,OAAO,KAAK;AACd;;;;;;;;;;;AA2CA,SAAgB,4BAA4B,OAmB1C;CACA,MAAM,eAAe,WAAW,MAAM,WAAW;CACjD,MAAM,aAAa,WAAW,MAAM,SAAS;CAC7C,MAAM,aAAa,WAAW,MAAM,SAAS;CAC7C,MAAM,iBAAiB,WAAW,MAAM,aAAa;CACrD,MAAM,cAAc,WAAW,MAAM,UAAU;CAC/C,MAAM,YAAY,WAAW,MAAM,QAAQ;CAC3C,MAAM,eAAe,WAAW,MAAM,WAAW;CAGjD,MAAM,iBAAiB,aAAa,iBAAiB;CACrD,MAAM,gBAAgB,aAAa,iBAAiB;CAGpD,MAAM,cAAc,aAAa,iBAAiB;CAClD,MAAM,aAAa,aAAa,iBAAiB;CAGjD,MAAM,eAAe,eAAe;CAEpC,OAAO;EAEL,cAAc,aAAa;EAG3B,YAAY;EACZ,aAAa;EACb,YAAY;EAGZ,MAAM,eAAe,eAAe,mBAAmB;EACvD,MAAM,aAAa,eAAe,mBAAmB;EAGrD,UAAU,iBAAiB;EAG3B,UAAU;EACV,OAAO,eAAe,mBAAmB;EACzC,SAAS;EACT,OAAO,eAAe,mBAAmB;EACzC,MAAM,eAAe,mBAAmB;EAGxC,KAAK,eAAe,mBAAmB;EAGvC,OAAO;EACP,MAAM,eAAe,mBAAmB;EACxC,MAAM;EACN,MAAM;EAGN,aAAa;CACf;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"colorHelpers.js","names":[],"sources":["../../src/utils/colorHelpers.ts"],"sourcesContent":["/**\n * Color utility functions for Three.js components\n * \n * @module utils/colorHelpers\n * @category Utilities\n */\n\nimport { colorUtils } from '../types/constants/colors';\n\n/**\n * Convert a numeric color value to a hex string with # prefix\n * \n * @param color - Numeric color value (e.g., 0xff6b6b)\n * @returns Hex color string with # prefix (e.g., \"#ff6b6b\")\n * @korean 색상변환\n * \n * @example\n * ```typescript\n * toHexColor(0xff6b6b) // \"#ff6b6b\"\n * toHexColor(KOREAN_COLORS.PRIMARY_CYAN) // \"#00ffff\"\n * ```\n */\nexport function toHexColor(color: number): string {\n return `#${color.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Extract RGB components from hex color value\n * Re-export of colorUtils.hexToRgb for mobile component convenience\n * \n * @param color - Hex color value (e.g., 0x00ffff)\n * @returns RGB components as object with r, g, b properties (0-255)\n * @korean RGB추출\n * \n * @example\n * ```typescript\n * const { r, g, b } = getColorRGB(0x00ffff);\n * // { r: 0, g: 255, b: 255 }\n * \n * // Use in CSS rgba\n * const cssColor = `rgba(${r}, ${g}, ${b}, 0.8)`;\n * ```\n */\nexport const getColorRGB = colorUtils.hexToRgb;\n\n/**\n * Mix two hex colors with a given ratio\n * \n * Performs linear interpolation between two colors using bit-shifting operations.\n * Useful for damage visualization, bruising effects, and color transitions.\n * \n * @param color1 - First color (hex value, e.g., 0xffdbac)\n * @param color2 - Second color (hex value, e.g., 0x663366)\n * @param ratio - Mix ratio (0-1, where 0 = color1, 1 = color2)\n * @returns Mixed color as hex value\n * @korean 색상혼합\n * \n * @example\n * ```typescript\n * // Mix skin color with bruise color at 30%\n * const skinColor = 0xffdbac;\n * const bruiseColor = 0x663366;\n * const damaged = mixColors(skinColor, bruiseColor, 0.3);\n * \n * // Gradually transition between colors\n * const transitionColor = mixColors(startColor, endColor, progress);\n * ```\n */\nexport function mixColors(color1: number, color2: number, ratio: number): number {\n // Extract RGB components from color1\n const r1 = (color1 >> 16) & 0xff;\n const g1 = (color1 >> 8) & 0xff;\n const b1 = color1 & 0xff;\n \n // Extract RGB components from color2\n const r2 = (color2 >> 16) & 0xff;\n const g2 = (color2 >> 8) & 0xff;\n const b2 = color2 & 0xff;\n \n // Linear interpolation for each channel\n const r = Math.floor(r1 * (1 - ratio) + r2 * ratio);\n const g = Math.floor(g1 * (1 - ratio) + g2 * ratio);\n const b = Math.floor(b1 * (1 - ratio) + b2 * ratio);\n \n // Combine back into hex value\n return (r << 16) | (g << 8) | b;\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,WAAW,OAAuB;CAChD,OAAO,IAAI,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"colorHelpers.js","names":[],"sources":["../../src/utils/colorHelpers.ts"],"sourcesContent":["/**\n * Color utility functions for Three.js components\n * \n * @module utils/colorHelpers\n * @category Utilities\n */\n\nimport { colorUtils } from '../types/constants/colors';\n\n/**\n * Convert a numeric color value to a hex string with # prefix\n * \n * @param color - Numeric color value (e.g., 0xff6b6b)\n * @returns Hex color string with # prefix (e.g., \"#ff6b6b\")\n * @korean 색상변환\n * \n * @example\n * ```typescript\n * toHexColor(0xff6b6b) // \"#ff6b6b\"\n * toHexColor(KOREAN_COLORS.PRIMARY_CYAN) // \"#00ffff\"\n * ```\n */\nexport function toHexColor(color: number): string {\n return `#${color.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Extract RGB components from hex color value\n * Re-export of colorUtils.hexToRgb for mobile component convenience\n * \n * @param color - Hex color value (e.g., 0x00ffff)\n * @returns RGB components as object with r, g, b properties (0-255)\n * @korean RGB추출\n * \n * @example\n * ```typescript\n * const { r, g, b } = getColorRGB(0x00ffff);\n * // { r: 0, g: 255, b: 255 }\n * \n * // Use in CSS rgba\n * const cssColor = `rgba(${r}, ${g}, ${b}, 0.8)`;\n * ```\n */\nexport const getColorRGB = colorUtils.hexToRgb;\n\n/**\n * Mix two hex colors with a given ratio\n * \n * Performs linear interpolation between two colors using bit-shifting operations.\n * Useful for damage visualization, bruising effects, and color transitions.\n * \n * @param color1 - First color (hex value, e.g., 0xffdbac)\n * @param color2 - Second color (hex value, e.g., 0x663366)\n * @param ratio - Mix ratio (0-1, where 0 = color1, 1 = color2)\n * @returns Mixed color as hex value\n * @korean 색상혼합\n * \n * @example\n * ```typescript\n * // Mix skin color with bruise color at 30%\n * const skinColor = 0xffdbac;\n * const bruiseColor = 0x663366;\n * const damaged = mixColors(skinColor, bruiseColor, 0.3);\n * \n * // Gradually transition between colors\n * const transitionColor = mixColors(startColor, endColor, progress);\n * ```\n */\nexport function mixColors(color1: number, color2: number, ratio: number): number {\n // Extract RGB components from color1\n const r1 = (color1 >> 16) & 0xff;\n const g1 = (color1 >> 8) & 0xff;\n const b1 = color1 & 0xff;\n \n // Extract RGB components from color2\n const r2 = (color2 >> 16) & 0xff;\n const g2 = (color2 >> 8) & 0xff;\n const b2 = color2 & 0xff;\n \n // Linear interpolation for each channel\n const r = Math.floor(r1 * (1 - ratio) + r2 * ratio);\n const g = Math.floor(g1 * (1 - ratio) + g2 * ratio);\n const b = Math.floor(b1 * (1 - ratio) + b2 * ratio);\n \n // Combine back into hex value\n return (r << 16) | (g << 8) | b;\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,WAAW,OAAuB;CAChD,OAAO,IAAI,MAAM,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC/C;;;;;;;;;;;;;;;;;;AAmBA,IAAa,cAAc,WAAW;;;;;;;;;;;;;;;;;;;;;;;;AAyBtC,SAAgB,UAAU,QAAgB,QAAgB,OAAuB;CAE/E,MAAM,KAAM,UAAU,KAAM;CAC5B,MAAM,KAAM,UAAU,IAAK;CAC3B,MAAM,KAAK,SAAS;CAGpB,MAAM,KAAM,UAAU,KAAM;CAC5B,MAAM,KAAM,UAAU,IAAK;CAC3B,MAAM,KAAK,SAAS;CAGpB,MAAM,IAAI,KAAK,MAAM,MAAM,IAAI,SAAS,KAAK,KAAK;CAClD,MAAM,IAAI,KAAK,MAAM,MAAM,IAAI,SAAS,KAAK,KAAK;CAClD,MAAM,IAAI,KAAK,MAAM,MAAM,IAAI,SAAS,KAAK,KAAK;CAGlD,OAAQ,KAAK,KAAO,KAAK,IAAK;AAChC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"colorUtils.js","names":[],"sources":["../../src/utils/colorUtils.ts"],"sourcesContent":["import type { PlayerArchetype } from \"../types/common\";\nimport { KOREAN_COLORS } from \"../types/constants/colors\";\n\n/**\n * Convert RGB components to hex color\n */\nexport function rgbToHex(r: number, g: number, b: number): number {\n return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);\n}\n\n/**\n * Convert hex color to RGB components\n */\nexport function hexToRgb(hex: number): { r: number; g: number; b: number } {\n return {\n r: (hex >> 16) & 0xff,\n g: (hex >> 8) & 0xff,\n b: hex & 0xff,\n };\n}\n\n/**\n * Darken a color by a specified amount\n */\nexport function darkenColor(color: number, amount: number = 0.1): number {\n const { r, g, b } = hexToRgb(color);\n const factor = Math.max(0, 1 - amount);\n\n return rgbToHex(\n Math.floor(r * factor),\n Math.floor(g * factor),\n Math.floor(b * factor),\n );\n}\n\n/**\n * Lighten a color by a specified amount\n */\nexport function lightenColor(color: number, amount: number = 0.1): number {\n const { r, g, b } = hexToRgb(color);\n const factor = Math.min(1, amount);\n\n return rgbToHex(\n Math.min(255, Math.floor(r + (255 - r) * factor)),\n Math.min(255, Math.floor(g + (255 - g) * factor)),\n Math.min(255, Math.floor(b + (255 - b) * factor)),\n );\n}\n\n/**\n * Get archetype colors\n */\nexport function getArchetypeColors(archetype: PlayerArchetype): {\n primary: number;\n secondary: number;\n} {\n const colorMap: Record<\n PlayerArchetype,\n { primary: number; secondary: number }\n > = {\n musa: {\n primary: KOREAN_COLORS.ACCENT_GOLD,\n secondary: KOREAN_COLORS.SECONDARY_BROWN_DARK, // Fix: Use SECONDARY_BROWN_DARK\n },\n amsalja: {\n primary: KOREAN_COLORS.PRIMARY_CYAN, // Stealth assassin - cyan theme\n secondary: KOREAN_COLORS.KOREAN_BLACK,\n },\n hacker: {\n primary: KOREAN_COLORS.SECONDARY_PURPLE, // Cyber hacker - purple theme\n secondary: KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n },\n jeongbo_yowon: {\n primary: KOREAN_COLORS.ACCENT_BLUE, // Intelligence operative - blue tactical theme\n secondary: KOREAN_COLORS.UI_BACKGROUND_DARK,\n },\n jojik_pokryeokbae: {\n primary: KOREAN_COLORS.ACCENT_RED,\n secondary: KOREAN_COLORS.SECONDARY_BROWN_DARK, // Fix: Use SECONDARY_BROWN_DARK\n },\n };\n\n return (\n colorMap[archetype] || {\n primary: KOREAN_COLORS.WHITE_SOLID,\n secondary: KOREAN_COLORS.UI_STEEL_GRAY,\n }\n );\n}\n\n/**\n * Interpolate between two colors\n */\nexport function interpolateColor(\n color1: number,\n color2: number,\n factor: number,\n): number {\n const rgb1 = hexToRgb(color1);\n const rgb2 = hexToRgb(color2);\n\n const r = Math.floor(rgb1.r + (rgb2.r - rgb1.r) * factor);\n const g = Math.floor(rgb1.g + (rgb2.g - rgb1.g) * factor);\n const b = Math.floor(rgb1.b + (rgb2.b - rgb1.b) * factor);\n\n return rgbToHex(r, g, b);\n}\n\n/**\n * Get color with alpha\n */\nexport function getColorWithAlpha(color: number, alpha: number): number {\n return (Math.floor(alpha * 255) << 24) | color;\n}\n\n/**\n * Get contrast color (black or white) for given background\n */\nexport function getContrastColor(backgroundColor: number): number {\n const { r, g, b } = hexToRgb(backgroundColor);\n const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n return luminance > 0.5\n ? KOREAN_COLORS.BLACK_SOLID\n : KOREAN_COLORS.WHITE_SOLID;\n}\n\n/**\n * Get color based on health percentage\n */\nexport function getHealthColor(healthPercentage: number): number {\n if (healthPercentage > 0.6) {\n return KOREAN_COLORS.POSITIVE_GREEN;\n } else if (healthPercentage > 0.3) {\n return KOREAN_COLORS.SECONDARY_YELLOW;\n } else {\n return KOREAN_COLORS.ACCENT_RED;\n }\n}\n\n/**\n * Convert hex color to RGBA string for CSS\n * @param hex - Hex color value (e.g., 0x1a1a1a)\n * @param alpha - Alpha value between 0 and 1 (default: 1)\n * @returns RGBA string (e.g., \"rgba(26, 26, 26, 0.96)\")\n */\nexport function hexToRgbaString(hex: number, alpha: number = 1): string {\n const { r, g, b } = hexToRgb(hex);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n/**\n * Convert numeric color to hex string for CSS\n * @param color - Numeric color value (e.g., 0x00ffff)\n * @returns Hex color string without # prefix (e.g., \"00ffff\")\n * @example\n * toHex(0x00ffff) // \"00ffff\"\n * toHex(KOREAN_COLORS.PRIMARY_CYAN) // \"00ffff\"\n */\nexport function toHex(color: number): string {\n return color.toString(16).padStart(6, \"0\");\n}\n\n/**\n * Convert numeric color to CSS hex string with # prefix\n * @param color - Numeric color value (e.g., 0x00ffff)\n * @returns CSS hex color string (e.g., \"#00ffff\")\n * @example\n * hexColorToCSS(0x00ffff) // \"#00ffff\"\n * hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN) // \"#00ffff\"\n */\nexport function hexColorToCSS(color: number): string {\n return `#${color.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Skin tone definitions for archetype visual differentiation\n *\n * **Korean**: 원형별 피부색 (Archetype Skin Tones)\n *\n * Each archetype has a unique skin tone that reflects their\n * background and lifestyle:\n * - MUSA: Healthy tan from outdoor training\n * - AMSALJA: Pale from stealth operations in darkness\n * - HACKER: Slightly pale from indoor tech work\n * - JEONGBO_YOWON: Natural healthy tone\n * - JOJIK_POKRYEOKBAE: Weathered from street life\n *\n * @korean 원형피부색\n */\nconst ARCHETYPE_SKIN_TONES: Record<PlayerArchetype, number> = {\n musa: 0xdbb896, // Healthy tan - outdoor military training\n amsalja: 0xe8d4c4, // Pale complexion - stealth operative\n hacker: 0xf0d8c8, // Light skin - indoor tech work\n jeongbo_yowon: 0xe0c4a8, // Natural healthy tone - government operative\n jojik_pokryeokbae: 0xc8a882, // Weathered tan - street fighter\n};\n\n/**\n * Get skin tone color for archetype\n *\n * **Korean**: 원형 피부색 가져오기 (Get Archetype Skin Tone)\n *\n * Returns the skin tone color appropriate for the character's\n * background and lifestyle. Used for body, face, and exposed skin.\n *\n * @param archetype - Player archetype\n * @returns Skin tone color as hex number\n *\n * @example\n * ```typescript\n * const skinColor = getArchetypeSkinTone(PlayerArchetype.MUSA);\n * // Returns: 0xdbb896 (healthy tan)\n * ```\n *\n * @korean 원형피부색가져오기\n */\nexport function getArchetypeSkinTone(archetype: PlayerArchetype): number {\n return ARCHETYPE_SKIN_TONES[archetype] ?? KOREAN_COLORS.SKIN_TONE;\n}\n\n/**\n * Get extended archetype visual properties\n *\n * **Korean**: 원형 시각 속성 (Archetype Visual Properties)\n *\n * Returns comprehensive visual properties for an archetype including:\n * - Primary and secondary colors (from clothing theme)\n * - Skin tone color\n * - Emissive accent color for glow effects\n * - Material properties (metalness, roughness)\n *\n * @param archetype - Player archetype\n * @returns Extended visual properties object\n *\n * @korean 원형시각속성\n */\nexport function getArchetypeVisualProperties(archetype: PlayerArchetype): {\n readonly primary: number;\n readonly secondary: number;\n readonly skinTone: number;\n readonly emissive: number;\n readonly emissiveIntensity: number;\n} {\n const colors = getArchetypeColors(archetype);\n const skinTone = getArchetypeSkinTone(archetype);\n\n // Emissive colors based on archetype theme\n const emissiveMap: Record<\n PlayerArchetype,\n { color: number; intensity: number }\n > = {\n musa: { color: KOREAN_COLORS.ACCENT_GOLD, intensity: 0.1 },\n amsalja: { color: KOREAN_COLORS.PRIMARY_CYAN, intensity: 0.3 },\n hacker: { color: KOREAN_COLORS.SECONDARY_PURPLE, intensity: 0.25 },\n jeongbo_yowon: { color: KOREAN_COLORS.ACCENT_BLUE, intensity: 0.15 },\n jojik_pokryeokbae: { color: KOREAN_COLORS.ACCENT_RED, intensity: 0.2 },\n };\n\n const emissiveProps = emissiveMap[archetype] ?? {\n color: 0x000000,\n intensity: 0,\n };\n\n return {\n primary: colors.primary,\n secondary: colors.secondary,\n skinTone,\n emissive: emissiveProps.color,\n emissiveIntensity: emissiveProps.intensity,\n };\n}\n\n// DO NOT ADD ANY MORE FUNCTIONS BELOW THIS LINE\n// All functions are already exported above using individual export statements\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"colorUtils.js","names":[],"sources":["../../src/utils/colorUtils.ts"],"sourcesContent":["import type { PlayerArchetype } from \"../types/common\";\nimport { KOREAN_COLORS } from \"../types/constants/colors\";\n\n/**\n * Convert RGB components to hex color\n */\nexport function rgbToHex(r: number, g: number, b: number): number {\n return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);\n}\n\n/**\n * Convert hex color to RGB components\n */\nexport function hexToRgb(hex: number): { r: number; g: number; b: number } {\n return {\n r: (hex >> 16) & 0xff,\n g: (hex >> 8) & 0xff,\n b: hex & 0xff,\n };\n}\n\n/**\n * Darken a color by a specified amount\n */\nexport function darkenColor(color: number, amount: number = 0.1): number {\n const { r, g, b } = hexToRgb(color);\n const factor = Math.max(0, 1 - amount);\n\n return rgbToHex(\n Math.floor(r * factor),\n Math.floor(g * factor),\n Math.floor(b * factor),\n );\n}\n\n/**\n * Lighten a color by a specified amount\n */\nexport function lightenColor(color: number, amount: number = 0.1): number {\n const { r, g, b } = hexToRgb(color);\n const factor = Math.min(1, amount);\n\n return rgbToHex(\n Math.min(255, Math.floor(r + (255 - r) * factor)),\n Math.min(255, Math.floor(g + (255 - g) * factor)),\n Math.min(255, Math.floor(b + (255 - b) * factor)),\n );\n}\n\n/**\n * Get archetype colors\n */\nexport function getArchetypeColors(archetype: PlayerArchetype): {\n primary: number;\n secondary: number;\n} {\n const colorMap: Record<\n PlayerArchetype,\n { primary: number; secondary: number }\n > = {\n musa: {\n primary: KOREAN_COLORS.ACCENT_GOLD,\n secondary: KOREAN_COLORS.SECONDARY_BROWN_DARK, // Fix: Use SECONDARY_BROWN_DARK\n },\n amsalja: {\n primary: KOREAN_COLORS.PRIMARY_CYAN, // Stealth assassin - cyan theme\n secondary: KOREAN_COLORS.KOREAN_BLACK,\n },\n hacker: {\n primary: KOREAN_COLORS.SECONDARY_PURPLE, // Cyber hacker - purple theme\n secondary: KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n },\n jeongbo_yowon: {\n primary: KOREAN_COLORS.ACCENT_BLUE, // Intelligence operative - blue tactical theme\n secondary: KOREAN_COLORS.UI_BACKGROUND_DARK,\n },\n jojik_pokryeokbae: {\n primary: KOREAN_COLORS.ACCENT_RED,\n secondary: KOREAN_COLORS.SECONDARY_BROWN_DARK, // Fix: Use SECONDARY_BROWN_DARK\n },\n };\n\n return (\n colorMap[archetype] || {\n primary: KOREAN_COLORS.WHITE_SOLID,\n secondary: KOREAN_COLORS.UI_STEEL_GRAY,\n }\n );\n}\n\n/**\n * Interpolate between two colors\n */\nexport function interpolateColor(\n color1: number,\n color2: number,\n factor: number,\n): number {\n const rgb1 = hexToRgb(color1);\n const rgb2 = hexToRgb(color2);\n\n const r = Math.floor(rgb1.r + (rgb2.r - rgb1.r) * factor);\n const g = Math.floor(rgb1.g + (rgb2.g - rgb1.g) * factor);\n const b = Math.floor(rgb1.b + (rgb2.b - rgb1.b) * factor);\n\n return rgbToHex(r, g, b);\n}\n\n/**\n * Get color with alpha\n */\nexport function getColorWithAlpha(color: number, alpha: number): number {\n return (Math.floor(alpha * 255) << 24) | color;\n}\n\n/**\n * Get contrast color (black or white) for given background\n */\nexport function getContrastColor(backgroundColor: number): number {\n const { r, g, b } = hexToRgb(backgroundColor);\n const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n return luminance > 0.5\n ? KOREAN_COLORS.BLACK_SOLID\n : KOREAN_COLORS.WHITE_SOLID;\n}\n\n/**\n * Get color based on health percentage\n */\nexport function getHealthColor(healthPercentage: number): number {\n if (healthPercentage > 0.6) {\n return KOREAN_COLORS.POSITIVE_GREEN;\n } else if (healthPercentage > 0.3) {\n return KOREAN_COLORS.SECONDARY_YELLOW;\n } else {\n return KOREAN_COLORS.ACCENT_RED;\n }\n}\n\n/**\n * Convert hex color to RGBA string for CSS\n * @param hex - Hex color value (e.g., 0x1a1a1a)\n * @param alpha - Alpha value between 0 and 1 (default: 1)\n * @returns RGBA string (e.g., \"rgba(26, 26, 26, 0.96)\")\n */\nexport function hexToRgbaString(hex: number, alpha: number = 1): string {\n const { r, g, b } = hexToRgb(hex);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n/**\n * Convert numeric color to hex string for CSS\n * @param color - Numeric color value (e.g., 0x00ffff)\n * @returns Hex color string without # prefix (e.g., \"00ffff\")\n * @example\n * toHex(0x00ffff) // \"00ffff\"\n * toHex(KOREAN_COLORS.PRIMARY_CYAN) // \"00ffff\"\n */\nexport function toHex(color: number): string {\n return color.toString(16).padStart(6, \"0\");\n}\n\n/**\n * Convert numeric color to CSS hex string with # prefix\n * @param color - Numeric color value (e.g., 0x00ffff)\n * @returns CSS hex color string (e.g., \"#00ffff\")\n * @example\n * hexColorToCSS(0x00ffff) // \"#00ffff\"\n * hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN) // \"#00ffff\"\n */\nexport function hexColorToCSS(color: number): string {\n return `#${color.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Skin tone definitions for archetype visual differentiation\n *\n * **Korean**: 원형별 피부색 (Archetype Skin Tones)\n *\n * Each archetype has a unique skin tone that reflects their\n * background and lifestyle:\n * - MUSA: Healthy tan from outdoor training\n * - AMSALJA: Pale from stealth operations in darkness\n * - HACKER: Slightly pale from indoor tech work\n * - JEONGBO_YOWON: Natural healthy tone\n * - JOJIK_POKRYEOKBAE: Weathered from street life\n *\n * @korean 원형피부색\n */\nconst ARCHETYPE_SKIN_TONES: Record<PlayerArchetype, number> = {\n musa: 0xdbb896, // Healthy tan - outdoor military training\n amsalja: 0xe8d4c4, // Pale complexion - stealth operative\n hacker: 0xf0d8c8, // Light skin - indoor tech work\n jeongbo_yowon: 0xe0c4a8, // Natural healthy tone - government operative\n jojik_pokryeokbae: 0xc8a882, // Weathered tan - street fighter\n};\n\n/**\n * Get skin tone color for archetype\n *\n * **Korean**: 원형 피부색 가져오기 (Get Archetype Skin Tone)\n *\n * Returns the skin tone color appropriate for the character's\n * background and lifestyle. Used for body, face, and exposed skin.\n *\n * @param archetype - Player archetype\n * @returns Skin tone color as hex number\n *\n * @example\n * ```typescript\n * const skinColor = getArchetypeSkinTone(PlayerArchetype.MUSA);\n * // Returns: 0xdbb896 (healthy tan)\n * ```\n *\n * @korean 원형피부색가져오기\n */\nexport function getArchetypeSkinTone(archetype: PlayerArchetype): number {\n return ARCHETYPE_SKIN_TONES[archetype] ?? KOREAN_COLORS.SKIN_TONE;\n}\n\n/**\n * Get extended archetype visual properties\n *\n * **Korean**: 원형 시각 속성 (Archetype Visual Properties)\n *\n * Returns comprehensive visual properties for an archetype including:\n * - Primary and secondary colors (from clothing theme)\n * - Skin tone color\n * - Emissive accent color for glow effects\n * - Material properties (metalness, roughness)\n *\n * @param archetype - Player archetype\n * @returns Extended visual properties object\n *\n * @korean 원형시각속성\n */\nexport function getArchetypeVisualProperties(archetype: PlayerArchetype): {\n readonly primary: number;\n readonly secondary: number;\n readonly skinTone: number;\n readonly emissive: number;\n readonly emissiveIntensity: number;\n} {\n const colors = getArchetypeColors(archetype);\n const skinTone = getArchetypeSkinTone(archetype);\n\n // Emissive colors based on archetype theme\n const emissiveMap: Record<\n PlayerArchetype,\n { color: number; intensity: number }\n > = {\n musa: { color: KOREAN_COLORS.ACCENT_GOLD, intensity: 0.1 },\n amsalja: { color: KOREAN_COLORS.PRIMARY_CYAN, intensity: 0.3 },\n hacker: { color: KOREAN_COLORS.SECONDARY_PURPLE, intensity: 0.25 },\n jeongbo_yowon: { color: KOREAN_COLORS.ACCENT_BLUE, intensity: 0.15 },\n jojik_pokryeokbae: { color: KOREAN_COLORS.ACCENT_RED, intensity: 0.2 },\n };\n\n const emissiveProps = emissiveMap[archetype] ?? {\n color: 0x000000,\n intensity: 0,\n };\n\n return {\n primary: colors.primary,\n secondary: colors.secondary,\n skinTone,\n emissive: emissiveProps.color,\n emissiveIntensity: emissiveProps.intensity,\n };\n}\n\n// DO NOT ADD ANY MORE FUNCTIONS BELOW THIS LINE\n// All functions are already exported above using individual export statements\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combatReadiness.js","names":[],"sources":["../../src/utils/combatReadiness.ts"],"sourcesContent":["/**\n * Combat Readiness Calculation System\n * \n * Calculates overall combat readiness from multiple factors:\n * - Body part health (40% weight)\n * - Pain level (20% weight)\n * - Consciousness (20% weight)\n * - Balance state (20% weight)\n * \n * @module utils/combatReadiness\n */\n\nimport type { PlayerState } from \"../systems/player\";\nimport type { BodyPartHealth } from \"../systems/bodypart/types\";\n\n/**\n * Combat readiness level thresholds and colors\n */\nexport const COMBAT_READINESS_THRESHOLDS = {\n FULL_CAPABILITY: { min: 80, max: 100, color: 0x00ff00, label: { korean: \"전투 준비\", english: \"Combat Ready\" } },\n LIGHT_IMPAIRMENT: { min: 60, max: 79, color: 0xffff00, label: { korean: \"경미 손상\", english: \"Light Damage\" } },\n MODERATE_IMPAIRMENT: { min: 40, max: 59, color: 0xff8800, label: { korean: \"중간 손상\", english: \"Moderate Damage\" } },\n HEAVY_IMPAIRMENT: { min: 20, max: 39, color: 0xff3333, label: { korean: \"중증 손상\", english: \"Heavy Damage\" } },\n CRITICAL: { min: 0, max: 19, color: 0x990000, label: { korean: \"위급 상태\", english: \"Critical\" } },\n} as const;\n\n/**\n * Weight factors for combat readiness calculation\n */\nconst READINESS_WEIGHTS = {\n BODY_HEALTH: 0.4, // 40% - Primary survival metric\n PAIN: 0.2, // 20% - Affects performance\n CONSCIOUSNESS: 0.2, // 20% - Awareness and response\n BALANCE: 0.2, // 20% - Stability and mobility\n} as const;\n\n/**\n * Calculate average body part health percentage\n * \n * @param bodyHealth - Body part health state\n * @returns Average health percentage (0-100)\n * @throws {Error} If bodyHealth is null or undefined\n */\nexport function calculateBodyHealthPercentage(bodyHealth: BodyPartHealth): number {\n if (!bodyHealth) {\n throw new Error(\"bodyHealth cannot be null or undefined\");\n }\n\n const parts = [\n bodyHealth.head,\n bodyHealth.torsoUpper,\n bodyHealth.torsoLower,\n bodyHealth.armLeft,\n bodyHealth.armRight,\n bodyHealth.legLeft,\n bodyHealth.legRight,\n ];\n \n const total = parts.reduce((sum, hp) => sum + hp, 0);\n const average = total / parts.length;\n \n return Math.max(0, Math.min(100, average));\n}\n\n/**\n * Calculate combat readiness from player state\n * \n * Combines multiple factors with weighted importance:\n * - Body part health (40%): Average health of all body parts\n * - Pain (20%): Inverted pain level (0 pain = 100% contribution)\n * - Consciousness (20%): Direct consciousness level\n * - Balance (20%): Direct balance level\n * \n * @param player - Current player state\n * @returns Combat readiness percentage (0-100)\n * @throws {Error} If player is null or undefined\n * \n * @example\n * ```typescript\n * const readiness = calculateCombatReadiness(playerState);\n * console.log(`Combat Readiness: ${readiness}%`);\n * // Output: \"Combat Readiness: 85%\"\n * ```\n */\nexport function calculateCombatReadiness(player: PlayerState): number {\n if (!player) {\n throw new Error(\"player cannot be null or undefined\");\n }\n\n // 1. Body health contribution (40%)\n // Use bodyPartHealth if available, otherwise use aggregate health\n let bodyHealthPercent: number;\n if (player.bodyPartHealth) {\n bodyHealthPercent = calculateBodyHealthPercentage(player.bodyPartHealth);\n } else {\n // Fall back to aggregate health percentage\n const maxHealth = player.maxHealth || 100; // Prevent division by zero\n bodyHealthPercent = maxHealth > 0 ? (player.health / maxHealth) * 100 : 0;\n }\n const bodyHealthScore = bodyHealthPercent * READINESS_WEIGHTS.BODY_HEALTH;\n \n // 2. Pain contribution (20%) - inverted (less pain = better readiness)\n const painPercent = Math.max(0, Math.min(100, player.pain));\n const painScore = (100 - painPercent) * READINESS_WEIGHTS.PAIN;\n \n // 3. Consciousness contribution (20%)\n const consciousnessPercent = Math.max(0, Math.min(100, player.consciousness));\n const consciousnessScore = consciousnessPercent * READINESS_WEIGHTS.CONSCIOUSNESS;\n \n // 4. Balance contribution (20%)\n const balancePercent = Math.max(0, Math.min(100, player.balance));\n const balanceScore = balancePercent * READINESS_WEIGHTS.BALANCE;\n \n // Combine all factors\n const totalReadiness = bodyHealthScore + painScore + consciousnessScore + balanceScore;\n \n return Math.max(0, Math.min(100, Math.round(totalReadiness)));\n}\n\n/**\n * Get color for combat readiness level\n * \n * @param readiness - Combat readiness percentage (0-100)\n * @returns Hex color code\n * @throws {Error} If readiness is NaN\n */\nexport function getCombatReadinessColor(readiness: number): number {\n if (Number.isNaN(readiness)) {\n throw new Error(\"readiness cannot be NaN\");\n }\n\n if (readiness >= COMBAT_READINESS_THRESHOLDS.FULL_CAPABILITY.min) {\n return COMBAT_READINESS_THRESHOLDS.FULL_CAPABILITY.color;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.LIGHT_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.LIGHT_IMPAIRMENT.color;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.MODERATE_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.MODERATE_IMPAIRMENT.color;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.HEAVY_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.HEAVY_IMPAIRMENT.color;\n }\n return COMBAT_READINESS_THRESHOLDS.CRITICAL.color;\n}\n\n/**\n * Get label for combat readiness level\n * \n * @param readiness - Combat readiness percentage (0-100)\n * @returns Korean and English labels\n * @throws {Error} If readiness is NaN\n */\nexport function getCombatReadinessLabel(readiness: number): { korean: string; english: string } {\n if (Number.isNaN(readiness)) {\n throw new Error(\"readiness cannot be NaN\");\n }\n\n if (readiness >= COMBAT_READINESS_THRESHOLDS.FULL_CAPABILITY.min) {\n return COMBAT_READINESS_THRESHOLDS.FULL_CAPABILITY.label;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.LIGHT_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.LIGHT_IMPAIRMENT.label;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.MODERATE_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.MODERATE_IMPAIRMENT.label;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.HEAVY_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.HEAVY_IMPAIRMENT.label;\n }\n return COMBAT_READINESS_THRESHOLDS.CRITICAL.label;\n}\n\n/**\n * Get number of filled bars for combat readiness\n * \n * Uses ceiling behavior: any non-zero readiness shows at least 1 bar.\n * This provides visual feedback even for minimal combat capability.\n * \n * @param readiness - Combat readiness percentage (0-100)\n * @param totalBars - Total number of bars (default: 10)\n * @returns Number of filled bars (0-totalBars)\n * @throws {Error} If readiness is NaN or totalBars is not positive\n * \n * @example\n * ```typescript\n * getCombatReadinessBars(0, 10); // Returns 0 bars\n * getCombatReadinessBars(5, 10); // Returns 1 bar (ceil behavior)\n * getCombatReadinessBars(50, 10); // Returns 5 bars\n * getCombatReadinessBars(100, 10); // Returns 10 bars\n * ```\n */\nexport function getCombatReadinessBars(readiness: number, totalBars: number = 10): number {\n if (Number.isNaN(readiness)) {\n throw new Error(\"readiness cannot be NaN\");\n }\n if (totalBars <= 0 || !Number.isInteger(totalBars)) {\n throw new Error(\"totalBars must be a positive integer\");\n }\n\n const percentage = Math.max(0, Math.min(100, readiness));\n \n // Return 0 bars only for exactly 0% readiness\n if (percentage === 0) return 0;\n \n // Use ceiling for any non-zero value to provide visual feedback\n return Math.ceil((percentage / 100) * totalBars);\n}\n"],"mappings":";;;;AAkBA,IAAa,8BAA8B;CACzC,iBAAiB;EAAE,KAAK;EAAI,KAAK;EAAK,OAAO;EAAU,OAAO;GAAE,QAAQ;GAAS,SAAS;GAAgB;EAAE;CAC5G,kBAAkB;EAAE,KAAK;EAAI,KAAK;EAAI,OAAO;EAAU,OAAO;GAAE,QAAQ;GAAS,SAAS;GAAgB;EAAE;CAC5G,qBAAqB;EAAE,KAAK;EAAI,KAAK;EAAI,OAAO;EAAU,OAAO;GAAE,QAAQ;GAAS,SAAS;GAAmB;EAAE;CAClH,kBAAkB;EAAE,KAAK;EAAI,KAAK;EAAI,OAAO;EAAU,OAAO;GAAE,QAAQ;GAAS,SAAS;GAAgB;EAAE;CAC5G,UAAU;EAAE,KAAK;EAAG,KAAK;EAAI,OAAO;EAAU,OAAO;GAAE,QAAQ;GAAS,SAAS;GAAY;EAAE;CAChG;;;;AAKD,IAAM,oBAAoB;CACxB,aAAa;CACb,MAAM;CACN,eAAe;CACf,SAAS;CACV;;;;;;;;AASD,SAAgB,8BAA8B,YAAoC;CAChF,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,yCAAyC;CAG3D,MAAM,QAAQ;EACZ,WAAW;EACX,WAAW;EACX,WAAW;EACX,WAAW;EACX,WAAW;EACX,WAAW;EACX,WAAW;EACZ;CAGD,MAAM,UADQ,MAAM,QAAQ,KAAK,OAAO,MAAM,IAAI,EAClC,GAAQ,MAAM;CAE9B,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;AAuB5C,SAAgB,yBAAyB,QAA6B;CACpE,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,qCAAqC;CAKvD,IAAI;CACJ,IAAI,OAAO,gBACT,oBAAoB,8BAA8B,OAAO,eAAe;MACnE;EAEL,MAAM,YAAY,OAAO,aAAa;EACtC,oBAAoB,YAAY,IAAK,OAAO,SAAS,YAAa,MAAM;;CAE1E,MAAM,kBAAkB,oBAAoB,kBAAkB;CAI9D,MAAM,aAAa,MADC,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,OAAO,KAAK,CACjC,IAAe,kBAAkB;CAI1D,MAAM,qBADuB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,OAAO,cAAc,CACjD,GAAuB,kBAAkB;CAIpE,MAAM,eADiB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,OAAO,QAAQ,CAC3C,GAAiB,kBAAkB;CAGxD,MAAM,iBAAiB,kBAAkB,YAAY,qBAAqB;CAE1E,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,eAAe,CAAC,CAAC;;;;;;;;;AAU/D,SAAgB,wBAAwB,WAA2B;CACjE,IAAI,OAAO,MAAM,UAAU,EACzB,MAAM,IAAI,MAAM,0BAA0B;CAG5C,IAAI,aAAa,4BAA4B,gBAAgB,KAC3D,OAAO,4BAA4B,gBAAgB;CAErD,IAAI,aAAa,4BAA4B,iBAAiB,KAC5D,OAAO,4BAA4B,iBAAiB;CAEtD,IAAI,aAAa,4BAA4B,oBAAoB,KAC/D,OAAO,4BAA4B,oBAAoB;CAEzD,IAAI,aAAa,4BAA4B,iBAAiB,KAC5D,OAAO,4BAA4B,iBAAiB;CAEtD,OAAO,4BAA4B,SAAS;;;;;;;;;AAU9C,SAAgB,wBAAwB,WAAwD;CAC9F,IAAI,OAAO,MAAM,UAAU,EACzB,MAAM,IAAI,MAAM,0BAA0B;CAG5C,IAAI,aAAa,4BAA4B,gBAAgB,KAC3D,OAAO,4BAA4B,gBAAgB;CAErD,IAAI,aAAa,4BAA4B,iBAAiB,KAC5D,OAAO,4BAA4B,iBAAiB;CAEtD,IAAI,aAAa,4BAA4B,oBAAoB,KAC/D,OAAO,4BAA4B,oBAAoB;CAEzD,IAAI,aAAa,4BAA4B,iBAAiB,KAC5D,OAAO,4BAA4B,iBAAiB;CAEtD,OAAO,4BAA4B,SAAS;;;;;;;;;;;;;;;;;;;;;AAsB9C,SAAgB,uBAAuB,WAAmB,YAAoB,IAAY;CACxF,IAAI,OAAO,MAAM,UAAU,EACzB,MAAM,IAAI,MAAM,0BAA0B;CAE5C,IAAI,aAAa,KAAK,CAAC,OAAO,UAAU,UAAU,EAChD,MAAM,IAAI,MAAM,uCAAuC;CAGzD,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,UAAU,CAAC;CAGxD,IAAI,eAAe,GAAG,OAAO;CAG7B,OAAO,KAAK,KAAM,aAAa,MAAO,UAAU"}
|
|
1
|
+
{"version":3,"file":"combatReadiness.js","names":[],"sources":["../../src/utils/combatReadiness.ts"],"sourcesContent":["/**\n * Combat Readiness Calculation System\n * \n * Calculates overall combat readiness from multiple factors:\n * - Body part health (40% weight)\n * - Pain level (20% weight)\n * - Consciousness (20% weight)\n * - Balance state (20% weight)\n * \n * @module utils/combatReadiness\n */\n\nimport type { PlayerState } from \"../systems/player\";\nimport type { BodyPartHealth } from \"../systems/bodypart/types\";\n\n/**\n * Combat readiness level thresholds and colors\n */\nexport const COMBAT_READINESS_THRESHOLDS = {\n FULL_CAPABILITY: { min: 80, max: 100, color: 0x00ff00, label: { korean: \"전투 준비\", english: \"Combat Ready\" } },\n LIGHT_IMPAIRMENT: { min: 60, max: 79, color: 0xffff00, label: { korean: \"경미 손상\", english: \"Light Damage\" } },\n MODERATE_IMPAIRMENT: { min: 40, max: 59, color: 0xff8800, label: { korean: \"중간 손상\", english: \"Moderate Damage\" } },\n HEAVY_IMPAIRMENT: { min: 20, max: 39, color: 0xff3333, label: { korean: \"중증 손상\", english: \"Heavy Damage\" } },\n CRITICAL: { min: 0, max: 19, color: 0x990000, label: { korean: \"위급 상태\", english: \"Critical\" } },\n} as const;\n\n/**\n * Weight factors for combat readiness calculation\n */\nconst READINESS_WEIGHTS = {\n BODY_HEALTH: 0.4, // 40% - Primary survival metric\n PAIN: 0.2, // 20% - Affects performance\n CONSCIOUSNESS: 0.2, // 20% - Awareness and response\n BALANCE: 0.2, // 20% - Stability and mobility\n} as const;\n\n/**\n * Calculate average body part health percentage\n * \n * @param bodyHealth - Body part health state\n * @returns Average health percentage (0-100)\n * @throws {Error} If bodyHealth is null or undefined\n */\nexport function calculateBodyHealthPercentage(bodyHealth: BodyPartHealth): number {\n if (!bodyHealth) {\n throw new Error(\"bodyHealth cannot be null or undefined\");\n }\n\n const parts = [\n bodyHealth.head,\n bodyHealth.torsoUpper,\n bodyHealth.torsoLower,\n bodyHealth.armLeft,\n bodyHealth.armRight,\n bodyHealth.legLeft,\n bodyHealth.legRight,\n ];\n \n const total = parts.reduce((sum, hp) => sum + hp, 0);\n const average = total / parts.length;\n \n return Math.max(0, Math.min(100, average));\n}\n\n/**\n * Calculate combat readiness from player state\n * \n * Combines multiple factors with weighted importance:\n * - Body part health (40%): Average health of all body parts\n * - Pain (20%): Inverted pain level (0 pain = 100% contribution)\n * - Consciousness (20%): Direct consciousness level\n * - Balance (20%): Direct balance level\n * \n * @param player - Current player state\n * @returns Combat readiness percentage (0-100)\n * @throws {Error} If player is null or undefined\n * \n * @example\n * ```typescript\n * const readiness = calculateCombatReadiness(playerState);\n * console.log(`Combat Readiness: ${readiness}%`);\n * // Output: \"Combat Readiness: 85%\"\n * ```\n */\nexport function calculateCombatReadiness(player: PlayerState): number {\n if (!player) {\n throw new Error(\"player cannot be null or undefined\");\n }\n\n // 1. Body health contribution (40%)\n // Use bodyPartHealth if available, otherwise use aggregate health\n let bodyHealthPercent: number;\n if (player.bodyPartHealth) {\n bodyHealthPercent = calculateBodyHealthPercentage(player.bodyPartHealth);\n } else {\n // Fall back to aggregate health percentage\n const maxHealth = player.maxHealth || 100; // Prevent division by zero\n bodyHealthPercent = maxHealth > 0 ? (player.health / maxHealth) * 100 : 0;\n }\n const bodyHealthScore = bodyHealthPercent * READINESS_WEIGHTS.BODY_HEALTH;\n \n // 2. Pain contribution (20%) - inverted (less pain = better readiness)\n const painPercent = Math.max(0, Math.min(100, player.pain));\n const painScore = (100 - painPercent) * READINESS_WEIGHTS.PAIN;\n \n // 3. Consciousness contribution (20%)\n const consciousnessPercent = Math.max(0, Math.min(100, player.consciousness));\n const consciousnessScore = consciousnessPercent * READINESS_WEIGHTS.CONSCIOUSNESS;\n \n // 4. Balance contribution (20%)\n const balancePercent = Math.max(0, Math.min(100, player.balance));\n const balanceScore = balancePercent * READINESS_WEIGHTS.BALANCE;\n \n // Combine all factors\n const totalReadiness = bodyHealthScore + painScore + consciousnessScore + balanceScore;\n \n return Math.max(0, Math.min(100, Math.round(totalReadiness)));\n}\n\n/**\n * Get color for combat readiness level\n * \n * @param readiness - Combat readiness percentage (0-100)\n * @returns Hex color code\n * @throws {Error} If readiness is NaN\n */\nexport function getCombatReadinessColor(readiness: number): number {\n if (Number.isNaN(readiness)) {\n throw new Error(\"readiness cannot be NaN\");\n }\n\n if (readiness >= COMBAT_READINESS_THRESHOLDS.FULL_CAPABILITY.min) {\n return COMBAT_READINESS_THRESHOLDS.FULL_CAPABILITY.color;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.LIGHT_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.LIGHT_IMPAIRMENT.color;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.MODERATE_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.MODERATE_IMPAIRMENT.color;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.HEAVY_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.HEAVY_IMPAIRMENT.color;\n }\n return COMBAT_READINESS_THRESHOLDS.CRITICAL.color;\n}\n\n/**\n * Get label for combat readiness level\n * \n * @param readiness - Combat readiness percentage (0-100)\n * @returns Korean and English labels\n * @throws {Error} If readiness is NaN\n */\nexport function getCombatReadinessLabel(readiness: number): { korean: string; english: string } {\n if (Number.isNaN(readiness)) {\n throw new Error(\"readiness cannot be NaN\");\n }\n\n if (readiness >= COMBAT_READINESS_THRESHOLDS.FULL_CAPABILITY.min) {\n return COMBAT_READINESS_THRESHOLDS.FULL_CAPABILITY.label;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.LIGHT_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.LIGHT_IMPAIRMENT.label;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.MODERATE_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.MODERATE_IMPAIRMENT.label;\n }\n if (readiness >= COMBAT_READINESS_THRESHOLDS.HEAVY_IMPAIRMENT.min) {\n return COMBAT_READINESS_THRESHOLDS.HEAVY_IMPAIRMENT.label;\n }\n return COMBAT_READINESS_THRESHOLDS.CRITICAL.label;\n}\n\n/**\n * Get number of filled bars for combat readiness\n * \n * Uses ceiling behavior: any non-zero readiness shows at least 1 bar.\n * This provides visual feedback even for minimal combat capability.\n * \n * @param readiness - Combat readiness percentage (0-100)\n * @param totalBars - Total number of bars (default: 10)\n * @returns Number of filled bars (0-totalBars)\n * @throws {Error} If readiness is NaN or totalBars is not positive\n * \n * @example\n * ```typescript\n * getCombatReadinessBars(0, 10); // Returns 0 bars\n * getCombatReadinessBars(5, 10); // Returns 1 bar (ceil behavior)\n * getCombatReadinessBars(50, 10); // Returns 5 bars\n * getCombatReadinessBars(100, 10); // Returns 10 bars\n * ```\n */\nexport function getCombatReadinessBars(readiness: number, totalBars: number = 10): number {\n if (Number.isNaN(readiness)) {\n throw new Error(\"readiness cannot be NaN\");\n }\n if (totalBars <= 0 || !Number.isInteger(totalBars)) {\n throw new Error(\"totalBars must be a positive integer\");\n }\n\n const percentage = Math.max(0, Math.min(100, readiness));\n \n // Return 0 bars only for exactly 0% readiness\n if (percentage === 0) return 0;\n \n // Use ceiling for any non-zero value to provide visual feedback\n return Math.ceil((percentage / 100) * totalBars);\n}\n"],"mappings":"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"}
|