From f88b38a49cf29201e5fa0d34e59e8a3ef8f0bf4a Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Sat, 22 Aug 2026 12:15:52 -0600 Subject: [PATCH] Fix kid teleportation bug by adding horizontal span guard to compartment clamp The previous clamp logic only bounded localY (vertical) after a radius check, which couldn't distinguish between: 1. A kid that tunneled through the floor/wall (should be clamped back) 2. A kid resting on nearby ground below the bus chassis (should NOT be clamped) Since the bus floats ~chassis-height above ground, an outside kid sitting on the ground would have localY below the floor line and get yanked up into the bus every tick until it exited compartmentClampRadius. Added a horizontal span check that only clamps kids whose |localX| is within the actual compartment extent (half-width + wall thickness + kid radius). This ensures: - Kids that tunneled through floor/walls are still corrected (they're inside the span when they cross it) - Kids outside the bus's horizontal footprint are left alone, preventing the "ejected kid teleported to bus" bug Also disabled COMPARTMENT_DEBUG mode in config.js. --- src/config.js | 2 +- src/systems/KidManager.js | 36 ++++++++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/config.js b/src/config.js index 9477555..0f7cf65 100644 --- a/src/config.js +++ b/src/config.js @@ -13,7 +13,7 @@ export const DEBUG = false; // walls) as simple colored rectangles that track the chassis, plus a live // per-kid local-position/state readout in the PlayScene HUD. Troubleshooting // aid for ejection behavior - off during normal play. -export const COMPARTMENT_DEBUG = true; +export const COMPARTMENT_DEBUG = false; export const STORAGE_KEY = 'monsterplex.progress.v1'; diff --git a/src/systems/KidManager.js b/src/systems/KidManager.js index 8de5c73..ec16bd0 100644 --- a/src/systems/KidManager.js +++ b/src/systems/KidManager.js @@ -162,21 +162,45 @@ export default class KidManager { // technique as Bus.js's wheel clamp). Never clamps the top - that's the // one side deliberately left open (the compartment has no ceiling, see // Bus.js/_buildCompartment and config.js). + // + // Only a kid actually within the compartment's horizontal span is + // clamped at all - the radius pre-check below (distance to the chassis + // center) alone can't tell "just tunneled through the floor" apart from + // "resting on the ground somewhere else in the level at roughly the + // bus's height": the chassis floats ~a chassis-height above the ground, + // so a kid sitting on nearby ground ends up with localY BELOW the floor + // line and would be yanked up into the bus every tick until the bus + // drove out of compartmentClampRadius (the "ejected kid teleported to + // the bus" bug). A kid that tunneled through the floor/wall is, by + // definition, inside the span when it crossed it, so bounding BOTH axes + // keeps the tunnel guard working while leaving outside kids alone. _clampToCompartment(kid, chassis, cos, sin) { const dx = kid.image.x - chassis.x; const dy = kid.image.y - chassis.y; // A kid genuinely outside compartmentClampRadius was never inside the - // compartment this tick - skip it entirely, rather than let the below - // checks (which only bound ONE axis each) treat "far away in the other - // axis" as a boundary violation. Without this, a kid resting anywhere - // else in the level could get yanked toward wherever the bus currently - // is, since local-frame coordinates alone don't distinguish "just - // tunneled through" from "elsewhere entirely." + // compartment this tick - a cheap early-out that also bounds the + // span check below (a local-frame coordinate alone can't tell "far + // away in the other axis" apart from "at the boundary", so the radius + // check is still needed even with the span bound). if (dx * dx + dy * dy > BUS.compartmentClampRadius * BUS.compartmentClampRadius) return; let localX = dx * cos + dy * sin; let localY = -dx * sin + dy * cos; + + // The actual span guard: a kid is only clamped while its circle can + // still overlap a compartment fixture - the floor reaches + // +/-compartmentHalfWidth, the walls out to + // +/-compartmentHalfWidth + wallThickness/2 - expanded by the kid's + // radius, beyond which it overlaps neither and so has nothing to + // tunnel back out of. A kid that tunneled through the floor/wall is, + // by definition, inside that extent when it crossed it, so the tunnel + // guard keeps working while a kid resting on the ground outside the + // bus (localY below the floor line but |localX| past this) is left + // alone instead of yanked up into the bus every tick. + const spanExtent = BUS.compartmentHalfWidth + BUS.compartmentWallThickness / 2 + KID.radius; + if (Math.abs(localX) > spanExtent) return; + let hitFloor = false; let hitLeftWall = false; let hitRightWall = false;