@@ -79,6 +79,9 @@ const BATTLE_ARROW_HEAD = 170;
7979const BATTLE_ARROW_RECOVER_TIME = 0.22 ;
8080const BATTLE_DESPAWN_MARGIN = 360 ;
8181const BATTLE_EXPLOSION_MASK = PERF_TIER >= 1.5 ? 31 : 63 ;
82+ const ATTACK_INTENT_SPEED = 2.4 ;
83+ const ULTIMATE_INTENT_SPEED = 7.5 ;
84+ const PLAYER_ENGAGED_MS = 4500 ;
8285const BATTLE_TINT_P1 = { r : 0.44 , g : 0.36 , b : 1.0 } ;
8386const BATTLE_TINT_P2 = { r : 0.82 , g : 0.28 , b : 1.0 } ;
8487const BATTLE_EXPLOSION_DEFAULT = { r : 1 , g : 0.7 , b : 0.2 } ;
@@ -129,7 +132,8 @@ const battle = {
129132 explosions : [ ] ,
130133 roundOver : false ,
131134 roundResetTime : 0 ,
132- winner : null
135+ winner : null ,
136+ lastPlayerActionAt : 0
133137} ;
134138
135139// === AUDIO ===
@@ -266,6 +270,7 @@ function resetBattle() {
266270 battle . roundOver = false ;
267271 battle . roundResetTime = 0 ;
268272 battle . winner = null ;
273+ battle . lastPlayerActionAt = 0 ;
269274}
270275
271276function getBattleGesture ( landmarks , isPinching ) {
@@ -398,6 +403,7 @@ function tryFireChargedAttack(fighter) {
398403 const enemy = ( fighter === battle . p1 ) ? battle . p2 : battle . p1 ;
399404 if ( enemy . active && fighter . chargeAmount > 0.08 ) {
400405 fireBattleBeam ( fighter , enemy , fighter . lastAttackGesture || 'normal' ) ;
406+ if ( fighter === battle . p1 ) battle . lastPlayerActionAt = Date . now ( ) ;
401407 } else {
402408 fighter . chargeAmount = 0 ;
403409 fighter . charging = false ;
@@ -424,7 +430,10 @@ function updateFighterFromHand(fighter, hand, landmarks) {
424430 fighter . shielding = isShielding ;
425431 fighter . shieldStrength += ( ( isShielding ? 1.0 : 0 ) - fighter . shieldStrength ) * 0.25 ;
426432
427- const isAttacking = ( gesture === 'pinch' || gesture === 'volley' || gesture === 'fireball' ) ;
433+ const isDirectAttack = gesture === 'pinch' ;
434+ const isGestureAttack = ( gesture === 'volley' || gesture === 'fireball' ) ;
435+ const hasAttackIntent = isDirectAttack || fighter . charging || hand . speed > ATTACK_INTENT_SPEED ;
436+ const isAttacking = isDirectAttack || ( isGestureAttack && hasAttackIntent ) ;
428437 const wasAttacking = fighter . charging ;
429438
430439 if ( gesture === 'fist' && wasAttacking && ! fighter . shielding ) {
@@ -438,6 +447,7 @@ function updateFighterFromHand(fighter, hand, landmarks) {
438447 fighter . chargeAmount = Math . min ( 1 , fighter . chargeAmount + ( gesture === 'fireball' ? 0.025 : 0.032 ) ) ;
439448 fighter . pinchLossFrames = 0 ;
440449 fighter . lastAttackGesture = gesture ;
450+ if ( fighter === battle . p1 ) battle . lastPlayerActionAt = Date . now ( ) ;
441451 } else if ( wasAttacking && ! isAttacking && ! fighter . shielding ) {
442452 fighter . pinchLossFrames = ( fighter . pinchLossFrames || 0 ) + 1 ;
443453 if ( fighter . pinchLossFrames < 4 ) {
@@ -1133,7 +1143,8 @@ function updateBattleState(hasTwoHands) {
11331143 }
11341144 battle . p2 . pos . lerp ( battle . p2 . targetPos , 0.01 ) ;
11351145
1136- if ( ! battle . roundOver && battle . p1 . active ) {
1146+ const playerEngaged = battle . p1 . charging || battle . p1 . beam . active || Date . now ( ) - battle . lastPlayerActionAt < PLAYER_ENGAGED_MS ;
1147+ if ( ! battle . roundOver && battle . p1 . active && playerEngaged ) {
11371148 if ( ! battle . p2 . charging && Math . random ( ) < 0.004 ) battle . p2 . charging = true ;
11381149 if ( battle . p2 . charging ) {
11391150 battle . p2 . chargeAmount = Math . min ( 1 , battle . p2 . chargeAmount + 0.012 ) ;
@@ -1147,6 +1158,11 @@ function updateBattleState(hasTwoHands) {
11471158 if ( battle . p2 . shielding && Math . random ( ) < 0.025 ) battle . p2 . shielding = false ;
11481159 battle . p2 . shieldStrength += ( ( battle . p2 . shielding ? 1.0 : 0 ) - battle . p2 . shieldStrength ) * 0.12 ;
11491160 }
1161+ } else {
1162+ battle . p2 . charging = false ;
1163+ battle . p2 . chargeAmount *= 0.88 ;
1164+ battle . p2 . shielding = false ;
1165+ battle . p2 . shieldStrength *= 0.9 ;
11501166 }
11511167 } else {
11521168 if ( hasTwoHands ) {
@@ -1173,7 +1189,8 @@ function updateBattleState(hasTwoHands) {
11731189 }
11741190
11751191 // ULTIMATE DIAMOND COMBO DETECT (works in both Battle and Survival if you use 2 hands)
1176- if ( battleModeActive && ! battle . roundOver && comboCount >= 0 && hands . left . active && hands . right . active ) {
1192+ const ultimateIntent = ( hands . left . pinch && hands . right . pinch ) || ( hands . left . speed + hands . right . speed ) > ULTIMATE_INTENT_SPEED ;
1193+ if ( battleModeActive && ! battle . roundOver && comboCount >= 0 && hands . left . active && hands . right . active && ultimateIntent ) {
11771194 const dx = hands . left . pos . x - hands . right . pos . x ;
11781195 const dy = hands . left . pos . y - hands . right . pos . y ;
11791196 const distSq = dx * dx + dy * dy ;
@@ -1257,7 +1274,8 @@ function animate() {
12571274 const p2BeamPush = 5 + p2Beam . power * 7 ;
12581275
12591276 // Supernova trigger (duel mode)
1260- if ( isDuelMode && hasTwoHands ) {
1277+ const duelIntent = ( hands . left . pinch && hands . right . pinch ) || ( hands . left . speed + hands . right . speed ) > ULTIMATE_INTENT_SPEED ;
1278+ if ( isDuelMode && hasTwoHands && duelIntent ) {
12611279 const ax = hands . right . pos . x - hands . left . pos . x ;
12621280 const ay = hands . right . pos . y - hands . left . pos . y ;
12631281 const az = hands . right . pos . z - hands . left . pos . z ;
0 commit comments