zeroed-some/sprong / 7f416a3

Browse files

BOPPPP

Authored by espadonne
SHA
7f416a334ca10a80dce2e938b1d3722a6966f60a
Parents
420b0f7
Tree
005b51a

1 changed file

StatusFile+-
M sprong.js 436 75
sprong.jsmodified
@@ -6,23 +6,73 @@ const Bodies = Matter.Bodies;
66
 const Render = Matter.Render;
77
 const Constraint = Matter.Constraint;
88
 
9
+// Canvas settings
10
+const CANVAS_WIDTH  = 800;
11
+const CANVAS_HEIGHT = 400;
12
+
13
+// Game constants
14
+const BALL_SPEED    = 6;
15
+const BALL_RADIUS   = 12;
16
+const PADDLE_WIDTH  = 20;
17
+const PADDLE_HEIGHT = 80;
18
+
19
+// Enhanced movement constants (tuned for faster response)
20
+const SUPPORT_SPEED     = 6.5;  // Bumped up
21
+const SUPPORT_ACCEL     = 1.2;  // Increased acceleration
22
+const INPUT_SMOOTHING   = 0.25; // More responsive
23
+const SUPPORT_MAX_SPEED = 8;    // Higher max speed
24
+
25
+// Touch/mouse control constants
26
+const MOUSE_SPEED_LIMIT = 4;    // Max speed for mouse movement
27
+const MOUSE_LAG_FACTOR  = 0.12; // How much lag in mouse following
28
+const TOUCH_SENSITIVITY = 1.2;  // Touch movement multiplier
29
+
30
+// Spring physics constants (using your current settings)
31
+const PADDLE_MASS       = 0.8;  
32
+const SPRING_LENGTH     = 50;   
33
+const SPRING_DAMPING    = 0.6;  
34
+const SPRING_STIFFNESS  = 0.025;
35
+
36
+// Visual enhancement constants
37
+const TRAIL_SEGMENTS        = 8;
38
+const PADDLE_GLOW_DISTANCE  = 25;
39
+const SPRING_GLOW_INTENSITY = 120; // More intense glow
40
+
41
+// Particle system constants
42
+const MAX_PARTICLES         = 100;
43
+const PARTICLE_LIFE         = 60;
44
+const IMPACT_PARTICLES      = 8;
45
+const SPRING_PARTICLE_RATE  = 0.3;
46
+
47
+// Bop system constants
48
+const BOP_FORCE             = 1.0;
49
+const BOP_DURATION          = 300;
50
+const BOP_COOLDOWN          = 500;
51
+const ANCHOR_RECOIL         = 40;     // How far the anchor moves backward during bop
52
+const BOP_RANGE             = 600;    // How far the paddle can thrust forward
53
+const BOP_VELOCITY_BOOST    = 12;     // Initial velocity boost for paddle
54
+
955
 // Game variables
1056
 let ball;
1157
 let world;
1258
 let engine;
1359
 
60
+// Particle systems
61
+let particles = [];
62
+let impactParticles = [];
63
+
1464
 // Spring paddle system components
1565
 let boundaries = [];
1666
 let leftSupport, leftPaddle, leftSpring;
1767
 let rightSupport, rightPaddle, rightSpring;
1868
 
1969
 // Game state
20
-let leftScore = 0;
70
+let leftScore  = 0;
2171
 let rightScore = 0;
22
-let gameStarted = false;
23
-let gameState = 'menu'; // 'menu', 'playing', 'paused'
24
-let gameMode = 'vs-cpu'; // 'vs-cpu' or 'vs-human'
2572
 let aiEnabled = true;
73
+let gameState = 'menu';   // 'menu', 'playing', 'paused'
74
+let gameMode  = 'vs-cpu'; // 'vs-cpu' or 'vs-human'
75
+let gameStarted = false;
2676
 
2777
 // Menu state
