Back to Pursuit PIT
SOURCE / PINNED RELEASE

Made of little things.

Pursuit PIT

Release
565f4981bdc3…
Author-recorded commit
197afdbed44b…
License
LICENSE
Author’s source reference
nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-951414e7fd7

Archive hash verified: 3b0485589942fd5c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tools/bot.ts
// Headless check of the core loop: a scripted pursuer chases the suspect and tries to PIT it.
// It also measures the "always a route" rule: the most highway lanes traffic ever blocks
// in any 50 m stretch near the chase. Run with: bun tools/bot.ts [runs]
import { LANES, ROUTE_BAND, Sim, forwardSpeed, wrapAngle, type Input } from '../src/game/sim.ts';

function bot(sim: Sim): Input {
  const p = sim.player, t = sim.suspect;
  const gap = t.s - p.s;
  // Aim beside the suspect's rear quarter, then lean into it.
  const side = p.x <= t.x ? -1 : 1;
  const close = gap < 7 && gap > 1;
  const targetX = close ? t.x + side * 1.2 : gap < 25 ? t.x + side * 2.2 : t.x;
  const wantYaw = Math.max(-0.25, Math.min(0.25, (targetX - p.x) * 0.12 - p.vx * 0.05));
  const steer = Math.max(-1, Math.min(1, (wantYaw - wrapAngle(p.yaw)) * 6));
  const closing = forwardSpeed(p) - forwardSpeed(t);
  const throttle = gap > 20 ? 1 : closing > (gap - 3) * 0.8 ? -0.4 : 1;
  return { throttle, steer };
}

const runs = Number(process.argv[2] ?? 10);
const results: Record<string, number> = {};
let worstBlock = 0, blockedSamples = 0, samples = 0, rampMerged = 0;
for (let r = 0; r < runs; r++) {
  const sim = new Sim(1);
  let pits = 0, step = 0;
  const seenRamp = new Set<number>();
  while (sim.outcome === 'running' && sim.time < 240) {
    sim.step(1 / 120, bot(sim));
    for (const e of sim.events) if (e.type === 'pit') pits++;
    sim.events.length = 0;
    for (const c of sim.cars) if (c.ramp !== null) seenRamp.add(c.id);
    if (step++ % 30 === 0) {
      for (let s = sim.player.s - 50; s < sim.player.s + 300; s += 10) {
        const n = sim.lanesUsed(s, s + ROUTE_BAND).size;
        worstBlock = Math.max(worstBlock, n);
        if (n > LANES - 2) blockedSamples++;
        samples++;
      }
    }
  }
  rampMerged += [...seenRamp].filter((id) => sim.cars.some((c) => c.id === id && c.ramp === null && c.lane < LANES)).length;
  const key = sim.time >= 240 ? 'timeout' : sim.outcome;
  results[key] = (results[key] ?? 0) + 1;
  const traffic = sim.cars.filter((c) => c.kind === 'traffic').length;
  console.log(`run ${r}: ${key} at ${sim.time.toFixed(1)}s, pits=${pits}, gap=${sim.gap.toFixed(0)}m, traffic=${traffic}, rampCars=${seenRamp.size}`);
}
console.log(results);
console.log(`worst lanes blocked in any ROUTE_BAND stretch: ${worstBlock}/${LANES}; samples over ${LANES - 2}: ${blockedSamples}/${samples}; ramp cars merged and still present: ${rampMerged}`);