From 9981c48bf69e25452eece8727e0d9dc31ea32669 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 10 Sep 2026 14:52:02 -0400 Subject: [PATCH] Pinball: close the left outlane, seal the shooter lane after launch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The ball kept escaping unsaveably down the far left. The left outlane is now closed by a solid deflector from the wall to the left flipper pivot, so a ball down that side is always fed onto the flipper. Only the centre gap between the flipper tips is a free drain now. - The shooter-lane divider runs the full height (y 120–474) so a ball in play can't fall back into the lane after launch ("close it after launch"). Plus a safety: a slow ball that does end up deep in the lane re-arms the plunger. Right wall extends down as the return chute. Sim: ball stays within x≥60, drains cleanly through the centre, game completes; no NaN. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu --- src/lib/pinball.js | 47 +++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/lib/pinball.js b/src/lib/pinball.js index cc4a987..6e070f2 100644 --- a/src/lib/pinball.js +++ b/src/lib/pinball.js @@ -44,22 +44,27 @@ const TABLE = { { a: [42, 20], b: [250, 20] }, // top { a: [250, 20], b: [300, 56] }, // top-right corner { a: [300, 56], b: [306, 96] }, - { a: [306, 96], b: [306, 474] }, // right wall / shooter-lane outer - { a: [288, 474], b: [288, 236] }, // shooter-lane divider + { a: [306, 96], b: [306, 548] }, // right wall / shooter-lane outer (continues down as the return chute) + { a: [288, 474], b: [288, 120] }, // shooter-lane divider (tall — the ball can't fall back in after launch) { a: [288, 474], b: [306, 474] }, // shooter-lane floor - { a: [14, 60], b: [14, 452] }, // left wall - { a: [14, 452], b: [40, 478] }, // left lower guide (rounds toward the flipper) - { a: [288, 474], b: [264, 492] }, // right lower guide + { a: [14, 60], b: [14, 448] }, // left wall - // left slingshot — triangle above the flipper's outer half - { a: [88, 442], b: [104, 486], e: PHYS.slingE, k: PHYS.slingKick }, // kicking face - { a: [88, 442], b: [58, 486] }, // outer face - { a: [58, 486], b: [104, 486] }, // bottom + // left outlane is CLOSED: a solid deflector from the wall to the flipper + // pivot, so a ball down the left is always fed onto the left flipper. + { a: [14, 448], b: [50, 470] }, + { a: [50, 470], b: [86, 504] }, - // right slingshot - { a: [232, 442], b: [216, 486], e: PHYS.slingE, k: PHYS.slingKick }, - { a: [232, 442], b: [262, 486] }, - { a: [216, 486], b: [262, 486] }, + // right side stays open below the divider so a ball can trickle to the + // return; a short guide nudges it that way. + { a: [288, 474], b: [262, 494] }, + + // slingshots (triangles above each flipper's outer half) + { a: [96, 434], b: [110, 476], e: PHYS.slingE, k: PHYS.slingKick }, // L kicking face + { a: [96, 434], b: [76, 476] }, + { a: [76, 476], b: [110, 476] }, + { a: [230, 434], b: [214, 476], e: PHYS.slingE, k: PHYS.slingKick }, // R kicking face + { a: [230, 434], b: [250, 476] }, + { a: [214, 476], b: [250, 476] }, ], bumpers: [ { x: 80, y: 132, r: 15 }, @@ -210,6 +215,10 @@ export function createPinball(canvas, cbs = {}) { b.vx *= 0.6; b.x += (TABLE.laneX - b.x) * 0.35; } + // Re-arm the plunger if a ball ends up sitting in the lane again. + if (!b.inLane && !b.launching && b.x > 292 && b.y > 430 && Math.hypot(b.vx, b.vy) < 60) { + b.inLane = true; + } // Gate kick: once the launched ball clears the divider, fling it left // into the playfield (a straight-up launch has nowhere else to go). if (b.launching && b.y < 236) { @@ -472,15 +481,15 @@ export function createPinball(canvas, cbs = {}) { }; ctx.fillStyle = accentA(0.22); tri([ - [88, 442], - [104, 486], - [58, 486], + [96, 434], + [110, 476], + [76, 476], ]); ctx.fill(); tri([ - [232, 442], - [216, 486], - [262, 486], + [230, 434], + [214, 476], + [250, 476], ]); ctx.fill();