zeroed-some/cob / 9d78b64

Browse files

turns out it was collision on one side

Authored by espadonne
SHA
9d78b646e628aba2700d4a06f83419ecc23bfb98
Parents
e0cda7f
Tree
8163911

2 changed files

StatusFile+-
M js/entities.js 621 575
M js/game.js 8 5
js/entities.jsmodified
1258 lines changed — click to load
@@ -1,20 +1,20 @@
11
 // entities.js - All game entity classes
22
 
33
 class Spider {
4
-    constructor(x, y) {
5
-        this.pos = createVector(x, y);
6
-        this.vel = createVector(0, 0);
7
-        this.acc = createVector(0, 0);
8
-        this.radius = 8;
9
-        this.isAirborne = false;
10
-        this.canJump = true;
11
-        this.lastAnchorPoint = null;
12
-        this.gravity = createVector(0, 0.3);
13
-        this.jumpPower = 12;
14
-        this.maxSpeed = 15;
15
-        this.munchRadius = 20;
16
-        this.munchCooldown = 0;
17
-    }
4
+  constructor (x, y) {
5
+    this.pos = createVector(x, y)
6
+    this.vel = createVector(0, 0)
7
+    this.acc = createVector(0, 0)
8
+    this.radius = 8
9
+    this.isAirborne = false
10
+    this.canJump = true
11
+    this.lastAnchorPoint = null
12
+    this.gravity = createVector(0, 0.3)
13
+    this.jumpPower = 12
14
+    this.maxSpeed = 15
15
+    this.munchRadius = 20
16
+    this.munchCooldown = 0
17
+  }
1818
 
1919
 jump(targetX, targetY) {
2020
         if (!this.canJump) return;
@@ -75,617 +75,663 @@ jump(targetX, targetY) {
7575
         }
7676
     }
7777
     
78
-    munch() {
79
-        if (this.munchCooldown > 0) return;
80
-        
81
-        isMunching = true;
82
-        this.munchCooldown = 30;
83
-        
84
-        for (let i = flies.length - 1; i >= 0; i--) {
85
-            let fly = flies[i];
86
-            let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y);
87
-            if (d < this.munchRadius) {
88
-                fliesMunched++;
89
-                webSilk = min(webSilk + 15, maxWebSilk);
90
-                
91
-                for (let j = 0; j < 12; j++) {
92
-                    let p = new Particle(fly.pos.x, fly.pos.y);
93
-                    p.color = color(255, random(100, 255), 0);
94
-                    particles.push(p);
95
-                }
96
-                
97
-                flies.splice(i, 1);
98
-                break;
99
-            }
78
+  munch () {
79
+    if (this.munchCooldown > 0) return
80
+
81
+    isMunching = true
82
+    this.munchCooldown = 30
83
+
84
+    for (let i = flies.length - 1; i >= 0; i--) {
85
+      let fly = flies[i]
86
+      let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y)
87
+      if (d < this.munchRadius) {
88
+        fliesMunched++
89
+        webSilk = min(webSilk + 15, maxWebSilk)
90
+
91
+        for (let j = 0; j < 12; j++) {
92
+          let p = new Particle(fly.pos.x, fly.pos.y)
93
+          p.color = color(255, random(100, 255), 0)
94
+          particles.push(p)
10095
         }
96
+
97
+        flies.splice(i, 1)
98
+        break
99
+      }
101100
     }
101
+  }
102102
 
103
-    update() {
104
-        if (this.isAirborne) {
105
-            this.acc.add(this.gravity);
106
-        }
107
-        
108
-        this.vel.add(this.acc);
109
-        this.vel.limit(this.maxSpeed);
110
-        this.pos.add(this.vel);
111
-        this.acc.mult(0);
112
-        
113
-        if (this.munchCooldown > 0) {
114
-            this.munchCooldown--;
115
-            if (this.munchCooldown === 0) {
116
-                isMunching = false;
117
-            }
118
-        }
103
+  update () {
104
+    if (this.isAirborne) {
105
+      this.acc.add(this.gravity)
106
+    }
119107
 
120
-        // Check ground collision
121
-        if (this.pos.y >= height - this.radius) {
122
-            this.pos.y = height - this.radius;
123
-            this.land();
124
-        }
108
+    this.vel.add(this.acc)
109
+    this.vel.limit(this.maxSpeed)
110
+    this.pos.add(this.vel)
111
+    this.acc.mult(0)
125112
 
126
-        // Check wall collisions
127
-        if (this.pos.x <= this.radius || this.pos.x >= width - this.radius) {
128
-            this.pos.x = constrain(this.pos.x, this.radius, width - this.radius);
129
-            this.vel.x *= -0.5;
130
-        }
113
+    if (this.munchCooldown > 0) {
114
+      this.munchCooldown--
115
+      if (this.munchCooldown === 0) {
116
+        isMunching = false
117
+      }
118
+    }
131119
 
132
-        // Check ceiling
133
-        if (this.pos.y <= this.radius) {
134
-            this.pos.y = this.radius;
135
-            this.vel.y *= -0.5; // Bounce off ceiling, don't land
136
-        }
137
-        
138
-        // Check home branch collision (one-way platform)
139
-        if (window.homeBranch && this.isAirborne && this.vel.y > 0.1) { // Only when actually falling
140
-            let branch = window.homeBranch;
141
-            
142
-            // Check if spider is within branch X range
143
-            let branchStart = Math.min(branch.startX, branch.endX);
144
-            let branchEnd = Math.max(branch.startX, branch.endX);
145
-            
146
-            // Since the branch angle is very small (0.05 radians ≈ 3 degrees), 
147
-            // we can use a simpler approximation
148
-            if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) {
149
-                // Calculate position along branch (0 to 1)
150
-                let t = (this.pos.x - branchStart) / (branchEnd - branchStart);
151
-                t = constrain(t, 0, 1);
152
-                
153
-                // Branch visual thickness tapers from full at start to 35% at end
154
-                // This matches exactly how it's drawn in the bezier curves
155
-                let branchTopThickness = lerp(branch.thickness * 0.9, branch.thickness * 0.35, t);
156
-                
157
-                // The branch is drawn centered at branch.y
158
-                // With small angle approximation: the top of the branch is at
159
-                let branchSurfaceY = branch.y - branchTopThickness;
160
-                
161
-                // Add slight angle correction (for small angles, tan ≈ sin ≈ angle in radians)
162
-                let angleCorrection = (this.pos.x - branchStart) * branch.angle;
163
-                branchSurfaceY += angleCorrection;
164
-                
165
-                // Check if spider is crossing the branch from above
166
-                let prevY = this.pos.y - this.vel.y;
167
-                
168
-                if (prevY <= branchSurfaceY && // Was above
169
-                    this.pos.y + this.radius >= branchSurfaceY && // Now at or below
170
-                    this.pos.y < branch.y + branch.thickness) { // Not too far below
171
-                    
172
-                    // Place spider on the branch surface
173
-                    this.pos.y = branchSurfaceY - this.radius;
174
-                    this.land();
175
-                }
176
-            }
177
-        }
120
+    // Check ground collision
121
+    if (this.pos.y >= height - this.radius) {
122
+      this.pos.y = height - this.radius
123
+      this.land()
124
+    }
178125
 
179
-        // Check obstacle collisions
180
-        for (let obstacle of obstacles) {
181
-            if (this.checkObstacleCollision(obstacle)) {
182
-                this.landOnObstacle(obstacle);
183
-            }
184
-        }
126
+    // Check wall collisions
127
+    if (this.pos.x <= this.radius || this.pos.x >= width - this.radius) {
128
+      this.pos.x = constrain(this.pos.x, this.radius, width - this.radius)
129
+      this.vel.x *= -0.5
130
+    }
185131
 
186
-        // Check web strand collisions
187
-        for (let strand of webStrands) {
188
-            if (strand === currentStrand) continue;
189
-            
190
-            if (this.isAirborne && this.checkStrandCollision(strand)) {
191
-                this.landOnStrand(strand);
192
-            }
193
-        }
194
-        
195
-        // Check food box collisions
196
-        for (let i = foodBoxes.length - 1; i >= 0; i--) {
197
-            let box = foodBoxes[i];
198
-            if (dist(this.pos.x, this.pos.y, box.pos.x, box.pos.y) < this.radius + box.radius) {
199
-                box.collect();
200
-                foodBoxes.splice(i, 1);
201
-            }
132
+    // Check ceiling
133
+    if (this.pos.y <= this.radius) {
134
+      this.pos.y = this.radius
135
+      this.vel.y *= -0.5 // Bounce off ceiling, don't land
136
+    }
137
+
138
+    // Check home branch collision (one-way platform)
139
+    if (window.homeBranch && this.isAirborne && this.vel.y > 0.1) {
140
+      // Only when actually falling
141
+      let branch = window.homeBranch
142
+
143
+      // Check if spider is within branch X range
144
+      let branchStart = Math.min(branch.startX, branch.endX)
145
+      let branchEnd = Math.max(branch.startX, branch.endX)
146
+
147
+      // Since the branch angle is very small (0.05 radians ≈ 3 degrees),
148
+      // we can use a simpler approximation
149
+      if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) {
150
+        // Calculate position along branch (0 to 1)
151
+        let t = (this.pos.x - branchStart) / (branchEnd - branchStart)
152
+        t = constrain(t, 0, 1)
153
+
154
+        // Branch visual thickness tapers from full at start to 35% at end
155
+        // This matches exactly how it's drawn in the bezier curves
156
+        let branchTopThickness = lerp(
157
+          branch.thickness * 0.9,
158
+          branch.thickness * 0.35,
159
+          t
160
+        )
161
+
162
+        // The branch is drawn centered at branch.y
163
+        // With small angle approximation: the top of the branch is at
164
+        let branchSurfaceY = branch.y - branchTopThickness
165
+
166
+        // Add slight angle correction (for small angles, tan ≈ sin ≈ angle in radians)
167
+        let angleCorrection = (this.pos.x - branchStart) * branch.angle
168
+        branchSurfaceY += angleCorrection
169
+
170
+        // Check if spider is crossing the branch from above
171
+        let prevY = this.pos.y - this.vel.y
172
+
173
+        if (
174
+          prevY <= branchSurfaceY && // Was above
175
+          this.pos.y + this.radius >= branchSurfaceY && // Now at or below
176
+          this.pos.y < branch.y + branch.thickness
177
+        ) {
178
+          // Not too far below
179
+
180
+          // Place spider on the branch surface
181
+          this.pos.y = branchSurfaceY - this.radius
182
+          this.land()
202183
         }
184
+      }
203185
     }
204186
 
205
-    checkObstacleCollision(obstacle) {
206
-        let d = dist(this.pos.x, this.pos.y, obstacle.x, obstacle.y);
207
-        return d < this.radius + obstacle.radius;
187
+    // Check obstacle collisions
188
+    for (let obstacle of obstacles) {
189
+      if (this.checkObstacleCollision(obstacle)) {
190
+        this.landOnObstacle(obstacle)
191
+      }
208192
     }
209193
 
210
-    checkStrandCollision(strand) {
211
-        let d = this.pointToLineDistance(this.pos, strand.start, strand.end);
212
-        return d < this.radius + 2;
194
+    // Check web strand collisions
195
+    for (let strand of webStrands) {
196
+      if (strand === currentStrand) continue
197
+
198
+      if (this.isAirborne && this.checkStrandCollision(strand)) {
199
+        this.landOnStrand(strand)
200
+      }
213201
     }
214202
 
215
-    pointToLineDistance(point, lineStart, lineEnd) {
216
-        let line = p5.Vector.sub(lineEnd, lineStart);
217
-        let lineLength = line.mag();
218
-        line.normalize();
219
-        
220
-        let pointToStart = p5.Vector.sub(point, lineStart);
221
-        let projLength = constrain(pointToStart.dot(line), 0, lineLength);
222
-        
223
-        let closestPoint = p5.Vector.add(lineStart, p5.Vector.mult(line, projLength));
224
-        return p5.Vector.dist(point, closestPoint);
203
+    // Check food box collisions
204
+    for (let i = foodBoxes.length - 1; i >= 0; i--) {
205
+      let box = foodBoxes[i]
206
+      if (
207
+        dist(this.pos.x, this.pos.y, box.pos.x, box.pos.y) <
208
+        this.radius + box.radius
209
+      ) {
210
+        box.collect()
211
+        foodBoxes.splice(i, 1)
212
+      }
225213
     }
214
+  }
215
+
216
+  checkObstacleCollision (obstacle) {
217
+    let d = dist(this.pos.x, this.pos.y, obstacle.x, obstacle.y)
218
+    return d < this.radius + obstacle.radius
219
+  }
220
+
221
+  checkStrandCollision (strand) {
222
+    let d = this.pointToLineDistance(this.pos, strand.start, strand.end)
223
+    return d < this.radius + 2
224
+  }
225
+
226
+  pointToLineDistance (point, lineStart, lineEnd) {
227
+    let line = p5.Vector.sub(lineEnd, lineStart)
228
+    let lineLength = line.mag()
229
+    line.normalize()
230
+
231
+    let pointToStart = p5.Vector.sub(point, lineStart)
232
+    let projLength = constrain(pointToStart.dot(line), 0, lineLength)
233
+
234
+    let closestPoint = p5.Vector.add(
235
+      lineStart,
236
+      p5.Vector.mult(line, projLength)
237
+    )
238
+    return p5.Vector.dist(point, closestPoint)
239
+  }
240
+
241
+  landOnObstacle (obstacle) {
242
+    let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x)
243
+    this.pos.x = obstacle.x + cos(angle) * (obstacle.radius + this.radius)
244
+    this.pos.y = obstacle.y + sin(angle) * (obstacle.radius + this.radius)
245
+    this.land()
246
+  }
247
+
248
+  landOnStrand (strand) {
249
+    let line = p5.Vector.sub(strand.end, strand.start)
250
+    let lineLength = line.mag()
251
+    line.normalize()
252
+
253
+    let pointToStart = p5.Vector.sub(this.pos, strand.start)
254
+    let projLength = constrain(pointToStart.dot(line), 0, lineLength)
255
+
256
+    let closestPoint = p5.Vector.add(
257
+      strand.start,
258
+      p5.Vector.mult(line, projLength)
259
+    )
260
+    this.pos = closestPoint
261
+    this.land()
262
+  }
263
+
264
+  land () {
265
+    this.vel.mult(0)
266
+    this.isAirborne = false
267
+    this.canJump = true
226268
 
227
-    landOnObstacle(obstacle) {
228
-        let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x);
229
-        this.pos.x = obstacle.x + cos(angle) * (obstacle.radius + this.radius);
230
-        this.pos.y = obstacle.y + sin(angle) * (obstacle.radius + this.radius);
231
-        this.land();
269
+    if (currentStrand && isDeployingWeb && spacePressed) {
270
+      currentStrand.end = this.pos.copy()
271
+      currentStrand.path.push(this.pos.copy())
272
+      webNodes.push(new WebNode(this.pos.x, this.pos.y))
232273
     }
233274
 
234
-    landOnStrand(strand) {
235
-        let line = p5.Vector.sub(strand.end, strand.start);
236
-        let lineLength = line.mag();
237
-        line.normalize();
238
-        
239
-        let pointToStart = p5.Vector.sub(this.pos, strand.start);
240
-        let projLength = constrain(pointToStart.dot(line), 0, lineLength);
241
-        
242
-        let closestPoint = p5.Vector.add(strand.start, p5.Vector.mult(line, projLength));
243
-        this.pos = closestPoint;
244
-        this.land();
275
+    currentStrand = null
276
+    isDeployingWeb = false
277
+  }
278
+
279
+  display () {
280
+    push()
281
+    translate(this.pos.x, this.pos.y)
282
+
283
+    if (isMunching && this.munchCooldown > 15) {
284
+      push()
285
+      fill(255, 100, 100, 150)
286
+      noStroke()
287
+      let munchSize = 15 + sin(frameCount * 0.5) * 5
288
+      arc(0, 0, munchSize, munchSize, 0, PI + HALF_PI, PIE)
289
+      pop()
245290
     }
246291
 
247
-    land() {
248
-        this.vel.mult(0);
249
-        this.isAirborne = false;
250
-        this.canJump = true;
251
-        
252
-        if (currentStrand && isDeployingWeb && spacePressed) {
253
-            currentStrand.end = this.pos.copy();
254
-            currentStrand.path.push(this.pos.copy());
255
-            webNodes.push(new WebNode(this.pos.x, this.pos.y));
256
-        }
257
-        
258
-        currentStrand = null;
259
-        isDeployingWeb = false;
292
+    fill(20)
293
+    stroke(0)
294
+    strokeWeight(1)
295
+    ellipse(0, 0, this.radius * 2)
296
+
297
+    fill(40)
298
+    noStroke()
299
+    ellipse(0, -2, this.radius * 1.2, this.radius * 1.5)
300
+
301
+    if (gamePhase === 'NIGHT') {
302
+      fill(255, 100, 100)
303
+    } else {
304
+      fill(255, 0, 0)
260305
     }
306
+    ellipse(-3, -3, 3)
307
+    ellipse(3, -3, 3)
261308
 
262
-    display() {
263
-        push();
264
-        translate(this.pos.x, this.pos.y);
265
-        
266
-        if (isMunching && this.munchCooldown > 15) {
267
-            push();
268
-            fill(255, 100, 100, 150);
269
-            noStroke();
270
-            let munchSize = 15 + sin(frameCount * 0.5) * 5;
271
-            arc(0, 0, munchSize, munchSize, 0, PI + HALF_PI, PIE);
272
-            pop();
273
-        }
274
-        
275
-        fill(20);
276
-        stroke(0);
277
-        strokeWeight(1);
278
-        ellipse(0, 0, this.radius * 2);
279
-        
280
-        fill(40);
281
-        noStroke();
282
-        ellipse(0, -2, this.radius * 1.2, this.radius * 1.5);
283
-        
284
-        if (gamePhase === 'NIGHT') {
285
-            fill(255, 100, 100);
286
-        } else {
287
-            fill(255, 0, 0);
288
-        }
289
-        ellipse(-3, -3, 3);
290
-        ellipse(3, -3, 3);
291
-        
292
-        stroke(0);
293
-        strokeWeight(1.5);
294
-        for (let i = 0; i < 4; i++) {
295
-            let angle = PI/6 + (i * PI/8);
296
-            line(0, 0, cos(angle) * 12, sin(angle) * 8);
297
-            line(0, 0, -cos(angle) * 12, sin(angle) * 8);
298
-        }
299
-        
300
-        if (webSilk < 20) {
301
-            fill(255, 100, 100, 150 + sin(frameCount * 0.2) * 50);
302
-            noStroke();
303
-            ellipse(0, -15, 8);
304
-        }
305
-        
306
-        pop();
309
+    stroke(0)
310
+    strokeWeight(1.5)
311
+    for (let i = 0; i < 4; i++) {
312
+      let angle = PI / 6 + (i * PI) / 8
313
+      line(0, 0, cos(angle) * 12, sin(angle) * 8)
314
+      line(0, 0, -cos(angle) * 12, sin(angle) * 8)
315
+    }
316
+
317
+    if (webSilk < 20) {
318
+      fill(255, 100, 100, 150 + sin(frameCount * 0.2) * 50)
319
+      noStroke()
320
+      ellipse(0, -15, 8)
307321
     }
322
+
323
+    pop()
324
+  }
308325
 }
309326
 
310327
 class Fly {
311
-    constructor() {
312
-        if (random() < 0.5) {
313
-            this.pos = createVector(random() < 0.5 ? -20 : width + 20, random(50, height - 100));
314
-        } else {
315
-            this.pos = createVector(random(width), random() < 0.5 ? -20 : height + 20);
316
-        }
317
-        
318
-        this.vel = createVector(random(-2, 2), random(-1, 1));
319
-        this.acc = createVector(0, 0);
320
-        this.radius = 4;
321
-        this.caught = false;
322
-        this.stuck = false;
323
-        this.wingPhase = random(TWO_PI);
324
-        this.wanderAngle = random(TWO_PI);
325
-        this.glowIntensity = random(150, 255);
326
-        this.touchedStrands = new Set();
327
-        this.slowedBy = new Set(); // Track which strands are slowing us
328
-        this.baseSpeed = 3;
329
-        this.currentSpeed = this.baseSpeed;
328
+  constructor () {
329
+    if (random() < 0.5) {
330
+      this.pos = createVector(
331
+        random() < 0.5 ? -20 : width + 20,
332
+        random(50, height - 100)
333
+      )
334
+    } else {
335
+      this.pos = createVector(random(width), random() < 0.5 ? -20 : height + 20)
330336
     }
331337
 
332
-    update() {
333
-        if (this.stuck) return;
334
-        
335
-        if (this.caught) {
336
-            this.vel.mult(0.95);
337
-            if (this.vel.mag() < 0.1) {
338
-                this.stuck = true;
339
-                fliesCaught++;
340
-                webSilk = min(webSilk + 5, maxWebSilk);
341
-            }
342
-            return;
343
-        }
344
-        
345
-        this.wanderAngle += random(-0.3, 0.3);
346
-        let wanderForce = createVector(cos(this.wanderAngle), sin(this.wanderAngle));
347
-        wanderForce.mult(0.1);
348
-        this.acc.add(wanderForce);
349
-        
350
-        // Apply current speed (which may be slowed)
351
-        this.vel.add(this.acc);
352
-        this.vel.limit(this.currentSpeed);
353
-        this.pos.add(this.vel);
354
-        this.acc.mult(0);
355
-        
356
-        if (this.pos.x < -30) this.pos.x = width + 30;
357
-        if (this.pos.x > width + 30) this.pos.x = -30;
358
-        if (this.pos.y < -30) this.pos.y = height + 30;
359
-        if (this.pos.y > height + 30) this.pos.y = -30;
360
-        
361
-        // Check web collisions
362
-        this.checkWebCollisions();
338
+    this.vel = createVector(random(-2, 2), random(-1, 1))
339
+    this.acc = createVector(0, 0)
340
+    this.radius = 4
341
+    this.caught = false
342
+    this.stuck = false
343
+    this.wingPhase = random(TWO_PI)
344
+    this.wanderAngle = random(TWO_PI)
345
+    this.glowIntensity = random(150, 255)
346
+    this.touchedStrands = new Set()
347
+    this.slowedBy = new Set() // Track which strands are slowing us
348
+    this.baseSpeed = 3
349
+    this.currentSpeed = this.baseSpeed
350
+  }
351
+
352
+  update () {
353
+    if (this.stuck) return
354
+
355
+    if (this.caught) {
356
+      this.vel.mult(0.95)
357
+      if (this.vel.mag() < 0.1) {
358
+        this.stuck = true
359
+        fliesCaught++
360
+        webSilk = min(webSilk + 5, maxWebSilk)
361
+      }
362
+      return
363363
     }
364364
 
365
-    checkWebCollisions() {
366
-        let currentlyTouching = new Set();
367
-        
368
-        for (let strand of webStrands) {
369
-            let touching = false;
370
-            
371
-            // Check collision with strand path
372
-            if (strand.path && strand.path.length > 1) {
373
-                for (let i = 0; i < strand.path.length - 1; i++) {
374
-                    let p1 = strand.path[i];
375
-                    let p2 = strand.path[i + 1];
376
-                    let d = this.pointToLineDistance(this.pos, p1, p2);
377
-                    if (d < this.radius + 3) {
378
-                        touching = true;
379
-                        break;
380
-                    }
381
-                }
382
-            } else if (strand.start && strand.end) {
383
-                // Fallback for strands without path
384
-                let d = this.pointToLineDistance(this.pos, strand.start, strand.end);
385
-                if (d < this.radius + 3) {
386
-                    touching = true;
387
-                }
365
+    this.wanderAngle += random(-0.3, 0.3)
366
+    let wanderForce = createVector(cos(this.wanderAngle), sin(this.wanderAngle))
367
+    wanderForce.mult(0.1)
368
+    this.acc.add(wanderForce)
369
+
370
+    // Apply current speed (which may be slowed)
371
+    this.vel.add(this.acc)
372
+    this.vel.limit(this.currentSpeed)
373
+    this.pos.add(this.vel)
374
+    this.acc.mult(0)
375
+
376
+    if (this.pos.x < -30) this.pos.x = width + 30
377
+    if (this.pos.x > width + 30) this.pos.x = -30
378
+    if (this.pos.y < -30) this.pos.y = height + 30
379
+    if (this.pos.y > height + 30) this.pos.y = -30
380
+
381
+    // Check web collisions
382
+    this.checkWebCollisions()
383
+  }
384
+
385
+  checkWebCollisions () {
386
+    let currentlyTouching = new Set()
387
+
388
+    for (let strand of webStrands) {
389
+      let touching = false
390
+
391
+      // Check collision with strand path
392
+      if (strand.path && strand.path.length > 1) {
393
+        for (let i = 0; i < strand.path.length - 1; i++) {
394
+          let p1 = strand.path[i]
395
+          let p2 = strand.path[i + 1]
396
+          let d = this.pointToLineDistance(this.pos, p1, p2)
397
+          if (d < this.radius + 3) {
398
+            touching = true
399
+            break
400
+          }
401
+        }
402
+      } else if (strand.start && strand.end) {
403
+        // Fallback for strands without path
404
+        let d = this.pointToLineDistance(this.pos, strand.start, strand.end)
405
+        if (d < this.radius + 3) {
406
+          touching = true
407
+        }
408
+      }
409
+
410
+      if (touching) {
411
+        currentlyTouching.add(strand)
412
+
413
+        // If this is a new strand we're touching
414
+        if (!this.touchedStrands.has(strand)) {
415
+          this.touchedStrands.add(strand)
416
+
417
+          // Vibrate the web when first touching
418
+          strand.vibrate(3)
419
+
420
+          // First strand slows us down
421
+          if (this.touchedStrands.size === 1) {
422
+            this.currentSpeed = this.baseSpeed * 0.4 // Slow to 40% speed
423
+            this.slowedBy.add(strand)
424
+
425
+            // Visual feedback - yellow particles for slowing
426
+            for (let j = 0; j < 3; j++) {
427
+              let p = new Particle(this.pos.x, this.pos.y)
428
+              p.color = color(255, 255, 0, 150)
429
+              p.vel = createVector(random(-1, 1), random(-1, 1))
430
+              p.size = 3
431
+              particles.push(p)
388432
             }
389
-            
390
-            if (touching) {
391
-                currentlyTouching.add(strand);
392
-                
393
-                // If this is a new strand we're touching
394
-                if (!this.touchedStrands.has(strand)) {
395
-                    this.touchedStrands.add(strand);
396
-                    
397
-                    // Vibrate the web when first touching
398
-                    strand.vibrate(3);
399
-                    
400
-                    // First strand slows us down
401
-                    if (this.touchedStrands.size === 1) {
402
-                        this.currentSpeed = this.baseSpeed * 0.4; // Slow to 40% speed
403
-                        this.slowedBy.add(strand);
404
-                        
405
-                        // Visual feedback - yellow particles for slowing
406
-                        for (let j = 0; j < 3; j++) {
407
-                            let p = new Particle(this.pos.x, this.pos.y);
408
-                            p.color = color(255, 255, 0, 150);
409
-                            p.vel = createVector(random(-1, 1), random(-1, 1));
410
-                            p.size = 3;
411
-                            particles.push(p);
412
-                        }
413
-                    }
414
-                    // Second strand catches us
415
-                    else if (this.touchedStrands.size >= 2 && !this.caught) {
416
-                        this.caught = true;
417
-                        this.currentSpeed = 0;
418
-                        
419
-                        // Stronger vibration when caught
420
-                        strand.vibrate(8);
421
-                        
422
-                        // Also vibrate nearby strands
423
-                        for (let otherStrand of webStrands) {
424
-                            if (otherStrand !== strand) {
425
-                                for (let touchedStrand of this.touchedStrands) {
426
-                                    let d1 = dist(otherStrand.start.x, otherStrand.start.y, touchedStrand.start.x, touchedStrand.start.y);
427
-                                    let d2 = dist(otherStrand.start.x, otherStrand.start.y, touchedStrand.end.x, touchedStrand.end.y);
428
-                                    let d3 = dist(otherStrand.end.x, otherStrand.end.y, touchedStrand.start.x, touchedStrand.start.y);
429
-                                    let d4 = dist(otherStrand.end.x, otherStrand.end.y, touchedStrand.end.x, touchedStrand.end.y);
430
-                                    if (min(d1, d2, d3, d4) < 50) {
431
-                                        otherStrand.vibrate(2);
432
-                                        break;
433
-                                    }
434
-                                }
435
-                            }
436
-                        }
437
-                        
438
-                        // Create caught particles
439
-                        for (let j = 0; j < 6; j++) {
440
-                            let p = new Particle(this.pos.x, this.pos.y);
441
-                            p.color = color(255, 200, 0, 200);
442
-                            p.vel = createVector(random(-2, 2), random(-2, 2));
443
-                            particles.push(p);
444
-                        }
445
-                    }
433
+          }
434
+          // Second strand catches us
435
+          else if (this.touchedStrands.size >= 2 && !this.caught) {
436
+            this.caught = true
437
+            this.currentSpeed = 0
438
+
439
+            // Stronger vibration when caught
440
+            strand.vibrate(8)
441
+
442
+            // Also vibrate nearby strands
443
+            for (let otherStrand of webStrands) {
444
+              if (otherStrand !== strand) {
445
+                for (let touchedStrand of this.touchedStrands) {
446
+                  let d1 = dist(
447
+                    otherStrand.start.x,
448
+                    otherStrand.start.y,
449
+                    touchedStrand.start.x,
450
+                    touchedStrand.start.y
451
+                  )
452
+                  let d2 = dist(
453
+                    otherStrand.start.x,
454
+                    otherStrand.start.y,
455
+                    touchedStrand.end.x,
456
+                    touchedStrand.end.y
457
+                  )
458
+                  let d3 = dist(
459
+                    otherStrand.end.x,
460
+                    otherStrand.end.y,
461
+                    touchedStrand.start.x,
462
+                    touchedStrand.start.y
463
+                  )
464
+                  let d4 = dist(
465
+                    otherStrand.end.x,
466
+                    otherStrand.end.y,
467
+                    touchedStrand.end.x,
468
+                    touchedStrand.end.y
469
+                  )
470
+                  if (min(d1, d2, d3, d4) < 50) {
471
+                    otherStrand.vibrate(2)
472
+                    break
473
+                  }
446474
                 }
475
+              }
447476
             }
477
+
478
+            // Create caught particles
479
+            for (let j = 0; j < 6; j++) {
480
+              let p = new Particle(this.pos.x, this.pos.y)
481
+              p.color = color(255, 200, 0, 200)
482
+              p.vel = createVector(random(-2, 2), random(-2, 2))
483
+              particles.push(p)
484
+            }
485
+          }
448486
         }
449
-        
450
-        // If we're no longer touching strands we were slowed by, speed back up
451
-        if (this.slowedBy.size > 0 && currentlyTouching.size === 0) {
452
-            this.currentSpeed = this.baseSpeed;
453
-            this.slowedBy.clear();
454
-        }
487
+      }
455488
     }
456489
 
457
-    pointToLineDistance(point, lineStart, lineEnd) {
458
-        let line = p5.Vector.sub(lineEnd, lineStart);
459
-        let lineLength = line.mag();
460
-        line.normalize();
461
-        
462
-        let pointToStart = p5.Vector.sub(point, lineStart);
463
-        let projLength = constrain(pointToStart.dot(line), 0, lineLength);
464
-        
465
-        let closestPoint = p5.Vector.add(lineStart, p5.Vector.mult(line, projLength));
466
-        return p5.Vector.dist(point, closestPoint);
490
+    // If we're no longer touching strands we were slowed by, speed back up
491
+    if (this.slowedBy.size > 0 && currentlyTouching.size === 0) {
492
+      this.currentSpeed = this.baseSpeed
493
+      this.slowedBy.clear()
467494
     }
495
+  }
468496
 
469
-    display() {
470
-        push();
471
-        translate(this.pos.x, this.pos.y);
472
-        
473
-        // Show slowdown effect
474
-        if (this.slowedBy.size > 0 && !this.caught) {
475
-            stroke(255, 255, 0, 100);
476
-            strokeWeight(1);
477
-            noFill();
478
-            ellipse(0, 0, 20);
479
-        }
480
-        
481
-        if (gamePhase === 'NIGHT') {
482
-            noStroke();
483
-            fill(255, 255, 150, this.glowIntensity * 0.3);
484
-            ellipse(0, 0, 30);
485
-            fill(255, 255, 100, this.glowIntensity * 0.5);
486
-            ellipse(0, 0, 20);
487
-        }
488
-        
489
-        fill(30);
490
-        stroke(0);
491
-        strokeWeight(0.5);
492
-        ellipse(0, 0, this.radius * 2);
493
-        
494
-        if (!this.stuck) {
495
-            this.wingPhase += 0.5;
496
-            // Wing animation slows down when slowed
497
-            let wingSpeed = this.slowedBy.size > 0 ? 0.25 : 0.5;
498
-            this.wingPhase += wingSpeed;
499
-            let wingSpread = sin(this.wingPhase) * 5;
500
-            
501
-            fill(255, 255, 255, 150);
502
-            noStroke();
503
-            ellipse(-wingSpread, 0, 6, 4);
504
-            ellipse(wingSpread, 0, 6, 4);
505
-        }
506
-        
507
-        if (gamePhase === 'NIGHT') {
508
-            fill(255, 255, 100, this.glowIntensity);
509
-            noStroke();
510
-            ellipse(0, 2, 3);
511
-        }
512
-        
513
-        pop();
497
+  pointToLineDistance (point, lineStart, lineEnd) {
498
+    let line = p5.Vector.sub(lineEnd, lineStart)
499
+    let lineLength = line.mag()
500
+    line.normalize()
501
+
502
+    let pointToStart = p5.Vector.sub(point, lineStart)
503
+    let projLength = constrain(pointToStart.dot(line), 0, lineLength)
504
+
505
+    let closestPoint = p5.Vector.add(
506
+      lineStart,
507
+      p5.Vector.mult(line, projLength)
508
+    )
509
+    return p5.Vector.dist(point, closestPoint)
510
+  }
511
+
512
+  display () {
513
+    push()
514
+    translate(this.pos.x, this.pos.y)
515
+
516
+    // Show slowdown effect
517
+    if (this.slowedBy.size > 0 && !this.caught) {
518
+      stroke(255, 255, 0, 100)
519
+      strokeWeight(1)
520
+      noFill()
521
+      ellipse(0, 0, 20)
514522
     }
523
+
524
+    if (gamePhase === 'NIGHT') {
525
+      noStroke()
526
+      fill(255, 255, 150, this.glowIntensity * 0.3)
527
+      ellipse(0, 0, 30)
528
+      fill(255, 255, 100, this.glowIntensity * 0.5)
529
+      ellipse(0, 0, 20)
530
+    }
531
+
532
+    fill(30)
533
+    stroke(0)
534
+    strokeWeight(0.5)
535
+    ellipse(0, 0, this.radius * 2)
536
+
537
+    if (!this.stuck) {
538
+      this.wingPhase += 0.5
539
+      // Wing animation slows down when slowed
540
+      let wingSpeed = this.slowedBy.size > 0 ? 0.25 : 0.5
541
+      this.wingPhase += wingSpeed
542
+      let wingSpread = sin(this.wingPhase) * 5
543
+
544
+      fill(255, 255, 255, 150)
545
+      noStroke()
546
+      ellipse(-wingSpread, 0, 6, 4)
547
+      ellipse(wingSpread, 0, 6, 4)
548
+    }
549
+
550
+    if (gamePhase === 'NIGHT') {
551
+      fill(255, 255, 100, this.glowIntensity)
552
+      noStroke()
553
+      ellipse(0, 2, 3)
554
+    }
555
+
556
+    pop()
557
+  }
515558
 }
516559
 
517560
 class Obstacle {
518
-    constructor(x, y, radius, type) {
519
-        this.x = x;
520
-        this.y = y;
521
-        this.radius = radius;
522
-        this.type = type || (random() < 0.5 ? 'branch' : 'leaf');
523
-        this.rotation = random(TWO_PI);
524
-        this.leafPoints = [];
525
-        
526
-        if (this.type === 'leaf') {
527
-            let numPoints = 8;
528
-            for (let i = 0; i < numPoints; i++) {
529
-                let angle = (TWO_PI / numPoints) * i;
530
-                let r = radius * random(0.7, 1.2);
531
-                if (i === 0 || i === numPoints/2) r = radius * 1.3;
532
-                this.leafPoints.push({angle: angle, radius: r});
533
-            }
534
-        }
561
+  constructor (x, y, radius, type) {
562
+    this.x = x
563
+    this.y = y
564
+    this.radius = radius
565
+    this.type = type || (random() < 0.5 ? 'branch' : 'leaf')
566
+    this.rotation = random(TWO_PI)
567
+    this.leafPoints = []
568
+
569
+    if (this.type === 'leaf') {
570
+      let numPoints = 8
571
+      for (let i = 0; i < numPoints; i++) {
572
+        let angle = (TWO_PI / numPoints) * i
573
+        let r = radius * random(0.7, 1.2)
574
+        if (i === 0 || i === numPoints / 2) r = radius * 1.3
575
+        this.leafPoints.push({ angle: angle, radius: r })
576
+      }
535577
     }
578
+  }
536579
 
537
-    display() {
538
-        push();
539
-        translate(this.x, this.y);
540
-        rotate(this.rotation);
541
-        
542
-        if (this.type === 'branch') {
543
-            if (gamePhase === 'NIGHT') {
544
-                stroke(40, 20, 0);
545
-                fill(50, 25, 5);
546
-            } else {
547
-                stroke(101, 67, 33);
548
-                fill(139, 90, 43);
549
-            }
550
-            strokeWeight(3);
551
-            
552
-            push();
553
-            strokeWeight(this.radius / 3);
554
-            line(-this.radius, 0, this.radius, 0);
555
-            
556
-            strokeWeight(2);
557
-            line(-this.radius/2, 0, -this.radius/2 - 10, -10);
558
-            line(this.radius/3, 0, this.radius/3 + 8, -8);
559
-            line(0, 0, 5, -15);
560
-            
561
-            stroke(80, 50, 20, 100);
562
-            strokeWeight(1);
563
-            for (let i = -this.radius; i < this.radius; i += 5) {
564
-                line(i, -2, i + 2, 2);
565
-            }
566
-            pop();
567
-            
568
-            noStroke();
569
-            fill(255, 255, 255, 30);
570
-            ellipse(0, 0, this.radius * 2);
571
-            
572
-        } else if (this.type === 'leaf') {
573
-            if (gamePhase === 'NIGHT') {
574
-                fill(20, 40, 20);
575
-                stroke(10, 20, 10);
576
-            } else {
577
-                fill(34, 139, 34);
578
-                stroke(25, 100, 25);
579
-            }
580
-            strokeWeight(2);
581
-            
582
-            beginShape();
583
-            for (let point of this.leafPoints) {
584
-                let x = cos(point.angle) * point.radius;
585
-                let y = sin(point.angle) * point.radius;
586
-                curveVertex(x, y);
587
-            }
588
-            let firstPoint = this.leafPoints[0];
589
-            curveVertex(cos(firstPoint.angle) * firstPoint.radius, 
590
-                       sin(firstPoint.angle) * firstPoint.radius);
591
-            let secondPoint = this.leafPoints[1];
592
-            curveVertex(cos(secondPoint.angle) * secondPoint.radius, 
593
-                       sin(secondPoint.angle) * secondPoint.radius);
594
-            endShape();
595
-            
596
-            stroke(25, 100, 25, 100);
597
-            strokeWeight(1);
598
-            line(0, -this.radius, 0, this.radius);
599
-            line(0, 0, -this.radius/2, -this.radius/2);
600
-            line(0, 0, this.radius/2, -this.radius/2);
601
-            line(0, 0, -this.radius/2, this.radius/2);
602
-            line(0, 0, this.radius/2, this.radius/2);
603
-        }
604
-        
605
-        pop();
580
+  display () {
581
+    push()
582
+    translate(this.x, this.y)
583
+    rotate(this.rotation)
584
+
585
+    if (this.type === 'branch') {
586
+      if (gamePhase === 'NIGHT') {
587
+        stroke(40, 20, 0)
588
+        fill(50, 25, 5)
589
+      } else {
590
+        stroke(101, 67, 33)
591
+        fill(139, 90, 43)
592
+      }
593
+      strokeWeight(3)
594
+
595
+      push()
596
+      strokeWeight(this.radius / 3)
597
+      line(-this.radius, 0, this.radius, 0)
598
+
599
+      strokeWeight(2)
600
+      line(-this.radius / 2, 0, -this.radius / 2 - 10, -10)
601
+      line(this.radius / 3, 0, this.radius / 3 + 8, -8)
602
+      line(0, 0, 5, -15)
603
+
604
+      stroke(80, 50, 20, 100)
605
+      strokeWeight(1)
606
+      for (let i = -this.radius; i < this.radius; i += 5) {
607
+        line(i, -2, i + 2, 2)
608
+      }
609
+      pop()
610
+
611
+      noStroke()
612
+      fill(255, 255, 255, 30)
613
+      ellipse(0, 0, this.radius * 2)
614
+    } else if (this.type === 'leaf') {
615
+      if (gamePhase === 'NIGHT') {
616
+        fill(20, 40, 20)
617
+        stroke(10, 20, 10)
618
+      } else {
619
+        fill(34, 139, 34)
620
+        stroke(25, 100, 25)
621
+      }
622
+      strokeWeight(2)
623
+
624
+      beginShape()
625
+      for (let point of this.leafPoints) {
626
+        let x = cos(point.angle) * point.radius
627
+        let y = sin(point.angle) * point.radius
628
+        curveVertex(x, y)
629
+      }
630
+      let firstPoint = this.leafPoints[0]
631
+      curveVertex(
632
+        cos(firstPoint.angle) * firstPoint.radius,
633
+        sin(firstPoint.angle) * firstPoint.radius
634
+      )
635
+      let secondPoint = this.leafPoints[1]
636
+      curveVertex(
637
+        cos(secondPoint.angle) * secondPoint.radius,
638
+        sin(secondPoint.angle) * secondPoint.radius
639
+      )
640
+      endShape()
641
+
642
+      stroke(25, 100, 25, 100)
643
+      strokeWeight(1)
644
+      line(0, -this.radius, 0, this.radius)
645
+      line(0, 0, -this.radius / 2, -this.radius / 2)
646
+      line(0, 0, this.radius / 2, -this.radius / 2)
647
+      line(0, 0, -this.radius / 2, this.radius / 2)
648
+      line(0, 0, this.radius / 2, this.radius / 2)
606649
     }
650
+
651
+    pop()
652
+  }
607653
 }
608654
 
609655
 class FoodBox {
610
-    constructor(x, y) {
611
-        this.pos = createVector(x, y);
612
-        this.radius = 10;
613
-        this.collected = false;
614
-        this.floatOffset = random(TWO_PI);
615
-        this.silkValue = random(20, 35);
616
-        this.glowPhase = random(TWO_PI);
617
-    }
618
-    
619
-    collect() {
620
-        webSilk = min(webSilk + this.silkValue, maxWebSilk);
621
-        
622
-        for (let i = 0; i < 8; i++) {
623
-            particles.push(new Particle(this.pos.x, this.pos.y));
624
-        }
625
-    }
626
-    
627
-    display() {
628
-        push();
629
-        let floatY = sin(frameCount * 0.05 + this.floatOffset) * 3;
630
-        translate(this.pos.x, this.pos.y + floatY);
631
-        
632
-        let glowIntensity = 100 + sin(frameCount * 0.1 + this.glowPhase) * 50;
633
-        noStroke();
634
-        fill(255, 200, 100, glowIntensity * 0.3);
635
-        ellipse(0, 0, 40);
636
-        fill(255, 220, 150, glowIntensity * 0.5);
637
-        ellipse(0, 0, 25);
638
-        
639
-        rectMode(CENTER);
640
-        
641
-        fill(0, 0, 0, 50);
642
-        rect(2, 2, this.radius * 2, this.radius * 1.8, 3);
643
-        
644
-        fill(139, 69, 19);
645
-        stroke(100, 50, 0);
646
-        strokeWeight(1);
647
-        rect(0, 0, this.radius * 2, this.radius * 1.8, 3);
648
-        
649
-        stroke(100, 50, 0);
650
-        strokeWeight(1);
651
-        line(-this.radius, 0, this.radius, 0);
652
-        line(0, -this.radius * 0.9, 0, this.radius * 0.9);
653
-        
654
-        noStroke();
655
-        fill(255, 200, 100);
656
-        ellipse(-5, -4, 4);
657
-        ellipse(5, -4, 3);
658
-        ellipse(-4, 5, 3);
659
-        ellipse(4, 4, 4);
660
-        
661
-        pop();
656
+  constructor (x, y) {
657
+    this.pos = createVector(x, y)
658
+    this.radius = 10
659
+    this.collected = false
660
+    this.floatOffset = random(TWO_PI)
661
+    this.silkValue = random(20, 35)
662
+    this.glowPhase = random(TWO_PI)
663
+  }
664
+
665
+  collect () {
666
+    webSilk = min(webSilk + this.silkValue, maxWebSilk)
667
+
668
+    for (let i = 0; i < 8; i++) {
669
+      particles.push(new Particle(this.pos.x, this.pos.y))
662670
     }
671
+  }
672
+
673
+  display () {
674
+    push()
675
+    let floatY = sin(frameCount * 0.05 + this.floatOffset) * 3
676
+    translate(this.pos.x, this.pos.y + floatY)
677
+
678
+    let glowIntensity = 100 + sin(frameCount * 0.1 + this.glowPhase) * 50
679
+    noStroke()
680
+    fill(255, 200, 100, glowIntensity * 0.3)
681
+    ellipse(0, 0, 40)
682
+    fill(255, 220, 150, glowIntensity * 0.5)
683
+    ellipse(0, 0, 25)
684
+
685
+    rectMode(CENTER)
686
+
687
+    fill(0, 0, 0, 50)
688
+    rect(2, 2, this.radius * 2, this.radius * 1.8, 3)
689
+
690
+    fill(139, 69, 19)
691
+    stroke(100, 50, 0)
692
+    strokeWeight(1)
693
+    rect(0, 0, this.radius * 2, this.radius * 1.8, 3)
694
+
695
+    stroke(100, 50, 0)
696
+    strokeWeight(1)
697
+    line(-this.radius, 0, this.radius, 0)
698
+    line(0, -this.radius * 0.9, 0, this.radius * 0.9)
699
+
700
+    noStroke()
701
+    fill(255, 200, 100)
702
+    ellipse(-5, -4, 4)
703
+    ellipse(5, -4, 3)
704
+    ellipse(-4, 5, 3)
705
+    ellipse(4, 4, 4)
706
+
707
+    pop()
708
+  }
663709
 }
664710
 
665711
 class Particle {
666
-    constructor(x, y) {
667
-        this.pos = createVector(x, y);
668
-        this.vel = createVector(random(-3, 3), random(-5, -2));
669
-        this.lifetime = 255;
670
-        this.color = color(255, random(200, 255), random(100, 200));
671
-        this.size = 6;  // Default size
672
-    }
673
-    
674
-    update() {
675
-        this.vel.y += 0.2;
676
-        this.pos.add(this.vel);
677
-        this.lifetime -= 8;
678
-    }
679
-    
680
-    display() {
681
-        push();
682
-        noStroke();
683
-        fill(red(this.color), green(this.color), blue(this.color), this.lifetime);
684
-        ellipse(this.pos.x, this.pos.y, this.size);
685
-        pop();
686
-    }
687
-    
688
-    isDead() {
689
-        return this.lifetime <= 0;
690
-    }
691
-}
712
+  constructor (x, y) {
713
+    this.pos = createVector(x, y)
714
+    this.vel = createVector(random(-3, 3), random(-5, -2))
715
+    this.lifetime = 255
716
+    this.color = color(255, random(200, 255), random(100, 200))
717
+    this.size = 6 // Default size
718
+  }
719
+
720
+  update () {
721
+    this.vel.y += 0.2
722
+    this.pos.add(this.vel)
723
+    this.lifetime -= 8
724
+  }
725
+
726
+  display () {
727
+    push()
728
+    noStroke()
729
+    fill(red(this.color), green(this.color), blue(this.color), this.lifetime)
730
+    ellipse(this.pos.x, this.pos.y, this.size)
731
+    pop()
732
+  }
733
+
734
+  isDead () {
735
+    return this.lifetime <= 0
736
+  }
737
+}
js/game.jsmodified
@@ -90,15 +90,18 @@ function setup() {
90
     // Place spider at the tip of the branch
90
     // Place spider at the tip of the branch
91
     let spiderStartX = branchEndX; // Place at the end/tip
91
     let spiderStartX = branchEndX; // Place at the end/tip
92
     
92
     
93
-    // At the tip (t=1), thickness is 35% of original
93
+    // The branch is drawn with a taper - at the tip it's 35% thickness
94
+    // The branch rendering uses push/translate/rotate, so we need to account for that
94
     let branchTopThickness = homeBranchThickness * 0.35;
95
     let branchTopThickness = homeBranchThickness * 0.35;
95
     
96
     
96
-    // The branch is drawn centered at branch.y, so the top is at:
97
+    // The branch is drawn centered at branch.y after rotation
98
+    // Since the rotation is small, we can approximate
97
     let branchSurfaceY = homeBranchY - branchTopThickness;
99
     let branchSurfaceY = homeBranchY - branchTopThickness;
98
     
100
     
99
-    // Add slight angle correction
101
+    // The branch rotates around (0, homeBranchY), so points further from origin rotate more
100
-    let angleCorrection = (spiderStartX - branchStartX) * window.homeBranch.angle;
102
+    // For small angles: y_rotated ≈ y + x * sin(angle) ≈ y + x * angle
101
-    branchSurfaceY += angleCorrection;
103
+    let rotationOffset = spiderStartX * window.homeBranch.angle;
104
+    branchSurfaceY += rotationOffset;
102
     
105
     
103
     // Place spider on top of the visual branch at the tip (8 is spider radius)
106
     // Place spider on top of the visual branch at the tip (8 is spider radius)
104
     spider = new Spider(spiderStartX, branchSurfaceY - 8);
107
     spider = new Spider(spiderStartX, branchSurfaceY - 8);