blacktrigram 0.7.19 → 0.7.23

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/README.md CHANGED
@@ -184,7 +184,7 @@ Built for **combat realism** and **authentic simulation**:
184
184
 
185
185
  ### 🎮 Rendering Engine
186
186
 
187
- ![Three.js](https://img.shields.io/badge/Three.js-0.183-000000?style=for-the-badge&logo=three.js)
187
+ ![Three.js](https://img.shields.io/badge/Three.js-0.184-000000?style=for-the-badge&logo=three.js)
188
188
  ![React Three Fiber](https://img.shields.io/badge/R3F-9.5-00ffff?style=for-the-badge&logo=react)
189
189
  ![React](https://img.shields.io/badge/React-19-61dafb?style=for-the-badge&logo=react)
190
190
  ![TypeScript](https://img.shields.io/badge/TypeScript-Strict-3178c6?style=for-the-badge&logo=typescript)
@@ -380,7 +380,7 @@ George Dorn provides detailed repository deep-dives based on actual code inspect
380
380
 
381
381
  **[Repository Deep-Dive](https://hack23.com/blog-george-dorn-trigram-code.html)**
382
382
 
383
- **Stack:** TypeScript 6.0, React 19, Three.js 0.183, Vite 8
383
+ **Stack:** TypeScript 6.0, React 19, Three.js 0.184, Vite 8
384
384
  **Metrics:** 132 TypeScript files, 70 vital points system, 5 fighter archetypes
385
385
 
386
386
  Examined package.json dependencies, explored src/ structure, verified combat system implementation, and reviewed AI integrations.
@@ -65,10 +65,6 @@ export interface CombatScreen3DProps {
65
65
  */
66
66
  readonly showPerformanceOverlay?: boolean;
67
67
  }
68
- /**
69
- * CombatScreen3D Component
70
- * Three.js-based combat screen with 3D characters and effects
71
- */
72
68
  export declare const CombatScreen3D: React.FC<CombatScreen3DProps>;
73
69
  export default CombatScreen3D;
74
70
  //# sourceMappingURL=CombatScreen3D.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CombatScreen3D.d.ts","sourceRoot":"","sources":["../../../../src/components/screens/combat/CombatScreen3D.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAMN,MAAM,OAAO,CAAC;AAUf,OAAO,EAAa,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAsB1D,OAAO,EAEL,QAAQ,EAIT,MAAM,gBAAgB,CAAC;AAkFxB;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,CACvB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,KAC1B,IAAI,CAAC;IACV;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,IAAI,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC;;OAEG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAC3C;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAsgGxD,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"CombatScreen3D.d.ts","sourceRoot":"","sources":["../../../../src/components/screens/combat/CombatScreen3D.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAMN,MAAM,OAAO,CAAC;AAUf,OAAO,EAAa,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAsB1D,OAAO,EAEL,QAAQ,EAIT,MAAM,gBAAgB,CAAC;AAkFxB;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,CACvB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,KAC1B,IAAI,CAAC;IACV;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,IAAI,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC;;OAEG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAC3C;AAgCD,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA0+FxD,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -90,6 +90,18 @@ import { Bloom, EffectComposer, Noise, Vignette } from "@react-three/postprocess
90
90
  * CombatScreen3D Component
91
91
  * Three.js-based combat screen with 3D characters and effects
92
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
+ };
93
105
  var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, isPaused, onReturnToMenu, onGameEnd, width = 1200, height = 800, enableAdaptiveQuality, showPerformanceOverlay = false }) => {
94
106
  const [contentReady, setContentReady] = useState(false);
95
107
  const contextLossCountRef = useRef(0);
@@ -150,22 +162,6 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
150
162
  };
151
163
  }, [isMobile, width]);
152
164
  const shouldEnableAdaptiveQuality = enableAdaptiveQuality ?? isMobile;
153
- /**
154
- * AdaptiveQualityWrapper - Internal component to use adaptive quality hook
155
- * Must be inside Canvas to use useFrame from @react-three/fiber
156
- *
157
- * Memoized with useMemo so the component type is stable across renders.
158
- * Note: renderConfig is intentionally NOT in dependencies to prevent
159
- * component recreation when performance settings change, which would
160
- * reset the internal state of useAdaptiveQuality hook.
161
- */
162
- const AdaptiveQualityWrapper = useMemo(() => {
163
- const Component = ({ children }) => {
164
- useAdaptiveQuality(shouldEnableAdaptiveQuality, isMobile, (newQuality) => {});
165
- return /* @__PURE__ */ jsx(Fragment, { children });
166
- };
167
- return Component;
168
- }, [shouldEnableAdaptiveQuality, isMobile]);
169
165
  const { state: combatState, actions: combatActions } = useCombatState();
170
166
  const [overlayVisible, setOverlayVisible] = useState(false);
171
167
  const [severityFilters, setSeverityFilters] = useState([]);
@@ -210,7 +206,7 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
210
206
  setTimeout(() => setMatchScore(newScore), 0);
211
207
  }, []);
