The level now only fails on "all kids lost" once every ejected kid has
settled outside the bus (restSpeed/settleDuration hysteresis) or fallen
past the camera bounds, giving a rescue window after a jump. Also fails
when the bus itself drops below the level, and guards the compartment
clamp with a distance radius so far-away kids aren't yanked toward the
bus.
Ejection used to be g-force-triggered, but the suspension isolates the
chassis from shocks so the old threshold was effectively unreachable -
kids were never actually being ejected. Instead the bus now carries an
invisible three-sided box (floor + left/right walls, deliberately no
ceiling) pinned to the chassis with rigid constraints. The fixtures are
standalone bodies in a dedicated kid collision category, not compounded
into the chassis, because Matter only consults the root body's filter
on compounds, so only standalone bodies can be made to collide with kids
while the chassis part stays exempt.
KidManager now checks each kid's rotation-corrected chassis-local
position every physics tick and flips state in both directions: crossing
above the open top marks the kid ejected (light custom gravity,
kid_ejected texture), and drifting back down through it re-marks the kid
aboard. A per-tick hard clamp keeps kids from tunneling through the
floor/walls on a fast hit. Seat constraints and the settle timer are
gone - containment is purely physical collision. GForceMonitor is
demoted to a debug-only readout for landing diagnosis.
Kid art is now 5-frame spritesheets (one distinct kid per seat), with
the frame picked by seat index in Kid.js, and render order is fixed with
explicit depths (kids 1, bus chassis/wheels 2).
- Ejection: drop GFORCE.ejectThresholdG from 6 to 0.5 - the bouncier
suspension isolates the chassis from shock, so the old threshold was
effectively unreachable and kids were never actually being ejected.
Ejected kids now pop straight up through the roof (ignoring chassis
velocity/tilt) and fly under a lighter gravity (KID.ejectedGravityScale
0.35, applied via KidManager's beforeupdate handler) for a floaty,
Snuggle-Truck-style arc.
- Cut maxLeanAngularVelocity and leanTorqueStep in half for a slower,
more gradual lean that's easier to fine-tune mid-air.
- Parallax: far layer is now screen-fixed, scaled uniformly (no stretch),
and pans exactly once from start to goal by level progress; mid/near
layers are bottom-anchored and tiled at their own uniform scales. Kids
render at depth -1 so the bus always draws over them.
- Level builder: size camera bounds to the level's actual terrain min/max
Y (with padding, 1000px floor) instead of a fixed 1000 height, so long
runs of declines no longer push the bus below the camera's reach.
- Add level 04 "Mega Drop" (5 kids, extended terrain ending in the mega
drop sequence).
- New browser level editor (editor.html + src/editor/): assemble levels
from snap-together section generators (flat/inclines/hills/ditch/jump)
with a live canvas preview, metadata form, and export to ready-to-drop
levelNN.js source; example export registered as level04.js.
- Add parallax background sprites (bg_far/mid/near).
- Bus retune: softer, bouncier suspension (stiffness 0.45->0.35, damping
0.08->0.035, travel 12->30, restitution 0.3->0.45) with wheel rest
point raised 22->34 for a bouncier stance; drive force cut ~60%
(0.011->0.0045) for a much longer runway and lower top speed.
- Gravity 1 -> 0.675 for ~1.5x hang time off jumps; constraintIterations
2 -> 10 so the soft wishbone springs converge within one step.
- Bus: clamp each wheel to the underside of its wishbone anchor line on
every matter afterupdate, fixing wheels punching through to the
mirrored solution and sticking high on the chassis after hard impacts;
add no-op destroy() for the world listener.
- CameraRig: speed-driven horizontal lookahead (bus framed ~10% from the
left edge at rest, out to 1/3 of the screen at speed) via followOffset;
PlayScene scales the g-force grace period to 5.5s for the new gravity
and calls the new update()/destroy() hooks.
- Extend level01's terrain runway by 30x.
Throttle/brake now apply a direct, ground-only force to the chassis on
top of spinning the wheel, since friction-mediated propulsion alone
couldn't reach a satisfying top speed. isGrounded() checks the engine's
live collision pairs instead of tallying start/end events, which left a
counter permanently stuck above zero when crossing segment seams and
turned airborne throttle into a rocket thruster.
Kid.destroy() and GForceMonitor.destroy() no longer call
matter.world.removeConstraint()/off(): by the time a scene's 'shutdown'
handler runs, the Matter World plugin has already torn down and nulled
this.matter.world, throwing 'Cannot read properties of null'. Matter's
World#shutdown() clears all bodies/constraints/listeners itself anyway,
so both removals were redundant, not just unsafe.
"