blacktrigram 0.7.39 → 0.7.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanCulture.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +5 -5
|
@@ -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;AAC7C,QAAO,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;AAEpC,QAAO;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;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 +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;
|
|
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,GAAG,CAAC,SAAS,GAAG,IAAI;;;;;;;;;;;;;;;;;;;AAoBhD,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,MAAM;CACnD,MAAM,IAAI,KAAK,MAAM,MAAM,IAAI,SAAS,KAAK,MAAM;CACnD,MAAM,IAAI,KAAK,MAAM,MAAM,IAAI,SAAS,KAAK,MAAM;CAGnD,OAAQ,KAAK,KAAO,KAAK,IAAK"}
|
|
@@ -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":"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"}
|
|
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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controlMapping.js","names":[],"sources":["../../src/utils/controlMapping.ts"],"sourcesContent":["/**\n * Control Mapping System for Black Trigram\n * Manages keyboard control bindings with localStorage persistence\n * \n * @module utils/controlMapping\n * @category Input System\n * @korean 컨트롤 매핑 시스템\n */\n\nimport { TrigramStance } from \"../types/common\";\nimport { TRIGRAM_STANCES_ORDER } from \"../systems/trigram/types\";\n\n/**\n * Control binding configuration\n * Maps game actions to keyboard keys\n * \n * @category Input System\n * @korean 컨트롤 바인딩\n */\nexport interface ControlBinding {\n /** Stance selection keys (1-8 by default) */\n readonly stances: readonly string[];\n /** Technique execution keys (Q-E-R-T-Y-F-G-Z-X-C by default) */\n readonly techniques: readonly string[];\n /** Attack action key */\n readonly attack: string;\n /** Block/Guard action key */\n readonly block: string;\n /** Movement keys */\n readonly movement: {\n readonly up: string;\n readonly down: string;\n readonly left: string;\n readonly right: string;\n };\n /** Vital points overlay toggle key */\n readonly vitalPointsOverlay: string;\n /** Stance side switch key (swap front foot) */\n readonly stanceSideSwitch?: string;\n /** Pause/Menu keys */\n readonly pause: readonly string[];\n}\n\n/**\n * Storage key for localStorage persistence\n */\nconst STORAGE_KEY = \"blacktrigram_controls\";\n\n/**\n * Default control bindings following game design specifications\n * NEW: Conflict-free control scheme with Q-E-R-T-Y-F-G-Z-X-C for techniques\n * \n * Uses keys around WASD that are easy to reach with left hand\n * No conflicts with movement, browser shortcuts, or function keys\n * \n * All keys stored in lowercase for consistent comparison\n * \n * @see CONTROLS.md for complete documentation\n */\nconst DEFAULT_BINDINGS: ControlBinding = {\n stances: [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n techniques: [\"q\", \"e\", \"r\", \"t\", \"y\", \"f\", \"g\", \"z\", \"x\", \"c\"],\n attack: \" \", // Spacebar\n block: \"b\",\n movement: {\n up: \"w\",\n down: \"s\",\n left: \"a\",\n right: \"d\",\n },\n vitalPointsOverlay: \"v\",\n stanceSideSwitch: \"h\", // H key for switching front foot\n pause: [\"escape\", \"m\"],\n};\n\n/**\n * Control Mapper class for managing keyboard bindings\n * Handles loading, saving, and querying control mappings\n * \n * @example\n * ```typescript\n * const mapper = new ControlMapper();\n * \n * // Get stance for key press\n * const stance = mapper.getStanceForKey(\"3\"); // Returns 2 (0-indexed)\n * \n * // Update bindings\n * mapper.saveBindings({\n * ...mapper.getBindings(),\n * stances: ['q', 'w', 'e', 'r', 'a', 's', 'd', 'f']\n * });\n * ```\n * \n * @public\n * @korean 컨트롤매퍼\n */\nexport class ControlMapper {\n private bindings: ControlBinding;\n\n /**\n * Creates a new ControlMapper instance\n * Automatically loads bindings from localStorage\n */\n constructor() {\n this.bindings = this.loadBindings();\n }\n\n /**\n * Load bindings from localStorage\n * Returns default bindings if none exist\n * \n * @returns Control bindings from storage or defaults\n * @korean 바인딩 로드\n */\n loadBindings(): ControlBinding {\n try {\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) {\n const parsed = JSON.parse(saved) as ControlBinding;\n // Validate loaded bindings\n if (this.validateBindings(parsed)) {\n return parsed;\n }\n }\n } catch (error) {\n console.warn(\"Failed to load control bindings:\", error);\n }\n return this.getDefaultBindings();\n }\n\n /**\n * Save bindings to localStorage\n * \n * @param bindings - Control bindings to save\n * @korean 바인딩 저장\n */\n saveBindings(bindings: ControlBinding): void {\n if (!this.validateBindings(bindings)) {\n console.error(\"Invalid control bindings, not saving\");\n return;\n }\n\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(bindings));\n this.bindings = bindings;\n } catch (error) {\n console.error(\"Failed to save control bindings:\", error);\n }\n }\n\n /**\n * Get default control bindings\n * \n * @returns Default control configuration\n * @korean 기본 바인딩\n */\n getDefaultBindings(): ControlBinding {\n return { ...DEFAULT_BINDINGS };\n }\n\n /**\n * Get current control bindings\n * \n * @returns Current control configuration\n * @korean 현재 바인딩\n */\n getBindings(): ControlBinding {\n return { ...this.bindings };\n }\n\n /**\n * Reset bindings to default\n * \n * @korean 기본값으로 리셋\n */\n resetToDefaults(): void {\n this.saveBindings(this.getDefaultBindings());\n }\n\n /**\n * Get stance index for a given key\n * Returns null if key is not bound to a stance\n * \n * @param key - Keyboard key to check\n * @returns Stance index (0-7) or null\n * @korean 키에 대한 자세 조회\n */\n getStanceForKey(key: string): number | null {\n const normalizedKey = key.toLowerCase();\n const index = this.bindings.stances.findIndex(\n (k) => k.toLowerCase() === normalizedKey\n );\n return index >= 0 ? index : null;\n }\n\n /**\n * Get key for a given stance index\n * \n * @param stanceIndex - Stance index (0-7)\n * @returns Key bound to that stance\n * @korean 자세에 대한 키 조회\n */\n getKeyForStance(stanceIndex: number): string | null {\n if (stanceIndex < 0 || stanceIndex >= this.bindings.stances.length) {\n return null;\n }\n return this.bindings.stances[stanceIndex];\n }\n\n /**\n * Get TrigramStance enum for a given key\n * \n * @param key - Keyboard key to check\n * @returns TrigramStance or null\n * @korean 키에 대한 팔괘 자세\n */\n getTrigramStanceForKey(key: string): TrigramStance | null {\n const index = this.getStanceForKey(key);\n if (index === null) return null;\n return TRIGRAM_STANCES_ORDER[index] ?? null;\n }\n\n /**\n * Get technique index for a key\n * \n * @param key - Keyboard key\n * @returns Technique index (0-9) or null\n * @korean 기술 키 확인\n */\n getTechniqueForKey(key: string): number | null {\n const normalizedKey = key.toLowerCase();\n const index = this.bindings.techniques.indexOf(normalizedKey);\n return index >= 0 ? index : null;\n }\n\n /**\n * Get key for technique index\n * \n * @param index - Technique index (0-9)\n * @returns Key or null\n * @korean 기술 인덱스로 키 조회\n */\n getKeyForTechnique(index: number): string | null {\n if (index < 0 || index >= this.bindings.techniques.length) {\n return null;\n }\n return this.bindings.techniques[index];\n }\n\n /**\n * Check if a key is bound to an action\n * \n * @param key - Keyboard key to check\n * @returns Action name or null\n * @korean 키 바인딩 확인\n */\n getActionForKey(key: string): string | null {\n const normalizedKey = key.toLowerCase();\n\n // Check stance keys\n if (this.getStanceForKey(key) !== null) {\n return \"stance\";\n }\n\n // Check other actions\n if (normalizedKey === this.bindings.attack.toLowerCase()) return \"attack\";\n if (normalizedKey === this.bindings.block.toLowerCase()) return \"block\";\n\n // Check movement\n const movement = this.bindings.movement;\n if (normalizedKey === movement.up.toLowerCase()) return \"move_up\";\n if (normalizedKey === movement.down.toLowerCase()) return \"move_down\";\n if (normalizedKey === movement.left.toLowerCase()) return \"move_left\";\n if (normalizedKey === movement.right.toLowerCase()) return \"move_right\";\n\n // Check vital points overlay\n if (normalizedKey === this.bindings.vitalPointsOverlay.toLowerCase())\n return \"vital_points_overlay\";\n\n // Check stance side switch\n if (\n normalizedKey === this.bindings.stanceSideSwitch?.toLowerCase()\n ) {\n return \"stance_side_switch\";\n }\n\n // Check pause keys\n if (\n this.bindings.pause.some((k) => k.toLowerCase() === normalizedKey)\n ) {\n return \"pause\";\n }\n\n return null;\n }\n\n /**\n * Validate control bindings\n * Ensures no duplicate keys and all required keys are present\n * \n * @param bindings - Bindings to validate\n * @returns True if valid\n * @korean 바인딩 검증\n */\n private validateBindings(bindings: ControlBinding): boolean {\n // Check that stances array has exactly 8 entries\n if (\n !bindings.stances ||\n !Array.isArray(bindings.stances) ||\n bindings.stances.length !== 8\n ) {\n return false;\n }\n\n // Check that techniques array has up to 10 entries\n if (\n !bindings.techniques ||\n !Array.isArray(bindings.techniques) ||\n bindings.techniques.length === 0 ||\n bindings.techniques.length > 10\n ) {\n return false;\n }\n\n // Check for duplicate keys\n const allKeys = [\n ...bindings.stances,\n ...bindings.techniques,\n bindings.attack,\n bindings.block,\n bindings.movement.up,\n bindings.movement.down,\n bindings.movement.left,\n bindings.movement.right,\n bindings.vitalPointsOverlay,\n ...bindings.pause,\n ];\n\n const uniqueKeys = new Set(allKeys.map((k) => k.toLowerCase()));\n if (uniqueKeys.size !== allKeys.length) {\n // Duplicate keys found\n return false;\n }\n\n return true;\n }\n\n /**\n * Check if bindings have conflicts\n * Returns array of conflicting keys\n * \n * @param bindings - Bindings to check\n * @returns Array of duplicate keys\n * @korean 바인딩 충돌 확인\n */\n getConflicts(bindings: ControlBinding): string[] {\n const keyCount = new Map<string, number>();\n const conflicts: string[] = [];\n\n const allKeys = [\n ...bindings.stances,\n ...bindings.techniques,\n bindings.attack,\n bindings.block,\n bindings.movement.up,\n bindings.movement.down,\n bindings.movement.left,\n bindings.movement.right,\n bindings.vitalPointsOverlay,\n ...bindings.pause,\n ];\n\n allKeys.forEach((key) => {\n const normalized = key.toLowerCase();\n keyCount.set(normalized, (keyCount.get(normalized) ?? 0) + 1);\n });\n\n keyCount.forEach((count, key) => {\n if (count > 1) {\n conflicts.push(key);\n }\n });\n\n return conflicts;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"controlMapping.js","names":[],"sources":["../../src/utils/controlMapping.ts"],"sourcesContent":["/**\n * Control Mapping System for Black Trigram\n * Manages keyboard control bindings with localStorage persistence\n * \n * @module utils/controlMapping\n * @category Input System\n * @korean 컨트롤 매핑 시스템\n */\n\nimport { TrigramStance } from \"../types/common\";\nimport { TRIGRAM_STANCES_ORDER } from \"../systems/trigram/types\";\n\n/**\n * Control binding configuration\n * Maps game actions to keyboard keys\n * \n * @category Input System\n * @korean 컨트롤 바인딩\n */\nexport interface ControlBinding {\n /** Stance selection keys (1-8 by default) */\n readonly stances: readonly string[];\n /** Technique execution keys (Q-E-R-T-Y-F-G-Z-X-C by default) */\n readonly techniques: readonly string[];\n /** Attack action key */\n readonly attack: string;\n /** Block/Guard action key */\n readonly block: string;\n /** Movement keys */\n readonly movement: {\n readonly up: string;\n readonly down: string;\n readonly left: string;\n readonly right: string;\n };\n /** Vital points overlay toggle key */\n readonly vitalPointsOverlay: string;\n /** Stance side switch key (swap front foot) */\n readonly stanceSideSwitch?: string;\n /** Pause/Menu keys */\n readonly pause: readonly string[];\n}\n\n/**\n * Storage key for localStorage persistence\n */\nconst STORAGE_KEY = \"blacktrigram_controls\";\n\n/**\n * Default control bindings following game design specifications\n * NEW: Conflict-free control scheme with Q-E-R-T-Y-F-G-Z-X-C for techniques\n * \n * Uses keys around WASD that are easy to reach with left hand\n * No conflicts with movement, browser shortcuts, or function keys\n * \n * All keys stored in lowercase for consistent comparison\n * \n * @see CONTROLS.md for complete documentation\n */\nconst DEFAULT_BINDINGS: ControlBinding = {\n stances: [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n techniques: [\"q\", \"e\", \"r\", \"t\", \"y\", \"f\", \"g\", \"z\", \"x\", \"c\"],\n attack: \" \", // Spacebar\n block: \"b\",\n movement: {\n up: \"w\",\n down: \"s\",\n left: \"a\",\n right: \"d\",\n },\n vitalPointsOverlay: \"v\",\n stanceSideSwitch: \"h\", // H key for switching front foot\n pause: [\"escape\", \"m\"],\n};\n\n/**\n * Control Mapper class for managing keyboard bindings\n * Handles loading, saving, and querying control mappings\n * \n * @example\n * ```typescript\n * const mapper = new ControlMapper();\n * \n * // Get stance for key press\n * const stance = mapper.getStanceForKey(\"3\"); // Returns 2 (0-indexed)\n * \n * // Update bindings\n * mapper.saveBindings({\n * ...mapper.getBindings(),\n * stances: ['q', 'w', 'e', 'r', 'a', 's', 'd', 'f']\n * });\n * ```\n * \n * @public\n * @korean 컨트롤매퍼\n */\nexport class ControlMapper {\n private bindings: ControlBinding;\n\n /**\n * Creates a new ControlMapper instance\n * Automatically loads bindings from localStorage\n */\n constructor() {\n this.bindings = this.loadBindings();\n }\n\n /**\n * Load bindings from localStorage\n * Returns default bindings if none exist\n * \n * @returns Control bindings from storage or defaults\n * @korean 바인딩 로드\n */\n loadBindings(): ControlBinding {\n try {\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) {\n const parsed = JSON.parse(saved) as ControlBinding;\n // Validate loaded bindings\n if (this.validateBindings(parsed)) {\n return parsed;\n }\n }\n } catch (error) {\n console.warn(\"Failed to load control bindings:\", error);\n }\n return this.getDefaultBindings();\n }\n\n /**\n * Save bindings to localStorage\n * \n * @param bindings - Control bindings to save\n * @korean 바인딩 저장\n */\n saveBindings(bindings: ControlBinding): void {\n if (!this.validateBindings(bindings)) {\n console.error(\"Invalid control bindings, not saving\");\n return;\n }\n\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(bindings));\n this.bindings = bindings;\n } catch (error) {\n console.error(\"Failed to save control bindings:\", error);\n }\n }\n\n /**\n * Get default control bindings\n * \n * @returns Default control configuration\n * @korean 기본 바인딩\n */\n getDefaultBindings(): ControlBinding {\n return { ...DEFAULT_BINDINGS };\n }\n\n /**\n * Get current control bindings\n * \n * @returns Current control configuration\n * @korean 현재 바인딩\n */\n getBindings(): ControlBinding {\n return { ...this.bindings };\n }\n\n /**\n * Reset bindings to default\n * \n * @korean 기본값으로 리셋\n */\n resetToDefaults(): void {\n this.saveBindings(this.getDefaultBindings());\n }\n\n /**\n * Get stance index for a given key\n * Returns null if key is not bound to a stance\n * \n * @param key - Keyboard key to check\n * @returns Stance index (0-7) or null\n * @korean 키에 대한 자세 조회\n */\n getStanceForKey(key: string): number | null {\n const normalizedKey = key.toLowerCase();\n const index = this.bindings.stances.findIndex(\n (k) => k.toLowerCase() === normalizedKey\n );\n return index >= 0 ? index : null;\n }\n\n /**\n * Get key for a given stance index\n * \n * @param stanceIndex - Stance index (0-7)\n * @returns Key bound to that stance\n * @korean 자세에 대한 키 조회\n */\n getKeyForStance(stanceIndex: number): string | null {\n if (stanceIndex < 0 || stanceIndex >= this.bindings.stances.length) {\n return null;\n }\n return this.bindings.stances[stanceIndex];\n }\n\n /**\n * Get TrigramStance enum for a given key\n * \n * @param key - Keyboard key to check\n * @returns TrigramStance or null\n * @korean 키에 대한 팔괘 자세\n */\n getTrigramStanceForKey(key: string): TrigramStance | null {\n const index = this.getStanceForKey(key);\n if (index === null) return null;\n return TRIGRAM_STANCES_ORDER[index] ?? null;\n }\n\n /**\n * Get technique index for a key\n * \n * @param key - Keyboard key\n * @returns Technique index (0-9) or null\n * @korean 기술 키 확인\n */\n getTechniqueForKey(key: string): number | null {\n const normalizedKey = key.toLowerCase();\n const index = this.bindings.techniques.indexOf(normalizedKey);\n return index >= 0 ? index : null;\n }\n\n /**\n * Get key for technique index\n * \n * @param index - Technique index (0-9)\n * @returns Key or null\n * @korean 기술 인덱스로 키 조회\n */\n getKeyForTechnique(index: number): string | null {\n if (index < 0 || index >= this.bindings.techniques.length) {\n return null;\n }\n return this.bindings.techniques[index];\n }\n\n /**\n * Check if a key is bound to an action\n * \n * @param key - Keyboard key to check\n * @returns Action name or null\n * @korean 키 바인딩 확인\n */\n getActionForKey(key: string): string | null {\n const normalizedKey = key.toLowerCase();\n\n // Check stance keys\n if (this.getStanceForKey(key) !== null) {\n return \"stance\";\n }\n\n // Check other actions\n if (normalizedKey === this.bindings.attack.toLowerCase()) return \"attack\";\n if (normalizedKey === this.bindings.block.toLowerCase()) return \"block\";\n\n // Check movement\n const movement = this.bindings.movement;\n if (normalizedKey === movement.up.toLowerCase()) return \"move_up\";\n if (normalizedKey === movement.down.toLowerCase()) return \"move_down\";\n if (normalizedKey === movement.left.toLowerCase()) return \"move_left\";\n if (normalizedKey === movement.right.toLowerCase()) return \"move_right\";\n\n // Check vital points overlay\n if (normalizedKey === this.bindings.vitalPointsOverlay.toLowerCase())\n return \"vital_points_overlay\";\n\n // Check stance side switch\n if (\n normalizedKey === this.bindings.stanceSideSwitch?.toLowerCase()\n ) {\n return \"stance_side_switch\";\n }\n\n // Check pause keys\n if (\n this.bindings.pause.some((k) => k.toLowerCase() === normalizedKey)\n ) {\n return \"pause\";\n }\n\n return null;\n }\n\n /**\n * Validate control bindings\n * Ensures no duplicate keys and all required keys are present\n * \n * @param bindings - Bindings to validate\n * @returns True if valid\n * @korean 바인딩 검증\n */\n private validateBindings(bindings: ControlBinding): boolean {\n // Check that stances array has exactly 8 entries\n if (\n !bindings.stances ||\n !Array.isArray(bindings.stances) ||\n bindings.stances.length !== 8\n ) {\n return false;\n }\n\n // Check that techniques array has up to 10 entries\n if (\n !bindings.techniques ||\n !Array.isArray(bindings.techniques) ||\n bindings.techniques.length === 0 ||\n bindings.techniques.length > 10\n ) {\n return false;\n }\n\n // Check for duplicate keys\n const allKeys = [\n ...bindings.stances,\n ...bindings.techniques,\n bindings.attack,\n bindings.block,\n bindings.movement.up,\n bindings.movement.down,\n bindings.movement.left,\n bindings.movement.right,\n bindings.vitalPointsOverlay,\n ...bindings.pause,\n ];\n\n const uniqueKeys = new Set(allKeys.map((k) => k.toLowerCase()));\n if (uniqueKeys.size !== allKeys.length) {\n // Duplicate keys found\n return false;\n }\n\n return true;\n }\n\n /**\n * Check if bindings have conflicts\n * Returns array of conflicting keys\n * \n * @param bindings - Bindings to check\n * @returns Array of duplicate keys\n * @korean 바인딩 충돌 확인\n */\n getConflicts(bindings: ControlBinding): string[] {\n const keyCount = new Map<string, number>();\n const conflicts: string[] = [];\n\n const allKeys = [\n ...bindings.stances,\n ...bindings.techniques,\n bindings.attack,\n bindings.block,\n bindings.movement.up,\n bindings.movement.down,\n bindings.movement.left,\n bindings.movement.right,\n bindings.vitalPointsOverlay,\n ...bindings.pause,\n ];\n\n allKeys.forEach((key) => {\n const normalized = key.toLowerCase();\n keyCount.set(normalized, (keyCount.get(normalized) ?? 0) + 1);\n });\n\n keyCount.forEach((count, key) => {\n if (count > 1) {\n conflicts.push(key);\n }\n });\n\n return conflicts;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deviceDetection.js","names":[],"sources":["../../src/utils/deviceDetection.ts"],"sourcesContent":["/**\n * Device Detection Utility\n *\n * Provides robust mobile device detection combining:\n * - User-agent string analysis\n * - Screen size detection\n * - Touch capability detection\n *\n * This ensures mobile controls are shown on all mobile devices,\n * including high-resolution phones that exceed typical mobile breakpoints.\n *\n * @module utils/deviceDetection\n * @category Mobile\n * @korean 기기감지유틸리티\n */\n\n/**\n * Device type classification\n */\nexport enum DeviceType {\n /** Desktop computer or laptop */\n DESKTOP = \"desktop\",\n /** Mobile phone (iOS, Android, etc.) */\n MOBILE = \"mobile\",\n /** Tablet device (iPad, Android tablets) */\n TABLET = \"tablet\",\n}\n\n/**\n * Platform detection results\n */\nexport interface PlatformInfo {\n /** Operating system type */\n readonly os: \"ios\" | \"android\" | \"windows\" | \"macos\" | \"linux\" | \"unknown\";\n /** Device type classification */\n readonly deviceType: DeviceType;\n /** Whether device has touch capability */\n readonly hasTouch: boolean;\n /** Whether device is mobile phone */\n readonly isMobile: boolean;\n /** Whether device is tablet */\n readonly isTablet: boolean;\n /** Whether device is desktop */\n readonly isDesktop: boolean;\n /** Screen width in pixels */\n readonly screenWidth: number;\n /** Screen height in pixels */\n readonly screenHeight: number;\n}\n\n/**\n * Detect if user-agent indicates a mobile device\n * Checks for common mobile device identifiers in user-agent string\n *\n * @param userAgent - Browser user-agent string\n * @returns True if user-agent indicates mobile device\n */\nfunction isMobileUserAgent(userAgent: string): boolean {\n const mobileKeywords = [\n \"Android\",\n \"webOS\",\n \"iPhone\",\n // 'iPad' is handled separately in isTabletUserAgent\n \"iPod\",\n \"BlackBerry\",\n \"IEMobile\",\n \"Opera Mini\",\n \"Mobile\",\n \"mobile\",\n ];\n\n return mobileKeywords.some((keyword) => userAgent.includes(keyword));\n}\n\n/**\n * Detect if user-agent indicates a tablet device\n *\n * @param userAgent - Browser user-agent string\n * @returns True if user-agent indicates tablet\n */\nfunction isTabletUserAgent(userAgent: string): boolean {\n // iPad is always a tablet\n if (userAgent.includes(\"iPad\")) {\n return true;\n }\n\n // Android tablets typically include \"Tablet\" or have Mobile absent\n if (userAgent.includes(\"Android\")) {\n return userAgent.includes(\"Tablet\") || !userAgent.includes(\"Mobile\");\n }\n\n return false;\n}\n\n/**\n * Detect operating system from user-agent\n *\n * @param userAgent - Browser user-agent string\n * @returns Operating system identifier\n */\nfunction detectOS(userAgent: string): PlatformInfo[\"os\"] {\n if (\n userAgent.includes(\"iPhone\") ||\n userAgent.includes(\"iPad\") ||\n userAgent.includes(\"iPod\")\n ) {\n return \"ios\";\n }\n if (userAgent.includes(\"Android\")) {\n return \"android\";\n }\n if (userAgent.includes(\"Windows\")) {\n return \"windows\";\n }\n if (userAgent.includes(\"Mac\")) {\n // Handle iPadOS 13+ in desktop mode, which reports a Mac-like user agent\n // e.g. \"Macintosh; Intel Mac OS X\" but still has touch support\n const isLikelyIPadOSDesktop =\n typeof navigator !== \"undefined\" &&\n typeof navigator.maxTouchPoints === \"number\" &&\n navigator.maxTouchPoints > 1 &&\n userAgent.includes(\"Macintosh\");\n\n if (isLikelyIPadOSDesktop) {\n return \"ios\";\n }\n return \"macos\";\n }\n if (userAgent.includes(\"Linux\")) {\n return \"linux\";\n }\n return \"unknown\";\n}\n\n/**\n * Detect if device has touch capability\n * Uses multiple methods for reliability\n *\n * @returns True if touch is supported\n */\nfunction hasTouchSupport(): boolean {\n // Check for touch events support\n if (\"ontouchstart\" in window) {\n return true;\n }\n\n // Check for touch points (must be defined and > 0)\n if (\n typeof navigator !== \"undefined\" &&\n typeof navigator.maxTouchPoints !== \"undefined\" &&\n navigator.maxTouchPoints > 0\n ) {\n return true;\n }\n\n // Check for pointer events with touch\n if (\n typeof window !== \"undefined\" &&\n window.matchMedia?.(\"(pointer: coarse)\")?.matches\n ) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Mobile screen size breakpoint\n * Devices with width <= this value are considered mobile by size\n */\nexport const MOBILE_BREAKPOINT = 768;\n\n/**\n * Tablet screen size breakpoint\n * Devices with width > MOBILE_BREAKPOINT and <= TABLET_BREAKPOINT are tablets\n */\nexport const TABLET_BREAKPOINT = 1024;\n\n/**\n * Cached CSS environment variable insets\n */\nlet cachedCSSEnvInsets: { top: number; bottom: number } | null = null;\n\n/**\n * Read CSS environment variables for safe area insets\n * Results are cached as they don't change during a session\n */\nfunction readCSSEnvInsets(): { top: number; bottom: number } | null {\n if (cachedCSSEnvInsets !== null) {\n return cachedCSSEnvInsets;\n }\n\n if (typeof window !== \"undefined\" && typeof getComputedStyle === \"function\") {\n try {\n const root = document.documentElement;\n const style = getComputedStyle(root);\n const topEnv = style.getPropertyValue(\"env(safe-area-inset-top)\");\n const bottomEnv = style.getPropertyValue(\"env(safe-area-inset-bottom)\");\n\n if (topEnv || bottomEnv) {\n const result = {\n top: parseInt(topEnv || \"0\", 10) || 0,\n bottom: parseInt(bottomEnv || \"0\", 10) || 0,\n };\n cachedCSSEnvInsets = result;\n return result;\n }\n } catch {\n // Fall through to null\n }\n }\n\n return null;\n}\n\n/**\n * Cached platform information to avoid re-parsing user-agent on every call\n */\nlet cachedPlatform: PlatformInfo | null = null;\nlet cachedScreenWidth = 0;\nlet cachedScreenHeight = 0;\n\n/**\n * Clear the cached platform information\n * Useful when window is resized or device emulation changes\n * Also clears CSS environment variable cache\n *\n * @public\n */\nexport function clearPlatformCache(): void {\n cachedPlatform = null;\n cachedScreenWidth = 0;\n cachedScreenHeight = 0;\n cachedCSSEnvInsets = null;\n}\n\n/**\n * Detect device type and platform information\n *\n * Combines multiple detection methods for reliability:\n * 1. User-agent string analysis (most reliable for device type)\n * 2. Screen dimensions\n * 3. Touch capability\n *\n * This ensures mobile controls are shown on:\n * - Standard mobile phones (< 768px width)\n * - High-resolution phones (>= 768px width but mobile user-agent)\n * - Android 15/16 devices with 2K/4K resolutions (1200px+, 1440px+)\n * - Tablets (user preference via touch support)\n *\n * **User-agent detection takes priority over screen size**, ensuring that\n * high-end Android phones with desktop-class resolutions (e.g., Galaxy S23 Ultra,\n * Pixel 9 Pro) are correctly identified as mobile devices.\n *\n * Results are cached to avoid re-parsing user-agent on every call.\n * Cache is invalidated when screen dimensions change.\n *\n * @returns Complete platform information\n *\n * @example\n * ```typescript\n * const platform = detectPlatform();\n *\n * if (platform.isMobile) {\n * // Show mobile controls even on 4K Android phones\n * return <MobileControls />;\n * }\n * ```\n *\n * @public\n * @korean 플랫폼감지\n */\nexport function detectPlatform(): PlatformInfo {\n const userAgent = typeof navigator !== \"undefined\" ? navigator.userAgent : \"\";\n const screenWidth = typeof window !== \"undefined\" ? window.innerWidth : 1920;\n const screenHeight =\n typeof window !== \"undefined\" ? window.innerHeight : 1080;\n\n // Return cached result if screen dimensions haven't changed\n if (\n cachedPlatform !== null &&\n cachedScreenWidth === screenWidth &&\n cachedScreenHeight === screenHeight\n ) {\n return cachedPlatform;\n }\n\n // Detect OS\n const os = detectOS(userAgent);\n\n // Detect touch capability\n const hasTouch = hasTouchSupport();\n\n // Detect if mobile by user-agent (most reliable method)\n const isMobileUA = isMobileUserAgent(userAgent);\n\n // Detect if tablet by user-agent\n const isTabletUA = isTabletUserAgent(userAgent);\n\n // Detect by screen size (fallback method)\n const isMobileBySize = screenWidth <= MOBILE_BREAKPOINT;\n const isTabletBySize =\n screenWidth > MOBILE_BREAKPOINT && screenWidth <= TABLET_BREAKPOINT;\n\n // Determine device type\n // Priority: User-agent > Screen size\n let deviceType: DeviceType;\n let isMobile: boolean;\n let isTablet: boolean;\n\n if (isMobileUA && !isTabletUA) {\n // User-agent indicates phone\n deviceType = DeviceType.MOBILE;\n isMobile = true;\n isTablet = false;\n } else if (isTabletUA) {\n // User-agent indicates tablet\n deviceType = DeviceType.TABLET;\n isMobile = false;\n isTablet = true;\n } else if (isMobileBySize) {\n // Small screen, assume mobile\n deviceType = DeviceType.MOBILE;\n isMobile = true;\n isTablet = false;\n } else if (isTabletBySize && hasTouch) {\n // Medium screen with touch, assume tablet\n deviceType = DeviceType.TABLET;\n isMobile = false;\n isTablet = true;\n } else {\n // Desktop\n deviceType = DeviceType.DESKTOP;\n isMobile = false;\n isTablet = false;\n }\n\n const isDesktop = deviceType === DeviceType.DESKTOP;\n\n const result: PlatformInfo = {\n os,\n deviceType,\n hasTouch,\n isMobile,\n isTablet,\n isDesktop,\n screenWidth,\n screenHeight,\n };\n\n // Cache the result along with screen dimensions\n cachedPlatform = result;\n cachedScreenWidth = screenWidth;\n cachedScreenHeight = screenHeight;\n\n return result;\n}\n\n/**\n * Simple mobile check for backward compatibility\n * Returns true for both mobile phones and tablets\n *\n * @returns True if device is mobile or tablet\n *\n * @public\n * @korean 모바일확인\n */\nexport function isMobileDevice(): boolean {\n const platform = detectPlatform();\n return platform.isMobile || platform.isTablet;\n}\n\n/**\n * Check if device should use mobile controls\n * Takes into account device type, screen size, and touch capability\n *\n * Uses user-agent detection to correctly identify mobile devices regardless\n * of screen resolution. This ensures high-end Android 15/16 phones with\n * 2K/4K displays (1200px+, 1440px+) show mobile controls.\n *\n * Also ensures tablets and touch-enabled devices always show mobile controls\n * regardless of resolution, for better UX on touch devices.\n *\n * @returns True if mobile controls should be shown\n *\n * @example\n * ```typescript\n * // High-res Android phone (1440x3168) → returns true via user-agent\n * // Desktop with 1440px screen → returns false (no mobile user-agent)\n * // iPad Pro 12.9\" (1024x1366) → returns true (tablet user-agent)\n * // Surface Pro in tablet mode → returns true (touch + tablet size)\n * if (shouldUseMobileControls()) {\n * return <VirtualDPad />; // Touch-optimized controls\n * }\n * ```\n *\n * @public\n * @korean 모바일컨트롤사용\n */\nexport function shouldUseMobileControls(): boolean {\n const platform = detectPlatform();\n\n // Always use mobile controls on phones\n if (platform.isMobile) {\n return true;\n }\n\n // Use mobile controls on tablets (better touch experience)\n if (platform.isTablet) {\n return true;\n }\n\n // Use mobile controls on any touch-enabled device up to tablet breakpoint\n // This catches Windows tablets, Chromebooks in tablet mode, etc.\n if (platform.hasTouch && platform.screenWidth <= TABLET_BREAKPOINT) {\n return true;\n }\n\n // Use mobile controls on small desktop screens with touch\n if (platform.screenWidth <= MOBILE_BREAKPOINT && platform.hasTouch) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Get safe area insets for device\n * Returns appropriate values based on device type and OS\n *\n * For iOS devices, attempts to detect if device has a notch by checking\n * screen dimensions. Falls back to CSS environment variables if available.\n *\n * @returns Safe area insets in pixels\n *\n * @public\n * @korean 안전영역인셋\n */\nexport function getSafeAreaInsets() {\n const platform = detectPlatform();\n\n // iOS devices - distinguish between notched and non-notched\n if (platform.os === \"ios\" && platform.isMobile) {\n // Try to read CSS environment variables first (most accurate)\n const cssEnvInsets = readCSSEnvInsets();\n if (cssEnvInsets) {\n return {\n top: cssEnvInsets.top,\n bottom: cssEnvInsets.bottom,\n left: 0,\n right: 0,\n };\n }\n\n // Detect orientation\n const isLandscape = platform.screenWidth > platform.screenHeight;\n\n // Heuristic: iPhone X and later have notches and specific screen dimensions\n // iPhone X/XS/11 Pro: 375x812, iPhone XR/11: 414x896, iPhone 12/13/14: 390x844, etc.\n // Only devices with height >= 812 (portrait) or width >= 812 (landscape) have notches\n const hasNotch =\n platform.screenHeight >= 812 || platform.screenWidth >= 812;\n\n if (hasNotch) {\n if (isLandscape) {\n // In landscape, notch is on the side\n return {\n top: 0,\n bottom: 21, // Home indicator\n left: 44, // Notch side\n right: 44, // Opposite side for symmetry\n };\n } else {\n // In portrait, notch is at top\n return {\n top: 44,\n bottom: 34,\n left: 0,\n right: 0,\n };\n }\n } else {\n // Older iPhones without notch (iPhone 8, SE, etc.)\n return {\n top: 20, // Status bar height\n bottom: 0,\n left: 0,\n right: 0,\n };\n }\n }\n\n // Android devices (standard status bar)\n if (platform.os === \"android\" && platform.isMobile) {\n return {\n top: 24,\n bottom: 0,\n left: 0,\n right: 0,\n };\n }\n\n // Tablets and desktop - no safe area needed\n return {\n top: 0,\n bottom: 0,\n left: 0,\n right: 0,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"deviceDetection.js","names":[],"sources":["../../src/utils/deviceDetection.ts"],"sourcesContent":["/**\n * Device Detection Utility\n *\n * Provides robust mobile device detection combining:\n * - User-agent string analysis\n * - Screen size detection\n * - Touch capability detection\n *\n * This ensures mobile controls are shown on all mobile devices,\n * including high-resolution phones that exceed typical mobile breakpoints.\n *\n * @module utils/deviceDetection\n * @category Mobile\n * @korean 기기감지유틸리티\n */\n\n/**\n * Device type classification\n */\nexport enum DeviceType {\n /** Desktop computer or laptop */\n DESKTOP = \"desktop\",\n /** Mobile phone (iOS, Android, etc.) */\n MOBILE = \"mobile\",\n /** Tablet device (iPad, Android tablets) */\n TABLET = \"tablet\",\n}\n\n/**\n * Platform detection results\n */\nexport interface PlatformInfo {\n /** Operating system type */\n readonly os: \"ios\" | \"android\" | \"windows\" | \"macos\" | \"linux\" | \"unknown\";\n /** Device type classification */\n readonly deviceType: DeviceType;\n /** Whether device has touch capability */\n readonly hasTouch: boolean;\n /** Whether device is mobile phone */\n readonly isMobile: boolean;\n /** Whether device is tablet */\n readonly isTablet: boolean;\n /** Whether device is desktop */\n readonly isDesktop: boolean;\n /** Screen width in pixels */\n readonly screenWidth: number;\n /** Screen height in pixels */\n readonly screenHeight: number;\n}\n\n/**\n * Detect if user-agent indicates a mobile device\n * Checks for common mobile device identifiers in user-agent string\n *\n * @param userAgent - Browser user-agent string\n * @returns True if user-agent indicates mobile device\n */\nfunction isMobileUserAgent(userAgent: string): boolean {\n const mobileKeywords = [\n \"Android\",\n \"webOS\",\n \"iPhone\",\n // 'iPad' is handled separately in isTabletUserAgent\n \"iPod\",\n \"BlackBerry\",\n \"IEMobile\",\n \"Opera Mini\",\n \"Mobile\",\n \"mobile\",\n ];\n\n return mobileKeywords.some((keyword) => userAgent.includes(keyword));\n}\n\n/**\n * Detect if user-agent indicates a tablet device\n *\n * @param userAgent - Browser user-agent string\n * @returns True if user-agent indicates tablet\n */\nfunction isTabletUserAgent(userAgent: string): boolean {\n // iPad is always a tablet\n if (userAgent.includes(\"iPad\")) {\n return true;\n }\n\n // Android tablets typically include \"Tablet\" or have Mobile absent\n if (userAgent.includes(\"Android\")) {\n return userAgent.includes(\"Tablet\") || !userAgent.includes(\"Mobile\");\n }\n\n return false;\n}\n\n/**\n * Detect operating system from user-agent\n *\n * @param userAgent - Browser user-agent string\n * @returns Operating system identifier\n */\nfunction detectOS(userAgent: string): PlatformInfo[\"os\"] {\n if (\n userAgent.includes(\"iPhone\") ||\n userAgent.includes(\"iPad\") ||\n userAgent.includes(\"iPod\")\n ) {\n return \"ios\";\n }\n if (userAgent.includes(\"Android\")) {\n return \"android\";\n }\n if (userAgent.includes(\"Windows\")) {\n return \"windows\";\n }\n if (userAgent.includes(\"Mac\")) {\n // Handle iPadOS 13+ in desktop mode, which reports a Mac-like user agent\n // e.g. \"Macintosh; Intel Mac OS X\" but still has touch support\n const isLikelyIPadOSDesktop =\n typeof navigator !== \"undefined\" &&\n typeof navigator.maxTouchPoints === \"number\" &&\n navigator.maxTouchPoints > 1 &&\n userAgent.includes(\"Macintosh\");\n\n if (isLikelyIPadOSDesktop) {\n return \"ios\";\n }\n return \"macos\";\n }\n if (userAgent.includes(\"Linux\")) {\n return \"linux\";\n }\n return \"unknown\";\n}\n\n/**\n * Detect if device has touch capability\n * Uses multiple methods for reliability\n *\n * @returns True if touch is supported\n */\nfunction hasTouchSupport(): boolean {\n // Check for touch events support\n if (\"ontouchstart\" in window) {\n return true;\n }\n\n // Check for touch points (must be defined and > 0)\n if (\n typeof navigator !== \"undefined\" &&\n typeof navigator.maxTouchPoints !== \"undefined\" &&\n navigator.maxTouchPoints > 0\n ) {\n return true;\n }\n\n // Check for pointer events with touch\n if (\n typeof window !== \"undefined\" &&\n window.matchMedia?.(\"(pointer: coarse)\")?.matches\n ) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Mobile screen size breakpoint\n * Devices with width <= this value are considered mobile by size\n */\nexport const MOBILE_BREAKPOINT = 768;\n\n/**\n * Tablet screen size breakpoint\n * Devices with width > MOBILE_BREAKPOINT and <= TABLET_BREAKPOINT are tablets\n */\nexport const TABLET_BREAKPOINT = 1024;\n\n/**\n * Cached CSS environment variable insets\n */\nlet cachedCSSEnvInsets: { top: number; bottom: number } | null = null;\n\n/**\n * Read CSS environment variables for safe area insets\n * Results are cached as they don't change during a session\n */\nfunction readCSSEnvInsets(): { top: number; bottom: number } | null {\n if (cachedCSSEnvInsets !== null) {\n return cachedCSSEnvInsets;\n }\n\n if (typeof window !== \"undefined\" && typeof getComputedStyle === \"function\") {\n try {\n const root = document.documentElement;\n const style = getComputedStyle(root);\n const topEnv = style.getPropertyValue(\"env(safe-area-inset-top)\");\n const bottomEnv = style.getPropertyValue(\"env(safe-area-inset-bottom)\");\n\n if (topEnv || bottomEnv) {\n const result = {\n top: parseInt(topEnv || \"0\", 10) || 0,\n bottom: parseInt(bottomEnv || \"0\", 10) || 0,\n };\n cachedCSSEnvInsets = result;\n return result;\n }\n } catch {\n // Fall through to null\n }\n }\n\n return null;\n}\n\n/**\n * Cached platform information to avoid re-parsing user-agent on every call\n */\nlet cachedPlatform: PlatformInfo | null = null;\nlet cachedScreenWidth = 0;\nlet cachedScreenHeight = 0;\n\n/**\n * Clear the cached platform information\n * Useful when window is resized or device emulation changes\n * Also clears CSS environment variable cache\n *\n * @public\n */\nexport function clearPlatformCache(): void {\n cachedPlatform = null;\n cachedScreenWidth = 0;\n cachedScreenHeight = 0;\n cachedCSSEnvInsets = null;\n}\n\n/**\n * Detect device type and platform information\n *\n * Combines multiple detection methods for reliability:\n * 1. User-agent string analysis (most reliable for device type)\n * 2. Screen dimensions\n * 3. Touch capability\n *\n * This ensures mobile controls are shown on:\n * - Standard mobile phones (< 768px width)\n * - High-resolution phones (>= 768px width but mobile user-agent)\n * - Android 15/16 devices with 2K/4K resolutions (1200px+, 1440px+)\n * - Tablets (user preference via touch support)\n *\n * **User-agent detection takes priority over screen size**, ensuring that\n * high-end Android phones with desktop-class resolutions (e.g., Galaxy S23 Ultra,\n * Pixel 9 Pro) are correctly identified as mobile devices.\n *\n * Results are cached to avoid re-parsing user-agent on every call.\n * Cache is invalidated when screen dimensions change.\n *\n * @returns Complete platform information\n *\n * @example\n * ```typescript\n * const platform = detectPlatform();\n *\n * if (platform.isMobile) {\n * // Show mobile controls even on 4K Android phones\n * return <MobileControls />;\n * }\n * ```\n *\n * @public\n * @korean 플랫폼감지\n */\nexport function detectPlatform(): PlatformInfo {\n const userAgent = typeof navigator !== \"undefined\" ? navigator.userAgent : \"\";\n const screenWidth = typeof window !== \"undefined\" ? window.innerWidth : 1920;\n const screenHeight =\n typeof window !== \"undefined\" ? window.innerHeight : 1080;\n\n // Return cached result if screen dimensions haven't changed\n if (\n cachedPlatform !== null &&\n cachedScreenWidth === screenWidth &&\n cachedScreenHeight === screenHeight\n ) {\n return cachedPlatform;\n }\n\n // Detect OS\n const os = detectOS(userAgent);\n\n // Detect touch capability\n const hasTouch = hasTouchSupport();\n\n // Detect if mobile by user-agent (most reliable method)\n const isMobileUA = isMobileUserAgent(userAgent);\n\n // Detect if tablet by user-agent\n const isTabletUA = isTabletUserAgent(userAgent);\n\n // Detect by screen size (fallback method)\n const isMobileBySize = screenWidth <= MOBILE_BREAKPOINT;\n const isTabletBySize =\n screenWidth > MOBILE_BREAKPOINT && screenWidth <= TABLET_BREAKPOINT;\n\n // Determine device type\n // Priority: User-agent > Screen size\n let deviceType: DeviceType;\n let isMobile: boolean;\n let isTablet: boolean;\n\n if (isMobileUA && !isTabletUA) {\n // User-agent indicates phone\n deviceType = DeviceType.MOBILE;\n isMobile = true;\n isTablet = false;\n } else if (isTabletUA) {\n // User-agent indicates tablet\n deviceType = DeviceType.TABLET;\n isMobile = false;\n isTablet = true;\n } else if (isMobileBySize) {\n // Small screen, assume mobile\n deviceType = DeviceType.MOBILE;\n isMobile = true;\n isTablet = false;\n } else if (isTabletBySize && hasTouch) {\n // Medium screen with touch, assume tablet\n deviceType = DeviceType.TABLET;\n isMobile = false;\n isTablet = true;\n } else {\n // Desktop\n deviceType = DeviceType.DESKTOP;\n isMobile = false;\n isTablet = false;\n }\n\n const isDesktop = deviceType === DeviceType.DESKTOP;\n\n const result: PlatformInfo = {\n os,\n deviceType,\n hasTouch,\n isMobile,\n isTablet,\n isDesktop,\n screenWidth,\n screenHeight,\n };\n\n // Cache the result along with screen dimensions\n cachedPlatform = result;\n cachedScreenWidth = screenWidth;\n cachedScreenHeight = screenHeight;\n\n return result;\n}\n\n/**\n * Simple mobile check for backward compatibility\n * Returns true for both mobile phones and tablets\n *\n * @returns True if device is mobile or tablet\n *\n * @public\n * @korean 모바일확인\n */\nexport function isMobileDevice(): boolean {\n const platform = detectPlatform();\n return platform.isMobile || platform.isTablet;\n}\n\n/**\n * Check if device should use mobile controls\n * Takes into account device type, screen size, and touch capability\n *\n * Uses user-agent detection to correctly identify mobile devices regardless\n * of screen resolution. This ensures high-end Android 15/16 phones with\n * 2K/4K displays (1200px+, 1440px+) show mobile controls.\n *\n * Also ensures tablets and touch-enabled devices always show mobile controls\n * regardless of resolution, for better UX on touch devices.\n *\n * @returns True if mobile controls should be shown\n *\n * @example\n * ```typescript\n * // High-res Android phone (1440x3168) → returns true via user-agent\n * // Desktop with 1440px screen → returns false (no mobile user-agent)\n * // iPad Pro 12.9\" (1024x1366) → returns true (tablet user-agent)\n * // Surface Pro in tablet mode → returns true (touch + tablet size)\n * if (shouldUseMobileControls()) {\n * return <VirtualDPad />; // Touch-optimized controls\n * }\n * ```\n *\n * @public\n * @korean 모바일컨트롤사용\n */\nexport function shouldUseMobileControls(): boolean {\n const platform = detectPlatform();\n\n // Always use mobile controls on phones\n if (platform.isMobile) {\n return true;\n }\n\n // Use mobile controls on tablets (better touch experience)\n if (platform.isTablet) {\n return true;\n }\n\n // Use mobile controls on any touch-enabled device up to tablet breakpoint\n // This catches Windows tablets, Chromebooks in tablet mode, etc.\n if (platform.hasTouch && platform.screenWidth <= TABLET_BREAKPOINT) {\n return true;\n }\n\n // Use mobile controls on small desktop screens with touch\n if (platform.screenWidth <= MOBILE_BREAKPOINT && platform.hasTouch) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Get safe area insets for device\n * Returns appropriate values based on device type and OS\n *\n * For iOS devices, attempts to detect if device has a notch by checking\n * screen dimensions. Falls back to CSS environment variables if available.\n *\n * @returns Safe area insets in pixels\n *\n * @public\n * @korean 안전영역인셋\n */\nexport function getSafeAreaInsets() {\n const platform = detectPlatform();\n\n // iOS devices - distinguish between notched and non-notched\n if (platform.os === \"ios\" && platform.isMobile) {\n // Try to read CSS environment variables first (most accurate)\n const cssEnvInsets = readCSSEnvInsets();\n if (cssEnvInsets) {\n return {\n top: cssEnvInsets.top,\n bottom: cssEnvInsets.bottom,\n left: 0,\n right: 0,\n };\n }\n\n // Detect orientation\n const isLandscape = platform.screenWidth > platform.screenHeight;\n\n // Heuristic: iPhone X and later have notches and specific screen dimensions\n // iPhone X/XS/11 Pro: 375x812, iPhone XR/11: 414x896, iPhone 12/13/14: 390x844, etc.\n // Only devices with height >= 812 (portrait) or width >= 812 (landscape) have notches\n const hasNotch =\n platform.screenHeight >= 812 || platform.screenWidth >= 812;\n\n if (hasNotch) {\n if (isLandscape) {\n // In landscape, notch is on the side\n return {\n top: 0,\n bottom: 21, // Home indicator\n left: 44, // Notch side\n right: 44, // Opposite side for symmetry\n };\n } else {\n // In portrait, notch is at top\n return {\n top: 44,\n bottom: 34,\n left: 0,\n right: 0,\n };\n }\n } else {\n // Older iPhones without notch (iPhone 8, SE, etc.)\n return {\n top: 20, // Status bar height\n bottom: 0,\n left: 0,\n right: 0,\n };\n }\n }\n\n // Android devices (standard status bar)\n if (platform.os === \"android\" && platform.isMobile) {\n return {\n top: 24,\n bottom: 0,\n left: 0,\n right: 0,\n };\n }\n\n // Tablets and desktop - no safe area needed\n return {\n top: 0,\n bottom: 0,\n left: 0,\n right: 0,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effectUtils.js","names":[],"sources":["../../src/utils/effectUtils.ts"],"sourcesContent":["import { HitEffect, StatusEffect } from \"../systems\";\nimport { EffectIntensity, EffectType, HitEffectType } from \"../systems/effects\";\nimport type { KoreanText, Position } from \"../types\";\n\n/**\n * Create a hit effect for visual feedback\n */\nexport function createHitEffect(\n id: string,\n type: HitEffectType,\n position: Position,\n intensity: number,\n duration: number = 1000\n): HitEffect {\n return {\n id,\n type,\n attackerId: \"unknown\",\n defenderId: \"unknown\",\n timestamp: Date.now(),\n duration,\n position,\n intensity,\n lifespan: duration,\n alpha: 1.0,\n size: 1.0,\n startTime: Date.now(),\n };\n}\n\n/**\n * Get hit effect color based on type\n */\nexport function getHitEffectColor(type: HitEffectType): number {\n switch (type) {\n case HitEffectType.CRITICAL_HIT:\n return 0xff6b00; // Orange\n case HitEffectType.VITAL_POINT_STRIKE:\n return 0xff0000; // Red\n case HitEffectType.BLOCK:\n return 0x4a90e2; // Blue\n case HitEffectType.MISS:\n return 0x888888; // Gray\n default:\n return 0xffffff; // White\n }\n}\n\n/**\n * Create a status effect\n */\nexport function createStatusEffect(\n id: string,\n type: EffectType,\n intensity: EffectIntensity,\n duration: number,\n description: KoreanText,\n source: string = \"unknown\"\n): StatusEffect {\n const currentTime = Date.now();\n return {\n id,\n type,\n intensity,\n duration,\n description,\n stackable: false,\n source,\n startTime: currentTime,\n endTime: currentTime + duration,\n };\n}\n\n/**\n * Calculate effect intensity based on damage\n */\nexport function calculateEffectIntensity(damage: number): EffectIntensity {\n // Fix: Use proper EffectIntensity values that match the type definition\n if (damage >= 80) return \"severe\" as EffectIntensity; // was \"extreme\"\n if (damage >= 50) return \"severe\" as EffectIntensity; // was \"critical\"\n if (damage >= 30) return \"moderate\" as EffectIntensity; // was \"high\"\n if (damage >= 15) return \"medium\" as EffectIntensity; // this one is correct\n if (damage >= 5) return \"minor\" as EffectIntensity; // was \"low\"\n return \"weak\" as EffectIntensity; // this one is correct\n}\n\n/**\n * Get effect duration modifier based on type\n */\nexport function getEffectDurationModifier(type: EffectType): number {\n switch (type) {\n case \"bleed\": // Fix: Use string literal instead of enum\n return 1.5;\n case \"poison\":\n return 2.0;\n case \"stun\":\n return 0.5;\n case \"burn\":\n return 1.2;\n default:\n return 1.0;\n }\n}\n\n/**\n * Apply effect to player stats\n */\nexport function applyEffectModifiers(\n baseValue: number,\n effects: readonly StatusEffect[],\n statType: \"attack\" | \"defense\" | \"speed\"\n): number {\n let modifier = 1.0;\n\n effects.forEach((effect) => {\n switch (effect.type) {\n case \"bleed\":\n if (statType === \"attack\") modifier *= 0.9;\n break;\n case \"strengthened\":\n if (statType === \"attack\") modifier *= 1.2;\n break;\n case \"weakened\":\n modifier *= 0.8;\n break;\n case \"exhausted\":\n if (statType === \"speed\") modifier *= 0.7;\n break;\n }\n });\n\n return Math.floor(baseValue * modifier);\n}\n\n/**\n * Check if effect is beneficial or harmful\n */\nexport function isEffectBeneficial(type: EffectType): boolean {\n const beneficialEffects: EffectType[] = [\n \"focused\",\n \"rage\",\n \"defensive\",\n \"strengthened\",\n ];\n return beneficialEffects.includes(type);\n}\n\n/**\n * Get effect description based on type and intensity\n */\nexport function getEffectDescription(\n type: EffectType,\n intensity: EffectIntensity\n): KoreanText {\n const descriptions: Record<\n EffectType,\n Record<EffectIntensity, KoreanText>\n > = {\n stun: {\n weak: { korean: \"가벼운 기절\", english: \"Light Stun\" },\n minor: { korean: \"경미한 기절\", english: \"Minor Stun\" },\n medium: { korean: \"기절\", english: \"Stun\" },\n moderate: { korean: \"기절\", english: \"Stun\" },\n high: { korean: \"심한 기절\", english: \"Heavy Stun\" },\n severe: { korean: \"심한 기절\", english: \"Severe Stun\" },\n critical: { korean: \"완전 기절\", english: \"Complete Stun\" },\n extreme: { korean: \"완전 기절\", english: \"Complete Stun\" },\n low: { korean: \"약한 기절\", english: \"Weak Stun\" },\n },\n poison: {\n weak: { korean: \"경미한 중독\", english: \"Minor Poison\" },\n minor: { korean: \"경미한 중독\", english: \"Minor Poison\" },\n medium: { korean: \"중독\", english: \"Poison\" },\n moderate: { korean: \"중독\", english: \"Poison\" },\n high: { korean: \"심한 중독\", english: \"Severe Poison\" },\n severe: { korean: \"심한 중독\", english: \"Severe Poison\" },\n critical: { korean: \"치명적 중독\", english: \"Lethal Poison\" },\n extreme: { korean: \"치명적 중독\", english: \"Lethal Poison\" },\n low: { korean: \"약한 중독\", english: \"Weak Poison\" },\n },\n weakened: {\n weak: { korean: \"약간 약화\", english: \"Slightly Weakened\" },\n minor: { korean: \"경미한 약화\", english: \"Minor Weakness\" },\n medium: { korean: \"약화\", english: \"Weakened\" },\n moderate: { korean: \"약화\", english: \"Weakened\" },\n high: { korean: \"심하게 약화\", english: \"Severely Weakened\" },\n severe: { korean: \"심하게 약화\", english: \"Severely Weakened\" },\n critical: { korean: \"극도로 약화\", english: \"Critically Weakened\" },\n extreme: { korean: \"극도로 약화\", english: \"Critically Weakened\" },\n low: { korean: \"약간 약화\", english: \"Slightly Weakened\" },\n },\n burn: {\n weak: { korean: \"가벼운 화상\", english: \"Minor Burn\" },\n minor: { korean: \"경미한 화상\", english: \"Minor Burn\" },\n medium: { korean: \"화상\", english: \"Burn\" },\n moderate: { korean: \"화상\", english: \"Burn\" },\n high: { korean: \"심한 화상\", english: \"Severe Burn\" },\n severe: { korean: \"심한 화상\", english: \"Severe Burn\" },\n critical: { korean: \"치명적 화상\", english: \"Critical Burn\" },\n extreme: { korean: \"치명적 화상\", english: \"Critical Burn\" },\n low: { korean: \"약한 화상\", english: \"Weak Burn\" },\n },\n bleed: {\n weak: { korean: \"가벼운 출혈\", english: \"Minor Bleeding\" },\n minor: { korean: \"경미한 출혈\", english: \"Minor Bleeding\" },\n medium: { korean: \"출혈\", english: \"Bleeding\" },\n moderate: { korean: \"출혈\", english: \"Bleeding\" },\n high: { korean: \"심한 출혈\", english: \"Heavy Bleeding\" },\n severe: { korean: \"심한 출혈\", english: \"Severe Bleeding\" },\n critical: { korean: \"치명적 출혈\", english: \"Critical Bleeding\" },\n extreme: { korean: \"치명적 출혈\", english: \"Critical Bleeding\" },\n low: { korean: \"약한 출혈\", english: \"Weak Bleeding\" },\n },\n exhausted: {\n weak: { korean: \"약간 지침\", english: \"Slightly Tired\" },\n minor: { korean: \"경미한 피로\", english: \"Minor Fatigue\" },\n medium: { korean: \"지침\", english: \"Exhausted\" },\n moderate: { korean: \"지침\", english: \"Exhausted\" },\n high: { korean: \"심하게 지침\", english: \"Severely Exhausted\" },\n severe: { korean: \"심하게 지침\", english: \"Severely Exhausted\" },\n critical: { korean: \"완전 탈진\", english: \"Completely Drained\" },\n extreme: { korean: \"완전 탈진\", english: \"Completely Drained\" },\n low: { korean: \"약간 피로\", english: \"Slightly Fatigued\" },\n },\n focused: {\n weak: { korean: \"약간 집중\", english: \"Slightly Focused\" },\n minor: { korean: \"경미한 집중\", english: \"Minor Focus\" },\n medium: { korean: \"집중\", english: \"Focused\" },\n moderate: { korean: \"집중\", english: \"Focused\" },\n high: { korean: \"깊은 집중\", english: \"Deeply Focused\" },\n severe: { korean: \"깊은 집중\", english: \"Deeply Focused\" },\n critical: { korean: \"완전 집중\", english: \"Completely Focused\" },\n extreme: { korean: \"완전 집중\", english: \"Completely Focused\" },\n low: { korean: \"약한 집중\", english: \"Weak Focus\" },\n },\n rage: {\n weak: { korean: \"약간 분노\", english: \"Slightly Enraged\" },\n minor: { korean: \"경미한 분노\", english: \"Minor Rage\" },\n medium: { korean: \"분노\", english: \"Enraged\" },\n moderate: { korean: \"분노\", english: \"Enraged\" },\n high: { korean: \"맹렬한 분노\", english: \"Furious\" },\n severe: { korean: \"맹렬한 분노\", english: \"Furious\" },\n critical: { korean: \"광분\", english: \"Berserk\" },\n extreme: { korean: \"광분\", english: \"Berserk\" },\n low: { korean: \"약한 분노\", english: \"Weak Rage\" },\n },\n defensive: {\n weak: { korean: \"약간 방어적\", english: \"Slightly Defensive\" },\n minor: { korean: \"경미한 방어\", english: \"Minor Defense\" },\n medium: { korean: \"방어적\", english: \"Defensive\" },\n moderate: { korean: \"방어적\", english: \"Defensive\" },\n high: { korean: \"높은 방어\", english: \"Highly Defensive\" },\n severe: { korean: \"높은 방어\", english: \"Highly Defensive\" },\n critical: { korean: \"완벽한 방어\", english: \"Perfect Defense\" },\n extreme: { korean: \"완벽한 방어\", english: \"Perfect Defense\" },\n low: { korean: \"약한 방어\", english: \"Weak Defense\" },\n },\n strengthened: {\n weak: { korean: \"약간 강화\", english: \"Slightly Strengthened\" },\n minor: { korean: \"경미한 강화\", english: \"Minor Strength\" },\n medium: { korean: \"강화\", english: \"Strengthened\" },\n moderate: { korean: \"강화\", english: \"Strengthened\" },\n high: { korean: \"크게 강화\", english: \"Greatly Strengthened\" },\n severe: { korean: \"크게 강화\", english: \"Greatly Strengthened\" },\n critical: { korean: \"극대 강화\", english: \"Maximally Strengthened\" },\n extreme: { korean: \"극대 강화\", english: \"Maximally Strengthened\" },\n low: { korean: \"약한 강화\", english: \"Weak Strength\" },\n },\n paralysis: {\n weak: { korean: \"가벼운 마비\", english: \"Minor Paralysis\" },\n minor: { korean: \"경미한 마비\", english: \"Minor Paralysis\" },\n medium: { korean: \"마비\", english: \"Paralysis\" },\n moderate: { korean: \"마비\", english: \"Paralysis\" },\n high: { korean: \"심한 마비\", english: \"Severe Paralysis\" },\n severe: { korean: \"심한 마비\", english: \"Severe Paralysis\" },\n critical: { korean: \"완전 마비\", english: \"Complete Paralysis\" },\n extreme: { korean: \"완전 마비\", english: \"Complete Paralysis\" },\n low: { korean: \"약한 마비\", english: \"Weak Paralysis\" },\n },\n confusion: {\n weak: { korean: \"약간 혼란\", english: \"Slight Confusion\" },\n minor: { korean: \"경미한 혼란\", english: \"Minor Confusion\" },\n medium: { korean: \"혼란\", english: \"Confusion\" },\n moderate: { korean: \"혼란\", english: \"Confusion\" },\n high: { korean: \"심한 혼란\", english: \"Severe Confusion\" },\n severe: { korean: \"심한 혼란\", english: \"Severe Confusion\" },\n critical: { korean: \"완전 혼란\", english: \"Complete Confusion\" },\n extreme: { korean: \"완전 혼란\", english: \"Complete Confusion\" },\n low: { korean: \"약한 혼란\", english: \"Weak Confusion\" },\n },\n vulnerability: {\n weak: { korean: \"약간 취약\", english: \"Slightly Vulnerable\" },\n minor: { korean: \"경미한 취약\", english: \"Minor Vulnerability\" },\n medium: { korean: \"취약\", english: \"Vulnerable\" },\n moderate: { korean: \"취약\", english: \"Vulnerable\" },\n high: { korean: \"매우 취약\", english: \"Very Vulnerable\" },\n severe: { korean: \"매우 취약\", english: \"Very Vulnerable\" },\n critical: { korean: \"극도로 취약\", english: \"Extremely Vulnerable\" },\n extreme: { korean: \"극도로 취약\", english: \"Extremely Vulnerable\" },\n low: { korean: \"약한 취약\", english: \"Weak Vulnerability\" },\n },\n stamina_drain: {\n weak: { korean: \"체력 소모\", english: \"Stamina Drain\" },\n minor: { korean: \"경미한 체력 소모\", english: \"Minor Stamina Drain\" },\n medium: { korean: \"체력 고갈\", english: \"Stamina Depletion\" },\n moderate: { korean: \"체력 고갈\", english: \"Stamina Depletion\" },\n high: { korean: \"심한 체력 고갈\", english: \"Severe Stamina Loss\" },\n severe: { korean: \"심한 체력 고갈\", english: \"Severe Stamina Loss\" },\n critical: { korean: \"완전 체력 고갈\", english: \"Complete Stamina Loss\" },\n extreme: { korean: \"완전 체력 고갈\", english: \"Complete Stamina Loss\" },\n low: { korean: \"약한 체력 소모\", english: \"Weak Stamina Drain\" },\n },\n };\n\n return (\n descriptions[type]?.[intensity] || {\n korean: `${type} 효과`,\n english: `${type} effect`,\n }\n );\n}\n\n/**\n * Update effect over time\n */\nexport function updateEffect(\n effect: StatusEffect,\n currentTime: number\n): StatusEffect | null {\n if (currentTime >= effect.endTime) {\n return null; // Effect has expired\n }\n\n return effect; // Effect is still active\n}\n\n/**\n * Combine multiple effects of same type\n */\nexport function combineEffects(effects: StatusEffect[]): StatusEffect[] {\n const combinedEffects: StatusEffect[] = [];\n // Fix: Use string type instead of EffectType since StatusEffect.type is string\n const effectsByType = new Map<string, StatusEffect[]>();\n\n // Group effects by type\n effects.forEach((effect) => {\n // Fix: effect.type is string, so use string methods\n if (!effectsByType.has(effect.type)) {\n effectsByType.set(effect.type, []);\n }\n const typeEffects = effectsByType.get(effect.type);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n });\n\n // Combine stackable effects, keep latest non-stackable\n effectsByType.forEach((typeEffects, _) => {\n if (typeEffects.length === 0) return;\n\n const firstEffect = typeEffects[0];\n if (firstEffect.stackable) {\n // Keep all stackable effects\n combinedEffects.push(...typeEffects);\n } else {\n // Keep only the latest non-stackable effect\n const latestEffect = typeEffects.reduce((latest, current) =>\n current.startTime > latest.startTime ? current : latest\n );\n combinedEffects.push(latestEffect);\n }\n });\n\n return combinedEffects;\n}\n\n/**\n * Remove expired effects\n */\nexport function removeExpiredEffects(effects: StatusEffect[]): StatusEffect[] {\n const currentTime = Date.now();\n return effects.filter((effect) => effect.endTime > currentTime);\n}\n\n/**\n * Create effect from Korean text\n */\nexport function createEffectFromKoreanText(\n koreanText: string,\n englishText: string,\n duration: number = 3000\n): StatusEffect {\n // Fix: Provide all required arguments\n return createStatusEffect(\n koreanText.toLowerCase(),\n \"stun\" as EffectType, // Use proper EffectType\n \"medium\" as EffectIntensity,\n duration,\n {\n korean: koreanText,\n english: englishText,\n }\n );\n}\n\n/**\n * Get effect display text\n */\nexport function getEffectDisplayText(\n effect: StatusEffect,\n preferKorean: boolean = true\n): string {\n return preferKorean ? effect.description.korean : effect.description.english;\n}\n\n// Fix: Korean martial arts specific effect utilities\nexport function createKoreanStatusEffect(\n koreanName: string,\n englishName: string,\n intensity: EffectIntensity = \"medium\" as EffectIntensity,\n duration: number = 3000\n): StatusEffect {\n return createStatusEffect(\n koreanName.toLowerCase().replace(/\\s+/g, \"_\"),\n \"stun\" as EffectType, // This is correct - keep as is\n intensity,\n duration,\n { korean: koreanName, english: englishName }\n );\n}\n\nexport function createTrigramEffect(\n stanceName: string,\n effectType: EffectType,\n duration: number = 2000\n): StatusEffect {\n return createStatusEffect(\n `trigram_${stanceName}_${effectType}`,\n effectType, // This is correct - use the parameter directly\n \"medium\" as EffectIntensity,\n duration,\n {\n korean: `${stanceName}괘 효과`,\n english: `${stanceName} trigram effect`,\n }\n );\n}\n\n// Fix: Group effects by type using proper typing\nexport function groupEffectsByTypeEnum(\n effects: StatusEffect[]\n): Map<EffectType, StatusEffect[]> {\n const effectsByType = new Map<EffectType, StatusEffect[]>();\n\n for (const effect of effects) {\n // Cast string to EffectType for enum operations\n const effectType = effect.type as EffectType;\n if (!effectsByType.has(effectType)) {\n effectsByType.set(effectType, []);\n }\n const typeEffects = effectsByType.get(effectType);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n }\n\n return effectsByType;\n}\n\n/**\n * Group effects by type using string keys (works with StatusEffect.type)\n */\nexport function groupEffectsByType(\n effects: StatusEffect[]\n): Map<string, StatusEffect[]> {\n const effectsByType = new Map<string, StatusEffect[]>();\n\n for (const effect of effects) {\n // Fix: Use string type directly since StatusEffect.type is string\n if (!effectsByType.has(effect.type)) {\n effectsByType.set(effect.type, []);\n }\n const typeEffects = effectsByType.get(effect.type);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n }\n\n return effectsByType;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"effectUtils.js","names":[],"sources":["../../src/utils/effectUtils.ts"],"sourcesContent":["import { HitEffect, StatusEffect } from \"../systems\";\nimport { EffectIntensity, EffectType, HitEffectType } from \"../systems/effects\";\nimport type { KoreanText, Position } from \"../types\";\n\n/**\n * Create a hit effect for visual feedback\n */\nexport function createHitEffect(\n id: string,\n type: HitEffectType,\n position: Position,\n intensity: number,\n duration: number = 1000\n): HitEffect {\n return {\n id,\n type,\n attackerId: \"unknown\",\n defenderId: \"unknown\",\n timestamp: Date.now(),\n duration,\n position,\n intensity,\n lifespan: duration,\n alpha: 1.0,\n size: 1.0,\n startTime: Date.now(),\n };\n}\n\n/**\n * Get hit effect color based on type\n */\nexport function getHitEffectColor(type: HitEffectType): number {\n switch (type) {\n case HitEffectType.CRITICAL_HIT:\n return 0xff6b00; // Orange\n case HitEffectType.VITAL_POINT_STRIKE:\n return 0xff0000; // Red\n case HitEffectType.BLOCK:\n return 0x4a90e2; // Blue\n case HitEffectType.MISS:\n return 0x888888; // Gray\n default:\n return 0xffffff; // White\n }\n}\n\n/**\n * Create a status effect\n */\nexport function createStatusEffect(\n id: string,\n type: EffectType,\n intensity: EffectIntensity,\n duration: number,\n description: KoreanText,\n source: string = \"unknown\"\n): StatusEffect {\n const currentTime = Date.now();\n return {\n id,\n type,\n intensity,\n duration,\n description,\n stackable: false,\n source,\n startTime: currentTime,\n endTime: currentTime + duration,\n };\n}\n\n/**\n * Calculate effect intensity based on damage\n */\nexport function calculateEffectIntensity(damage: number): EffectIntensity {\n // Fix: Use proper EffectIntensity values that match the type definition\n if (damage >= 80) return \"severe\" as EffectIntensity; // was \"extreme\"\n if (damage >= 50) return \"severe\" as EffectIntensity; // was \"critical\"\n if (damage >= 30) return \"moderate\" as EffectIntensity; // was \"high\"\n if (damage >= 15) return \"medium\" as EffectIntensity; // this one is correct\n if (damage >= 5) return \"minor\" as EffectIntensity; // was \"low\"\n return \"weak\" as EffectIntensity; // this one is correct\n}\n\n/**\n * Get effect duration modifier based on type\n */\nexport function getEffectDurationModifier(type: EffectType): number {\n switch (type) {\n case \"bleed\": // Fix: Use string literal instead of enum\n return 1.5;\n case \"poison\":\n return 2.0;\n case \"stun\":\n return 0.5;\n case \"burn\":\n return 1.2;\n default:\n return 1.0;\n }\n}\n\n/**\n * Apply effect to player stats\n */\nexport function applyEffectModifiers(\n baseValue: number,\n effects: readonly StatusEffect[],\n statType: \"attack\" | \"defense\" | \"speed\"\n): number {\n let modifier = 1.0;\n\n effects.forEach((effect) => {\n switch (effect.type) {\n case \"bleed\":\n if (statType === \"attack\") modifier *= 0.9;\n break;\n case \"strengthened\":\n if (statType === \"attack\") modifier *= 1.2;\n break;\n case \"weakened\":\n modifier *= 0.8;\n break;\n case \"exhausted\":\n if (statType === \"speed\") modifier *= 0.7;\n break;\n }\n });\n\n return Math.floor(baseValue * modifier);\n}\n\n/**\n * Check if effect is beneficial or harmful\n */\nexport function isEffectBeneficial(type: EffectType): boolean {\n const beneficialEffects: EffectType[] = [\n \"focused\",\n \"rage\",\n \"defensive\",\n \"strengthened\",\n ];\n return beneficialEffects.includes(type);\n}\n\n/**\n * Get effect description based on type and intensity\n */\nexport function getEffectDescription(\n type: EffectType,\n intensity: EffectIntensity\n): KoreanText {\n const descriptions: Record<\n EffectType,\n Record<EffectIntensity, KoreanText>\n > = {\n stun: {\n weak: { korean: \"가벼운 기절\", english: \"Light Stun\" },\n minor: { korean: \"경미한 기절\", english: \"Minor Stun\" },\n medium: { korean: \"기절\", english: \"Stun\" },\n moderate: { korean: \"기절\", english: \"Stun\" },\n high: { korean: \"심한 기절\", english: \"Heavy Stun\" },\n severe: { korean: \"심한 기절\", english: \"Severe Stun\" },\n critical: { korean: \"완전 기절\", english: \"Complete Stun\" },\n extreme: { korean: \"완전 기절\", english: \"Complete Stun\" },\n low: { korean: \"약한 기절\", english: \"Weak Stun\" },\n },\n poison: {\n weak: { korean: \"경미한 중독\", english: \"Minor Poison\" },\n minor: { korean: \"경미한 중독\", english: \"Minor Poison\" },\n medium: { korean: \"중독\", english: \"Poison\" },\n moderate: { korean: \"중독\", english: \"Poison\" },\n high: { korean: \"심한 중독\", english: \"Severe Poison\" },\n severe: { korean: \"심한 중독\", english: \"Severe Poison\" },\n critical: { korean: \"치명적 중독\", english: \"Lethal Poison\" },\n extreme: { korean: \"치명적 중독\", english: \"Lethal Poison\" },\n low: { korean: \"약한 중독\", english: \"Weak Poison\" },\n },\n weakened: {\n weak: { korean: \"약간 약화\", english: \"Slightly Weakened\" },\n minor: { korean: \"경미한 약화\", english: \"Minor Weakness\" },\n medium: { korean: \"약화\", english: \"Weakened\" },\n moderate: { korean: \"약화\", english: \"Weakened\" },\n high: { korean: \"심하게 약화\", english: \"Severely Weakened\" },\n severe: { korean: \"심하게 약화\", english: \"Severely Weakened\" },\n critical: { korean: \"극도로 약화\", english: \"Critically Weakened\" },\n extreme: { korean: \"극도로 약화\", english: \"Critically Weakened\" },\n low: { korean: \"약간 약화\", english: \"Slightly Weakened\" },\n },\n burn: {\n weak: { korean: \"가벼운 화상\", english: \"Minor Burn\" },\n minor: { korean: \"경미한 화상\", english: \"Minor Burn\" },\n medium: { korean: \"화상\", english: \"Burn\" },\n moderate: { korean: \"화상\", english: \"Burn\" },\n high: { korean: \"심한 화상\", english: \"Severe Burn\" },\n severe: { korean: \"심한 화상\", english: \"Severe Burn\" },\n critical: { korean: \"치명적 화상\", english: \"Critical Burn\" },\n extreme: { korean: \"치명적 화상\", english: \"Critical Burn\" },\n low: { korean: \"약한 화상\", english: \"Weak Burn\" },\n },\n bleed: {\n weak: { korean: \"가벼운 출혈\", english: \"Minor Bleeding\" },\n minor: { korean: \"경미한 출혈\", english: \"Minor Bleeding\" },\n medium: { korean: \"출혈\", english: \"Bleeding\" },\n moderate: { korean: \"출혈\", english: \"Bleeding\" },\n high: { korean: \"심한 출혈\", english: \"Heavy Bleeding\" },\n severe: { korean: \"심한 출혈\", english: \"Severe Bleeding\" },\n critical: { korean: \"치명적 출혈\", english: \"Critical Bleeding\" },\n extreme: { korean: \"치명적 출혈\", english: \"Critical Bleeding\" },\n low: { korean: \"약한 출혈\", english: \"Weak Bleeding\" },\n },\n exhausted: {\n weak: { korean: \"약간 지침\", english: \"Slightly Tired\" },\n minor: { korean: \"경미한 피로\", english: \"Minor Fatigue\" },\n medium: { korean: \"지침\", english: \"Exhausted\" },\n moderate: { korean: \"지침\", english: \"Exhausted\" },\n high: { korean: \"심하게 지침\", english: \"Severely Exhausted\" },\n severe: { korean: \"심하게 지침\", english: \"Severely Exhausted\" },\n critical: { korean: \"완전 탈진\", english: \"Completely Drained\" },\n extreme: { korean: \"완전 탈진\", english: \"Completely Drained\" },\n low: { korean: \"약간 피로\", english: \"Slightly Fatigued\" },\n },\n focused: {\n weak: { korean: \"약간 집중\", english: \"Slightly Focused\" },\n minor: { korean: \"경미한 집중\", english: \"Minor Focus\" },\n medium: { korean: \"집중\", english: \"Focused\" },\n moderate: { korean: \"집중\", english: \"Focused\" },\n high: { korean: \"깊은 집중\", english: \"Deeply Focused\" },\n severe: { korean: \"깊은 집중\", english: \"Deeply Focused\" },\n critical: { korean: \"완전 집중\", english: \"Completely Focused\" },\n extreme: { korean: \"완전 집중\", english: \"Completely Focused\" },\n low: { korean: \"약한 집중\", english: \"Weak Focus\" },\n },\n rage: {\n weak: { korean: \"약간 분노\", english: \"Slightly Enraged\" },\n minor: { korean: \"경미한 분노\", english: \"Minor Rage\" },\n medium: { korean: \"분노\", english: \"Enraged\" },\n moderate: { korean: \"분노\", english: \"Enraged\" },\n high: { korean: \"맹렬한 분노\", english: \"Furious\" },\n severe: { korean: \"맹렬한 분노\", english: \"Furious\" },\n critical: { korean: \"광분\", english: \"Berserk\" },\n extreme: { korean: \"광분\", english: \"Berserk\" },\n low: { korean: \"약한 분노\", english: \"Weak Rage\" },\n },\n defensive: {\n weak: { korean: \"약간 방어적\", english: \"Slightly Defensive\" },\n minor: { korean: \"경미한 방어\", english: \"Minor Defense\" },\n medium: { korean: \"방어적\", english: \"Defensive\" },\n moderate: { korean: \"방어적\", english: \"Defensive\" },\n high: { korean: \"높은 방어\", english: \"Highly Defensive\" },\n severe: { korean: \"높은 방어\", english: \"Highly Defensive\" },\n critical: { korean: \"완벽한 방어\", english: \"Perfect Defense\" },\n extreme: { korean: \"완벽한 방어\", english: \"Perfect Defense\" },\n low: { korean: \"약한 방어\", english: \"Weak Defense\" },\n },\n strengthened: {\n weak: { korean: \"약간 강화\", english: \"Slightly Strengthened\" },\n minor: { korean: \"경미한 강화\", english: \"Minor Strength\" },\n medium: { korean: \"강화\", english: \"Strengthened\" },\n moderate: { korean: \"강화\", english: \"Strengthened\" },\n high: { korean: \"크게 강화\", english: \"Greatly Strengthened\" },\n severe: { korean: \"크게 강화\", english: \"Greatly Strengthened\" },\n critical: { korean: \"극대 강화\", english: \"Maximally Strengthened\" },\n extreme: { korean: \"극대 강화\", english: \"Maximally Strengthened\" },\n low: { korean: \"약한 강화\", english: \"Weak Strength\" },\n },\n paralysis: {\n weak: { korean: \"가벼운 마비\", english: \"Minor Paralysis\" },\n minor: { korean: \"경미한 마비\", english: \"Minor Paralysis\" },\n medium: { korean: \"마비\", english: \"Paralysis\" },\n moderate: { korean: \"마비\", english: \"Paralysis\" },\n high: { korean: \"심한 마비\", english: \"Severe Paralysis\" },\n severe: { korean: \"심한 마비\", english: \"Severe Paralysis\" },\n critical: { korean: \"완전 마비\", english: \"Complete Paralysis\" },\n extreme: { korean: \"완전 마비\", english: \"Complete Paralysis\" },\n low: { korean: \"약한 마비\", english: \"Weak Paralysis\" },\n },\n confusion: {\n weak: { korean: \"약간 혼란\", english: \"Slight Confusion\" },\n minor: { korean: \"경미한 혼란\", english: \"Minor Confusion\" },\n medium: { korean: \"혼란\", english: \"Confusion\" },\n moderate: { korean: \"혼란\", english: \"Confusion\" },\n high: { korean: \"심한 혼란\", english: \"Severe Confusion\" },\n severe: { korean: \"심한 혼란\", english: \"Severe Confusion\" },\n critical: { korean: \"완전 혼란\", english: \"Complete Confusion\" },\n extreme: { korean: \"완전 혼란\", english: \"Complete Confusion\" },\n low: { korean: \"약한 혼란\", english: \"Weak Confusion\" },\n },\n vulnerability: {\n weak: { korean: \"약간 취약\", english: \"Slightly Vulnerable\" },\n minor: { korean: \"경미한 취약\", english: \"Minor Vulnerability\" },\n medium: { korean: \"취약\", english: \"Vulnerable\" },\n moderate: { korean: \"취약\", english: \"Vulnerable\" },\n high: { korean: \"매우 취약\", english: \"Very Vulnerable\" },\n severe: { korean: \"매우 취약\", english: \"Very Vulnerable\" },\n critical: { korean: \"극도로 취약\", english: \"Extremely Vulnerable\" },\n extreme: { korean: \"극도로 취약\", english: \"Extremely Vulnerable\" },\n low: { korean: \"약한 취약\", english: \"Weak Vulnerability\" },\n },\n stamina_drain: {\n weak: { korean: \"체력 소모\", english: \"Stamina Drain\" },\n minor: { korean: \"경미한 체력 소모\", english: \"Minor Stamina Drain\" },\n medium: { korean: \"체력 고갈\", english: \"Stamina Depletion\" },\n moderate: { korean: \"체력 고갈\", english: \"Stamina Depletion\" },\n high: { korean: \"심한 체력 고갈\", english: \"Severe Stamina Loss\" },\n severe: { korean: \"심한 체력 고갈\", english: \"Severe Stamina Loss\" },\n critical: { korean: \"완전 체력 고갈\", english: \"Complete Stamina Loss\" },\n extreme: { korean: \"완전 체력 고갈\", english: \"Complete Stamina Loss\" },\n low: { korean: \"약한 체력 소모\", english: \"Weak Stamina Drain\" },\n },\n };\n\n return (\n descriptions[type]?.[intensity] || {\n korean: `${type} 효과`,\n english: `${type} effect`,\n }\n );\n}\n\n/**\n * Update effect over time\n */\nexport function updateEffect(\n effect: StatusEffect,\n currentTime: number\n): StatusEffect | null {\n if (currentTime >= effect.endTime) {\n return null; // Effect has expired\n }\n\n return effect; // Effect is still active\n}\n\n/**\n * Combine multiple effects of same type\n */\nexport function combineEffects(effects: StatusEffect[]): StatusEffect[] {\n const combinedEffects: StatusEffect[] = [];\n // Fix: Use string type instead of EffectType since StatusEffect.type is string\n const effectsByType = new Map<string, StatusEffect[]>();\n\n // Group effects by type\n effects.forEach((effect) => {\n // Fix: effect.type is string, so use string methods\n if (!effectsByType.has(effect.type)) {\n effectsByType.set(effect.type, []);\n }\n const typeEffects = effectsByType.get(effect.type);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n });\n\n // Combine stackable effects, keep latest non-stackable\n effectsByType.forEach((typeEffects, _) => {\n if (typeEffects.length === 0) return;\n\n const firstEffect = typeEffects[0];\n if (firstEffect.stackable) {\n // Keep all stackable effects\n combinedEffects.push(...typeEffects);\n } else {\n // Keep only the latest non-stackable effect\n const latestEffect = typeEffects.reduce((latest, current) =>\n current.startTime > latest.startTime ? current : latest\n );\n combinedEffects.push(latestEffect);\n }\n });\n\n return combinedEffects;\n}\n\n/**\n * Remove expired effects\n */\nexport function removeExpiredEffects(effects: StatusEffect[]): StatusEffect[] {\n const currentTime = Date.now();\n return effects.filter((effect) => effect.endTime > currentTime);\n}\n\n/**\n * Create effect from Korean text\n */\nexport function createEffectFromKoreanText(\n koreanText: string,\n englishText: string,\n duration: number = 3000\n): StatusEffect {\n // Fix: Provide all required arguments\n return createStatusEffect(\n koreanText.toLowerCase(),\n \"stun\" as EffectType, // Use proper EffectType\n \"medium\" as EffectIntensity,\n duration,\n {\n korean: koreanText,\n english: englishText,\n }\n );\n}\n\n/**\n * Get effect display text\n */\nexport function getEffectDisplayText(\n effect: StatusEffect,\n preferKorean: boolean = true\n): string {\n return preferKorean ? effect.description.korean : effect.description.english;\n}\n\n// Fix: Korean martial arts specific effect utilities\nexport function createKoreanStatusEffect(\n koreanName: string,\n englishName: string,\n intensity: EffectIntensity = \"medium\" as EffectIntensity,\n duration: number = 3000\n): StatusEffect {\n return createStatusEffect(\n koreanName.toLowerCase().replace(/\\s+/g, \"_\"),\n \"stun\" as EffectType, // This is correct - keep as is\n intensity,\n duration,\n { korean: koreanName, english: englishName }\n );\n}\n\nexport function createTrigramEffect(\n stanceName: string,\n effectType: EffectType,\n duration: number = 2000\n): StatusEffect {\n return createStatusEffect(\n `trigram_${stanceName}_${effectType}`,\n effectType, // This is correct - use the parameter directly\n \"medium\" as EffectIntensity,\n duration,\n {\n korean: `${stanceName}괘 효과`,\n english: `${stanceName} trigram effect`,\n }\n );\n}\n\n// Fix: Group effects by type using proper typing\nexport function groupEffectsByTypeEnum(\n effects: StatusEffect[]\n): Map<EffectType, StatusEffect[]> {\n const effectsByType = new Map<EffectType, StatusEffect[]>();\n\n for (const effect of effects) {\n // Cast string to EffectType for enum operations\n const effectType = effect.type as EffectType;\n if (!effectsByType.has(effectType)) {\n effectsByType.set(effectType, []);\n }\n const typeEffects = effectsByType.get(effectType);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n }\n\n return effectsByType;\n}\n\n/**\n * Group effects by type using string keys (works with StatusEffect.type)\n */\nexport function groupEffectsByType(\n effects: StatusEffect[]\n): Map<string, StatusEffect[]> {\n const effectsByType = new Map<string, StatusEffect[]>();\n\n for (const effect of effects) {\n // Fix: Use string type directly since StatusEffect.type is string\n if (!effectsByType.has(effect.type)) {\n effectsByType.set(effect.type, []);\n }\n const typeEffects = effectsByType.get(effect.type);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n }\n\n return effectsByType;\n}\n"],"mappings":"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"}
|