blacktrigram 0.7.18 → 0.7.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +2 -2
  2. package/lib/components/screens/combat/CombatScreen3D.d.ts +0 -4
  3. package/lib/components/screens/combat/CombatScreen3D.d.ts.map +1 -1
  4. package/lib/components/screens/combat/CombatScreen3D.js +323 -293
  5. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  6. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.d.ts +9 -0
  7. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.d.ts.map +1 -1
  8. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js +8 -4
  9. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  10. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.d.ts +9 -0
  11. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.d.ts.map +1 -1
  12. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js +9 -4
  13. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  14. package/lib/components/screens/combat/components/effects/PainVignette.d.ts +10 -0
  15. package/lib/components/screens/combat/components/effects/PainVignette.d.ts.map +1 -1
  16. package/lib/components/screens/combat/components/effects/PainVignette.js +7 -3
  17. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  18. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.d.ts +43 -0
  19. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.d.ts.map +1 -0
  20. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js +166 -0
  21. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -0
  22. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.d.ts +8 -0
  23. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.d.ts.map +1 -1
  24. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js +5 -4
  25. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  26. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.d.ts +15 -0
  27. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.d.ts.map +1 -1
  28. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js +10 -6
  29. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  30. package/lib/components/screens/combat/components/hud/index.d.ts +2 -0
  31. package/lib/components/screens/combat/components/hud/index.d.ts.map +1 -1
  32. package/lib/components/screens/combat/components/indicators/StaminaWarning.d.ts +9 -0
  33. package/lib/components/screens/combat/components/indicators/StaminaWarning.d.ts.map +1 -1
  34. package/lib/components/screens/combat/components/indicators/StaminaWarning.js +7 -4
  35. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  36. package/lib/components/screens/combat/hooks/useCombatLayout.d.ts +1 -0
  37. package/lib/components/screens/combat/hooks/useCombatLayout.d.ts.map +1 -1
  38. package/lib/components/screens/combat/hooks/useCombatLayout.js +11 -5
  39. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  40. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  41. package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
  42. package/lib/components/screens/training/TrainingScreen3D.js +20 -8
  43. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  44. package/lib/components/screens/training/hooks/useTrainingLayout.d.ts +1 -0
  45. package/lib/components/screens/training/hooks/useTrainingLayout.d.ts.map +1 -1
  46. package/lib/components/screens/training/hooks/useTrainingLayout.js +10 -3
  47. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  48. package/lib/components/shared/three/effects/ThunderEffect3D.d.ts.map +1 -1
  49. package/lib/components/shared/ui/SplashScreen.js +2 -2
  50. package/lib/hooks/usePlayerAnimation.d.ts.map +1 -1
  51. package/lib/hooks/usePlayerAnimation.js +37 -50
  52. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  53. package/lib/systems/animation/core/AnimationRegistry.d.ts +31 -0
  54. package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
  55. package/lib/systems/animation/core/AnimationRegistry.js +59 -1
  56. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  57. package/lib/systems/animation/core/index.d.ts +1 -1
  58. package/lib/systems/animation/core/index.d.ts.map +1 -1
  59. package/lib/types/constants/layout.d.ts +11 -3
  60. package/lib/types/constants/layout.d.ts.map +1 -1
  61. package/lib/types/constants/layout.js +13 -4
  62. package/lib/types/constants/layout.js.map +1 -1
  63. package/lib/utils/deviceDetection.d.ts.map +1 -1
  64. package/lib/utils/deviceDetection.js +1 -1
  65. package/lib/utils/deviceDetection.js.map +1 -1
  66. package/lib/utils/inputSystem.d.ts.map +1 -1
  67. package/lib/utils/inputSystem.js +1 -1
  68. package/lib/utils/inputSystem.js.map +1 -1
  69. package/lib/utils/mobileLayoutHelpers.d.ts +28 -10
  70. package/lib/utils/mobileLayoutHelpers.d.ts.map +1 -1
  71. package/lib/utils/mobileLayoutHelpers.js +24 -14
  72. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  73. package/lib/utils/responsiveOrientationConstants.d.ts +71 -0
  74. package/lib/utils/responsiveOrientationConstants.d.ts.map +1 -0
  75. package/lib/utils/responsiveOrientationConstants.js +74 -0
  76. package/lib/utils/responsiveOrientationConstants.js.map +1 -0
  77. package/package.json +17 -15
@@ -11,10 +11,11 @@ import { Z_INDEX } from "../../../types/LayoutTypes.js";
11
11
  import { InjuryType } from "../../../types/injury.js";
12
12
  import { TRIGRAM_STANCES_ORDER } from "../../../systems/trigram/types.js";
13
13
  import { useKeyboardControls } from "../../../hooks/useKeyboardControls.js";
14
- import { getAnimation, getAnimationForTechnique } from "../../../systems/animation/core/AnimationRegistry.js";
14
+ import { getAnimation, resolveTechniqueAnimation } from "../../../systems/animation/core/AnimationRegistry.js";
15
15
  import { determineRecoveryType, getRecoveryAnimationState } from "../../../systems/animation/catalogs/RecoveryAnimations.js";
16
16
  import { usePlayerAnimation } from "../../../hooks/usePlayerAnimation.js";
17
17
  import useRoundTransition from "../../../hooks/useRoundTransition.js";
18
+ import { TRIGRAM_TECHNIQUES } from "../../../systems/trigram/techniques/index.js";
18
19
  import useTechniqueSelection from "../../../hooks/useTechniqueSelection.js";
19
20
  import { useWebGLContextLossHandler } from "../../../hooks/useWebGLContextLossHandler.js";
20
21
  import { injuryMovementModifier } from "../../../systems/movement/InjuryMovementModifier.js";
@@ -24,6 +25,7 @@ import BalanceSystem from "../../../systems/combat/BalanceSystem.js";
24
25
  import CombatSystem from "../../../systems/CombatSystem.js";
25
26
  import { getPersonalityByArchetype } from "../../../systems/ai/AIPersonality.js";
