zeroed-some/cob / 6237a51

Browse files

fixes, cleanup

Authored by espadonne
SHA
6237a51a44013f993f3d0de5b1ea02df81a80afe
Parents
827e77f
Tree
d2b83b9

4 changed files

StatusFile+-
M deploy.sh 0 1
M index.html 1 1
M js/entities.js 331 173
M js/game.js 179 129
deploy.shmodified
@@ -54,7 +54,6 @@ sudo chown -R root:root "$RELEASES/$STAMP"
5454
 sudo find "$RELEASES/$STAMP" -type d -exec chmod 0755 {} +
5555
 sudo find "$RELEASES/$STAMP" -type f -exec chmod 0644 {} +
5656
 
57
-# If the nginx user is different on your system, adjust here
5857
 NGINX_USER="nginx"
5958
 if id "$NGINX_USER" >/dev/null 2>&1; then
6059
   sudo -u "$NGINX_USER" test -r "$RELEASES/$STAMP/index.html" || { echo "✗ nginx user cannot read index.html"; exit 1; }
index.htmlmodified
@@ -13,7 +13,7 @@
1313
             Click to jump • Space to spin web • Shift to munch!<br>
1414
             Web Strands: <span id="strand-count">0</span><br>
1515
             Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span><br>
16
-            Total Score: <span id="total-score">0</span>
16
+            Points: <span id="player-points">0</span> | Total Score: <span id="total-score">0</span>
1717
         </div>
1818
         <div id="phase-indicator">
1919
             <span id="phase">DUSK</span><br>
