66const DEVICE_THREAD_HINT = navigator . hardwareConcurrency || 4 ;
77const DEVICE_MEMORY_HINT = navigator . deviceMemory || 4 ;
88const PERF_TIER = Math . min ( 2 , DEVICE_THREAD_HINT / 4 ) * Math . min ( 1.5 , DEVICE_MEMORY_HINT / 4 ) ;
9- const PARTICLE_COUNT = 8000 ;
10- const BATTLE_PARTICLE_SCALE = PERF_TIER >= 1.5 ? 0.58 : 0.5 ;
9+ const PARTICLE_COUNT = PERF_TIER >= 1.5 ? 6000 : 4200 ;
10+ const BATTLE_PARTICLE_SCALE = PERF_TIER >= 1.5 ? 0.46 : 0.36 ;
1111const SWARM_FORCE = 0.5 ;
1212const SHAPE_FORCE = 0.2 ;
1313const DAMPING = 0.96 ;
@@ -32,7 +32,8 @@ const COLOR_NEBULA = { r: 0.72, g: 0.32, b: 1.0 };
3232const COLOR_NEBULA_CORE = { r : 0.96 , g : 0.9 , b : 1.0 } ;
3333const COLOR_FLASH = { r : 1.0 , g : 1.0 , b : 1.0 } ;
3434const COLOR_BATTLE = { r : 1.0 , g : 0.9 , b : 0.2 } ;
35- const RENDER_PIXEL_RATIO = Math . min ( window . devicePixelRatio || 1 , PERF_TIER >= 1.4 ? 1.15 : 1.0 ) ;
35+ const RENDER_PIXEL_RATIO = Math . min ( window . devicePixelRatio || 1 , PERF_TIER >= 1.5 ? 1.0 : 0.85 ) ;
36+ const HAND_TRACK_INTERVAL_MS = PERF_TIER >= 1.5 ? 38 : 55 ;
3637const HAND_PERSIST_MS = 900 ; // keep hand "active" briefly after tracker flicker
3738const MAX_GPU_EXPLOSIONS = 4 ;
3839
@@ -69,15 +70,15 @@ const particleOwner = new Uint8Array(PARTICLE_COUNT);
6970for ( let i = 0 ; i < PARTICLE_COUNT ; i ++ ) particleOwner [ i ] = i % 2 ;
7071
7172// === BATTLE / KAMEHAMEHA 2.0 — Two-Player Combat ===
72- const BATTLE_PROJECTILE_COUNT = Math . floor ( PARTICLE_COUNT * ( PERF_TIER >= 1.5 ? 0.065 : 0.05 ) ) ;
73+ const BATTLE_PROJECTILE_COUNT = Math . floor ( PARTICLE_COUNT * ( PERF_TIER >= 1.5 ? 0.05 : 0.038 ) ) ;
7374const BATTLE_SHIELD_RADIUS = 155 ;
74- const BATTLE_PROJECTILE_SPEED = 24 ;
75+ const BATTLE_PROJECTILE_SPEED = 18 ;
7576const BATTLE_HIT_RADIUS = 60 ;
7677const BATTLE_ARROW_LENGTH = 520 ;
7778const BATTLE_ARROW_HEAD = 170 ;
7879const BATTLE_ARROW_RECOVER_TIME = 0.22 ;
7980const BATTLE_DESPAWN_MARGIN = 360 ;
80- const BATTLE_EXPLOSION_MASK = 15 ;
81+ const BATTLE_EXPLOSION_MASK = PERF_TIER >= 1.5 ? 31 : 63 ;
8182const BATTLE_TINT_P1 = { r : 0.44 , g : 0.36 , b : 1.0 } ;
8283const BATTLE_TINT_P2 = { r : 0.82 , g : 0.28 , b : 1.0 } ;
8384const BATTLE_EXPLOSION_DEFAULT = { r : 1 , g : 0.7 , b : 0.2 } ;
@@ -213,7 +214,7 @@ scene.add(particles);
213214
214215// --- Starfield ---
215216const starGeo = new THREE . BufferGeometry ( ) ;
216- const STAR_COUNT = 5000 ;
217+ const STAR_COUNT = PERF_TIER >= 1.5 ? 3500 : 2200 ;
217218const sPos = new Float32Array ( STAR_COUNT * 3 ) ;
218219const sCol = new Float32Array ( STAR_COUNT * 3 ) ;
219220for ( let i = 0 ; i < STAR_COUNT ; i ++ ) {
@@ -555,8 +556,7 @@ function chooseSingleHandSide(d) {
555556
556557function applyHandDetection ( side , d , now ) {
557558 const targetHand = hands [ side ] ;
558- // Much snappier lerp so particles feel like they follow the finger instantly
559- const lerpFactor = ( now - targetHand . lastSeen > 250 ) ? 0.7 : 0.82 ;
559+ const lerpFactor = ( now - targetHand . lastSeen > 280 ) ? 0.45 : 0.56 ;
560560 const prevX = targetHand . pos . x ;
561561 const prevY = targetHand . pos . y ;
562562 const prevZ = targetHand . pos . z ;
@@ -572,7 +572,7 @@ function applyHandDetection(side, d, now) {
572572 if ( hadPrevious ) {
573573 targetHand . vel . set ( targetHand . pos . x - prevX , targetHand . pos . y - prevY , targetHand . pos . z - prevZ ) ;
574574 const frameSpeed = targetHand . vel . length ( ) * ( 16.67 / dt ) ;
575- targetHand . speed += ( frameSpeed - targetHand . speed ) * 0.35 ;
575+ targetHand . speed += ( frameSpeed - targetHand . speed ) * 0.22 ;
576576 } else {
577577 targetHand . vel . set ( 0 , 0 , 0 ) ;
578578 targetHand . speed = 0 ;
@@ -1097,17 +1097,20 @@ function onResults(results) {
10971097
10981098const handTracker = new Hands ( { locateFile : ( file ) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${ file } ` } ) ;
10991099// Lower confidence so single extended index finger is reliably detected
1100- handTracker . setOptions ( { maxNumHands : 2 , modelComplexity : 1 , minDetectionConfidence : 0.35 , minTrackingConfidence : 0.3 } ) ;
1100+ handTracker . setOptions ( { maxNumHands : 2 , modelComplexity : PERF_TIER >= 1.5 ? 1 : 0 , minDetectionConfidence : 0.35 , minTrackingConfidence : 0.3 } ) ;
11011101handTracker . onResults ( onResults ) ;
11021102let isProcessingFrame = false ;
1103+ let lastHandFrameAt = 0 ;
11031104const cameraUtils = new Camera ( videoElement , {
11041105 onFrame : async ( ) => {
1105- if ( isProcessingFrame ) return ;
1106+ const now = performance . now ( ) ;
1107+ if ( isProcessingFrame || now - lastHandFrameAt < HAND_TRACK_INTERVAL_MS ) return ;
1108+ lastHandFrameAt = now ;
11061109 isProcessingFrame = true ;
11071110 try { await handTracker . send ( { image : videoElement } ) ; } catch ( e ) { }
11081111 finally { isProcessingFrame = false ; }
11091112 } ,
1110- width : 640 , height : 480
1113+ width : 480 , height : 360
11111114} ) ;
11121115cameraUtils . start ( ) ;
11131116
@@ -1124,25 +1127,25 @@ function updateBattleState(hasTwoHands) {
11241127 battle . p2 . targetTimer = ( battle . p2 . targetTimer || 0 ) - 1 ;
11251128 const cpuDx = battle . p2 . targetPos . x - battle . p2 . pos . x ;
11261129 const cpuDy = battle . p2 . targetPos . y - battle . p2 . pos . y ;
1127- if ( battle . p2 . targetTimer <= 0 || cpuDx * cpuDx + cpuDy * cpuDy < 9000 ) {
1128- battle . p2 . targetPos . set ( ( Math . random ( ) - 0.5 ) * width * 0.55 , ( Math . random ( ) - 0.5 ) * height * 0.5 , 0 ) ;
1129- battle . p2 . targetTimer = 140 + Math . floor ( Math . random ( ) * 120 ) ;
1130+ if ( battle . p2 . targetTimer <= 0 || cpuDx * cpuDx + cpuDy * cpuDy < 6400 ) {
1131+ battle . p2 . targetPos . set ( ( Math . random ( ) - 0.5 ) * width * 0.42 , ( Math . random ( ) - 0.5 ) * height * 0.38 , 0 ) ;
1132+ battle . p2 . targetTimer = 220 + Math . floor ( Math . random ( ) * 160 ) ;
11301133 }
1131- battle . p2 . pos . lerp ( battle . p2 . targetPos , 0.018 ) ;
1134+ battle . p2 . pos . lerp ( battle . p2 . targetPos , 0.01 ) ;
11321135
11331136 if ( ! battle . roundOver && battle . p1 . active ) {
1134- if ( ! battle . p2 . charging && Math . random ( ) < 0.006 ) battle . p2 . charging = true ;
1137+ if ( ! battle . p2 . charging && Math . random ( ) < 0.004 ) battle . p2 . charging = true ;
11351138 if ( battle . p2 . charging ) {
11361139 battle . p2 . chargeAmount = Math . min ( 1 , battle . p2 . chargeAmount + 0.012 ) ;
1137- if ( battle . p2 . chargeAmount > 0.72 && Math . random ( ) < 0.025 ) {
1140+ if ( battle . p2 . chargeAmount > 0.78 && Math . random ( ) < 0.018 ) {
11381141 const attacks = [ 'normal' , 'volley' , 'fireball' ] ;
11391142 fireBattleBeam ( battle . p2 , battle . p1 , attacks [ Math . floor ( Math . random ( ) * attacks . length ) ] ) ;
11401143 }
11411144 } else {
11421145 battle . p2 . chargeAmount *= 0.9 ;
1143- if ( Math . random ( ) < 0.006 ) battle . p2 . shielding = true ;
1144- if ( battle . p2 . shielding && Math . random ( ) < 0.035 ) battle . p2 . shielding = false ;
1145- battle . p2 . shieldStrength += ( ( battle . p2 . shielding ? 1.0 : 0 ) - battle . p2 . shieldStrength ) * 0.18 ;
1146+ if ( Math . random ( ) < 0.004 ) battle . p2 . shielding = true ;
1147+ if ( battle . p2 . shielding && Math . random ( ) < 0.025 ) battle . p2 . shielding = false ;
1148+ battle . p2 . shieldStrength += ( ( battle . p2 . shielding ? 1.0 : 0 ) - battle . p2 . shieldStrength ) * 0.12 ;
11461149 }
11471150 }
11481151 } else {
@@ -1161,11 +1164,11 @@ function updateBattleState(hasTwoHands) {
11611164 battle . p2 . targetTimer = ( battle . p2 . targetTimer || 0 ) - 1 ;
11621165 const dx = battle . p2 . targetPos . x - battle . p2 . pos . x ;
11631166 const dy = battle . p2 . targetPos . y - battle . p2 . pos . y ;
1164- if ( battle . p2 . targetTimer <= 0 || dx * dx + dy * dy < 6400 ) {
1165- battle . p2 . targetPos . set ( width * ( 0.18 + Math . random ( ) * 0.22 ) , ( Math . random ( ) - 0.5 ) * height * 0.45 , 0 ) ;
1166- battle . p2 . targetTimer = 160 + Math . floor ( Math . random ( ) * 140 ) ;
1167+ if ( battle . p2 . targetTimer <= 0 || dx * dx + dy * dy < 4900 ) {
1168+ battle . p2 . targetPos . set ( width * ( 0.16 + Math . random ( ) * 0.18 ) , ( Math . random ( ) - 0.5 ) * height * 0.36 , 0 ) ;
1169+ battle . p2 . targetTimer = 240 + Math . floor ( Math . random ( ) * 160 ) ;
11671170 }
1168- battle . p2 . pos . lerp ( battle . p2 . targetPos , 0.012 ) ;
1171+ battle . p2 . pos . lerp ( battle . p2 . targetPos , 0.008 ) ;
11691172 }
11701173 }
11711174
0 commit comments