26
27
  import { AdaptiveDifficulty } from "../../../systems/ai/AdaptiveDifficulty.js";
28
+ import { getMobileControlsBottom } from "../../../types/constants/layout.js";
27
29
  import { getAnimationTypeForTechnique } from "../../../data/techniqueMappings.js";
28
30
  import { toHexColor } from "../../../utils/colorHelpers.js";
29
31
  import { usePlayerMovement } from "../../../utils/inputSystem.js";
@@ -59,6 +61,7 @@ import CombatTopHUD from "./components/hud/CombatTopHUD.js";
59
61
  import CombatBottomHUD from "./components/hud/CombatBottomHUD.js";
60
62
  import CombatLeftHUD from "./components/hud/CombatLeftHUD.js";
61
63
  import CombatRightHUD from "./components/hud/CombatRightHUD.js";
64
+ import CombatPortraitStatusStrip from "./components/hud/CombatPortraitStatusStrip.js";
62
65
  import FPSMonitor from "./components/hud/FPSMonitor.js";
63
66
  import { PlayerStateOverlayHtml } from "./components/hud/PlayerStateOverlayHtml.js";
64
67
  import { BalanceIndicatorOverlayHtml } from "../../ui/combat/BalanceIndicatorOverlayHtml.js";
@@ -87,6 +90,18 @@ import { Bloom, EffectComposer, Noise, Vignette } from "@react-three/postprocess
87
90
  * CombatScreen3D Component
88
91
  * Three.js-based combat screen with 3D characters and effects
89
92
  */
93
+ /**
94
+ * AdaptiveQualityWrapper - Internal component to use adaptive quality hook
95
+ * Must be inside Canvas to use useFrame from @react-three/fiber
96
+ *
97
+ * Hoisted outside CombatScreen3D to avoid "Cannot create components during render"
98
+ * warnings from react-hooks/component-creation. Keeps the component type stable
99
+ * across renders of the parent.
100
+ */
101
+ var AdaptiveQualityWrapper = ({ enabled, isMobile, children }) => {
102
+ useAdaptiveQuality(enabled, isMobile, (newQuality) => {});
103
+ return /* @__PURE__ */ jsx(Fragment, { children });
104
+ };
90
105
  var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, isPaused, onReturnToMenu, onGameEnd, width = 1200, height = 800, enableAdaptiveQuality, showPerformanceOverlay = false }) => {
91
106
  const [contentReady, setContentReady] = useState(false);
92
107
  const contextLossCountRef = useRef(0);
@@ -107,7 +122,7 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
107
122
  }, []);
108
123
  const audio = useAudio();
109
124
  useEffect(() => {}, []);
110
- const { arenaBounds, isMobile, screenSize, layoutConstants } = useCombatLayout(width, height);
125
+ const { arenaBounds, isMobile, isPortrait, screenSize, layoutConstants } = useCombatLayout(width, height);
111
126
  const theme = useKoreanTheme({
112
127
  variant: "primary",
113
128
  size: "md",
@@ -123,7 +138,19 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
123
138
  default: return 1;
124
139
  }
125
140
  }, [screenSize]);
126
- const cameraConfig = useMemo(() => createCameraConfig(isMobile), [isMobile]);
141
+ const cameraConfig = useMemo(() => {
142
+ const base = createCameraConfig(isMobile);
143
+ if (!isPortrait) return base;
144
+ return {
145
+ ...base,
146
+ fov: Math.min(80, base.fov + 15),
147
+ position: [
148
+ base.position[0],
149
+ base.position[1],
150
+ base.position[2] + 4
151
+ ]
152
+ };
153
+ }, [isMobile, isPortrait]);
127
154
  const renderConfig = useMemo(() => {
128
155
  const performanceSettings = getPerformanceSettings(width, isMobile);
129
156
  return {
@@ -135,22 +162,6 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
135
162
  };
136
163
  }, [isMobile, width]);
137
164
  const shouldEnableAdaptiveQuality = enableAdaptiveQuality ?? isMobile;
138
- /**
139
- * AdaptiveQualityWrapper - Internal component to use adaptive quality hook
140
- * Must be inside Canvas to use useFrame from @react-three/fiber
141
- *
142
- * Memoized with useMemo so the component type is stable across renders.
143
- * Note: renderConfig is intentionally NOT in dependencies to prevent
144
- * component recreation when performance settings change, which would
145
- * reset the internal state of useAdaptiveQuality hook.
146
- */
147
- const AdaptiveQualityWrapper = useMemo(() => {
148
- const Component = ({ children }) => {
149
- useAdaptiveQuality(shouldEnableAdaptiveQuality, isMobile, (newQuality) => {});
150
- return /* @__PURE__ */ jsx(Fragment, { children });
151
- };
152
- return Component;
153
- }, [shouldEnableAdaptiveQuality, isMobile]);
154
165
  const { state: combatState, actions: combatActions } = useCombatState();
155
166
  const [overlayVisible, setOverlayVisible] = useState(false);
156
167
  const [severityFilters, setSeverityFilters] = useState([]);
@@ -195,7 +206,7 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
195
206
  setTimeout(() => setMatchScore(newScore), 0);
196
207
  }, []);
197
208
  const [internalRound, setInternalRound] = useState(currentRound);
198
- const [currentTime, setCurrentTime] = useState(Date.now());
209
+ const [currentTime, setCurrentTime] = useState(() => Date.now());
199
210
  const [hasShownMatchCountdown, setHasShownMatchCountdown] = useState(true);
200
211
  const [showMatchCountdown, setShowMatchCountdown] = useState(false);
201
212
  const [showRoundStart, setShowRoundStart] = useState(false);
@@ -346,7 +357,6 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
346
357
  playerPositions[1].y
347
358
  ];
348
359
  }, [playerPositions]);