212
208
  const [internalRound, setInternalRound] = useState(currentRound);
213
- const [currentTime, setCurrentTime] = useState(Date.now());
209
+ const [currentTime, setCurrentTime] = useState(() => Date.now());
214
210
  const [hasShownMatchCountdown, setHasShownMatchCountdown] = useState(true);
215
211
  const [showMatchCountdown, setShowMatchCountdown] = useState(false);
216
212
  const [showRoundStart, setShowRoundStart] = useState(false);
@@ -361,7 +357,6 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
361
357
  playerPositions[1].y
362
358
  ];
363
359
  }, [playerPositions]);
364
- const player1LastRotationRef = useRef(0);
365
360
  const player2Rotation = useMemo(() => {
366
361
  const dx = player1Position3D[0] - player2Position3D[0];
367
362
  const dz = player1Position3D[2] - player2Position3D[2];
@@ -483,16 +478,11 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
483
478
  accelerationOverride: player1SpeedModifiers.finalAcceleration
484
479
  });
485
480
  const player1Rotation = useMemo(() => {
486
- if (player1IsMoving && player1Velocity && (player1Velocity.x !== 0 || player1Velocity.y !== 0)) {
487
- const movementRotation = Math.atan2(player1Velocity.x, player1Velocity.y);
488
- player1LastRotationRef.current = movementRotation;
489
- return movementRotation;
490
- } else {
481
+ if (player1IsMoving && player1Velocity && (player1Velocity.x !== 0 || player1Velocity.y !== 0)) return Math.atan2(player1Velocity.x, player1Velocity.y);
482
+ else {
491
483
  const dx = player2Position3D[0] - player1Position3D[0];
492
484
  const dz = player2Position3D[2] - player1Position3D[2];
493
- const targetRotation = Math.atan2(dx, dz);
494
- player1LastRotationRef.current = targetRotation;
495
- return targetRotation;
485
+ return Math.atan2(dx, dz);
496
486
  }
497
487
  }, [
498
488
  player1IsMoving,
@@ -505,8 +495,8 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
505
495
  const validPlayersRefForAnimation = useRef(null);
506
496
  const player1HitTriggerFrameRef = useRef(6);
507
497
  const player1AttackHitFiredRef = useRef(false);
508
- const player1AttackDurationRef = useRef(.55);
509
- const player2AttackDurationRef = useRef(.55);
498
+ const [player1AttackDuration, setPlayer1AttackDuration] = useState(.55);
499
+ const [player2AttackDuration, setPlayer2AttackDuration] = useState(.55);
510
500
  const clearPlayer1AttackAnimation = useRef(() => {
511
501
  setPlayer1AttackAnimation(void 0);
512
502
  setPlayer1TechniqueId(void 0);
@@ -616,12 +606,12 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
616
606
  player1AnimationType: getTechniqueAnimationType(player1TechniqueId),
617
607
  player1Stance: player1Data.currentStance,
618
608
  player1BasePosition: player1Position3D,
619
- player1AnimationDuration: player1AttackDurationRef.current,
609
+ player1AnimationDuration: player1AttackDuration,
620
610
  player2Attacking: player2Animation.currentState === AnimationState.ATTACK,
621
611
  player2AnimationType: getTechniqueAnimationType(player2TechniqueId),
622
612
  player2Stance: validPlayers[1].currentStance,
623
613
  player2BasePosition: player2Position3D,
624
- player2AnimationDuration: player2AttackDurationRef.current
614
+ player2AnimationDuration: player2AttackDuration
625
615
  });
626
616
  const [player1LocalStance, setPlayer1LocalStance] = useState(validPlayers[0].currentStance);
627
617
  useEffect(() => {
@@ -818,7 +808,7 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
818
808
  const attackFrames = Math.max(1, Math.round(attackDuration * 60));
819
809
  player1HitTriggerFrameRef.current = Math.round(attackFrames * .4);
820
810
  player1AttackHitFiredRef.current = false;
821
- player1AttackDurationRef.current = attackDuration;
811
+ setPlayer1AttackDuration(attackDuration);
822
812
  player1Animation.transitionToAttack(attackDuration);
823
813
  combatActions.setExecutingTechnique(true);
824
814
  onPlayerUpdate(0, {
@@ -1193,13 +1183,13 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1193
1183
  setPlayer2AttackAnimation(p2AttackAnimName);
1194
1184
  if (selectedTechnique.id) setPlayer2TechniqueId(selectedTechnique.id);
1195
1185
  const p2AttackDur = getAnimation(p2AttackAnimName)?.duration ?? .55;
1196
- player2AttackDurationRef.current = p2AttackDur;
1186
+ setPlayer2AttackDuration(p2AttackDur);
1197
1187
  player2Animation.transitionToAttack(p2AttackDur);
1198
1188
  } else {
1199
1189
  setPlayer2AttackAnimation(aiFallbackAnim);
1200
1190
  if (aiFallbackTechnique?.id) setPlayer2TechniqueId(aiFallbackTechnique.id);
1201
1191
  const p2FallbackDur = getAnimation(aiFallbackAnim)?.duration ?? .55;
1202
- player2AttackDurationRef.current = p2FallbackDur;
1192
+ setPlayer2AttackDuration(p2FallbackDur);
1203
1193
  player2Animation.transitionToAttack(p2FallbackDur);
1204
1194
  }
1205
1195
  handleAIAttack(selectedTechnique, targetVitalPoint);
@@ -1215,13 +1205,13 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1215
1205
  setPlayer2AttackAnimation(p2TechAnimName);
1216
1206
  if (selectedTechnique.id) setPlayer2TechniqueId(selectedTechnique.id);
1217
1207
  const p2TechDur = getAnimation(p2TechAnimName)?.duration ?? .6;
1218
- player2AttackDurationRef.current = p2TechDur;
1208
+ setPlayer2AttackDuration(p2TechDur);
1219
1209
  player2Animation.transitionToAttack(p2TechDur);
1220
1210
  } else {
1221
1211
  setPlayer2AttackAnimation(aiFallbackAnim);
1222
1212
  if (aiFallbackTechnique?.id) setPlayer2TechniqueId(aiFallbackTechnique.id);
1223
1213
  const p2FallbackDur = getAnimation(aiFallbackAnim)?.duration ?? .6;
1224
- player2AttackDurationRef.current = p2FallbackDur;
1214
+ setPlayer2AttackDuration(p2FallbackDur);
1225
1215
  player2Animation.transitionToAttack(p2FallbackDur);
1226
1216
  }
1227
1217
  handleAITechnique(selectedTechnique, targetVitalPoint);
@@ -1264,13 +1254,13 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1264
1254
  setPlayer2AttackAnimation(p2CounterAnimName);
1265
1255
  if (selectedTechnique.id) setPlayer2TechniqueId(selectedTechnique.id);
1266
1256
  const p2CounterDur = getAnimation(p2CounterAnimName)?.duration ?? .6;
1267
- player2AttackDurationRef.current = p2CounterDur;
1257
+ setPlayer2AttackDuration(p2CounterDur);
1268
1258
  player2Animation.transitionToAttack(p2CounterDur);
1269
1259
  } else {
1270
1260
  setPlayer2AttackAnimation(aiFallbackAnim);
1271
1261
  if (aiFallbackTechnique?.id) setPlayer2TechniqueId(aiFallbackTechnique.id);
1272
1262
  const p2FallbackDur = getAnimation(aiFallbackAnim)?.duration ?? .6;
1273
- player2AttackDurationRef.current = p2FallbackDur;
1263
+ setPlayer2AttackDuration(p2FallbackDur);
1274
1264
  player2Animation.transitionToAttack(p2FallbackDur);
1275
1265
  }
1276
1266
  handleAIAttack(selectedTechnique, targetVitalPoint);
@@ -1478,245 +1468,249 @@ var CombatScreen3D = ({ players, onPlayerUpdate, currentRound, timeRemaining, is
1478
1468
  ],
1479
1469
  intensity: 1.2
1480
1470
  }),
1481
- /* @__PURE__ */ jsxs(AdaptiveQualityWrapper, { children: [
1482
- showPerformanceOverlay && !showPerformanceMonitor && /* @__PURE__ */ jsx(PerformanceOverlay3D, {}),
1483
- /* @__PURE__ */ jsx(CombatArena3D, {
1484
- lighting: "cyberpunk",
1485
- scale: arenaBounds.scale,
1486
- worldWidthMeters: arenaBounds.worldWidthMeters,
1487
- worldDepthMeters: arenaBounds.worldDepthMeters
1488
- }),
1489
- /* @__PURE__ */ jsx(AnimationUpdater, {
1490
- player1Animation,
1491
- player2Animation
1492
- }),
1493
- /* @__PURE__ */ jsx(AccelerationUpdater, {
1494
- isMoving: player1IsMoving,
1495
- velocity: player1Velocity,
1496
- movementTimeRef: player1MovementTimeRef,
1497
- lastDirectionRef: player1LastDirectionRef,
1498
- onSpeedUpdate: setPlayer1AccelerationBasedSpeed,
1499
- walkSpeed: player1WalkRunSpeeds.walkSpeed,
1500
- runSpeed: player1WalkRunSpeeds.runSpeed
1501
- }),
1502
- /* @__PURE__ */ jsx(Player3DWithTransitions, {
1503
- ...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,
1504
1586
  isMobile,
1505
- facing: "right",
1506
- enableFacialExpressions: true,
1507
- enableEyeTracking: true,
1508
- opponentPosition: player2PositionWithAttackMovement
1587
+ showFractures: true
1509
1588
  }),
1510
- currentAnimation: animationStateToPlayerAnimation(player1Animation.currentState),
1511
- attackAnimation: player1AttackAnimation,
1512
- laterality: combatState.playerLaterality[0],
1513
- enableTransitionEffects: !isMobile,
1514
- enableStanceSymbol: !isMobile,
1515
- enableStanceAudio: true
1516
- }),
1517
- /* @__PURE__ */ jsx(Player3DWithTransitions, {
1518
- ...convertPlayerStateToProps(validPlayers[1], player2PositionWithAttackMovement, player2Rotation, {
1589
+ /* @__PURE__ */ jsx(TraumaOverlay3D, {
1590
+ playerId: "enemy",
1591
+ health: validPlayers[1].health,
1592
+ injuries: player2Injuries,
1593
+ characterPosition: player2Position3D,
1519
1594
  isMobile,
1520
- facing: "right",
1521
- enableFacialExpressions: true,
1522
- enableEyeTracking: true,
1523
- opponentPosition: player1PositionWithAttackMovement
1595
+ showFractures: true
1524
1596
  }),
1525
- currentAnimation: animationStateToPlayerAnimation(player2Animation.currentState),
1526
- attackAnimation: player2AttackAnimation,
1527
- laterality: combatState.playerLaterality[1],
1528
- enableTransitionEffects: !isMobile,
1529
- enableStanceSymbol: !isMobile,
1530
- enableStanceAudio: true
1531
- }),
1532
- (player1MovementState.isLimping || player1MovementState.isSevereLimp) && /* @__PURE__ */ jsx(Html, {
1533
- position: [
1534
- player1Position3D[0],
1535
- player1Position3D[1] + 2.5,
1536
- player1Position3D[2]
1537
- ],
1538
- center: true,
1539
- "data-testid": "player1-movement-status",
1540
- children: /* @__PURE__ */ jsxs("div", {
1541
- style: {
1542
- fontSize: isMobile ? "12px" : "14px",
1543
- color: player1MovementState.isSevereLimp ? toHexColor(KOREAN_COLORS.TEXT_ERROR) : toHexColor(KOREAN_COLORS.ACCENT_GOLD),
1544
- fontFamily: FONT_FAMILY.KOREAN,
1545
- fontWeight: "bold",
1546
- textShadow: "0 0 4px rgba(0,0,0,0.8)",
1547
- background: "rgba(0, 0, 0, 0.6)",
1548
- padding: "4px 8px",
1549
- borderRadius: "4px",
1550
- whiteSpace: "nowrap"
1551
- },
1552
- children: [
1553
- player1MovementState.statusText.korean,
1554
- " |",
1555
- " ",
1556
- player1MovementState.statusText.english
1557
- ]
1558
- })
1559
- }),
1560
- (player2MovementState.isLimping || player2MovementState.isSevereLimp) && /* @__PURE__ */ jsx(Html, {
1561
- position: [
1562
- player2Position3D[0],
1563
- player2Position3D[1] + 2.5,
1564
- player2Position3D[2]
1565
- ],
1566
- center: true,
1567
- "data-testid": "player2-movement-status",
1568
- children: /* @__PURE__ */ jsxs("div", {
1569
- style: {
1570
- fontSize: isMobile ? "12px" : "14px",
1571
- color: player2MovementState.isSevereLimp ? toHexColor(KOREAN_COLORS.TEXT_ERROR) : toHexColor(KOREAN_COLORS.ACCENT_GOLD),
1572
- fontFamily: FONT_FAMILY.KOREAN,
1573
- fontWeight: "bold",
1574
- textShadow: "0 0 4px rgba(0,0,0,0.8)",
1575
- background: "rgba(0, 0, 0, 0.6)",
1576
- padding: "4px 8px",
1577
- borderRadius: "4px",
1578
- whiteSpace: "nowrap"
1579
- },
1580
- children: [
1581
- player2MovementState.statusText.korean,
1582
- " |",
1583
- " ",
1584
- player2MovementState.statusText.english
1585
- ]
1586
- })
1587
- }),
1588
- /* @__PURE__ */ jsx(TraumaOverlay3D, {
1589
- playerId: "player",
1590
- health: validPlayers[0].health,
1591
- injuries: player1Injuries,
1592
- characterPosition: player1Position3D,
1593
- isMobile,
1594
- showFractures: true
1595
- }),
1596
- /* @__PURE__ */ jsx(TraumaOverlay3D, {
1597
- playerId: "enemy",
1598
- health: validPlayers[1].health,
1599
- injuries: player2Injuries,
1600
- characterPosition: player2Position3D,
1601
- isMobile,
1602
- showFractures: true
1603
- }),
1604
- /* @__PURE__ */ jsx(HitEffects3D, {
1605
- effects: combatState.hitEffects,
1606
- onEffectComplete: handleEffectComplete,
1607
- arenaBounds
1608
- }),
1609
- /* @__PURE__ */ jsx(CombatParticleEffects3D, {
1610
- hitEffects: combatState.hitEffects,
1611
- enabled: true,
1612
- isMobile
1613
- }),
1614
- overlayVisible && /* @__PURE__ */ jsxs(Fragment, { children: [
1615
- /* @__PURE__ */ jsx(VitalPointMarkers3D, {
1616
- position: player1Position3D,
1617
- visible: overlayVisible,
1618
- severityFilter: severityFilters,
1619
- regionFilter,
1620
- searchQuery,
1621
- showLabels,
1622
- scale,
1623
- animated,
1624
- 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
1625
1606
  }),
1626
- /* @__PURE__ */ jsx(VitalPointMarkers3D, {
1627
- position: player2Position3D,
1628
- visible: overlayVisible,
1629
- severityFilter: severityFilters,
1630
- regionFilter,
1631
- searchQuery,
1632
- showLabels,
1633
- scale,
1634
- animated,
1635
- 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
1636
1656
  }),
1637
- /* @__PURE__ */ jsx(VitalPointOverlayControlsHtml, {
1638
- screenPosition: {
1639
- top: `${layoutConstants.hudHeight + layoutConstants.padding}px`,
1640
- left: `${layoutConstants.padding}px`
1641
- },
1642
- visible: overlayVisible,
1643
- onVisibleChange: setOverlayVisible,
1644
- severityFilters,
1645
- onSeverityFiltersChange: setSeverityFilters,
1646
- regionFilter,
1647
- onRegionFilterChange: setRegionFilter,
1648
- searchQuery,
1649
- onSearchQueryChange: setSearchQuery,
1650
- showLabels,
1651
- onShowLabelsChange: setShowLabels,
1652
- animated,
1653
- onAnimatedChange: setAnimated,
1654
- scale,
1655
- onScaleChange: setScale,
1657
+ /* @__PURE__ */ jsx(ActionFeedback, {
1658
+ feedbacks: feedbackState.actionFeedbacks,
1659
+ isMobile,
1660
+ arenaBounds
1661
+ }),
1662
+ /* @__PURE__ */ jsx(ComboCounter, {
1663
+ combo: feedbackState.comboCount,
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
+ ],
1656
1705
  isMobile
1706
+ }),
1707
+ process.env.NODE_ENV === "development" && showPerformanceMonitor && /* @__PURE__ */ jsx(FPSMonitor, {
1708
+ enabled: true,
1709
+ warningThreshold: 50,
1710
+ criticalThreshold: 30
1657
1711
  })
1658
- ] }),
1659
- /* @__PURE__ */ jsx(DamageNumbers, {
1660
- damages: feedbackState.damageNumbers,
1661
- isMobile,
1662
- arenaBounds
1663
- }),
1664
- /* @__PURE__ */ jsx(ActionFeedback, {
1665
- feedbacks: feedbackState.actionFeedbacks,
1666
- isMobile,
1667
- arenaBounds
1668
- }),
1669
- /* @__PURE__ */ jsx(ComboCounter, {
1670
- combo: feedbackState.comboCount,
1671
- isMobile
1672
- }),
1673
- feedbackState.currentTechnique && /* @__PURE__ */ jsx(TechniqueName, {
1674
- korean: feedbackState.currentTechnique.korean,
1675
- english: feedbackState.currentTechnique.english,
1676
- isMobile,
1677
- onComplete: () => feedbackActions.hideTechnique()
1678
- }),
1679
- false,
1680
- /* @__PURE__ */ jsx(StanceChangeIndicator, {
1681
- currentStance: currentStanceIndex,
1682
- previousStance,
1683
- isMobile
1684
- }),
1685
- /* @__PURE__ */ jsx(KeyboardHints, {
1686
- visible: showHints,
1687
- currentStance: currentStanceIndex,
1688
- isMobile
1689
- }),
1690
- /* @__PURE__ */ jsx(InputBufferDisplay, {
1691
- queuedInputs,
1692
- isMobile
1693
- }),
1694
- validPlayers[0] && /* @__PURE__ */ jsx(BalanceIndicatorOverlayHtml, {
1695
- player: validPlayers[0],
1696
- currentTime,
1697
- position: [
1698
- -2.5,
1699
- 2.5,
1700
- -1
1701
- ],
1702
- isMobile
1703
- }),
1704
- validPlayers[1] && /* @__PURE__ */ jsx(BalanceIndicatorOverlayHtml, {
1705
- player: validPlayers[1],
1706
- currentTime,
1707
- position: [
1708
- 2.5,
1709
- 2.5,
1710
- -1
1711
- ],
1712
- isMobile
1713
- }),
1714
- process.env.NODE_ENV === "development" && showPerformanceMonitor && /* @__PURE__ */ jsx(FPSMonitor, {
1715
- enabled: true,
1716
- warningThreshold: 50,
1717
- criticalThreshold: 30
1718
- })
1719
- ] }),
1712
+ ]
1713
+ }),
1720
1714
  isMobile ? /* @__PURE__ */ jsxs(EffectComposer, {
1721
1715
  multisampling: 0,
1722
1716
  children: [