// Every spatial (pixel) constant in this file and in src/data/levels/*.js is // multiplied by WORLD_SCALE, so the whole game - canvas, physics bodies, // terrain, UI - resizes together. Change this one number to rescale it again. export const WORLD_SCALE = 2; // 960x540 base design -> 1920x1080 export const GAME_WIDTH = 960 * WORLD_SCALE; export const GAME_HEIGHT = 540 * WORLD_SCALE; // Set true to show live g-force / debug readouts during PlayScene. export const DEBUG = false; export const STORAGE_KEY = 'monsterplex.progress.v1'; // Matter runs Phaser's default fixed 60Hz step. export const PHYSICS_TIMESTEP_SECONDS = 1 / 60; export const BUS = { chassisWidth: 170 * WORLD_SCALE, chassisHeight: 64 * WORLD_SCALE, // Measured directly from assets/sprites/bus_chassis.png's baked-in wheel // wells (it's a real photo bus - long rear overhang, short front hood, so // the axles are NOT symmetric around the chassis center). Offsets are from // the chassis center; re-measure these if bus_chassis.png is replaced. wheelRadius: 17 * WORLD_SCALE, rearWheelOffsetX: -35 * WORLD_SCALE, frontWheelOffsetX: 64 * WORLD_SCALE, // Where the wheel's CENTER rests at equilibrium. Raised from the // wheel-well-measured 22 to 34 to lift the chassis and drop the tires // further down for a bouncier stance - this now visibly departs from // bus_chassis.png's painted wheel wells (a gap opens above the tire), // a deliberate look/feel tradeoff rather than a measurement. wheelRestOffsetY: 34 * WORLD_SCALE, // Horizontal spread between the two suspension links each wheel hangs // from (a "wishbone") - a single point-to-point constraint can't resist // sideways drift at all (it's radially symmetric), so each wheel is held // by two constraints spread by this much, which resists side-to-side // wander while still allowing the wheel to compress/extend vertically. axleSpread: 6 * WORLD_SCALE, maxSeats: 5, // The invisible box kids ride in, local to the chassis (0,0 = chassis // center, y down): solid floor + left/right walls, but deliberately no // ceiling fixture at all - see Bus.js's _buildCompartment. A kid whose // own local Y (rotation-corrected relative to the chassis) rises above // compartmentTopY has nothing left to collide with and is airborne; the // same check runs both directions each tick (see KidManager), so falling // back below it while inside compartmentHalfWidth re-contains the kid. // Tune by playtesting - not physically derived. compartmentHalfWidth: 75 * WORLD_SCALE, compartmentWallThickness: 10 * WORLD_SCALE, compartmentFloorThickness: 10 * WORLD_SCALE, compartmentFloorY: 12 * WORLD_SCALE, compartmentTopY: -60 * WORLD_SCALE, // Density/friction/stiffness/damping are dimensionless ratios, not pixel // distances - they don't scale with WORLD_SCALE. chassisDensity: 0.0015, chassisFriction: 0.4, chassisFrictionAir: 0.01, wheelDensity: 0.004, wheelFriction: 0.9, wheelFrictionStatic: 1.4, // Lowered a lot from the original 0.02 - that was quietly eating most of // the wheel's spin-up each second (it's a per-tick multiplicative decay, // so 0.02 was burning off roughly a third of angular velocity per second) // and was the main reason the bus felt sluggish rather than Trials-fast. wheelFrictionAir: 0.008, // Some bounce on landing, not just suspension absorbing everything. wheelRestitution: 0.45, // Softer and less damped than before - more travel, and lets it actually // oscillate (bounce) instead of settling immediately - "more shocks." suspensionStiffness: 0.35, suspensionDamping: 0.035, // How far the wheel can move up/down from wheelRestOffsetY. This will // visibly pop the wheel outside its wheel well on a hard landing - that's // the tradeoff for real suspension travel/bounce instead of a stiff ride. suspensionTravel: 30 * WORLD_SCALE, // Angular velocities/torque steps are rotational (radians), not linear // pixel distances, so they're left unscaled - wheel radius already scaled, // so linear surface speed (angularVelocity * radius) scales automatically. // Pushed way up from the original values for Trials/Snuggle-Truck-style // speed - the old numbers made the bus crawl. maxWheelAngularVelocity: 5, driveTorque: 0.6, brakeTorque: 0.4, maxBrakeAngularVelocity: 3.5, // Measured via headless testing: spinning the wheel faster alone barely // moved the chassis - past a point the wheel just slips (friction force // is capped by frictionCoefficient * normalForce regardless of how fast // the wheel spins beyond matching ground speed), so throttle applies a // direct force to the chassis itself, along its current facing/tilt, on // top of the wheel spin. This is the standard fix in arcade 2D driving // games for exactly this problem. Expressed as target acceleration // (Bus.js computes force = chassis mass * this) rather than a raw force, // so it stays correct if chassisDensity/size ever changes - tune these // to change top speed feel. // Cut by ~60% (0.011 -> 0.0045) for a much longer runway to top speed. // Note this also lowers the top speed itself, not just the time to reach // it - top speed is where this force balances chassisFrictionAir drag, so // a smaller force settles at a lower equilibrium speed too. driveAcceleration: 0.0045, brakeAcceleration: 0.007, // Cut by more than half (both the cap and the per-tick torque step) for a // much slower, more gradual lean - easier to fine-tune mid-air at the // cost of snappiness. maxLeanAngularVelocity: 0.06, leanTorqueStep: 0.004, }; // Kid ejection is no longer impact-triggered (see KidManager/Bus's // compartment box) - GFORCE now only drives the DEBUG g-force readout in // PlayScene, kept because it's still a useful "how hard did that landing // hit" diagnostic on its own. export const GFORCE = { // Scaled with WORLD_SCALE so "meters" keeps the same real-world meaning // relative to the bus. pixelsPerMeter: 50 * WORLD_SCALE, gracePeriodMs: 500, }; export const KID = { radius: 14 * WORLD_SCALE, density: 0.001, friction: 0.6, // While outside the compartment (past the open top - see // BUS.compartmentTopY), a kid ignores the world's real gravity entirely // and KidManager applies this fraction of it instead (see // KidManager._onBeforeUpdate) - horizontal velocity is untouched either // way (this game's gravity is purely vertical), so a lighter fall is all // it takes for a kid to hang in the air and arc much further than the // bus does, Snuggle-Truck style. Primary tuning knob for "how floaty," // not physically derived. ejectedGravityScale: 0.35, }; export const CAMERA = { lerpX: 0.1, lerpY: 0.1, // Horizontal position is speed-driven (see CameraRig.update - it steers // via followOffset.x) rather than a dead zone, so this is kept tiny // rather than removed, just to dodge exact-equality edge cases in // Phaser's deadzone math. Vertical still uses a real dead zone below. deadzoneWidth: 2 * WORLD_SCALE, deadzoneHeight: 140 * WORLD_SCALE, // Where the bus sits on screen, as a fraction of screen width from the // left edge - primary tuning knobs, not physically derived, tune by // playtesting. restScreenFraction leaves enough room for the chassis // (half its width is ~0.09 of the screen) not to clip off the left edge // while still reading as "almost touching." restScreenFraction: 0.1, maxSpeedScreenFraction: 1 / 3, // Horizontal chassis speed (px/s) at which framing reaches // maxSpeedScreenFraction; clamped beyond it. There's no hard speed cap in // the drive physics (chassisFrictionAir just balances driveAcceleration // out asymptotically), so this is a starting estimate - tune by // playtesting alongside the fractions above. lookaheadMaxSpeed: 110 * WORLD_SCALE, }; export const TERRAIN_DEPTH = 400 * WORLD_SCALE;