349
- const player1LastRotationRef = useRef(0);
350
360
  const player2Rotation = useMemo(() => {
351
361
  const dx = player1Position3D[0] - player2Position3D[0];
352
362
  const dz = player1Position3D[2] - player2Position3D[2];
@@ -468,16 +478,11 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
468
478
  accelerationOverride: player1SpeedModifiers.finalAcceleration
469
479
  });
470
480
  const player1Rotation = useMemo(() => {
471
- if (player1IsMoving && player1Velocity && (player1Velocity.x !== 0 || player1Velocity.y !== 0)) {
472
- const movementRotation = Math.atan2(player1Velocity.x, player1Velocity.y);
473
- player1LastRotationRef.current = movementRotation;
474
- return movementRotation;
475
- } else {
481
+ if (player1IsMoving && player1Velocity && (player1Velocity.x !== 0 || player1Velocity.y !== 0)) return Math.atan2(player1Velocity.x, player1Velocity.y);
482
+ else {
476
483
  const dx = player2Position3D[0] - player1Position3D[0];
477
484
  const dz = player2Position3D[2] - player1Position3D[2];
478
- const targetRotation = Math.atan2(dx, dz);
479
- player1LastRotationRef.current = targetRotation;
480
- return targetRotation;
485
+ return Math.atan2(dx, dz);
481
486
  }
482
487
  }, [
483
488
  player1IsMoving,
@@ -490,8 +495,8 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
490
495
  const validPlayersRefForAnimation = useRef(null);
491
496
  const player1HitTriggerFrameRef = useRef(6);
492
497
  const player1AttackHitFiredRef = useRef(false);
493
- const player1AttackDurationRef = useRef(.55);
494
- const player2AttackDurationRef = useRef(.55);
498
+ const [player1AttackDuration, setPlayer1AttackDuration] = useState(.55);
499
+ const [player2AttackDuration, setPlayer2AttackDuration] = useState(.55);
495
500
  const clearPlayer1AttackAnimation = useRef(() => {
496
501
  setPlayer1AttackAnimation(void 0);
497
502
  setPlayer1TechniqueId(void 0);
@@ -601,12 +606,12 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
601
606
  player1AnimationType: getTechniqueAnimationType(player1TechniqueId),
602
607
  player1Stance: player1Data.currentStance,
603
608
  player1BasePosition: player1Position3D,
604
- player1AnimationDuration: player1AttackDurationRef.current,
609
+ player1AnimationDuration: player1AttackDuration,
605
610
  player2Attacking: player2Animation.currentState === AnimationState.ATTACK,
606
611
  player2AnimationType: getTechniqueAnimationType(player2TechniqueId),
607
612
  player2Stance: validPlayers[1].currentStance,
608
613
  player2BasePosition: player2Position3D,
609
- player2AnimationDuration: player2AttackDurationRef.current
614
+ player2AnimationDuration: player2AttackDuration
610
615
  });
611
616
  const [player1LocalStance, setPlayer1LocalStance] = useState(validPlayers[0].currentStance);
612
617
  useEffect(() => {
@@ -796,14 +801,14 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
796
801
  enabled: !isPaused && combatState.roundStarted && !combatState.roundEnded && matchCountdownComplete && !showRoundStart,
797
802
  onTechniqueExecute: useCallback((technique) => {
798
803
  feedbackActions.showTechnique(technique.name.korean, technique.name.english);
799
- const animationName = getAnimationForTechnique(technique.name.english || technique.id);
804
+ const animationName = resolveTechniqueAnimation(technique);
800
805
  setPlayer1AttackAnimation(animationName);
801
806
  setPlayer1TechniqueId(technique.id);
802
807
  const attackDuration = getAnimation(animationName)?.duration ?? .55;
803
808
  const attackFrames = Math.max(1, Math.round(attackDuration * 60));
804
809
  player1HitTriggerFrameRef.current = Math.round(attackFrames * .4);
805
810
  player1AttackHitFiredRef.current = false;
806
- player1AttackDurationRef.current = attackDuration;
811
+ setPlayer1AttackDuration(attackDuration);
807
812
  player1Animation.transitionToAttack(attackDuration);
808
813
  combatActions.setExecutingTechnique(true);
809
814
  onPlayerUpdate(0, {
@@ -892,7 +897,9 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
892
897
  combatState.roundEnded
893
898
  ]);
894
899
  const handleAttackWithFeedback = useCallback(() => {
895
- setPlayer1AttackAnimation("jab");
900
+ const basicTechnique = techniqueSelection.availableTechniques[0];
901
+ setPlayer1AttackAnimation(basicTechnique ? resolveTechniqueAnimation(basicTechnique) : "jab");
902
+ if (basicTechnique?.id) setPlayer1TechniqueId(basicTechnique.id);
896
903
  if (player1Animation.transitionTo(AnimationState.ATTACK)) combatActions.setExecutingTechnique(true);
897
904
  else {
898
905
  console.warn("Attack animation transition failed; executing attack logic directly.");
@@ -901,7 +908,8 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
901
908
  }, [
902
909
  player1Animation,
903
910
  combatActions,
904
- handleAttack
911
+ handleAttack,
912
+ techniqueSelection.availableTechniques
905
913
  ]);
906
914
  const handleDefendWithFeedback = useCallback(() => {
907
915
  const defenderPos = playerPositions[0];
@@ -1166,19 +1174,23 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1166
1174
  updateDifficultyTarget
1167
1175
  ]);
1168
1176
  const executeAIActionCallback = useCallback((action, targetPos, selectedTechnique, targetVitalPoint) => {
1177
+ const aiFallbackTechnique = TRIGRAM_TECHNIQUES[validPlayers[1]?.currentStance ?? TrigramStance.GEON]?.[0];
1178
+ const aiFallbackAnim = aiFallbackTechnique ? resolveTechniqueAnimation(aiFallbackTechnique) : "jab";
1169
1179
  switch (action) {
1170
1180
  case "attack":
1171
- if (selectedTechnique?.name?.english || selectedTechnique?.englishName) {
1172
- const p2AttackAnimName = getAnimationForTechnique(selectedTechnique.name?.english ?? selectedTechnique.englishName ?? "jab");
1181
+ if (selectedTechnique) {
1182
+ const p2AttackAnimName = resolveTechniqueAnimation(selectedTechnique);
1173
1183
  setPlayer2AttackAnimation(p2AttackAnimName);
1174
- if (selectedTechnique?.id) setPlayer2TechniqueId(selectedTechnique.id);
1184
+ if (selectedTechnique.id) setPlayer2TechniqueId(selectedTechnique.id);
1175
1185
  const p2AttackDur = getAnimation(p2AttackAnimName)?.duration ?? .55;
1176
- player2AttackDurationRef.current = p2AttackDur;
1186
+ setPlayer2AttackDuration(p2AttackDur);
1177
1187
  player2Animation.transitionToAttack(p2AttackDur);
1178
1188
  } else {
1179
- setPlayer2AttackAnimation("jab");
1180
- player2AttackDurationRef.current = .55;
1181
- player2Animation.transitionToAttack(.55);
1189
+ setPlayer2AttackAnimation(aiFallbackAnim);
1190
+ if (aiFallbackTechnique?.id) setPlayer2TechniqueId(aiFallbackTechnique.id);
1191
+ const p2FallbackDur = getAnimation(aiFallbackAnim)?.duration ?? .55;
1192
+ setPlayer2AttackDuration(p2FallbackDur);
1193
+ player2Animation.transitionToAttack(p2FallbackDur);
1182
1194
  }
1183
1195
  handleAIAttack(selectedTechnique, targetVitalPoint);
1184
1196
  break;
@@ -1188,17 +1200,19 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1188
1200
  break;
1189
1201
  case "technique":
1190
1202
  case "combo":
1191
- if (selectedTechnique?.name?.english || selectedTechnique?.englishName) {
1192
- const p2TechAnimName = getAnimationForTechnique(selectedTechnique.name?.english ?? selectedTechnique.englishName ?? "cross");
1203
+ if (selectedTechnique) {
1204
+ const p2TechAnimName = resolveTechniqueAnimation(selectedTechnique);
1193
1205
  setPlayer2AttackAnimation(p2TechAnimName);
1194
- if (selectedTechnique?.id) setPlayer2TechniqueId(selectedTechnique.id);
1206
+ if (selectedTechnique.id) setPlayer2TechniqueId(selectedTechnique.id);
1195
1207
  const p2TechDur = getAnimation(p2TechAnimName)?.duration ?? .6;
1196
- player2AttackDurationRef.current = p2TechDur;
1208
+ setPlayer2AttackDuration(p2TechDur);
1197
1209
  player2Animation.transitionToAttack(p2TechDur);
1198
1210
  } else {
1199
- setPlayer2AttackAnimation("cross");
1200
- player2AttackDurationRef.current = .6;
1201
- player2Animation.transitionToAttack(.6);
1211
+ setPlayer2AttackAnimation(aiFallbackAnim);
1212
+ if (aiFallbackTechnique?.id) setPlayer2TechniqueId(aiFallbackTechnique.id);
1213
+ const p2FallbackDur = getAnimation(aiFallbackAnim)?.duration ?? .6;
1214
+ setPlayer2AttackDuration(p2FallbackDur);
1215
+ player2Animation.transitionToAttack(p2FallbackDur);
1202
1216
  }
1203
1217
  handleAITechnique(selectedTechnique, targetVitalPoint);
1204
1218
  break;
@@ -1235,17 +1249,19 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1235
1249
  }
1236
1250
  break;
1237
1251
  case "counter":
1238
- if (selectedTechnique?.name?.english || selectedTechnique?.englishName) {
1239
- const p2CounterAnimName = getAnimationForTechnique(selectedTechnique.name?.english ?? selectedTechnique.englishName ?? "cross");
1252
+ if (selectedTechnique) {
1253
+ const p2CounterAnimName = resolveTechniqueAnimation(selectedTechnique);
1240
1254
  setPlayer2AttackAnimation(p2CounterAnimName);
1241
- if (selectedTechnique?.id) setPlayer2TechniqueId(selectedTechnique.id);
1255
+ if (selectedTechnique.id) setPlayer2TechniqueId(selectedTechnique.id);
1242
1256
  const p2CounterDur = getAnimation(p2CounterAnimName)?.duration ?? .6;
1243
- player2AttackDurationRef.current = p2CounterDur;
1257
+ setPlayer2AttackDuration(p2CounterDur);
1244
1258
  player2Animation.transitionToAttack(p2CounterDur);
1245
1259
  } else {
1246
- setPlayer2AttackAnimation("cross");
1247
- player2AttackDurationRef.current = .6;
1248
- player2Animation.transitionToAttack(.6);
1260
+ setPlayer2AttackAnimation(aiFallbackAnim);
1261
+ if (aiFallbackTechnique?.id) setPlayer2TechniqueId(aiFallbackTechnique.id);
1262
+ const p2FallbackDur = getAnimation(aiFallbackAnim)?.duration ?? .6;
1263
+ setPlayer2AttackDuration(p2FallbackDur);
1264
+ player2Animation.transitionToAttack(p2FallbackDur);
1249
1265
  }
1250
1266
  handleAIAttack(selectedTechnique, targetVitalPoint);
1251
1267
  addCombatMessage("AI 반격!", "AI Counter!");
@@ -1452,245 +1468,249 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1452
1468
  ],
1453
1469
  intensity: 1.2
1454
1470
  }),
1455
- /* @__PURE__ */ jsxs(AdaptiveQualityWrapper, { children: [
1456
- showPerformanceOverlay && !showPerformanceMonitor && /* @__PURE__ */ jsx(PerformanceOverlay3D, {}),
1457
- /* @__PURE__ */ jsx(CombatArena3D, {
1458
- lighting: "cyberpunk",
1459
- scale: arenaBounds.scale,
1460
- worldWidthMeters: arenaBounds.worldWidthMeters,
1461
- worldDepthMeters: arenaBounds.worldDepthMeters
1462
- }),
1463
- /* @__PURE__ */ jsx(AnimationUpdater, {
1464
- player1Animation,
1465
- player2Animation
1466
- }),
1467
- /* @__PURE__ */ jsx(AccelerationUpdater, {
1468
- isMoving: player1IsMoving,
1469
- velocity: player1Velocity,
1470
- movementTimeRef: player1MovementTimeRef,
1471
- lastDirectionRef: player1LastDirectionRef,
1472
- onSpeedUpdate: setPlayer1AccelerationBasedSpeed,
1473
- walkSpeed: player1WalkRunSpeeds.walkSpeed,
1474
- runSpeed: player1WalkRunSpeeds.runSpeed
1475
- }),
1476
- /* @__PURE__ */ jsx(Player3DWithTransitions, {
1477
- ...convertPlayerStateToProps(validPlayers[0], player1PositionWithAttackMovement, player1Rotation, {
1471
+ /* @__PURE__ */ jsxs(AdaptiveQualityWrapper, {
1472
+ enabled: shouldEnableAdaptiveQuality,
1473
+ isMobile,
1474
+ children: [
1475
+ showPerformanceOverlay && !showPerformanceMonitor && /* @__PURE__ */ jsx(PerformanceOverlay3D, {}),
1476
+ /* @__PURE__ */ jsx(CombatArena3D, {
1477
+ lighting: "cyberpunk",
1478
+ scale: arenaBounds.scale,
1479
+ worldWidthMeters: arenaBounds.worldWidthMeters,
1480
+ worldDepthMeters: arenaBounds.worldDepthMeters
1481
+ }),
1482
+ /* @__PURE__ */ jsx(AnimationUpdater, {
1483
+ player1Animation,
1484
+ player2Animation
1485
+ }),
1486
+ /* @__PURE__ */ jsx(AccelerationUpdater, {
1487
+ isMoving: player1IsMoving,
1488
+ velocity: player1Velocity,
1489
+ movementTimeRef: player1MovementTimeRef,
1490
+ lastDirectionRef: player1LastDirectionRef,
1491
+ onSpeedUpdate: setPlayer1AccelerationBasedSpeed,
1492
+ walkSpeed: player1WalkRunSpeeds.walkSpeed,
1493
+ runSpeed: player1WalkRunSpeeds.runSpeed
1494
+ }),
1495
+ /* @__PURE__ */ jsx(Player3DWithTransitions, {
1496
+ ...convertPlayerStateToProps(validPlayers[0], player1PositionWithAttackMovement, player1Rotation, {
1497
+ isMobile,
1498
+ facing: "right",
1499
+ enableFacialExpressions: true,
1500
+ enableEyeTracking: true,
1501
+ opponentPosition: player2PositionWithAttackMovement
1502
+ }),
1503
+ currentAnimation: animationStateToPlayerAnimation(player1Animation.currentState),
1504
+ attackAnimation: player1AttackAnimation,
1505
+ laterality: combatState.playerLaterality[0],
1506
+ enableTransitionEffects: !isMobile,
1507
+ enableStanceSymbol: !isMobile,
1508
+ enableStanceAudio: true
1509
+ }),
1510
+ /* @__PURE__ */ jsx(Player3DWithTransitions, {
1511
+ ...convertPlayerStateToProps(validPlayers[1], player2PositionWithAttackMovement, player2Rotation, {
1512
+ isMobile,
1513
+ facing: "right",
1514
+ enableFacialExpressions: true,
1515
+ enableEyeTracking: true,
1516
+ opponentPosition: player1PositionWithAttackMovement
1517
+ }),
1518
+ currentAnimation: animationStateToPlayerAnimation(player2Animation.currentState),
1519
+ attackAnimation: player2AttackAnimation,
1520
+ laterality: combatState.playerLaterality[1],
1521
+ enableTransitionEffects: !isMobile,
1522
+ enableStanceSymbol: !isMobile,
1523
+ enableStanceAudio: true
1524
+ }),
1525
+ (player1MovementState.isLimping || player1MovementState.isSevereLimp) && /* @__PURE__ */ jsx(Html, {
1526
+ position: [
1527
+ player1Position3D[0],
1528
+ player1Position3D[1] + 2.5,
1529
+ player1Position3D[2]
1530
+ ],
1531
+ center: true,
1532
+ "data-testid": "player1-movement-status",
1533
+ children: /* @__PURE__ */ jsxs("div", {
1534
+ style: {
1535
+ fontSize: isMobile ? "12px" : "14px",
1536
+ color: player1MovementState.isSevereLimp ? toHexColor(KOREAN_COLORS.TEXT_ERROR) : toHexColor(KOREAN_COLORS.ACCENT_GOLD),
1537
+ fontFamily: FONT_FAMILY.KOREAN,
1538
+ fontWeight: "bold",
1539
+ textShadow: "0 0 4px rgba(0,0,0,0.8)",
1540
+ background: "rgba(0, 0, 0, 0.6)",
1541
+ padding: "4px 8px",
1542
+ borderRadius: "4px",
1543
+ whiteSpace: "nowrap"
1544
+ },
1545
+ children: [
1546
+ player1MovementState.statusText.korean,
1547
+ " |",
1548
+ " ",
1549
+ player1MovementState.statusText.english
1550
+ ]
1551
+ })
1552
+ }),
1553
+ (player2MovementState.isLimping || player2MovementState.isSevereLimp) && /* @__PURE__ */ jsx(Html, {
1554
+ position: [
1555
+ player2Position3D[0],
1556
+ player2Position3D[1] + 2.5,
1557
+ player2Position3D[2]
1558
+ ],
1559
+ center: true,
1560
+ "data-testid": "player2-movement-status",
1561
+ children: /* @__PURE__ */ jsxs("div", {
1562
+ style: {
1563
+ fontSize: isMobile ? "12px" : "14px",
1564
+ color: player2MovementState.isSevereLimp ? toHexColor(KOREAN_COLORS.TEXT_ERROR) : toHexColor(KOREAN_COLORS.ACCENT_GOLD),
1565
+ fontFamily: FONT_FAMILY.KOREAN,
1566
+ fontWeight: "bold",
1567
+ textShadow: "0 0 4px rgba(0,0,0,0.8)",
1568
+ background: "rgba(0, 0, 0, 0.6)",
1569
+ padding: "4px 8px",
1570
+ borderRadius: "4px",
1571
+ whiteSpace: "nowrap"
1572
+ },
1573
+ children: [
1574
+ player2MovementState.statusText.korean,
1575
+ " |",
1576
+ " ",
1577
+ player2MovementState.statusText.english
1578
+ ]
1579
+ })
1580
+ }),
1581
+ /* @__PURE__ */ jsx(TraumaOverlay3D, {
1582
+ playerId: "player",
1583
+ health: validPlayers[0].health,
1584
+ injuries: player1Injuries,
1585
+ characterPosition: player1Position3D,
1478
1586
  isMobile,
1479
- facing: "right",
1480
- enableFacialExpressions: true,
1481
- enableEyeTracking: true,
1482
- opponentPosition: player2PositionWithAttackMovement
1587
+ showFractures: true
1483
1588
  }),
1484
- currentAnimation: animationStateToPlayerAnimation(player1Animation.currentState),
1485
- attackAnimation: player1AttackAnimation,
1486
- laterality: combatState.playerLaterality[0],
1487
- enableTransitionEffects: !isMobile,
1488
- enableStanceSymbol: !isMobile,
1489
- enableStanceAudio: true
1490
- }),
1491
- /* @__PURE__ */ jsx(Player3DWithTransitions, {
1492
- ...convertPlayerStateToProps(validPlayers[1], player2PositionWithAttackMovement, player2Rotation, {
1589
+ /* @__PURE__ */ jsx(TraumaOverlay3D, {
1590
+ playerId: "enemy",
1591
+ health: validPlayers[1].health,
1592
+ injuries: player2Injuries,
1593
+ characterPosition: player2Position3D,
1493
1594
  isMobile,
1494
- facing: "right",
1495
- enableFacialExpressions: true,
1496
- enableEyeTracking: true,
1497
- opponentPosition: player1PositionWithAttackMovement
1595
+ showFractures: true
1498
1596
  }),
1499
- currentAnimation: animationStateToPlayerAnimation(player2Animation.currentState),
1500
- attackAnimation: player2AttackAnimation,
1501
- laterality: combatState.playerLaterality[1],
1502
- enableTransitionEffects: !isMobile,
1503
- enableStanceSymbol: !isMobile,
1504
- enableStanceAudio: true
1505
- }),
1506
- (player1MovementState.isLimping || player1MovementState.isSevereLimp) && /* @__PURE__ */ jsx(Html, {
1507
- position: [
1508
- player1Position3D[0],
1509
- player1Position3D[1] + 2.5,
1510
- player1Position3D[2]
1511
- ],
1512
- center: true,
1513
- "data-testid": "player1-movement-status",
1514
- children: /* @__PURE__ */ jsxs("div", {
1515
- style: {
1516
- fontSize: isMobile ? "12px" : "14px",
1517
- color: player1MovementState.isSevereLimp ? toHexColor(KOREAN_COLORS.TEXT_ERROR) : toHexColor(KOREAN_COLORS.ACCENT_GOLD),
1518
- fontFamily: FONT_FAMILY.KOREAN,
1519
- fontWeight: "bold",
1520
- textShadow: "0 0 4px rgba(0,0,0,0.8)",
1521
- background: "rgba(0, 0, 0, 0.6)",
1522
- padding: "4px 8px",
1523
- borderRadius: "4px",
1524
- whiteSpace: "nowrap"
1525
- },
1526
- children: [
1527
- player1MovementState.statusText.korean,
1528
- " |",
1529
- " ",
1530
- player1MovementState.statusText.english
1531
- ]
1532
- })
1533
- }),
1534
- (player2MovementState.isLimping || player2MovementState.isSevereLimp) && /* @__PURE__ */ jsx(Html, {
1535
- position: [
1536
- player2Position3D[0],
1537
- player2Position3D[1] + 2.5,
1538
- player2Position3D[2]
1539
- ],
1540
- center: true,
1541
- "data-testid": "player2-movement-status",
1542
- children: /* @__PURE__ */ jsxs("div", {
1543
- style: {
1544
- fontSize: isMobile ? "12px" : "14px",
1545
- color: player2MovementState.isSevereLimp ? toHexColor(KOREAN_COLORS.TEXT_ERROR) : toHexColor(KOREAN_COLORS.ACCENT_GOLD),
1546
- fontFamily: FONT_FAMILY.KOREAN,
1547
- fontWeight: "bold",
1548
- textShadow: "0 0 4px rgba(0,0,0,0.8)",
1549
- background: "rgba(0, 0, 0, 0.6)",
1550
- padding: "4px 8px",
1551
- borderRadius: "4px",
1552
- whiteSpace: "nowrap"
1553
- },
1554
- children: [
1555
- player2MovementState.statusText.korean,
1556
- " |",
1557
- " ",
1558
- player2MovementState.statusText.english
1559
- ]
1560
- })
1561
- }),
1562
- /* @__PURE__ */ jsx(TraumaOverlay3D, {
1563
- playerId: "player",
1564
- health: validPlayers[0].health,
1565
- injuries: player1Injuries,
1566
- characterPosition: player1Position3D,
1567
- isMobile,
1568
- showFractures: true
1569
- }),
1570
- /* @__PURE__ */ jsx(TraumaOverlay3D, {
1571
- playerId: "enemy",
1572
- health: validPlayers[1].health,
1573
- injuries: player2Injuries,
1574
- characterPosition: player2Position3D,
1575
- isMobile,
1576
- showFractures: true
1577
- }),
1578
- /* @__PURE__ */ jsx(HitEffects3D, {
1579
- effects: combatState.hitEffects,
1580
- onEffectComplete: handleEffectComplete,
1581
- arenaBounds
1582
- }),
1583
- /* @__PURE__ */ jsx(CombatParticleEffects3D, {
1584
- hitEffects: combatState.hitEffects,
1585
- enabled: true,
1586
- isMobile
1587
- }),
1588
- overlayVisible && /* @__PURE__ */ jsxs(Fragment, { children: [
1589
- /* @__PURE__ */ jsx(VitalPointMarkers3D, {
1590
- position: player1Position3D,
1591
- visible: overlayVisible,
1592
- severityFilter: severityFilters,
1593
- regionFilter,
1594
- searchQuery,
1595
- showLabels,
1596
- scale,
1597
- animated,
1598
- onPointClick: () => {}
1597
+ /* @__PURE__ */ jsx(HitEffects3D, {
1598
+ effects: combatState.hitEffects,
1599
+ onEffectComplete: handleEffectComplete,
1600
+ arenaBounds
1601
+ }),
1602
+ /* @__PURE__ */ jsx(CombatParticleEffects3D, {
1603
+ hitEffects: combatState.hitEffects,
1604
+ enabled: true,
1605
+ isMobile
1599
1606
  }),
1600
- /* @__PURE__ */ jsx(VitalPointMarkers3D, {
1601
- position: player2Position3D,
1602
- visible: overlayVisible,
1603
- severityFilter: severityFilters,
1604
- regionFilter,
1605
- searchQuery,
1606
- showLabels,
1607
- scale,
1608
- animated,
1609
- onPointClick: () => {}
1607
+ overlayVisible && /* @__PURE__ */ jsxs(Fragment, { children: [
1608
+ /* @__PURE__ */ jsx(VitalPointMarkers3D, {
1609
+ position: player1Position3D,
1610
+ visible: overlayVisible,
1611
+ severityFilter: severityFilters,
1612
+ regionFilter,
1613
+ searchQuery,
1614
+ showLabels,
1615
+ scale,
1616
+ animated,
1617
+ onPointClick: () => {}
1618
+ }),
1619
+ /* @__PURE__ */ jsx(VitalPointMarkers3D, {
1620
+ position: player2Position3D,
1621
+ visible: overlayVisible,
1622
+ severityFilter: severityFilters,
1623
+ regionFilter,
1624
+ searchQuery,
1625
+ showLabels,
1626
+ scale,
1627
+ animated,
1628
+ onPointClick: () => {}
1629
+ }),
1630
+ /* @__PURE__ */ jsx(VitalPointOverlayControlsHtml, {
1631
+ screenPosition: {
1632
+ top: `${layoutConstants.hudHeight + layoutConstants.padding}px`,
1633
+ left: `${layoutConstants.padding}px`
1634
+ },
1635
+ visible: overlayVisible,
1636
+ onVisibleChange: setOverlayVisible,
1637
+ severityFilters,
1638
+ onSeverityFiltersChange: setSeverityFilters,
1639
+ regionFilter,
1640
+ onRegionFilterChange: setRegionFilter,
1641
+ searchQuery,
1642
+ onSearchQueryChange: setSearchQuery,
1643
+ showLabels,
1644
+ onShowLabelsChange: setShowLabels,
1645
+ animated,
1646
+ onAnimatedChange: setAnimated,
1647
+ scale,
1648
+ onScaleChange: setScale,
1649
+ isMobile
1650
+ })
1651
+ ] }),
1652
+ /* @__PURE__ */ jsx(DamageNumbers, {
1653
+ damages: feedbackState.damageNumbers,
1654
+ isMobile,
1655
+ arenaBounds
1610
1656
  }),
1611
- /* @__PURE__ */ jsx(VitalPointOverlayControlsHtml, {
1612
- screenPosition: {
1613
- top: `${layoutConstants.hudHeight + layoutConstants.padding}px`,
1614
- left: `${layoutConstants.padding}px`
1615
- },
1616
- visible: overlayVisible,
1617
- onVisibleChange: setOverlayVisible,
1618
- severityFilters,
1619
- onSeverityFiltersChange: setSeverityFilters,
1620
- regionFilter,
1621
- onRegionFilterChange: setRegionFilter,
1622
- searchQuery,
1623
- onSearchQueryChange: setSearchQuery,
1624
- showLabels,
1625
- onShowLabelsChange: setShowLabels,
1626
- animated,
1627
- onAnimatedChange: setAnimated,
1628
- scale,
1629
- onScaleChange: setScale,
1657
+ /* @__PURE__ */ jsx(ActionFeedback, {
1658
+ feedbacks: feedbackState.actionFeedbacks,
1659
+ isMobile,
1660
+ arenaBounds
1661
+ }),
1662
+ /* @__PURE__ */ jsx(ComboCounter, {
1663
+ combo: feedbackState.comboCount,
1630
1664
  isMobile
1665
+ }),
1666
+ feedbackState.currentTechnique && /* @__PURE__ */ jsx(TechniqueName, {
1667
+ korean: feedbackState.currentTechnique.korean,
1668
+ english: feedbackState.currentTechnique.english,
1669
+ isMobile,
1670
+ onComplete: () => feedbackActions.hideTechnique()
1671
+ }),
1672
+ false,
1673
+ /* @__PURE__ */ jsx(StanceChangeIndicator, {
1674
+ currentStance: currentStanceIndex,
1675
+ previousStance,
1676
+ isMobile
1677
+ }),
1678
+ /* @__PURE__ */ jsx(KeyboardHints, {
1679
+ visible: showHints,
1680
+ currentStance: currentStanceIndex,
1681
+ isMobile
1682
+ }),
1683
+ /* @__PURE__ */ jsx(InputBufferDisplay, {
1684
+ queuedInputs,
1685
+ isMobile
1686
+ }),
1687
+ validPlayers[0] && /* @__PURE__ */ jsx(BalanceIndicatorOverlayHtml, {
1688
+ player: validPlayers[0],
1689
+ currentTime,
1690
+ position: [
1691
+ -2.5,
1692
+ 2.5,
1693
+ -1
1694
+ ],
1695
+ isMobile
1696
+ }),
1697
+ validPlayers[1] && /* @__PURE__ */ jsx(BalanceIndicatorOverlayHtml, {
1698
+ player: validPlayers[1],
1699
+ currentTime,
1700
+ position: [
1701
+ 2.5,
1702
+ 2.5,
1703
+ -1
1704
+ ],
1705
+ isMobile
1706
+ }),
1707
+ process.env.NODE_ENV === "development" && showPerformanceMonitor && /* @__PURE__ */ jsx(FPSMonitor, {
1708
+ enabled: true,
1709
+ warningThreshold: 50,
1710
+ criticalThreshold: 30
1631
1711
  })
1632
- ] }),
1633
- /* @__PURE__ */ jsx(DamageNumbers, {
1634
- damages: feedbackState.damageNumbers,
1635
- isMobile,
1636
- arenaBounds
1637
- }),
1638
- /* @__PURE__ */ jsx(ActionFeedback, {
1639
- feedbacks: feedbackState.actionFeedbacks,
1640
- isMobile,
1641
- arenaBounds
1642
- }),
1643
- /* @__PURE__ */ jsx(ComboCounter, {
1644
- combo: feedbackState.comboCount,
1645
- isMobile
1646
- }),
1647
- feedbackState.currentTechnique && /* @__PURE__ */ jsx(TechniqueName, {
1648
- korean: feedbackState.currentTechnique.korean,
1649
- english: feedbackState.currentTechnique.english,
1650
- isMobile,
1651
- onComplete: () => feedbackActions.hideTechnique()
1652
- }),
1653
- false,
1654
- /* @__PURE__ */ jsx(StanceChangeIndicator, {
1655
- currentStance: currentStanceIndex,
1656
- previousStance,
1657
- isMobile
1658
- }),
1659
- /* @__PURE__ */ jsx(KeyboardHints, {
1660
- visible: showHints,
1661
- currentStance: currentStanceIndex,
1662
- isMobile
1663
- }),
1664
- /* @__PURE__ */ jsx(InputBufferDisplay, {
1665
- queuedInputs,
1666
- isMobile
1667
- }),
1668
- validPlayers[0] && /* @__PURE__ */ jsx(BalanceIndicatorOverlayHtml, {
1669
- player: validPlayers[0],
1670
- currentTime,
1671
- position: [
1672
- -2.5,
1673
- 2.5,
1674
- -1
1675
- ],
1676
- isMobile
1677
- }),
1678
- validPlayers[1] && /* @__PURE__ */ jsx(BalanceIndicatorOverlayHtml, {
1679
- player: validPlayers[1],
1680
- currentTime,
1681
- position: [
1682
- 2.5,
1683
- 2.5,
1684
- -1
1685
- ],
1686
- isMobile
1687
- }),
1688
- process.env.NODE_ENV === "development" && showPerformanceMonitor && /* @__PURE__ */ jsx(FPSMonitor, {
1689
- enabled: true,
1690
- warningThreshold: 50,
1691
- criticalThreshold: 30
1692
- })
1693
- ] }),
1712
+ ]
1713
+ }),
1694
1714
  isMobile ? /* @__PURE__ */ jsxs(EffectComposer, {
1695
1715
  multisampling: 0,
1696
1716
  children: [
@@ -1752,7 +1772,7 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1752
1772
  onReturnToMenu,
1753
1773
  isPaused: isPaused || showPauseMenu
1754
1774
  }),
1755
- /* @__PURE__ */ jsx(CombatLeftHUD, {
1775
+ !(isMobile && isPortrait) && /* @__PURE__ */ jsx(CombatLeftHUD, {
1756
1776
  width,
1757
1777
  height,
1758
1778
  isMobile,
@@ -1762,7 +1782,7 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1762
1782
  isInGuard: player1Animation.isInStanceGuard(),
1763
1783
  speedModifiers: player1SpeedModifiers
1764
1784
  }),
1765
- /* @__PURE__ */ jsx(CombatRightHUD, {
1785
+ !(isMobile && isPortrait) && /* @__PURE__ */ jsx(CombatRightHUD, {
1766
1786
  width,
1767
1787
  height,
1768
1788
  isMobile,
@@ -1772,6 +1792,14 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1772
1792
  speedModifiers: player2SpeedModifiers,
1773
1793
  difficultyTier: currentDifficultyTier
1774
1794
  }),
1795
+ isMobile && isPortrait && /* @__PURE__ */ jsx(CombatPortraitStatusStrip, {
1796
+ width,
1797
+ height,
1798
+ player1: validPlayers[0],
1799
+ player2: validPlayers[1],
1800
+ positionScale,
1801
+ topOffset: layoutConstants.hudHeight
1802
+ }),
1775
1803
  /* @__PURE__ */ jsx(CombatBottomHUD, {
1776
1804
  width,
1777
1805
  height,
@@ -1792,7 +1820,8 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1792
1820
  consciousness: validPlayers[0].consciousness,
1793
1821
  bloodLoss: 0,
1794
1822
  stamina: validPlayers[0].stamina,
1795
- isMobile
1823
+ isMobile,
1824
+ intensityScale: isMobile && isPortrait ? .5 : 1
1796
1825
  }),
1797
1826
  /* @__PURE__ */ jsx("div", {
1798
1827
  style: {
@@ -1878,7 +1907,8 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1878
1907
  /* @__PURE__ */ jsx(MobileControlsOverlay, {
1879
1908
  onMove: handleMobileMove,
1880
1909
  onAttack: handleMobileAttack,
1881
- onBlock: handleMobileBlock
1910
+ onBlock: handleMobileBlock,
1911
+ bottom: getMobileControlsBottom(height)
1882
1912
  }),
1883
1913
  /* @__PURE__ */ jsx(StanceWheelPure, {
1884
1914
  currentStance: currentStanceIndex,