Commit Graph

13 Commits

Author SHA1 Message Date
Brian Fertig 499d82d4d4 Add compartment debug overlay for kid ejection troubleshooting
Introduce `COMPARTMENT_DEBUG` config flag and a new `compartmentDebug.js` utility that renders the invisible bus compartment (floor, left/right walls, and open-top boundary) as colored rectangles tracking the chassis each frame. Includes per-kid position markers color-coded by state (aboard/ejected) and a live HUD readout showing local coordinates and ejection timing to diagnose why kids eject unexpectedly.
2026-08-22 11:47:11 -06:00
Brian Fertig ba3385ee19 Extract pill UI helper and replace text backgrounds with rounded pills
This change introduces a new `src/util/pill.js` module that provides reusable helpers for creating "pill"-style UI labels: a semi-transparent white rounded rectangle backing (with black stroke) sized to the text, plus optional interactivity. It replaces the previous pattern of using Phaser's plain rectangular `backgroundColor` on Text objects, which couldn't produce rounded corners or consistent padding/stroke styling.

Key changes:
- Add `createPill(scene, x, y, text, style, originX, originY)` returning a Container with graphics backing + text, supporting corner origins and automatic re-fitting when text updates.
- Add `pillInteractive(container, config)` to make pills clickable with a properly sized hit area (Containers lack texture frames by default).
- Update UI scenes (`MainMenuScene`, `PlayScene`, `LevelSelectScene`, `LevelScoreScene`) to use `createPill` for HUD elements, labels, score/time displays, and menu/back buttons.
- Remove per-call `backgroundColor` usage in favor of the shared pill styling constants in `pill.js`.

This centralizes pill styling and behavior, improves visual consistency (rounded corners, padding, stroke), and simplifies interaction setup across scenes.
2026-08-22 11:07:32 -06:00
Brian Fertig 6229f9529b Refactor level select into campaign map with auto-layout
Replace the flat level-grid level select screen with a campaign-based
map view. Levels are grouped into themed campaigns (e.g. "Sunny Suburbs",
"Dusk Junction") each with its own background art and node positions
rendered along a Catmull-Rom spline ribbon.

Key changes:
- Introduce CAMPAIGNS array in src/data/levels/index.js with helpers for
  campaign/level lookup.
- Add src/data/levels/positioning.js for auto-layout S-curve generation
  and Catmull-Rom spline sampling.
- Rewrite LevelSelectScene.js to render per-campaign maps with animated
  node entrance, idle pulse on the current level, hover states,
  tool-tips for locked levels, and ← → / P keyboard navigation.
- Remove the now-unused LevelCompleteScene; scores now record progress
  and return directly to the map via LevelScoreScene.
- Add map assets (campaign backgrounds, node/flag/tag sprites) and
  manifest entries with a documented 960x540 centered-canvas convention.
- Add src/util/mapArt.js for shared map geometry constants and scaling.
2026-08-21 12:41:49 -06:00
Brian Fertig 7f4c632cce feat: add LevelScoreScene with end-of-level tally and kid HUD portraits
Introduce LevelScoreScene to display a choreographed score breakdown (base score, per-kid results, time bonus) using a frozen snapshot of the win condition.
Replace generic icon_kid in the HUD with individual kid portraits that update based on each kid's specific status (aboard vs. ejected).
Extract _formatTime to a shared utility used by both PlayScene and LevelScoreScene.
Add audio assets for the scoring sequence.
2026-08-20 12:22:59 -06:00
Brian Fertig 17fe6efd10 feat: add level time limits with countdown timer
- Add `timeLimit` configuration to levels 1-4
- Implement countdown timer that starts when throttle is pressed
- Display remaining time in HUD, turning red when under 10s
- Add 'time-up' failure condition and message in LevelFailedScene
2026-08-20 11:03:20 -06:00
Brian Fertig 0c80e4774e feat: add finish line visual and kid fall voice lines
- Add checkered finish line entity with rippling flag pole (purely visual)
- Add three kid fall voice clips with random playback on kid ejection
- Add load tolerance for optional voice assets matching voiceLine.js pattern
- Preload kid fall sounds in PreloadScene and wire into KidManager
2026-08-20 11:00:31 -06:00
Brian Fertig 3b8c13fb4e feat: complete level 01, engine audio, menu music, editor enhancements
- Implement EngineSound system that pitches engine-heavy.mp3 based on rear
  wheel angular velocity, with rate smoothing to avoid stair-stepping
- Add continuous menu music across Intro → MainMenu → LevelSelect scenes
- Add per-level voice-over support (level01: "Get the Money!")
- Redesign level01 with hand-crafted terrain sections (Flat, Incline,
  Jump, Roller, Descent, Bumps, Flat, Descent, Bumps, Flat)
- Editor: per-section width and vertical scale (vScale) controls
- Editor: smooth entry blending between sections using angle-based
  interpolation (avoids slope overshoot from naive height blending)
- Editor: new size-panel UI with range/number inputs and smooth checkbox
- Fix KidManager re-boarding: ejected kids must be settled before
  re-boarding to prevent mid-air teleportation
- Load audio assets in PreloadScene; handle missing voice files gracefully
2026-08-20 10:10:19 -06:00
Brian Fertig f3e7f96220 Fix all-kids-lost failing too early; add bus-fell fail condition
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.
2026-08-19 21:40:03 -06:00
Brian Fertig c4954b2292 Replace impact-based kid ejection with open-top compartment
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).
2026-08-19 21:05:08 -06:00
Brian Fertig 9892df762c Rework ejection feel, parallax backgrounds, and camera bounds
- 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).
2026-08-19 20:58:01 -06:00
Brian Fertig 4973b22f7c Add level editor and retune physics/camera for bouncier, floatier driving
- 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.
2026-08-19 20:07:24 -06:00
Brian Fertig ffe29468d6 git commit -m "Fix throttle feel and null-reference shutdown crashes
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.
"
2026-08-18 20:55:02 -06:00
Brian Fertig 34960a2321 Initial Commit 2026-08-18 20:23:33 -06:00