js/entities.jsmodified
@@ -22,16 +22,21 @@ class Spider {
2222
 
2323
     // DAWN PHASE: Check and consume stamina
2424
     if (gamePhase === 'DAWN') {
25
+      // FIX: Only check if we ACTUALLY don't have enough stamina
2526
       if (jumpStamina < jumpCost) {
2627
         isExhausted = true
28
+        // Only show notification if we're truly out
29
+        if (jumpStamina < 5 && notifications.length < 3) {
30
+          notifications.push(
31
+            new Notification('NO STAMINA!', color(255, 50, 50))
32
+          )
33
+        }
2734
         return
2835
       }
2936
       jumpStamina -= jumpCost
3037
       stats.totalJumps++
3138
       // Delay stamina regen after each jump during DAWN
32
-      if (gamePhase === 'DAWN') {
33
-        staminaRegenCooldown = 60 // 1s at 60fps
34
-      }
39
+      staminaRegenCooldown = 60 // 1s at 60fps
3540
     }
3641
 
3742
     // PHASE 4B: Track wind jumps
@@ -119,15 +124,57 @@ class Spider {
119124
     if (this.munchCooldown > 0) return
120125
 
121126
     isMunching = true
122
-    this.munchCooldown = 30
127
+    this.munchCooldown = this.munchCooldownMax || 30 // Use upgrade value if available
123128
 
124129
     for (let i = flies.length - 1; i >= 0; i--) {
125130
       let fly = flies[i]
126131
       let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y)
127132
       if (d < this.munchRadius) {
128133
         fliesMunched++
134
+        stats.fliesMunchedInCurrentNight++ // Track for achievements
135
+
136
+        // POINTS: Award points for munching
137
+        let munchPoints = 4 // Base points for munch
138
+        if (fly.type === 'golden') munchPoints = 8
139
+        if (fly.type === 'queen') munchPoints = 10
140
+        if (fly.type === 'moth') munchPoints = 6
141
+        playerPoints += munchPoints
142
+        totalFliesCaught++ // Add to lifetime counter
143
+
129144
         webSilk = min(webSilk + 15, maxWebSilk)
130145
 
146
+        // PHASE 3: Metabolize upgrade effect
147
+        if (upgrades.metabolize && upgrades.metabolize.level > 0) {
148
+          // Heal nearby broken strands
149
+          for (let strand of webStrands) {
150
+            if (strand.broken) {
151
+              let distToStrand = Infinity
152
+              if (strand.path && strand.path.length > 0) {
153
+                for (let point of strand.path) {
154
+                  let d = dist(this.pos.x, this.pos.y, point.x, point.y)
155
+                  if (d < distToStrand) distToStrand = d
156
+                }
157
+              }
158
+
159
+              if (distToStrand < 100) {
160
+                strand.broken = false
161
+                strand.strength = 0.5 // Heal to half strength
162
+
163
+                // Green healing particles
164
+                for (let j = 0; j < 5; j++) {
165
+                  let p = new Particle(this.pos.x, this.pos.y)
166
+                  p.color = color(100, 255, 100)
167
+                  p.vel = createVector(random(-2, 2), random(-2, 2))
168
+                  p.size = 3
169
+                  particles.push(p)
170
+                }
171
+
172
+                break // Only heal one strand per munch
173
+              }
174
+            }
175
+          }
176
+        }
177
+
131178
         for (let j = 0; j < 12; j++) {
132179
           let p = new Particle(fly.pos.x, fly.pos.y)
133180
           p.color = color(255, random(100, 255), 0)
@@ -202,34 +249,62 @@ class Spider {
202249
       let branchStart = Math.min(branch.startX, branch.endX)
203250
       let branchEnd = Math.max(branch.startX, branch.endX)
204251
 
205
-      // Since the branch angle is very small (0.05 radians ≈ 3 degrees),
206
-      // we can use a simpler approximation
207
-      if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) {
252
+      // FIX: Extend collision detection beyond visual tip
253
+      // The visual branch ends at branchEnd, but we need collision slightly beyond
254
+      let collisionPadding = 15 // Extra collision at the tip
255
+
256
+      if (
257
+        this.pos.x >= branchStart - 10 &&
258
+        this.pos.x <= branchEnd + collisionPadding
259
+      ) {
208260
         // Calculate position along branch (0 to 1)
261
+        // FIX: Clamp t to ensure we handle tip properly
209262
         let t = (this.pos.x - branchStart) / (branchEnd - branchStart)
210
-        t = constrain(t, 0, 1)
263
+
264
+        // Allow t to go slightly beyond 1.0 for tip collision
265
+        if (this.pos.x > branchEnd) {
266
+          t = 1.0 // At the tip, use tip thickness
267
+        } else {
268
+          t = constrain(t, 0, 1)
269
+        }
211270
 
212271
         // Branch visual thickness tapers from full at start to 35% at end
213
-        // This matches exactly how it's drawn in the bezier curves
214
-        let branchTopThickness = lerp(
215
-          branch.thickness * 0.9,
216
-          branch.thickness * 0.35,
217
-          t
218
-        )
272
+        let branchTopThickness
273
+        if (t >= 1.0) {
274
+          // At the very tip, maintain minimum collision thickness
275
+          branchTopThickness = branch.thickness * 0.35
276
+        } else {
277
+          branchTopThickness = lerp(
278
+            branch.thickness * 0.9,
279
+            branch.thickness * 0.35,
280
+            t
281
+          )
282
+        }
219283
 
220284
         // The branch is drawn centered at branch.y
221
-        // With small angle approximation: the top of the branch is at
222285
         let branchSurfaceY = branch.y - branchTopThickness
223286
 
224
-        // Add slight angle correction (for small angles, tan ≈ sin ≈ angle in radians)
225
-        let angleCorrection = (this.pos.x - branchStart) * branch.angle
287
+        // FIX: Correct angle calculation based on branch side
288
+        let angleCorrection
289
+        if (branch.side === 'right') {
290
+          // Right branch slopes down to the left (negative angle)
291
+          // Use distance from the end point (which is on the left for right branches)
292
+          angleCorrection = -(this.pos.x - branchEnd) * abs(branch.angle)
293
+        } else {
294
+          // Left branch slopes down to the right (positive angle)
295
+          // Use distance from the start point
296
+          angleCorrection = (this.pos.x - branchStart) * abs(branch.angle)
297
+        }
226298
         branchSurfaceY += angleCorrection
227299
 
228300
         // Check if spider is crossing the branch from above
229301
         let prevY = this.pos.y - this.vel.y
230302
 
303
+        // FIX: More generous collision detection at the tip
304
+        let collisionBuffer = t >= 0.9 ? 5 : 0 // Extra buffer near tip
305
+
231306
         if (
232
-          prevY <= branchSurfaceY && // Was above
307
+          prevY <= branchSurfaceY + collisionBuffer && // Was above (with buffer)
233308
           this.pos.y + this.radius >= branchSurfaceY && // Now at or below
234309
           this.pos.y < branch.y + branch.thickness
235310
         ) {
@@ -350,132 +425,148 @@ class Spider {
350425
     this.land()
351426
   }
352427
 
353
-land() {
354
-  this.vel.mult(0);
355
-  this.isAirborne = false;
356
-  this.canJump = true;
357
-
358
-  // FIX: Check if we're actually landing on something valid
359
-  let landedOnSomething = false;
360
-  let landingPoint = null; // Store where we're landing for anchor
361
-  let landingObstacle = null; // NEW: Track which obstacle we're landing on
362
-
363
-  // Check if on ground
364
-  if (this.pos.y >= height - this.radius - 5) {
365
-    landedOnSomething = true;
366
-    landingPoint = createVector(this.pos.x, height);
367
-  }
428
+  land () {
429
+    this.vel.mult(0)
430
+    this.isAirborne = false
431
+    this.canJump = true
368432
 
369
-  // Check if on an obstacle
370
-  if (!landedOnSomething) {
371
-    for (let obstacle of obstacles) {
372
-      if (this.checkObstacleCollision(obstacle)) {
373
-        landedOnSomething = true;
374
-        landingObstacle = obstacle; // NEW: Store the obstacle
375
-        // Calculate edge point for anchor
376
-        let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x);
377
-        landingPoint = createVector(
378
-          obstacle.x + cos(angle) * obstacle.radius,
379
-          obstacle.y + sin(angle) * obstacle.radius
380
-        );
381
-        break;
433
+    // FIX: Check if we're actually landing on something valid
434
+    let landedOnSomething = false
435
+    let landingPoint = null // Store where we're landing for anchor
436
+    let landingObstacle = null // NEW: Track which obstacle we're landing on
437
+
438
+    // Check if on ground
439
+    if (this.pos.y >= height - this.radius - 5) {
440
+      landedOnSomething = true
441
+      landingPoint = createVector(this.pos.x, height)
442
+    }
443
+
444
+    // Check if on an obstacle
445
+    if (!landedOnSomething) {
446
+      for (let obstacle of obstacles) {
447
+        if (this.checkObstacleCollision(obstacle)) {
448
+          landedOnSomething = true
449
+          landingObstacle = obstacle // NEW: Store the obstacle
450
+          // Calculate edge point for anchor
451
+          let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x)
452
+          landingPoint = createVector(
453
+            obstacle.x + cos(angle) * obstacle.radius,
454
+            obstacle.y + sin(angle) * obstacle.radius
455
+          )
456
+          break
457
+        }
382458
       }
383459
     }
384
-  }
385460
 
386
-  // Check if on a web strand
387
-  if (!landedOnSomething) {
388
-    for (let strand of webStrands) {
389
-      if (strand !== currentStrand && !strand.broken && this.checkStrandCollision(strand)) {
390
-        landedOnSomething = true;
391
-        // For web strands, use spider position as anchor
392
-        landingPoint = this.pos.copy();
393
-        break;
461
+    // Check if on a web strand
462
+    if (!landedOnSomething) {
463
+      for (let strand of webStrands) {
464
+        if (
465
+          strand !== currentStrand &&
466
+          !strand.broken &&
467
+          this.checkStrandCollision(strand)
468
+        ) {
469
+          landedOnSomething = true
470
+          // For web strands, use spider position as anchor
471
+          landingPoint = this.pos.copy()
472
+          break
473
+        }
394474
       }
395475
     }
396
-  }
397476
 
398
-  // Check if on home branch
399
-  if (!landedOnSomething && window.homeBranch) {
400
-    let branch = window.homeBranch;
401
-    let branchStart = Math.min(branch.startX, branch.endX);
402
-    let branchEnd = Math.max(branch.startX, branch.endX);
403
-
404
-    if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) {
405
-      let t = (this.pos.x - branchStart) / (branchEnd - branchStart);
406
-      t = constrain(t, 0, 1);
407
-      let branchTopThickness = lerp(branch.thickness * 0.9, branch.thickness * 0.35, t);
408
-      let branchSurfaceY = branch.y - branchTopThickness;
409
-      let angleCorrection = (this.pos.x - branchStart) * branch.angle;
410
-      branchSurfaceY += angleCorrection;
411
-
412
-      if (abs(this.pos.y - branchSurfaceY) < this.radius + 10) {
413
-        landedOnSomething = true;
414
-        landingPoint = createVector(this.pos.x, branchSurfaceY);
477
+    // Check if on home branch
478
+    if (!landedOnSomething && window.homeBranch) {
479
+      let branch = window.homeBranch
480
+      let branchStart = Math.min(branch.startX, branch.endX)
481
+      let branchEnd = Math.max(branch.startX, branch.endX)
482
+
483
+      if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) {
484
+        let t = (this.pos.x - branchStart) / (branchEnd - branchStart)
485
+        t = constrain(t, 0, 1)
486
+        let branchTopThickness = lerp(
487
+          branch.thickness * 0.9,
488
+          branch.thickness * 0.35,
489
+          t
490
+        )
491
+        let branchSurfaceY = branch.y - branchTopThickness
492
+        let angleCorrection = (this.pos.x - branchStart) * branch.angle
493
+        branchSurfaceY += angleCorrection
494
+
495
+        if (abs(this.pos.y - branchSurfaceY) < this.radius + 10) {
496
+          landedOnSomething = true
497
+          landingPoint = createVector(this.pos.x, branchSurfaceY)
498
+        }
415499
       }
416500
     }
417
-  }
418501
 
419
-  // FIX: If we're deploying web but didn't land on anything valid, destroy the web
420
-  if (currentStrand && isDeployingWeb && (spacePressed || touchHolding)) {
421
-    if (landedOnSomething && landingPoint) {
422
-      // Valid landing - finalize the web at the landing point
423
-      currentStrand.end = landingPoint.copy(); // Use edge point, not spider center
424
-      
425
-      // NEW: Track obstacle attachment for the end point
426
-      if (landingObstacle) {
427
-        currentStrand.endObstacle = landingObstacle;
428
-        currentStrand.endAngle = atan2(
429
-          landingPoint.y - landingObstacle.y,
430
-          landingPoint.x - landingObstacle.x
431
-        );
432
-      }
433
-      
434
-      if (!currentStrand.path || currentStrand.path.length === 0) {
435
-        currentStrand.path = [landingPoint.copy()];
436
-      } else {
437
-        currentStrand.path.push(landingPoint.copy());
438
-      }
439
-      
440
-      let newNode = new WebNode(landingPoint.x, landingPoint.y);
441
-      // NEW: Track node attachment
442
-      if (landingObstacle) {
443
-        newNode.attachedObstacle = landingObstacle;
444
-        newNode.attachmentAngle = currentStrand.endAngle;
445
-      }
446
-      webNodes.push(newNode);
502
+    // FIX: If we're deploying web but didn't land on anything valid, destroy the web
503
+    if (currentStrand && isDeployingWeb && (spacePressed || touchHolding)) {
504
+      if (landedOnSomething && landingPoint) {
505
+        // Valid landing - finalize the web at the landing point
506
+        currentStrand.end = landingPoint.copy() // Use edge point, not spider center
447507
 
448
-      // Update last anchor for next web
449
-      this.lastAnchorPoint = landingPoint.copy();
450
-    } else {
451
-      // Invalid landing in mid-air - destroy the web!
452
-      if (webStrands.length > 0 && webStrands[webStrands.length - 1] === currentStrand) {
453
-        webStrands.pop(); // Remove the invalid strand
508
+        // POINTS: Award 1 point for successful web strand
509
+        playerPoints += 1
510
+        stats.strandsCreated++ // Track for stats
454511
 
455
-        // Create poof particles
456
-        for (let i = 0; i < 8; i++) {
457
-          let p = new Particle(this.pos.x, this.pos.y);
458
-          p.color = color(255, 255, 255, 150);
459
-          p.vel = createVector(random(-3, 3), random(-3, 3));
460
-          p.size = 4;
461
-          particles.push(p);
512
+        // NEW: Track obstacle attachment for the end point
513
+        if (landingObstacle) {
514
+          currentStrand.endObstacle = landingObstacle
515
+          currentStrand.endAngle = atan2(
516
+            landingPoint.y - landingObstacle.y,
517
+            landingPoint.x - landingObstacle.x
518
+          )
462519
         }
463520
 
464
-        // Notification
465
-        if (notifications.length < 3) {
466
-          notifications.push(new Notification("Web needs anchor point!", color(255, 150, 150)));
521
+        if (!currentStrand.path || currentStrand.path.length === 0) {
522
+          currentStrand.path = [landingPoint.copy()]
523
+        } else {
524
+          currentStrand.path.push(landingPoint.copy())
525
+        }
526
+
527
+        let newNode = new WebNode(landingPoint.x, landingPoint.y)
528
+        // NEW: Track node attachment
529
+        if (landingObstacle) {
530
+          newNode.attachedObstacle = landingObstacle
531
+          newNode.attachmentAngle = currentStrand.endAngle
532
+        }
533
+        webNodes.push(newNode)
534
+
535
+        // Update last anchor for next web
536
+        this.lastAnchorPoint = landingPoint.copy()
537
+      } else {
538
+        // Invalid landing in mid-air - destroy the web!
539
+        if (
540
+          webStrands.length > 0 &&
541
+          webStrands[webStrands.length - 1] === currentStrand
542
+        ) {
543
+          webStrands.pop() // Remove the invalid strand
544
+
545
+          // Create poof particles
546
+          for (let i = 0; i < 8; i++) {
547
+            let p = new Particle(this.pos.x, this.pos.y)
548
+            p.color = color(255, 255, 255, 150)
549
+            p.vel = createVector(random(-3, 3), random(-3, 3))
550
+            p.size = 4
551
+            particles.push(p)
552
+          }
553
+
554
+          // Notification
555
+          if (notifications.length < 3) {
556
+            notifications.push(
557
+              new Notification('Web needs anchor point!', color(255, 150, 150))
558
+            )
559
+          }
467560
         }
468561
       }
562
+    } else if (landedOnSomething && landingPoint) {
563
+      // Update last anchor point even when not deploying web
564
+      this.lastAnchorPoint = landingPoint.copy()
469565
     }
470
-  } else if (landedOnSomething && landingPoint) {
471
-    // Update last anchor point even when not deploying web
472
-    this.lastAnchorPoint = landingPoint.copy();
473
-  }
474
-
475
-  currentStrand = null;
476
-  isDeployingWeb = false;
477
-}
478566
 
567
+    currentStrand = null
568
+    isDeployingWeb = false
569
+  }
479570
 
480571
   display () {
481572
     push()
@@ -552,7 +643,6 @@ class Fly {
552643
 
553644
   update () {
554645
     if (this.stuck) {
555
-      // If stuck, check if we need to move with a drifting web
556646
       this.updatePositionOnWeb()
557647
       return
558648
     }
@@ -562,9 +652,17 @@ class Fly {
562652
       if (this.vel.mag() < 0.1) {
563653
         this.stuck = true
564654
         fliesCaught++
655
+        totalFliesCaught++ // Add to lifetime counter
656
+
657
+        // POINTS: Award points for catching fly
658
+        let catchPoints = 2 // Base points for catch
659
+        if (this.type === 'golden') catchPoints = 4
660
+        if (this.type === 'queen') catchPoints = 5
661
+        if (this.type === 'moth') catchPoints = 3
662
+        playerPoints += catchPoints
663
+
565664
         webSilk = min(webSilk + 5, maxWebSilk)
566665
       }
567
-      // While caught but not yet stuck, also follow the web
568666
       this.updatePositionOnWeb()
569667
       return
570668
     }
@@ -759,7 +857,29 @@ class Fly {
759857
       ellipse(0, 0, 20)
760858
     }
761859
 
762
-    if (gamePhase === 'NIGHT') {
860
+    // ENHANCED: Special golden fly glow
861
+    if (this.type === 'golden') {
862
+      // Multiple layers of golden glow for visibility
863
+      noStroke()
864
+      // Outermost glow - very faint but wide
865
+      fill(255, 215, 0, 15)
866
+      ellipse(0, 0, 80)
867
+      // Mid glow
868
+      fill(255, 200, 0, 25)
869
+      ellipse(0, 0, 60)
870
+      // Inner glow
871
+      fill(255, 185, 0, 40)
872
+      ellipse(0, 0, 40)
873
+      // Core glow
874
+      fill(255, 170, 0, 60)
875
+      ellipse(0, 0, 25)
876
+
877
+      // Pulsing effect
878
+      let pulse = sin(frameCount * 0.1) * 0.3 + 0.7
879
+      fill(255, 215, 0, 80 * pulse)
880
+      ellipse(0, 0, 20)
881
+    } else if (gamePhase === 'NIGHT') {
882
+      // Regular firefly glow
763883
       noStroke()
764884
       fill(255, 255, 150, this.glowIntensity * 0.3)
765885
       ellipse(0, 0, 30)
@@ -767,8 +887,21 @@ class Fly {
767887
       ellipse(0, 0, 20)
768888
     }
769889
 
770
-    fill(30)
771
-    stroke(0)
890
+    // Body color based on type
891
+    if (this.type === 'golden') {
892
+      fill(255, 215, 0) // Gold body
893
+      stroke(200, 150, 0)
894
+    } else if (this.type === 'moth') {
895
+      fill(60, 40, 30) // Brown moth
896
+      stroke(40, 20, 10)
897
+    } else if (this.type === 'queen') {
898
+      fill(150, 50, 200) // Purple queen
899
+      stroke(100, 0, 150)
900
+    } else {
901
+      fill(30) // Regular black
902
+      stroke(0)
903
+    }
904
+
772905
     strokeWeight(0.5)
773906
     ellipse(0, 0, this.radius * 2)
774907
 
@@ -778,13 +911,37 @@ class Fly {
778911
       this.wingPhase += wingSpeed
779912
       let wingSpread = sin(this.wingPhase) * 5
780913
 
781
-      fill(255, 255, 255, 150)
914
+      // Wing color based on type
915
+      if (this.type === 'golden') {
916
+        fill(255, 240, 150, 200) // Golden translucent wings
917
+      } else if (this.type === 'moth') {
918
+        fill(120, 100, 80, 150) // Brown moth wings
919
+      } else if (this.type === 'queen') {
920
+        fill(200, 150, 255, 180) // Purple queen wings
921
+      } else {
922
+        fill(255, 255, 255, 150) // Regular white wings
923
+      }
924
+
782925
       noStroke()
783926
       ellipse(-wingSpread, 0, 6, 4)
784927
       ellipse(wingSpread, 0, 6, 4)
785928
     }
786929
 
787
-    if (gamePhase === 'NIGHT') {
930
+    // Special markings for special types
931
+    if (this.type === 'golden') {
932
+      // Golden sparkle on body
933
+      fill(255, 255, 200)
934
+      noStroke()
935
+      ellipse(0, 0, 2)
936
+    } else if (this.type === 'queen') {
937
+      // Crown marking
938
+      stroke(255, 215, 0)
939
+      strokeWeight(1)
940
+      line(-2, -3, -1, -5)
941
+      line(0, -3, 0, -5)
942
+      line(2, -3, 1, -5)
943
+    } else if (gamePhase === 'NIGHT') {
944
+      // Regular glow abdomen
788945
       fill(255, 255, 100, this.glowIntensity)
789946
       noStroke()
790947
       ellipse(0, 2, 3)
@@ -911,50 +1068,49 @@ class Obstacle {
9111068
     }
9121069
   }
9131070
 
914
-updateAttachedStrands() {
915
-  // Update web strands that are connected to this obstacle
916
-  for (let strand of webStrands) {
917
-    if (!strand || strand.broken) continue;
918
-    
919
-    // Check if strand starts at this obstacle
920
-    if (strand.startObstacle === this) {
921
-      // Update the start position to maintain the attachment
922
-      let angle = strand.startAngle; // Use stored angle
923
-      strand.start.x = this.x + cos(angle) * this.radius;
924
-      strand.start.y = this.y + sin(angle) * this.radius;
925
-      
926
-      // Update path if it exists
927
-      if (strand.path && strand.path.length > 0) {
928
-        strand.path[0].x = strand.start.x;
929
-        strand.path[0].y = strand.start.y;
1071
+  updateAttachedStrands () {
1072
+    // Update web strands that are connected to this obstacle
1073
+    for (let strand of webStrands) {
1074
+      if (!strand || strand.broken) continue
1075
+
1076
+      // Check if strand starts at this obstacle
1077
+      if (strand.startObstacle === this) {
1078
+        // Update the start position to maintain the attachment
1079
+        let angle = strand.startAngle // Use stored angle
1080
+        strand.start.x = this.x + cos(angle) * this.radius
1081
+        strand.start.y = this.y + sin(angle) * this.radius
1082
+
1083
+        // Update path if it exists
1084
+        if (strand.path && strand.path.length > 0) {
1085
+          strand.path[0].x = strand.start.x
1086
+          strand.path[0].y = strand.start.y
1087
+        }
9301088
       }
931
-    }
932
-    
933
-    // Check if strand ends at this obstacle
934
-    if (strand.endObstacle === this) {
935
-      // Update the end position to maintain the attachment
936
-      let angle = strand.endAngle; // Use stored angle
937
-      strand.end.x = this.x + cos(angle) * this.radius;
938
-      strand.end.y = this.y + sin(angle) * this.radius;
939
-      
940
-      // Update path if it exists
941
-      if (strand.path && strand.path.length > 0) {
942
-        strand.path[strand.path.length - 1].x = strand.end.x;
943
-        strand.path[strand.path.length - 1].y = strand.end.y;
1089
+
1090
+      // Check if strand ends at this obstacle
1091
+      if (strand.endObstacle === this) {
1092
+        // Update the end position to maintain the attachment
1093
+        let angle = strand.endAngle // Use stored angle
1094
+        strand.end.x = this.x + cos(angle) * this.radius
1095
+        strand.end.y = this.y + sin(angle) * this.radius
1096
+
1097
+        // Update path if it exists
1098
+        if (strand.path && strand.path.length > 0) {
1099
+          strand.path[strand.path.length - 1].x = strand.end.x
1100
+          strand.path[strand.path.length - 1].y = strand.end.y
1101
+        }
9441102
       }
9451103
     }
946
-  }
947
-  
948
-  // Also update web nodes attached to this obstacle
949
-  for (let node of webNodes) {
950
-    if (node.attachedObstacle === this) {
951
-      let angle = node.attachmentAngle;
952
-      node.x = this.x + cos(angle) * this.radius;
953
-      node.y = this.y + sin(angle) * this.radius;
1104
+
1105
+    // Also update web nodes attached to this obstacle
1106
+    for (let node of webNodes) {
1107
+      if (node.attachedObstacle === this) {
1108
+        let angle = node.attachmentAngle
1109
+        node.x = this.x + cos(angle) * this.radius
1110
+        node.y = this.y + sin(angle) * this.radius
1111
+      }
9541112
     }
9551113
   }
956
-}
957
-
9581114
 
9591115
   breakAttachedStrands () {
9601116
     // Check for strands attached to this obstacle's edge
@@ -2072,7 +2228,6 @@ class Bird {
20722228
 
20732229
         // Warning notifications - but limited to prevent spam
20742230
         if (notifications.length < 3) {
2075
-          // Limit notifications
20762231
           if (jumpStamina <= 20) {
20772232
             notifications.push(
20782233
               new Notification('CRITICAL STAMINA!', color(255, 50, 50))
@@ -2083,6 +2238,9 @@ class Bird {
20832238
             )
20842239
           }
20852240
         }
2241
+
2242
+        // POINTS: Award 2 points for surviving a bird attack
2243
+        playerPoints += 2
20862244
       }
20872245
 
20882246
       // Bird bounces off
js/game.jsmodified
@@ -19,6 +19,7 @@ let gameOverTimer = 0
1919
 let deathReason = ''
2020
 let finalScore = 0
2121
 let screenShake = 0
22
+let fliesSpawnedThisNight = 0
2223
 
2324
 // Resources
2425
 let webSilk = 100
@@ -68,6 +69,7 @@ let isExhausted = false
6869
 let fliesMunchedLastNight = 0
6970
 let birds = []
7071
 let staminaRegenCooldown = 0
72
+let staminaBonus = 0;
7173
 
7274
 // PHASE 4B: Wind System
7375
 let windActive = false
@@ -733,33 +735,40 @@ function draw () {
733735
     gamePhase = 'DAWN'
734736
     phaseTimer = 0
735737
 
736
-    // Calculate dawn stamina with CLEAR BONUS DISPLAY
737
-    let baseStamina = 30
738
-    let flyBonus = fliesMunchedLastNight * 10
739
-    staminaBonus = flyBonus
738
+    // NEW STAMINA CALCULATION:
739
+    // Fixed 100 max stamina, but starting amount depends on performance
740
+    maxJumpStamina = 100 // Always 100 max
740741
 
741
-    maxJumpStamina = baseStamina + flyBonus
742
-    maxJumpStamina = min(maxJumpStamina, 200)
743
-    jumpStamina = maxJumpStamina
742
+    // Calculate percentage of flies munched
743
+    let totalFliesInNight = fliesSpawnedThisNight + flies.length // Spawned + any remaining
744
+    let munchPercentage = fliesMunchedLastNight / totalFliesInNight
744745
 
745
-    // IMPROVED: Combine notifications to reduce overlap
746
-    let staminaMessage = `Dawn: ${maxJumpStamina} stamina (${baseStamina} + ${flyBonus} from ${fliesMunchedLastNight} flies)`
747
-    let warningMessage = ''
746
+    // Base stamina: 20 minimum, up to 100 for 50% or more flies munched
747
+    if (munchPercentage >= 0.5) {
748
+      jumpStamina = 100 // Full stamina for eating 50%+ of flies
749
+    } else {
750
+      // Scale from 20 to 100 based on 0% to 50% munched
751
+      jumpStamina = Math.floor(20 + munchPercentage * 2 * 80)
752
+    }
753
+
754
+    // Create informative notification
755
+    let percentEaten = Math.floor(munchPercentage * 100)
756
+    let staminaMessage = `Dawn: ${jumpStamina}/100 stamina (${percentEaten}% of ${totalFliesInNight} flies eaten)`
748757
 
749
-    if (maxJumpStamina <= 50) {
750
-      warningMessage = ' ⚠️ DANGER!'
758
+    if (jumpStamina <= 30) {
751759
       notifications.push(
752
-        new Notification(staminaMessage + warningMessage, color(255, 50, 50))
760
+        new Notification(staminaMessage + ' ⚠️ DANGER!', color(255, 50, 50))
753761
       )
754
-    } else if (maxJumpStamina <= 80) {
755
-      warningMessage = ' - Low stamina!'
762
+    } else if (jumpStamina <= 60) {
756763
       notifications.push(
757
-        new Notification(staminaMessage + warningMessage, color(255, 150, 50))
764
+        new Notification(
765
+          staminaMessage + ' - Low stamina!',
766
+          color(255, 150, 50)
767
+        )
758768
       )
759
-    } else if (maxJumpStamina >= 150) {
760
-      warningMessage = ' - Well fed!'
769
+    } else if (jumpStamina >= 90) {
761770
       notifications.push(
762
-        new Notification(staminaMessage + warningMessage, color(100, 255, 100))
771
+        new Notification(staminaMessage + ' - Well fed!', color(100, 255, 100))
763772
       )
764773
     } else {
765774
       notifications.push(new Notification(staminaMessage, color(255, 200, 100)))
@@ -1116,6 +1125,8 @@ function draw () {
11161125
 
11171126
     jumpStamina += regen
11181127
     jumpStamina = min(jumpStamina, maxJumpStamina)
1128
+
1129
+    // FIX: Only set exhausted when truly out of stamina
11191130
     isExhausted = jumpStamina < jumpCost
11201131
 
11211132
     // Update and display birds
@@ -1234,6 +1245,7 @@ function draw () {
12341245
       if (flyType === 'queen') fly.baseSpeed *= 0.5
12351246
       fly.currentSpeed = fly.baseSpeed
12361247
       flies.push(fly)
1248
+      fliesSpawnedThisNight++ // Track dynamic spawn
12371249
     }
12381250
     if (phaseTimer % 300 === 0 && foodBoxes.length < 6) {
12391251
       spawnFoodBox()
@@ -1927,6 +1939,9 @@ function applyUpgradeEffects () {
19271939
 }
19281940
 
19291941
 function spawnNightFlies () {
1942
+  // Reset counter for new night
1943
+  fliesSpawnedThisNight = 0
1944
+
19301945
   // Base flies + more per night
19311946
   let numFlies = 5 + currentNight
19321947
 
@@ -1956,6 +1971,7 @@ function spawnNightFlies () {
19561971
     if (flyType === 'queen') fly.baseSpeed *= 0.5 // Queens are much slower
19571972
     fly.currentSpeed = fly.baseSpeed
19581973
     flies.push(fly)
1974
+    fliesSpawnedThisNight++ // Track spawn
19591975
   }
19601976
 
19611977
   // PHASE 2: Guarantee at least 1 golden fly per night
@@ -1964,6 +1980,7 @@ function spawnNightFlies () {
19641980
     goldenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 1.3
19651981
     goldenFly.currentSpeed = goldenFly.baseSpeed
19661982
     flies.push(goldenFly)
1983
+    fliesSpawnedThisNight++ // Track spawn
19671984
     // Add notification
19681985
     notifications.push(
19691986
       new Notification('Golden Firefly Appeared! ✨', color(255, 215, 0))
@@ -1979,6 +1996,7 @@ function spawnNightFlies () {
19791996
     queenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 0.5
19801997
     queenFly.currentSpeed = queenFly.baseSpeed
19811998
     flies.push(queenFly)
1999
+    fliesSpawnedThisNight++ // Track spawn
19822000
     // Add notification
19832001
     notifications.push(
19842002
       new Notification('Queen Firefly Arrived! 👑', color(200, 100, 255))
@@ -2509,50 +2527,57 @@ function updateResources () {
25092527
   }
25102528
 }
25112529
 
2512
-function handleWebDeployment() {
2530
+function handleWebDeployment () {
25132531
   // Handle keyboard-based web deployment
25142532
   if (spacePressed && spider.isAirborne && !isDeployingWeb && webSilk > 10) {
2515
-    isDeployingWeb = true;
2516
-    currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null);
2517
-    currentStrand.path = [spider.lastAnchorPoint.copy()];
2518
-    
2533
+    isDeployingWeb = true
2534
+    currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null)
2535
+    currentStrand.path = [spider.lastAnchorPoint.copy()]
2536
+
25192537
     // NEW: Check if starting from an obstacle
25202538
     for (let obstacle of obstacles) {
2521
-      let d = dist(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y, obstacle.x, obstacle.y);
2539
+      let d = dist(
2540
+        spider.lastAnchorPoint.x,
2541
+        spider.lastAnchorPoint.y,
2542
+        obstacle.x,
2543
+        obstacle.y
2544
+      )
25222545
       // Check if anchor is on obstacle edge (within tolerance)
25232546
       if (abs(d - obstacle.radius) < 10) {
2524
-        currentStrand.startObstacle = obstacle;
2547
+        currentStrand.startObstacle = obstacle
25252548
         currentStrand.startAngle = atan2(
25262549
           spider.lastAnchorPoint.y - obstacle.y,
25272550
           spider.lastAnchorPoint.x - obstacle.x
2528
-        );
2529
-        break;
2551
+        )
2552
+        break
25302553
       }
25312554
     }
2532
-    
2533
-    webStrands.push(currentStrand);
2534
-    
2535
-    let newNode = new WebNode(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y);
2555
+
2556
+    webStrands.push(currentStrand)
2557
+
2558
+    let newNode = new WebNode(
2559
+      spider.lastAnchorPoint.x,
2560
+      spider.lastAnchorPoint.y
2561
+    )
25362562
     // NEW: Track node attachment if on obstacle
25372563
     if (currentStrand.startObstacle) {
2538
-      newNode.attachedObstacle = currentStrand.startObstacle;
2539
-      newNode.attachmentAngle = currentStrand.startAngle;
2564
+      newNode.attachedObstacle = currentStrand.startObstacle
2565
+      newNode.attachmentAngle = currentStrand.startAngle
25402566
     }
2541
-    webNodes.push(newNode);
2567
+    webNodes.push(newNode)
25422568
   }
25432569
 
25442570
   // Update web for keyboard controls
25452571
   if (currentStrand && isDeployingWeb && spider.isAirborne && spacePressed) {
2546
-    currentStrand.end = spider.pos.copy();
2572
+    currentStrand.end = spider.pos.copy()
25472573
     if (frameCount % 2 === 0) {
2548
-      currentStrand.path.push(spider.pos.copy());
2574
+      currentStrand.path.push(spider.pos.copy())
25492575
     }
25502576
   }
25512577
 
25522578
   // Touch-based web deployment is handled in touchMoved()
25532579
 }
25542580
 
2555
-
25562581
 function updateUI () {
25572582
   // Update control instructions based on device
25582583
   let isMobile =
@@ -2583,14 +2608,15 @@ function updateUI () {
25832608
     '<br>' +
25842609
     'Web Strands: <span id="strand-count">0</span><br>' +
25852610
     'Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span><br>' +
2586
-    'Total Score: <span id="total-score">0</span>'
2611
+    'Points: <span id="player-points">0</span> | Total Score: <span id="total-score">0</span>'
25872612
 
2588
-  // PHASE 1 UPDATES
2613
+  // Update all the displays
25892614
   document.getElementById('strand-count').textContent = webStrands.filter(
25902615
     s => !s.broken
25912616
   ).length
25922617
   document.getElementById('flies-caught').textContent = fliesCaught
25932618
   document.getElementById('flies-munched').textContent = fliesMunched
2619
+  document.getElementById('player-points').textContent = playerPoints // NEW
25942620
   document.getElementById('total-score').textContent = totalFliesCaught
25952621
 
25962622
   // Update phase display
@@ -2622,27 +2648,40 @@ function updateUI () {
26222648
     timerText +
26232649
     `<br><small ${staminaColor}>Dawn Stamina: ${potentialStamina}</small>`
26242650
 
2625
-  if (gamePhase === 'DUSK') {
2626
-    let timeLeft = Math.ceil((DUSK_DURATION - phaseTimer) / 60)
2627
-    timerText = `${timeLeft}s to prepare!`
2628
-  } else if (gamePhase === 'NIGHT') {
2629
-    let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60)
2630
-    // PHASE 2: Count different fly types
2631
-    let regularCount = flies.filter(f => f.type === 'regular').length
2632
-    let goldenCount = flies.filter(f => f.type === 'golden').length
2633
-    let mothCount = flies.filter(f => f.type === 'moth').length
2634
-    let queenCount = flies.filter(f => f.type === 'queen').length
2635
-
2636
-    timerText = `${timeLeft}s • ${flies.length} flies`
2637
-
2638
-    // Show special fly counts if any
2639
-    if (goldenCount > 0 || mothCount > 0 || queenCount > 0) {
2640
-      let specialCounts = []
2641
-      if (queenCount > 0) specialCounts.push(`${queenCount}👑`)
2642
-      if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`)
2643
-      if (mothCount > 0) specialCounts.push(`${mothCount}🦋`)
2644
-      timerText += ` (${specialCounts.join(' ')})`
2645
-    }
2651
+  if (gamePhase === 'NIGHT') {
2652
+  let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60);
2653
+  
2654
+  // Calculate current munch percentage
2655
+  let totalFliesInNight = fliesSpawnedThisNight + flies.length;
2656
+  let currentMunchPercent = totalFliesInNight > 0 ? 
2657
+    Math.floor((fliesMunched / totalFliesInNight) * 100) : 0;
2658
+  
2659
+  // Calculate predicted dawn stamina
2660
+  let predictedStamina;
2661
+  if (currentMunchPercent >= 50) {
2662
+    predictedStamina = 100;
2663
+  } else {
2664
+    predictedStamina = Math.floor(20 + (currentMunchPercent * 2) * 0.8);
2665
+  }
2666
+  
2667
+  timerText = `${timeLeft}s • ${flies.length} flies`;
2668
+  
2669
+  // Show special fly counts if any
2670
+  let goldenCount = flies.filter(f => f.type === 'golden').length;
2671
+  let mothCount = flies.filter(f => f.type === 'moth').length;
2672
+  let queenCount = flies.filter(f => f.type === 'queen').length;
2673
+  
2674
+  if (goldenCount > 0 || mothCount > 0 || queenCount > 0) {
2675
+    let specialCounts = [];
2676
+    if (queenCount > 0) specialCounts.push(`${queenCount}👑`);
2677
+    if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`);
2678
+    if (mothCount > 0) specialCounts.push(`${mothCount}🦋`);
2679
+    timerText += ` (${specialCounts.join(' ')})`;
2680
+  }
2681
+   // Show munch progress
2682
+  document.getElementById('timer').innerHTML = timerText + 
2683
+    `<br><small style="color: ${predictedStamina < 40 ? '#ff4444' : predictedStamina < 70 ? '#ffaa44' : '#44ff44'}">` +
2684
+    `Munched: ${currentMunchPercent}% → ${predictedStamina} dawn stamina</small>`;
26462685
   } else if (gamePhase === 'DAWN') {
26472686
     let timeLeft = Math.ceil((DAWN_DURATION - phaseTimer) / 60)
26482687
     // PHASE 4: Show birds and exhaustion status
@@ -2687,48 +2726,49 @@ function updateUI () {
26872726
   }
26882727
 
26892728
   // PHASE 4: Update meter based on phase
2690
-  if (gamePhase === 'DAWN') {
2691
-    // Show stamina instead of silk during dawn
2692
-    document.getElementById('web-meter-label').textContent = 'STAMINA'
2693
-    let staminaPercent = (jumpStamina / maxJumpStamina) * 100
2694
-    document.getElementById('web-meter-fill').style.width = staminaPercent + '%'
2695
-
2696
-    // Color based on stamina level
2697
-    if (jumpStamina < jumpCost) {
2698
-      // Exhausted - red flash
2699
-      let flash = sin(frameCount * 0.3) * 0.5 + 0.5
2700
-      document.getElementById(
2701
-        'web-meter-fill'
2702
-      ).style.background = `linear-gradient(90deg, rgb(255, ${
2703
-        50 + flash * 50
2704
-      }, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))`
2705
-    } else if (jumpStamina < maxJumpStamina * 0.3) {
2706
-      // Very tired - orange-red
2707
-      document.getElementById('web-meter-fill').style.background =
2708
-        'linear-gradient(90deg, #FF6B35, #FF4444)'
2709
-    } else if (jumpStamina < maxJumpStamina * 0.5) {
2710
-      // Tired - orange
2711
-      document.getElementById('web-meter-fill').style.background =
2712
-        'linear-gradient(90deg, #FFA500, #FF8C00)'
2713
-    } else {
2714
-      // Good stamina - yellow-orange
2715
-      document.getElementById('web-meter-fill').style.background =
2716
-        'linear-gradient(90deg, #FFD700, #FFA500)'
2717
-    }
2718
-    if (jumpStamina <= 0 && !gameOver) {
2719
-      push()
2720
-      fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50)
2721
-      rect(0, 0, width, height)
2722
-
2723
-      textAlign(CENTER)
2724
-      textSize(32)
2725
-      fill(255, 50, 50)
2726
-      stroke(0)
2727
-      strokeWeight(3)
2728
-      text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2)
2729
-      pop()
2730
-    }
2729
+if (gamePhase === 'DAWN') {
2730
+  // Show stamina instead of silk during dawn
2731
+  document.getElementById('web-meter-label').textContent = 'STAMINA';
2732
+  
2733
+  // FIX: Always show percentage out of 100, not out of variable max
2734
+  let staminaPercent = (jumpStamina / 100) * 100; // Always out of 100
2735
+  document.getElementById('web-meter-fill').style.width = staminaPercent + '%';
2736
+
2737
+  // Color based on stamina level
2738
+  if (jumpStamina < 20) {
2739
+    // Exhausted - red flash
2740
+    let flash = sin(frameCount * 0.3) * 0.5 + 0.5;
2741
+    document.getElementById('web-meter-fill').style.background = 
2742
+      `linear-gradient(90deg, rgb(255, ${50 + flash * 50}, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))`;
2743
+  } else if (jumpStamina < 40) {
2744
+    // Very tired - orange-red
2745
+    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FF6B35, #FF4444)';
2746
+  } else if (jumpStamina < 60) {
2747
+    // Tired - orange
2748
+    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFA500, #FF8C00)';
2749
+  } else if (jumpStamina < 80) {
2750
+    // OK - yellow-orange
2751
+    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFD700, #FFA500)';
27312752
   } else {
2753
+    // Good stamina - green-yellow
2754
+    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #90EE90, #FFD700)';
2755
+  }
2756
+  
2757
+  // Show critical warning overlay
2758
+  if (jumpStamina <= 0 && !gameOver) {
2759
+    push();
2760
+    fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50);
2761
+    rect(0, 0, width, height);
2762
+
2763
+    textAlign(CENTER);
2764
+    textSize(32);
2765
+    fill(255, 50, 50);
2766
+    stroke(0);
2767
+    strokeWeight(3);
2768
+    text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2);
2769
+    pop();
2770
+  }
2771
+} else {
27322772
     // Normal silk meter
27332773
     document.getElementById('web-meter-label').textContent = 'SILK'
27342774
     let meterPercent = (webSilk / maxWebSilk) * 100
@@ -2840,6 +2880,7 @@ window.restartGame = function () {
28402880
   currentNight = 1
28412881
   fliesCaught = 0
28422882
   fliesMunched = 0
2883
+  playerPoints = 0
28432884
   totalFliesCaught = 0
28442885
   jumpStamina = 100
28452886
   maxJumpStamina = 100
@@ -3023,58 +3064,67 @@ function recycleNearbyWeb () {
30233064
   }
30243065
 }
30253066
 
3026
-function touchStarted() {
3067
+function touchStarted () {
30273068
   if (touches.length > 0) {
3028
-    touchStartTime = millis();
3029
-    touchStartX = touches[0].x;
3030
-    touchStartY = touches[0].y;
3069
+    touchStartTime = millis()
3070
+    touchStartX = touches[0].x
3071
+    touchStartY = touches[0].y
30313072
 
30323073
     // Check for double tap on spider to munch
3033
-    let touchOnSpider = dist(touches[0].x, touches[0].y, spider.pos.x, spider.pos.y) < 30;
3074
+    let touchOnSpider =
3075
+      dist(touches[0].x, touches[0].y, spider.pos.x, spider.pos.y) < 30
30343076
 
30353077
     if (touchOnSpider && millis() - lastTapTime < 300) {
30363078
       // Double tap detected on spider - MUNCH!
3037
-      spider.munch();
3038
-      lastTapTime = 0; // Reset to prevent triple tap
3079
+      spider.munch()
3080
+      lastTapTime = 0 // Reset to prevent triple tap
30393081
     } else if (!spider.isAirborne) {
30403082
       // Single tap while on ground - jump
3041
-      spider.jump(touches[0].x, touches[0].y);
3042
-      lastTapTime = millis();
3083
+      spider.jump(touches[0].x, touches[0].y)
3084
+      lastTapTime = millis()
30433085
     } else if (spider.isAirborne && webSilk > 10 && !isDeployingWeb) {
30443086
       // Start web deployment if airborne (only if not already deploying)
3045
-      touchHolding = true;
3046
-      isDeployingWeb = true;
3047
-      currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null);
3048
-      currentStrand.path = [spider.lastAnchorPoint.copy()];
3049
-      
3087
+      touchHolding = true
3088
+      isDeployingWeb = true
3089
+      currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null)
3090
+      currentStrand.path = [spider.lastAnchorPoint.copy()]
3091
+
30503092
       for (let obstacle of obstacles) {
3051
-        let d = dist(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y, obstacle.x, obstacle.y);
3093
+        let d = dist(
3094
+          spider.lastAnchorPoint.x,
3095
+          spider.lastAnchorPoint.y,
3096
+          obstacle.x,
3097
+          obstacle.y
3098
+        )
30523099
         // Check if anchor is on obstacle edge (within tolerance)
30533100
         if (abs(d - obstacle.radius) < 10) {
3054
-          currentStrand.startObstacle = obstacle;
3101
+          currentStrand.startObstacle = obstacle
30553102
           currentStrand.startAngle = atan2(
30563103
             spider.lastAnchorPoint.y - obstacle.y,
30573104
             spider.lastAnchorPoint.x - obstacle.x
3058
-          );
3059
-          break;
3105
+          )
3106
+          break
30603107
         }
30613108
       }
3062
-      
3063
-      webStrands.push(currentStrand);
3064
-      
3065
-      let newNode = new WebNode(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y);
3109
+
3110
+      webStrands.push(currentStrand)
3111
+
3112
+      let newNode = new WebNode(
3113
+        spider.lastAnchorPoint.x,
3114
+        spider.lastAnchorPoint.y
3115
+      )
30663116
       // NEW: Track node attachment if on obstacle
30673117
       if (currentStrand.startObstacle) {
3068
-        newNode.attachedObstacle = currentStrand.startObstacle;
3069
-        newNode.attachmentAngle = currentStrand.startAngle;
3118
+        newNode.attachedObstacle = currentStrand.startObstacle
3119
+        newNode.attachmentAngle = currentStrand.startAngle
30703120
       }
3071
-      webNodes.push(newNode);
3121
+      webNodes.push(newNode)
30723122
     } else if (spider.isAirborne && isDeployingWeb) {
30733123
       // If already deploying and user taps again, just continue (don't create new strand)
3074
-      touchHolding = true;
3124
+      touchHolding = true
30753125
     }
30763126
   }
3077
-  return false; // Prevent default
3127
+  return false // Prevent default
30783128
 }
30793129
 
30803130
 function touchMoved () {