clone

an endless marble-race generator in the browser; the url is the seed, so every track can be shared

M14a follow-up: pilot debug introspection + fuzz forensics

Dami committed 21441f9
autopilotForce gains an optional PilotDebug out-param (mode/wishspeed/
vSafe/deficit/grounded) for debug harnesses; phys-deep gains LAW=assist
A/B. 90-seed fuzz: 0 falls, 2 wedges — both PRE-EXISTING generation
band bugs (wedge identically under the assist law): slant under-crossing
wall face at channel level, island slope terrace leak. Forensics + the
tri-scan invisible-wall method recorded in progress.md; fix deferred to
a generation round (reshuffles every stream).
commit
21441f9b17c0f95d42f5788cc5bb66a143ba1a15
parent
16ac61af4b456a124b6d7ef1e642200dd5184a3b
author
Dami <dami.etoile@gmail.com>
2 files changed, +41 -0
Mprogress.md14++++++++++++++
Msrc/client/physics/autopilot.ts27+++++++++++++++++++++++++++
M progress.md +14 -0
M src/client/physics/autopilot.ts +27 -0
@@ -82,6 +82,16 @@ const AIR_HEIGHT = 0.45;
8282 const clamp = (v: number, lo: number, hi: number): number =>8383 v < lo ? lo : v > hi ? hi : v;8484 85+/** Per-step introspection (debug harnesses pass one in). */86+export interface PilotDebug {87+ mode?: string;88+ wishspeed?: number;89+ vSafe?: number;90+ deficit?: number;91+ grounded?: boolean;92+ look?: readonly [number, number, number];93+}94+8595 /**8696 * The complete autopilot step at a station. Same contract as8797 * rail.ts stationForce (drivers swap one call), plus a pilot.
@@ -94,6 +104,7 @@ export function autopilotForce(
94104 mass: number,95105 nextRail: Rail | null = null,96106 pilot: Pilot = DEFAULT_PILOT,107+ dbg?: PilotDebug,97108 ): [number, number, number] | null {98109 const t = rail.tans[idx];99110 if (!t) return null;
@@ -102,6 +113,7 @@ export function autopilotForce(
102113 // zones cancel part of gravity. (M14b adds the jump decision + air103114 // lineup on top.)104115 if (mod?.flight) {116+ if (dbg) dbg.mode = "flight";105117 if (mod.gravScale !== undefined && mod.gravScale !== 1) {106118 return [0, (1 - mod.gravScale) * -GRAVITY_Y * mass, 0];107119 }
@@ -134,6 +146,11 @@ export function autopilotForce(
134146 const look = tanAt(k);135147 const sk = sAt(k);136148 if (!look || sk === null) break;149+ // The sweep stops at a flight zone: its rail "curvature" is the priced150+ // ballistic profile diving through the gap, not floor geometry —151+ // reading it as a crest braked marbles below launch speed at jump lips152+ // (they crept off the lip and wedged in the catch pan).153+ if (modAt(k)?.flight) break;137154 const ds = sk - prevS;138155 if (ds > 1e-6) {139156 const hp = Math.hypot(prev[0], prev[2]);
@@ -201,6 +218,13 @@ export function autopilotForce(
201218 202219 const grounded = pos[1] - rail.pts[idx][1] < AIR_HEIGHT;203220 const out: [number, number, number] = [0, 0, 0];221+ if (dbg) {222+ dbg.wishspeed = wishspeed;223+ dbg.vSafe = vSafe;224+ dbg.grounded = grounded;225+ dbg.look = look;226+ dbg.mode = grounded ? "coast" : "air";227+ }204228 205229 if (grounded) {206230 // Project wishdir onto the ground plane (normal ≈ tangent-plane up)
@@ -230,6 +254,7 @@ export function autopilotForce(
230254 }231255 const vAlongT = vel[0] * t[0] + vel[1] * t[1] + vel[2] * t[2];232256 if (vAlongT > vSafe) {257+ if (dbg) dbg.mode = "brake";233258 // Decisive brake above the planned envelope — its own clamp, like234259 // MBHaxe braking vs rolling authority.235260 const a = Math.min(A_BRAKE_MAX, 3 + (vAlongT - vSafe) * 2);
@@ -239,7 +264,9 @@ export function autopilotForce(
239264 } else {240265 const vAlongW = vel[0] * wx + vel[1] * wy + vel[2] * wz;241266 const deficit = wishspeed - vAlongW;267+ if (dbg) dbg.deficit = deficit;242268 if (deficit > 0) {269+ if (dbg) dbg.mode = "accel";243270 // Icy: grip scales BOTH responsiveness and the ceiling — on ice244271 // the pilot wants the same speed and simply cannot have it.245272 const grip = mod?.icy ? 0.3 : 1;