@@ -242,76 +242,64 @@ class Spider { |
| 242 | 242 | |
| 243 | 243 | // Check home branch collision (one-way platform) |
| 244 | 244 | if (window.homeBranch && this.isAirborne && this.vel.y > 0.1) { |
| 245 | | - // Only when actually falling |
| 246 | 245 | let branch = window.homeBranch |
| 247 | 246 | |
| 248 | | - // Check if spider is within branch X range |
| 249 | | - let branchStart = Math.min(branch.startX, branch.endX) |
| 250 | | - let branchEnd = Math.max(branch.startX, branch.endX) |
| 247 | + // Calculate the actual geometric bounds |
| 248 | + let leftX = Math.min(branch.startX, branch.endX) |
| 249 | + let rightX = Math.max(branch.startX, branch.endX) |
| 251 | 250 | |
| 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 |
| 251 | + // IMPORTANT: Extend the check zone beyond the mathematical end |
| 252 | + // The branch visually extends past endX due to strokeWeight and bezier curves |
| 253 | + let checkPadding = 20 // Add padding for the visual overhang |
| 255 | 254 | |
| 256 | 255 | if ( |
| 257 | | - this.pos.x >= branchStart - 10 && |
| 258 | | - this.pos.x <= branchEnd + collisionPadding |
| 256 | + this.pos.x >= leftX - checkPadding && |
| 257 | + this.pos.x <= rightX + checkPadding |
| 259 | 258 | ) { |
| 260 | | - // Calculate position along branch (0 to 1) |
| 261 | | - // FIX: Clamp t to ensure we handle tip properly |
| 262 | | - let t = (this.pos.x - branchStart) / (branchEnd - branchStart) |
| 259 | + // Calculate normalized position along branch |
| 260 | + let t |
| 263 | 261 | |
| 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 |
| 262 | + if (branch.side === 'left') { |
| 263 | + // Left branch: startX is base, endX is tip |
| 264 | + t = (this.pos.x - branch.startX) / (branch.endX - branch.startX) |
| 267 | 265 | } else { |
| 268 | | - t = constrain(t, 0, 1) |
| 266 | + // Right branch: startX is base, endX is tip (but startX > endX) |
| 267 | + t = (branch.startX - this.pos.x) / (branch.startX - branch.endX) |
| 269 | 268 | } |
| 270 | 269 | |
| 271 | | - // Branch visual thickness tapers from full at start to 35% at end |
| 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 | | - } |
| 283 | | - |
| 284 | | - // The branch is drawn centered at branch.y |
| 285 | | - let branchSurfaceY = branch.y - branchTopThickness |
| 270 | + // CRITICAL FIX: Allow t to exceed 1 for the tip overhang |
| 271 | + // The visual branch extends past the mathematical endpoint |
| 272 | + let maxT = 1.15 // Allow 15% overshoot for visual overhang |
| 273 | + t = constrain(t, 0, maxT) |
| 286 | 274 | |
| 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) |
| 275 | + // Calculate thickness |
| 276 | + let visualThickness |
| 277 | + if (t > 1.0) { |
| 278 | + // Past the mathematical end - use minimum tip thickness |
| 279 | + visualThickness = branch.thickness * 0.35 |
| 293 | 280 | } 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) |
| 281 | + // Normal taper |
| 282 | + visualThickness = lerp(branch.thickness, branch.thickness * 0.35, t) |
| 297 | 283 | } |
| 298 | | - branchSurfaceY += angleCorrection |
| 299 | 284 | |
| 300 | | - // Check if spider is crossing the branch from above |
| 301 | | - let prevY = this.pos.y - this.vel.y |
| 285 | + // The branch is drawn centered at branch.y; compute top/bottom with rotation |
| 286 | + let rotationOffset = this.pos.x * branch.angle |
| 287 | + let branchTopY = (branch.y - visualThickness) + rotationOffset |
| 288 | + let branchBottomY = (branch.y + visualThickness) + rotationOffset |
| 302 | 289 | |
| 303 | | - // FIX: More generous collision detection at the tip |
| 304 | | - let collisionBuffer = t >= 0.9 ? 5 : 0 // Extra buffer near tip |
| 290 | + // Check collision |
| 291 | + let prevY = this.pos.y - this.vel.y |
| 305 | 292 | |
| 293 | + // One-way platform collision |
| 306 | 294 | if ( |
| 307 | | - prevY <= branchSurfaceY + collisionBuffer && // Was above (with buffer) |
| 308 | | - this.pos.y + this.radius >= branchSurfaceY && // Now at or below |
| 309 | | - this.pos.y < branch.y + branch.thickness |
| 295 | + prevY <= branchTopY && // Was above |
| 296 | + this.pos.y + this.radius >= branchTopY && // Now at or below |
| 297 | + this.pos.y - this.radius < branchBottomY // Not completely below the rotated bottom |
| 310 | 298 | ) { |
| 311 | | - // Not too far below |
| 299 | + // Not completely below |
| 312 | 300 | |
| 313 | | - // Place spider on the branch surface |
| 314 | | - this.pos.y = branchSurfaceY - this.radius |
| 301 | + // Land on branch |
| 302 | + this.pos.y = branchTopY - this.radius |
| 315 | 303 | this.land() |
| 316 | 304 | this.attachedObstacle = null |
| 317 | 305 | } |
@@ -2199,7 +2187,7 @@ class Bird { |
| 2199 | 2187 | |
| 2200 | 2188 | // If spider has no stamina, GAME OVER! |
| 2201 | 2189 | if (jumpStamina <= 0) { |
| 2202 | | - triggerGameOver('Exhausted spider caught by bird!') |
| 2190 | + triggerGameOver('Oof') |
| 2203 | 2191 | return |
| 2204 | 2192 | } |
| 2205 | 2193 | |