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;