1// Headless deep-drive: run the real wasm marble over 8 segments with the2// SHARED rail law (src/client/physics/rail.ts — one law, one truth with the3// client and tests); prints FELL/WEDGED with rail context.4// Usage: deno run --allow-read tools/physics-debug/phys-deep.ts <seed>5import { EndlessWorld } from "../../src/gen/endless/world.ts";6import {7 bumperProxies,8 channelProxies,9 islandProxies,10 wideScalesOf,11} from "../../src/gen/endless/proxies.ts";12import {13 PhysicsEngine,14 type PhysicsModule,15} from "../../src/client/physics/engine.ts";16import {17 concatRails,18 gapSegmentsOf,19 railForSegment,20 stationForce,21 stationGravityDelta,22 stationRestitution,23} from "../../src/client/physics/rail.ts";24import {25 autopilotForce,26 boostStep,27 FULL_TANK,28 type PilotDebug,29 pilotFor,30 pilotOffset,31 wantJump,32} from "../../src/client/physics/autopilot.ts";3334// LAW=assist runs the old choreographer for A/B; default is the autopilot.35const law = Deno.env.get("LAW") === "assist" ? stationForce : autopilotForce;3637const catalog = {38 packs: [{39 slug: "nature-kit",40 ids: JSON.parse(41 Deno.readTextFileSync(42 new URL("../../assets/nature-kit.manifest.json", import.meta.url),43 ),44 ).pieces.map((p: { id: string }) => p.id),45 }],46};47const m = await ((await import(48 new URL("../../static/dist/box3d.mjs", import.meta.url).href49)).default as () => Promise<PhysicsModule>)();50const w = new EndlessWorld(Deno.args[0] ?? "cua-seed-1", catalog);51const engine = new PhysicsEngine(m);52const rails = [];53for (let i = 0; i < 8; i++) {54 const seg = w.get(i);55 engine.addSegment(i, [56 ...channelProxies(seg.path, true, gapSegmentsOf(seg), wideScalesOf(seg)),57 ...seg.islands.flatMap((x) => islandProxies(x.placements)),58 ...bumperProxies(seg.placements),59 ]);60 rails.push(railForSegment(seg));61}62const rail = concatRails(rails);63engine.spawnMarble(64 0,65 [rail.pts[0][0], rail.pts[0][1] + 0.4, rail.pts[0][2]],66 [rail.tans[0][0] * 2.2, 0, rail.tans[0][2] * 2.2],67);68let nearest = 0;69let lastS = 0, stall = 0, fell = false;70const pilot = pilotFor(Deno.args[0] ?? "cua-seed-1");71let simT = 0;72let boost = { ...FULL_TANK };73const dbg: PilotDebug = {};74for (let step = 0; step < 240 * 60; step++) {75 engine.step(1 / 60, () => {76 const p = engine.state().pos;77 let bd = Infinity;78 for (79 let i = Math.max(0, nearest - 25);80 i <= Math.min(rail.pts.length - 1, nearest + 50);81 i++82 ) {83 const r = rail.pts[i];84 const d = (r[0] - p[0]) ** 2 + (r[1] - p[1]) ** 2 + (r[2] - p[2]) ** 2;85 if (d < bd) {86 bd = d;87 nearest = i;88 }89 }90 const st = engine.state();91 engine.setRestitution(0, stationRestitution(rail, nearest));92 const gd = stationGravityDelta(rail, nearest, engine.mass);93 let f: [number, number, number] | null;94 if (law === autopilotForce) {95 simT += 1 / 60;96 pilot.offset = pilotOffset(pilot, simT);97 const headroom = dbg.vSafe !== undefined && dbg.wishspeed !== undefined98 ? dbg.vSafe - dbg.wishspeed99 : null;100 boost = boostStep(boost, pilot, 1 / 60, headroom, null, null);101 f = autopilotForce(102 rail,103 nearest,104 st.pos,105 st.vel,106 engine.mass,107 null,108 pilot,109 dbg,110 undefined,111 boost.boosting,112 );113 const j = wantJump(rail, nearest, st.pos, st.vel, null, pilot);114 if (j) engine.jump(0, j[0], j[1]);115 } else {116 f = law(rail, nearest, st.pos, st.vel, engine.mass);117 }118 const fx = (f?.[0] ?? 0) + (gd?.[0] ?? 0);119 const fy = (f?.[1] ?? 0) + (gd?.[1] ?? 0);120 const fz = (f?.[2] ?? 0) + (gd?.[2] ?? 0);121 if (fx !== 0 || fy !== 0 || fz !== 0) engine.force(0, fx, fy, fz);122 });123 {124 const stq = engine.state();125 if (!fell && stq.pos[1] < rail.pts[nearest][1] - 6) {126 fell = true;127 console.log(128 `FELL at s=${rail.s[nearest].toFixed(1)} pos=(${129 stq.pos.map((v) => v.toFixed(2))130 }) ` +131 `rail=(${rail.pts[nearest].map((v) => v.toFixed(2))}) tan=(${132 rail.tans[nearest].map((v) => v.toFixed(2))133 }) ` +134 `mod=${JSON.stringify(rail.mods[nearest])} vel=(${135 stq.vel.map((v) => v.toFixed(2))136 })`,137 );138 }139 }140 const s = rail.s[nearest];141 if (s > lastS + 0.4) {142 lastS = s;143 stall = 0;144 } else stall++;145 if (stall > 15 * 60) {146 const st = engine.state();147 console.log(148 `WEDGED s=${s.toFixed(1)} pos=(${st.pos.map((v) => v.toFixed(4))}) ` +149 `rail=(${rail.pts[nearest].map((v) => v.toFixed(2))}) tan=(${150 rail.tans[nearest].map((v) => v.toFixed(2))151 }) mod=${JSON.stringify(rail.mods[nearest])}`,152 );153 break;154 }155}156console.log(157 "final s:",158 rail.s[nearest].toFixed(1),159 "/",160 rail.s[rail.s.length - 1].toFixed(1),161);