2878
 let menuState = {
@@ -59,13 +109,229 @@ let aiState = {
59109
     idleTarget: 200,      // Where AI "wants" to be when idle
60110
     microAdjustment: 0,   // Small random movements
61111
     breathingOffset: 0,   // Subtle breathing-like motion
62
-    lastMicroTime: 0      // For micro-movement timing
112
+    lastMicroTime: 0,     // For micro-movement timing
113
+    
114
+    // AI Bop system
115
+    consideringBop: false,  // Is AI thinking about bopping?
116
+    bopDecisionTime: 0,     // When AI decided to bop
117
+    bopTiming: 200          // How long before ball contact to bop (ms)
63118
 };
64119
 
65120
 // Player input
66121
 let keys = {};
67122
 let inputBuffer = { left: 0, right: 0 };
68123
 
124
+// Bop system
125
+let bopState = {
126
+    left: {
127
+        active: false,
128
+        startTime: 0,
129
+        duration: BOP_DURATION,
130
+        power: BOP_FORCE,
131
+        cooldown: BOP_COOLDOWN,
132
+        lastBopTime: 0,
133
+        originalPos: null
134
+    },
135
+    right: {
136
+        active: false,
137
+        startTime: 0,
138
+        duration: BOP_DURATION,
139
+        power: BOP_FORCE,
140
+        cooldown: BOP_COOLDOWN,
141
+        lastBopTime: 0,
142
+        originalPos: null
143
+    }
144
+};
145
+
146
+function handleBopInput() {
147
+    let currentTime = millis();
148
+    
149
+    // Left player bop - use Left Shift for both modes
150
+    let leftBopPressed = keys['Shift'] && !keys['Control']; // Left shift (without Ctrl)
151
+    
152
+    if (leftBopPressed && !bopState.left.active && 
153
+        currentTime - bopState.left.lastBopTime > bopState.left.cooldown) {
154
+        activateBop('left', currentTime);
155
+    }
156
+    
157
+    // Right player bop (Enter - only in two player mode)
158
+    if (!aiEnabled) {
159
+        let rightBopPressed = keys['Enter'];
160
+        
161
+        if (rightBopPressed && !bopState.right.active && 
162
+            currentTime - bopState.right.lastBopTime > bopState.right.cooldown) {
163
+            activateBop('right', currentTime);
164
+        }
165
+    }
166
+    
167
+    // Update active bops
168
+    updateBopStates(currentTime);
169
+}
170
+
171
+function activateBop(side, currentTime) {
172
+    bopState[side].active = true;
173
+    bopState[side].startTime = currentTime;
174
+    bopState[side].lastBopTime = currentTime;
175
+    
176
+    // Get the relevant bodies
177
+    let paddle = side === 'left' ? leftPaddle : rightPaddle;
178
+    let support = side === 'left' ? leftSupport : rightSupport;
179
+    
180
+    // Calculate direction from support to paddle (this is the bop direction)
181
+    let dx = paddle.position.x - support.position.x;
182
+    let dy = paddle.position.y - support.position.y;
183
+    
184
+    // Normalize direction
185
+    let magnitude = Math.sqrt(dx * dx + dy * dy);
186
+    if (magnitude > 0) {
187
+        dx /= magnitude;
188
+        dy /= magnitude;
189
+        
190
+        // Calculate anchor recoil distance
191
+        let anchorRecoilDistance = ANCHOR_RECOIL * 0.4;
192
+        
193
+        // Move the support BACKWARD (recoil effect)
194
+        let newSupportX = support.position.x - dx * anchorRecoilDistance;
195
+        let newSupportY = support.position.y - dy * anchorRecoilDistance;
196
+        
197
+        // Apply the support movement
198
+        Body.setPosition(support, { x: newSupportX, y: newSupportY });
199
+        
200
+        // Store original support position for recovery
201
+        bopState[side].originalPos = { 
202
+            x: support.position.x + dx * anchorRecoilDistance, 
203
+            y: support.position.y + dy * anchorRecoilDistance 
204
+        };
205
+        
206
+        // IMPORTANT: Set paddle velocity directly for immediate forward thrust
207
+        // This creates the "shooting forward" effect based on BOP_RANGE
208
+        let forwardSpeed = (BOP_RANGE / SPRING_LENGTH) * BOP_VELOCITY_BOOST;
209
+        Body.setVelocity(paddle, {
210
+            x: paddle.velocity.x + dx * forwardSpeed,
211
+            y: paddle.velocity.y + dy * forwardSpeed
212
+        });
213
+        
214
+        // Also apply a strong forward force for continued acceleration
215
+        Body.applyForce(paddle, paddle.position, {
216
+            x: dx * bopState[side].power * BOP_RANGE * 0.1,
217
+            y: dy * bopState[side].power * BOP_RANGE * 0.1
218
+        });
219
+        
220
+        // Create particle burst for visual feedback
221
+        for (let i = 0; i < 5; i++) {
222
+            let angle = Math.atan2(dy, dx) + (Math.random() - 0.5) * 0.5;
223
+            let speed = Math.random() * 4 + 2;
224
+            particles.push({
225
+                x: support.position.x,
226
+                y: support.position.y,
227
+                vx: Math.cos(angle) * speed * -1, // Particles go backward
228
+                vy: Math.sin(angle) * speed * -1,
229
+                size: Math.random() * 3 + 2,
230
+                life: 30,
231
+                maxLife: 30,
232
+                color: { r: 255, g: 255, b: 100 },
233
+                type: 'impact'
234
+            });
235
+        }
236
+        
237
+        // Force collision detection update
238
+        Engine.update(engine, 0);
239
+    }
240
+    
241
+    console.log(side + " player BOP!");
242
+}
243
+
244
+function updateBopStates(currentTime) {
245
+    // Update left bop
246
+    if (bopState.left.active) {
247
+        let elapsed = currentTime - bopState.left.startTime;
248
+        let progress = elapsed / bopState.left.duration;
249
+        
250
+        if (progress >= 1.0) {
251
+            // Bop finished
252
+            bopState.left.active = false;
253
+            bopState.left.originalPos = null;
254
+        } else {
255
+            // Smoothly return support to original position
256
+            if (bopState.left.originalPos) {
257
+                let support = leftSupport;
258
+                let currentX = support.position.x;
259
+                let currentY = support.position.y;
260
+                
261
+                // Ease back to original position
262
+                let returnSpeed = 0.15 * (1 - Math.pow(1 - progress, 3)); // Ease out cubic
263
+                let newX = currentX + (bopState.left.originalPos.x - currentX) * returnSpeed;
264
+                let newY = currentY + (bopState.left.originalPos.y - currentY) * returnSpeed;
265
+                
266
+                Body.setPosition(support, { x: newX, y: newY });
267
+            }
268
+            
269
+            // Apply range limiting during active bop
270
+            limitBopRange(leftSupport, leftPaddle);
271
+        }
272
+    }
273
+    
274
+    // Update right bop
275
+    if (bopState.right.active) {
276
+        let elapsed = currentTime - bopState.right.startTime;
277
+        let progress = elapsed / bopState.right.duration;
278
+        
279
+        if (progress >= 1.0) {
280
+            // Bop finished
281
+            bopState.right.active = false;
282
+            bopState.right.originalPos = null;
283
+        } else {
284
+            // Smoothly return support to original position
285
+            if (bopState.right.originalPos) {
286
+                let support = rightSupport;
287
+                let currentX = support.position.x;
288
+                let currentY = support.position.y;
289
+                
290
+                // Ease back to original position
291
+                let returnSpeed = 0.15 * (1 - Math.pow(1 - progress, 3)); // Ease out cubic
292
+                let newX = currentX + (bopState.right.originalPos.x - currentX) * returnSpeed;
293
+                let newY = currentY + (bopState.right.originalPos.y - currentY) * returnSpeed;
294
+                
295
+                Body.setPosition(support, { x: newX, y: newY });
296
+            }
297
+            
298
+            // Apply range limiting during active bop
299
+            limitBopRange(rightSupport, rightPaddle);
300
+        }
301
+    }
302
+}
303
+
304
+function limitBopRange(support, paddle) {
305
+    // Calculate current distance
306
+    let currentDistance = dist(support.position.x, support.position.y,
307
+                              paddle.position.x, paddle.position.y);
308
+    
309
+    // If paddle is beyond max range (spring length + bop range), pull it back
310
+    let maxDistance = SPRING_LENGTH + BOP_RANGE;
311
+    if (currentDistance > maxDistance) {
312
+        // Calculate direction from support to paddle
313
+        let dx = paddle.position.x - support.position.x;
314
+        let dy = paddle.position.y - support.position.y;
315
+        
316
+        // Normalize
317
+        let magnitude = Math.sqrt(dx * dx + dy * dy);
318
+        dx /= magnitude;
319
+        dy /= magnitude;
320
+        
321
+        // Set paddle position at max distance
322
+        let newX = support.position.x + dx * maxDistance;
323
+        let newY = support.position.y + dy * maxDistance;
324
+        
325
+        // Preserve some velocity but dampen it
326
+        let currentVel = paddle.velocity;
327
+        Body.setPosition(paddle, { x: newX, y: newY });
328
+        Body.setVelocity(paddle, { 
329
+            x: currentVel.x * 0.7, 
330
+            y: currentVel.y * 0.7 
331
+        });
332
+    }
333
+}
334
+
69335
 // Touch/mouse input
70336
 let mouseInput = {
71337
     active: false,
@@ -76,43 +342,13 @@ let mouseInput = {
76342
     deadZone: 15      // Minimum distance before movement starts
77343
 };
78344
 
79
-// Particle systems
80
-let particles = [];
81
-let impactParticles = [];
82
-
83
-// Canvas settings
84
-const CANVAS_WIDTH  = 800;
85
-const CANVAS_HEIGHT = 400;
86
-
87
-// Game constants
88
-const BALL_SPEED    = 6;
89
-const BALL_RADIUS   = 12;
90
-const PADDLE_WIDTH  = 20;
91
-const PADDLE_HEIGHT = 80;
92
-
93
-// Enhanced movement constants (tuned for faster response)
94
-const SUPPORT_SPEED     = 6.5;    // Bumped up from 4.5
95
-const SUPPORT_ACCEL     = 1.2;    // Increased acceleration
96
-const INPUT_SMOOTHING   = 0.25;   // More responsive
97
-const SUPPORT_MAX_SPEED = 8;      // Higher max speed
98
-
99
-// Touch/mouse control constants
100
-const MOUSE_LAG_FACTOR  = 0.12;   // How much lag in mouse following
101
-const MOUSE_SPEED_LIMIT = 4;      // Max speed for mouse movement
102
-const TOUCH_SENSITIVITY = 1.2;    // Touch movement multiplier
103
-
104
-// Spring physics constants (adjusted for better oscillation)
105
-const PADDLE_MASS       = 0.8;    // Back to more stable value
106
-const SPRING_LENGTH     = 50;     // Increased from 40 for more room
107
-const SPRING_DAMPING    = 0.6;    // More damping = less erratic
108
-const SPRING_STIFFNESS  = 0.025;  // Slightly lower for smoother motion
109345
 
110346
 // AI difficulty settings
111347
 const AI_SETTINGS = {
112348
     easy: {
113349
         reactionTime: 400,    // ms delay
114350
         accuracy: 0.7,        // 70% accuracy
115
-        speed: 0.6,           // 60% of normal speed
351
+        speed: 0.8,           // Increased from 0.6
116352
         prediction: 0.3,      // 30% prediction vs reaction
117353
         aggression: 0.2,      // Low aggression
118354
         oscillation: 0.3      // Minimal oscillation
@@ -120,7 +356,7 @@ const AI_SETTINGS = {
120356
     medium: {
121357
         reactionTime: 250,
122358
         accuracy: 0.85,
123
-        speed: 0.8,
359
+        speed: 1.0,           // Increased from 0.8
124360
         prediction: 0.6,
125361
         aggression: 0.5,      // Moderate aggression
126362
         oscillation: 0.7      // Good oscillation technique
@@ -128,24 +364,13 @@ const AI_SETTINGS = {
128364
     hard: {
129365
         reactionTime: 150,
130366
         accuracy: 0.95,
131
-        speed: 1.0,
367
+        speed: 1.2,           // Increased from 1.0 
132368
         prediction: 0.8,
133369
         aggression: 0.8,      // High aggression
134370
         oscillation: 1.0      // Master-level oscillation
135371
     }
136372
 };
137373
 
138
-// Visual enhancement constants
139
-const TRAIL_SEGMENTS        = 8;
140
-const PADDLE_GLOW_DISTANCE  = 25;
141
-const SPRING_GLOW_INTENSITY = 120; // More intense glow
142
-
143
-// Particle system constants
144
-const MAX_PARTICLES         = 100;
145
-const PARTICLE_LIFE         = 60;
146
-const IMPACT_PARTICLES      = 8;
147
-const SPRING_PARTICLE_RATE  = 0.3;
148
-
149374
 function setup() {
150375
     // Create p5.js canvas
151376
     let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
@@ -179,10 +404,42 @@ function setup() {
179404
         rightSupport, rightPaddle, rightSpring
180405
     ]);
181406
     
182
-    console.log("Sprong Phase 5 Complete!");
183
-    console.log("Particle effects system");
184
-    console.log("Tuned physics for maximum bounce");
185
-    console.log("Faster, more responsive paddles");
407
+    // Set up collision events
408
+    Matter.Events.on(engine, 'collisionStart', function(event) {
409
+        let pairs = event.pairs;
410
+        
411
+        for (let i = 0; i < pairs.length; i++) {
412
+            let pair = pairs[i];
413
+            
414
+            // Check if collision involves ball and paddle
415
+            if ((pair.bodyA === ball && (pair.bodyB === leftPaddle || pair.bodyB === rightPaddle)) ||
416
+                (pair.bodyB === ball && (pair.bodyA === leftPaddle || pair.bodyA === rightPaddle))) {
417
+                
418
+                let paddle = pair.bodyA === ball ? pair.bodyB : pair.bodyA;
419
+                let isLeftPaddle = paddle === leftPaddle;
420
+                
421
+                // Apply bop boost if paddle is currently bopping
422
+                if ((isLeftPaddle && bopState.left.active) || (!isLeftPaddle && bopState.right.active)) {
423
+                    // Get current velocities
424
+                    let ballVel = ball.velocity;
425
+                    let paddleVel = paddle.velocity;
426
+                    
427
+                    // Calculate boost based on paddle velocity
428
+                    let boostX = paddleVel.x * 0.5;
429
+                    let boostY = paddleVel.y * 0.5;
430
+                    
431
+                    // Apply extra velocity to ball
432
+                    Body.setVelocity(ball, {
433
+                        x: ballVel.x * 1.3 + boostX,
434
+                        y: ballVel.y * 1.3 + boostY
435
+                    });
436
+                    
437
+                    // Create extra impact particles
438
+                    createImpactParticles(ball.position.x, ball.position.y, ballVel.x, ballVel.y);
439
+                }
440
+            }
441
+        }
442
+    });
186443
 }
187444
 
188445
 function createSpringPaddleSystem(side) {
@@ -202,9 +459,20 @@ function createSpringPaddleSystem(side) {
202459
             mass: PADDLE_MASS,
203460
             restitution: 1.3,  // Even bouncier!
204461
             friction: 0,
205
-            frictionAir: 0.005 // Less air resistance
462
+            frictionAir: 0.005, // Less air resistance
463
+            isSensor: false,
464
+            slop: 0.01,  // Tighter collision detection
465
+            render: {
466
+                fillStyle: '#00ff88'
467
+            }
206468
         });
207469
         
470
+        // Enable continuous collision detection for better bop collisions
471
+        leftPaddle.collisionFilter = {
472
+            category: 0x0002,
473
+            mask: 0xFFFF
474
+        };
475
+        
208476
         // Spring constraint connecting support to paddle
209477
         leftSpring = Constraint.create({
210478
             bodyA: leftSupport,
@@ -225,9 +493,20 @@ function createSpringPaddleSystem(side) {
225493
             mass: PADDLE_MASS,
226494
             restitution: 1.2,  // Slightly toned down
227495
             friction: 0,
228
-            frictionAir: 0.008 // Bit more air resistance for stability
496
+            frictionAir: 0.008, // Bit more air resistance for stability
497
+            isSensor: false,
498
+            slop: 0.01,  // Tighter collision detection
499
+            render: {
500
+                fillStyle: '#ff6464'
501
+            }
229502
         });
230503
         
504
+        // Enable continuous collision detection for better bop collisions
505
+        rightPaddle.collisionFilter = {
506
+            category: 0x0004,
507
+            mask: 0xFFFF
508
+        };
509
+        
231510
         // Spring constraint connecting support to paddle
232511
         rightSpring = Constraint.create({
233512
             bodyA: rightSupport,
@@ -256,6 +535,13 @@ function draw() {
256535
         updateParticles();
257536
         checkCollisions();
258537
         
538
+        // Enhanced collision detection during bops - just more frequent updates
539
+        if (bopState.left.active || bopState.right.active) {
540
+            // Multiple smaller physics updates for better collision detection
541
+            Engine.update(engine, 8);
542
+            Engine.update(engine, 8);
543
+        }
544
+        
259545
         // Check for scoring
260546
         checkBallPosition();
261547
         
@@ -283,6 +569,9 @@ function handleEnhancedInput() {
283569
     handleKeyboardInput();
284570
     handleMouseTouchInput();
285571
     
572
+    // Handle bop mechanics
573
+    handleBopInput();
574
+    
286575
     // Handle AI if enabled
287576
     if (aiEnabled && gameStarted) {
288577
         handleAI();
@@ -448,13 +737,44 @@ function handleAITracking(currentTime, ballPos, ballVel, aiSettings) {
448737
     desiredPaddleY = constrain(desiredPaddleY, 80, height - 80);
449738
     
450739
     // Estimate anchor position needed to get paddle to desired position
451
-    // This is tricky because spring physics affects paddle position
452740
     let anchorOffsetNeeded = calculateAnchorOffset(desiredPaddleY, paddlePos, anchorPos);
453741
     let targetAnchorY = desiredPaddleY + anchorOffsetNeeded;
454742
     
455743
     // Always apply some level of paddle-aware tracking
456744
     aiState.targetY = lerp(aiState.targetY, targetAnchorY, trackingIntensity);
457745
     
746
+    // AI Bop decision logic
747
+    if (ballApproaching && ballDistance < 150 && !aiState.consideringBop) {
748
+        // Consider bopping if ball is close and conditions are right
749
+        let shouldConsiderBop = ballSpeed > 8 &&  // Fast incoming ball
750
+                               Math.abs(ballVel.y) < 4 &&  // Not too much vertical movement
751
+                               random() < aiSettings.aggression * 0.4; // Chance based on aggression
752
+        
753
+        if (shouldConsiderBop) {
754
+            aiState.consideringBop = true;
755
+            aiState.bopDecisionTime = currentTime;
756
+        }
757
+    }
758
+    
759
+    // Execute bop at the right moment
760
+    if (aiState.consideringBop && ballApproaching) {
761
+        let timeToBop = currentTime - aiState.bopDecisionTime;
762
+        let shouldBop = timeToBop > aiState.bopTiming && 
763
+                       ballDistance < 100 && 
764
+                       !bopState.right.active &&
765
+                       currentTime - bopState.right.lastBopTime > BOP_COOLDOWN;
766
+        
767
+        if (shouldBop) {
768
+            activateBop('right', currentTime);
769
+            aiState.consideringBop = false;
770
+        }
771
+        
772
+        // Cancel bop consideration if ball gets too far
773
+        if (ballDistance > 150) {
774
+            aiState.consideringBop = false;
775
+        }
776
+    }
777
+    
458778
     // Only do advanced prediction and windup logic if enough time has passed (reaction delay)
459779
     if (currentTime - aiState.lastUpdateTime > aiSettings.reactionTime) {
460780
         
@@ -486,6 +806,7 @@ function handleAITracking(currentTime, ballPos, ballVel, aiSettings) {
486806
                               ballDistance > 200 &&       // Lots of distance required  
487807
                               Math.abs(ballVel.y) < 3 &&  // Very limited vertical movement
488808
                               Math.abs(ballVel.x) > 1 &&  // Ball must be actually moving toward AI
809
+                              !aiState.consideringBop &&  // Don't windup if considering bop
489810
                               random() < aiSettings.oscillation * aiState.aggressionLevel * 0.3; // Much lower chance
490811
             
491812
             if (shouldWindUp) {
@@ -529,28 +850,39 @@ function calculateAnchorOffset(targetPaddleY, currentPaddlePos, currentAnchorPos
529850
 
530851
 function handleAIWindup(currentTime, ballPos, ballVel, aiSettings) {
531852
     let windupTime = currentTime - aiState.windupStartTime;
532
-    let maxWindupTime = 500 / Math.max(aiState.aggressionLevel, 0.3); // Longer windup for bigger movement
533853
     
534
-    // Calculate windup target with much bigger distance
535
-    let paddlePos = rightPaddle.position;
536
-    let windupTarget = paddlePos.y + aiState.windupDirection * aiState.windupDistance * aiState.aggressionLevel;
537
-    windupTarget = constrain(windupTarget, 50, height - 50); // Allow closer to edges for big windup
854
+    // Smooth windup progression with easing
855
+    let maxWindupTime = 800; // Longer time for smooth movement
856
+    let timeProgress = Math.min(windupTime / maxWindupTime, 1.0);
857
+    
858
+    // Ease-in-out for smooth acceleration/deceleration
859
+    let easedProgress = timeProgress < 0.5 
860
+        ? 2 * timeProgress * timeProgress 
861
+        : 1 - Math.pow(-2 * timeProgress + 2, 3) / 2;
862
+    
863
+    aiState.windupProgress = easedProgress;
864
+    
865
+    // Calculate smooth windup target based on intercept position
866
+    let windupTargetY = aiState.interceptY + aiState.windupDirection * aiState.windupDistance * aiState.aggressionLevel * easedProgress;
867
+    windupTargetY = constrain(windupTargetY, 50, height - 50);
538868
     
539869
     // Convert paddle target to anchor target using paddle awareness
540
-    let anchorOffsetNeeded = calculateAnchorOffset(windupTarget, paddlePos, rightSupport.position);
541
-    aiState.targetY = windupTarget + anchorOffsetNeeded;
870
+    let anchorOffsetNeeded = calculateAnchorOffset(windupTargetY, rightPaddle.position, rightSupport.position);
871
+    aiState.targetY = windupTargetY + anchorOffsetNeeded;
542872
     
543
-    // Check if it's time to swing - allow more time for bigger windups
873
+    // Check if it's time to swing
544874
     let ballDistance = width - ballPos.x;
545875
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
546876
     
547877
     let shouldSwing = windupTime > maxWindupTime || 
548878
                      ballDistance < 120 || 
549
-                     ballSpeed > 6; // Abort if ball speeds up even slightly
879
+                     ballSpeed > 6 ||
880
+                     easedProgress > 0.85; // Swing when windup is mostly complete
550881
     
551882
     if (shouldSwing) {
552883
         aiState.mode = 'SWINGING';
553884
         aiState.swingStartTime = currentTime;
885
+        aiState.windupProgress = 0; // Reset for next time
554886
     }
555887
 }
556888
 
@@ -608,25 +940,27 @@ function executeAIMovement(aiSettings) {
608940
     let deltaY = aiState.targetY - currentY;
609941
     
610942
     if (Math.abs(deltaY) > 1) { // Very small dead zone for responsive tracking
611
-        let baseSpeed = 0.08 * aiSettings.speed; // Back to more responsive speed
943
+        let baseSpeed = 0.12 * aiSettings.speed; // Increased base speed significantly
612944
         
613945
         // Apply swing power during swing phase
614946
         if (aiState.mode === 'SWINGING') {
615947
             baseSpeed *= aiState.swingPower * (1 + aiState.aggressionLevel * 0.3);
616948
         } else if (aiState.mode === 'WINDING_UP') {
617
-            baseSpeed *= 0.6; // Slower during windup for more deliberate movement
949
+            // Slower at start of windup, faster as it progresses
950
+            let windupSpeedMultiplier = 0.3 + (aiState.windupProgress * 0.4);
951
+            baseSpeed *= windupSpeedMultiplier;
618952
         }
619953
         
620954
         // Apply aggression multiplier
621
-        baseSpeed *= (1 + aiState.aggressionLevel * 0.2);
955
+        baseSpeed *= (1 + aiState.aggressionLevel * 0.3);
622956
         
623957
         let movement = deltaY * baseSpeed;
624
-        movement = constrain(movement, -SUPPORT_SPEED * 0.9, SUPPORT_SPEED * 0.9);
958
+        movement = constrain(movement, -SUPPORT_SPEED * 1.1, SUPPORT_SPEED * 1.1); // Allow slightly faster than player
625959
         
626960
         moveSupportEnhanced(rightSupport, movement);
627961
         
628962
         // Update input buffer for visual effects
629
-        inputBuffer.right = constrain(movement / (SUPPORT_SPEED * 0.9), -1, 1);
963
+        inputBuffer.right = constrain(movement / (SUPPORT_SPEED * 1.1), -1, 1);
630964
     } else {
631965
         // Gradually reduce input buffer when AI is not moving
632966
         inputBuffer.right *= 0.95;
@@ -864,11 +1198,24 @@ function drawSinglePaddleEnhanced(paddle, ballDistance) {
8641198
     
8651199
     // Check if this is the AI paddle
8661200
     let isAI = aiEnabled && paddle === rightPaddle;
1201
+    let isLeft = paddle === leftPaddle;
8671202
     
8681203
     // Calculate glow intensity based on ball proximity
8691204
     let glowIntensity = map(ballDistance, 0, PADDLE_GLOW_DISTANCE, 150, 0);
8701205
     glowIntensity = constrain(glowIntensity, 0, 150);
8711206
     
1207
+    // Add bop glow effect
1208
+    let bopGlow = 0;
1209
+    if (isLeft && bopState.left.active) {
1210
+        let bopProgress = (millis() - bopState.left.startTime) / bopState.left.duration;
1211
+        bopGlow = (1 - bopProgress) * 100; // Fade out over bop duration
1212
+    } else if (!isLeft && !isAI && bopState.right.active) {
1213
+        let bopProgress = (millis() - bopState.right.startTime) / bopState.right.duration;
1214
+        bopGlow = (1 - bopProgress) * 100;
1215
+    }
1216
+    
1217
+    glowIntensity += bopGlow;
1218
+    
8721219
     // Add AI state-based effects
8731220
     if (isAI) {
8741221
         // Enhance glow during aggressive states
@@ -896,6 +1243,11 @@ function drawSinglePaddleEnhanced(paddle, ballDistance) {
8961243
         paddleColor = [255, 50, 50]; // Bright red during swing
8971244
     }
8981245
     
1246
+    // Bop color override
1247
+    if ((isLeft && bopState.left.active) || (!isLeft && !isAI && bopState.right.active)) {
1248
+        paddleColor = [255, 255, 100]; // Bright yellow during bop
1249
+    }
1250
+    
8991251
     // Draw enhanced glow effect first
9001252
     if (glowIntensity > 0) {
9011253
         fill(paddleColor[0], paddleColor[1], paddleColor[2], glowIntensity * 0.6);
@@ -1109,9 +1461,9 @@ function drawMenu() {
11091461
     textSize(12);
11101462
     fill(255, 100);
11111463
     if (menuState.selectedOption === 0) {
1112
-        text("Controls: W/S keys or Mouse/Touch to move paddle", width/2, height - 30);
1464
+        text("Controls: W/S keys + LEFT SHIFT (bop) or Mouse/Touch", width/2, height - 30);
11131465
     } else {
1114
-        text("Controls: Player 1 (W/S) | Player 2 (↑/↓) | Mouse/Touch", width/2, height - 30);
1466
+        text("Controls: P1 (W/S + L.Shift) | P2 (↑/↓ + Enter) | Mouse/Touch", width/2, height - 30);
11151467
     }
11161468
 }
11171469
 
@@ -1169,11 +1521,19 @@ function resetBall() {
11691521
         World.remove(world, ball);
11701522
     }
11711523
     
1172
-    // Create new ball at center
1524
+    // Create new ball at center with collision filter
11731525
     ball = Bodies.circle(width/2, height/2, BALL_RADIUS, {
11741526
         restitution: 1,
11751527
         friction: 0,
1176
-        frictionAir: 0
1528
+        frictionAir: 0,
1529
+        slop: 0.01,  // Tighter collision detection
1530
+        collisionFilter: {
1531
+            category: 0x0001,
1532
+            mask: 0xFFFF
1533
+        },
1534
+        render: {
1535
+            fillStyle: '#ff6464'
1536
+        }
11771537
     });
11781538
     
11791539
     if (world) {
@@ -1275,6 +1635,7 @@ function keyPressed() {
12751635
         aiState.lastUpdateTime = 0;
12761636
         aiState.mode = 'ANTICIPATING';
12771637
         aiState.aggressionLevel = AI_SETTINGS[aiState.difficulty].aggression;
1638
+        aiState.windupProgress = 0;
12781639
         
12791640
         // Clear particles
12801641
         particles = [];
@@ -1283,7 +1644,7 @@ function keyPressed() {
12831644
     }
12841645
     
12851646
     // Return to menu with ESC
1286
-    if (keyCode === 27) {
1647
+    if (keyCode === 27) { // ESC key
12871648
         gameState = 'menu';
12881649
         gameStarted = false;
12891650
         particles = [];