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.
This commit is contained in:
parent
2e4b67db89
commit
499d82d4d4
|
|
@ -9,6 +9,12 @@ export const GAME_HEIGHT = 540 * WORLD_SCALE;
|
||||||
// Set true to show live g-force / debug readouts during PlayScene.
|
// Set true to show live g-force / debug readouts during PlayScene.
|
||||||
export const DEBUG = false;
|
export const DEBUG = false;
|
||||||
|
|
||||||
|
// Set true to render the invisible kid compartment (floor + left/right
|
||||||
|
// 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 STORAGE_KEY = 'monsterplex.progress.v1';
|
export const STORAGE_KEY = 'monsterplex.progress.v1';
|
||||||
|
|
||||||
// Matter runs Phaser's default fixed 60Hz step.
|
// Matter runs Phaser's default fixed 60Hz step.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import Phaser from 'phaser';
|
import Phaser from 'phaser';
|
||||||
import { GAME_WIDTH, GAME_HEIGHT, DEBUG, WORLD_SCALE } from '../config.js';
|
import { GAME_WIDTH, GAME_HEIGHT, DEBUG, WORLD_SCALE, COMPARTMENT_DEBUG } from '../config.js';
|
||||||
import { getLevelById } from '../data/levels/index.js';
|
import { getLevelById } from '../data/levels/index.js';
|
||||||
import Bus from '../entities/Bus.js';
|
import Bus from '../entities/Bus.js';
|
||||||
import Terrain from '../entities/Terrain.js';
|
import Terrain from '../entities/Terrain.js';
|
||||||
|
|
@ -13,6 +13,7 @@ import { stopMenuMusic } from '../util/music.js';
|
||||||
import { playLevelVoiceLine } from '../util/voiceLine.js';
|
import { playLevelVoiceLine } from '../util/voiceLine.js';
|
||||||
import { formatTime } from '../util/formatTime.js';
|
import { formatTime } from '../util/formatTime.js';
|
||||||
import { createPill } from '../util/pill.js';
|
import { createPill } from '../util/pill.js';
|
||||||
|
import buildCompartmentDebug from '../util/compartmentDebug.js';
|
||||||
|
|
||||||
export default class PlayScene extends Phaser.Scene {
|
export default class PlayScene extends Phaser.Scene {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -41,6 +42,11 @@ export default class PlayScene extends Phaser.Scene {
|
||||||
|
|
||||||
this.kidManager = new KidManager(this, this.bus, this.level.kidsAboard);
|
this.kidManager = new KidManager(this, this.bus, this.level.kidsAboard);
|
||||||
|
|
||||||
|
// Troubleshooting overlay: renders the invisible compartment fixtures
|
||||||
|
// (floor + walls) as simple rectangles tracking the bus, plus per-kid
|
||||||
|
// state readout. Off by default - toggle COMPARTMENT_DEBUG in config.js.
|
||||||
|
this.compartmentDebug = COMPARTMENT_DEBUG ? buildCompartmentDebug(this, this.bus, this.kidManager) : null;
|
||||||
|
|
||||||
this.inputController = new InputController(this);
|
this.inputController = new InputController(this);
|
||||||
|
|
||||||
this.engineSound = new EngineSound(this, this.bus);
|
this.engineSound = new EngineSound(this, this.bus);
|
||||||
|
|
@ -320,6 +326,7 @@ export default class PlayScene extends Phaser.Scene {
|
||||||
// Matter.World#shutdown() already calls removeAllListeners() on itself
|
// Matter.World#shutdown() already calls removeAllListeners() on itself
|
||||||
// internally (verified against the vendored source), so this was always
|
// internally (verified against the vendored source), so this was always
|
||||||
// redundant, not just now-unsafe.
|
// redundant, not just now-unsafe.
|
||||||
|
if (this.compartmentDebug) this.compartmentDebug.destroy();
|
||||||
if (this.gForceMonitor) this.gForceMonitor.destroy();
|
if (this.gForceMonitor) this.gForceMonitor.destroy();
|
||||||
if (this.kidManager) this.kidManager.destroy();
|
if (this.kidManager) this.kidManager.destroy();
|
||||||
if (this.engineSound) this.engineSound.destroy();
|
if (this.engineSound) this.engineSound.destroy();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
import Phaser from 'phaser';
|
||||||
|
import { BUS, KID, WORLD_SCALE } from '../config.js';
|
||||||
|
|
||||||
|
// Debug-only visualisation of the invisible kid compartment (see
|
||||||
|
// Bus._buildCompartment): one simple colored rectangle per fixture
|
||||||
|
// (floor + left wall + right wall) tracking the chassis every frame, plus
|
||||||
|
// the open-top line and one marker per kid showing its state. Used for
|
||||||
|
// troubleshooting ejection behavior (does the kid visually leave through
|
||||||
|
// the open top before KidManager latches it? where does it sit relative to
|
||||||
|
// the floor/walls while aboard?).
|
||||||
|
//
|
||||||
|
// Rectangles are plain Phaser.GameObjects.Rectangle (rendering only - they
|
||||||
|
// are NOT physics bodies, deliberately, so they can't interfere with the
|
||||||
|
// very collision behavior being observed).
|
||||||
|
export default function buildCompartmentDebug(scene, bus, kidManager) {
|
||||||
|
const depth = 5;
|
||||||
|
|
||||||
|
const mkRect = (w, h, color, alpha) =>
|
||||||
|
scene.add.rectangle(0, 0, w, h, color, alpha).setOrigin(0.5).setDepth(depth);
|
||||||
|
|
||||||
|
const floor = mkRect(BUS.compartmentHalfWidth * 2, BUS.compartmentFloorThickness, 0x00e676, 0.75);
|
||||||
|
const leftWall = mkRect(BUS.compartmentWallThickness, (BUS.compartmentFloorY - BUS.compartmentTopY) + BUS.compartmentFloorThickness, 0x40c4ff, 0.75);
|
||||||
|
const rightWall = mkRect(BUS.compartmentWallThickness, (BUS.compartmentFloorY - BUS.compartmentTopY) + BUS.compartmentFloorThickness, 0xff5252, 0.75);
|
||||||
|
|
||||||
|
// The open-top boundary (BUS.compartmentTopY) is where the aboard/ejected
|
||||||
|
// transition happens - drawn as a thin dashed-style line so "crossing
|
||||||
|
// this line" is visible in gameplay.
|
||||||
|
const topLine = mkRect(BUS.compartmentHalfWidth * 2 + BUS.compartmentWallThickness * 2, 2 * WORLD_SCALE, 0xffeb3b, 0.9);
|
||||||
|
|
||||||
|
// One marker per kid: small rectangle at the kid's position, color-coded
|
||||||
|
// by state (green = aboard, red = ejected).
|
||||||
|
const kidMarkers = kidManager.kids.map((kid) => mkRect(KID.radius * 2 + 4 * WORLD_SCALE, KID.radius * 2 + 4 * WORLD_SCALE, 0xffffff, 0.0));
|
||||||
|
|
||||||
|
// HUD readout, same style as the other pills.
|
||||||
|
const label = scene.add.text(24 * WORLD_SCALE, 110 * WORLD_SCALE, '', {
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: `${13 * WORLD_SCALE}px`,
|
||||||
|
color: '#1a1f29',
|
||||||
|
}).setScrollFactor(0).setDepth(10);
|
||||||
|
|
||||||
|
const shapes = [floor, leftWall, rightWall, topLine];
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
const chassis = bus.chassis;
|
||||||
|
const cos = Math.cos(chassis.rotation);
|
||||||
|
const sin = Math.sin(chassis.rotation);
|
||||||
|
const toWorld = (localX, localY) => ({
|
||||||
|
x: chassis.x + localX * cos - localY * sin,
|
||||||
|
y: chassis.y + localX * sin + localY * cos,
|
||||||
|
});
|
||||||
|
|
||||||
|
const f = toWorld(0, BUS.compartmentFloorY);
|
||||||
|
floor.setPosition(f.x, f.y);
|
||||||
|
floor.setAngle(chassis.rotation);
|
||||||
|
|
||||||
|
const wallHeight = (BUS.compartmentFloorY - BUS.compartmentTopY) + BUS.compartmentFloorThickness;
|
||||||
|
const wallCenterY = (BUS.compartmentFloorY + BUS.compartmentTopY) / 2;
|
||||||
|
const l = toWorld(-BUS.compartmentHalfWidth, wallCenterY);
|
||||||
|
leftWall.setPosition(l.x, l.y);
|
||||||
|
leftWall.setAngle(chassis.rotation);
|
||||||
|
const r = toWorld(BUS.compartmentHalfWidth, wallCenterY);
|
||||||
|
rightWall.setPosition(r.x, r.y);
|
||||||
|
rightWall.setAngle(chassis.rotation);
|
||||||
|
|
||||||
|
const t = toWorld(0, BUS.compartmentTopY);
|
||||||
|
topLine.setPosition(t.x, t.y);
|
||||||
|
topLine.setAngle(chassis.rotation);
|
||||||
|
|
||||||
|
let lines = [`floor: x${-BUS.compartmentHalfWidth}..${BUS.compartmentHalfWidth} y=${BUS.compartmentFloorY}`];
|
||||||
|
lines.push(`walls: x=${-BUS.compartmentHalfWidth} / x=${BUS.compartmentHalfWidth} top(open): y=${BUS.compartmentTopY}`);
|
||||||
|
for (let i = 0; i < kidManager.kids.length; i++) {
|
||||||
|
const kid = kidManager.kids[i];
|
||||||
|
const dx = kid.image.x - chassis.x;
|
||||||
|
const dy = kid.image.y - chassis.y;
|
||||||
|
const localX = dx * cos + dy * sin;
|
||||||
|
const localY = -dx * sin + dy * cos;
|
||||||
|
const marker = kidMarkers[i];
|
||||||
|
marker.setPosition(kid.image.x, kid.image.y);
|
||||||
|
marker.setFillStyle(kid.state === 'aboard' ? 0x00e676 : 0xff5252, 0.9);
|
||||||
|
marker.setSize(KID.radius * 2 + 4 * WORLD_SCALE, KID.radius * 2 + 4 * WORLD_SCALE);
|
||||||
|
lines.push(`kid${i}: ${kid.state} local=(${localX.toFixed(0)}, ${localY.toFixed(0)})${kid.state === 'ejected' && kid.ejectedAt !== null ? ` outFor=${((scene.time.now - kid.ejectedAt) / 1000).toFixed(1)}s` : ''}`);
|
||||||
|
}
|
||||||
|
label.setText(lines.join('\n'), { align: 'left' });
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.events.on('update', update);
|
||||||
|
scene.events.once('shutdown', () => {
|
||||||
|
scene.events.off('update', update);
|
||||||
|
for (const s of shapes) s.destroy();
|
||||||
|
for (const m of kidMarkers) m.destroy();
|
||||||
|
label.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
return { shapes, kidMarkers, label, destroy: () => { shapes.concat(kidMarkers).concat([label]).forEach((s) => s.destroy()); } };
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue