Back to Napplet Machines V8
SOURCE / PINNED RELEASE

Made of little things.

Napplet Machines V8

Release
b3f614e739f0…
Author-recorded commit
534f19acb4ba…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-b5572362d4a

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

tests/gameplay.test.mjs
import assert from 'node:assert/strict';
import test from 'node:test';
import { load } from './load.mjs';
const {
  Road,
  presets,
  spline,
  validateTrack,
  decodeTrack,
  roadEdge,
  roadBoundary,
  TRACK_HALF_SIZE,
  MAX_POINT_WIDTH,
} = load('src/track.ts');
const {
  createWorld,
  spawn,
  advanceBody,
  stepWorld,
  collide,
  cars,
  idleInput,
  DT,
} = load('src/physics.ts');
const { validWorld, validInput, validRoom } = load('src/network.ts');
const driver = (car = 0) => ({ id: 'you', name: 'You', car, ai: false });
test('closed spline is continuous in position and tangent at the seam', () => {
  for (const track of presets) {
    assert.ok(validateTrack(track));
    const road = new Road(track),
      a = spline(track, 0),
      b = spline(track, track.points.length),
      l = road.samples[0],
      r = road.samples.at(-1);
    assert.ok(Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z) < 1e-8);
    assert.ok(Math.hypot(l.tx - r.tx, l.ty - r.ty) < 1e-6);
    assert.ok(road.length > 150);
  }
});
test('width, height and wall flags survive track sampling', () => {
  const t = structuredClone(presets[0]);
  t.points[0].width = 13;
  t.points[1].width = 5;
  t.points[1].wallThickness = 1.8;
  t.points[0].z = 20;
  t.points[0].wall = false;
  const r = new Road(t);
  assert.equal(r.samples[0].width, 13);
  const mid = r.samples.findIndex((p) => Math.abs(p.u - 0.5) < 1e-9);
  assert.equal(r.samples[mid].width, 9);
  assert.equal(r.samples.find((p) => p.u === 1).width, 5);
  assert.equal(r.samples.find((p) => p.u === 1).wallThickness, 1.8);
  assert.equal(r.samples.at(-1).width, 13);
  for (let i = 0; r.samples[i].u <= 1; i++)
    assert.ok(r.samples[i].width >= 5 && r.samples[i].width <= 13);
  const between = r.at(
    (r.samples[mid].distance + r.samples[mid + 1].distance) / 2,
  );
  assert.ok(between.width < 9 && between.width > r.samples[mid + 1].width);
  assert.ok(
    Math.abs(
      between.distance -
        (r.samples[mid].distance + r.samples[mid + 1].distance) / 2,
    ) < 1e-8,
  );
  assert.equal(r.samples[0].z, 20);
  assert.equal(r.samples[0].wall, false);
  const p = r.nearest(t.points[0].x, t.points[0].y);
  assert.ok(Math.abs(p.offset) < 1e-9);
});
test('mass-weighted impact conserves planar momentum and separates cars', () => {
  const road = new Road(presets[0]),
    a = spawn(driver(0), road, 0),
    b = spawn({ ...driver(2), id: 'other' }, road, 1);
  Object.assign(a, { x: 0, y: 0, z: 3, vx: 15, vy: 0, angle: 0 });
  Object.assign(b, { x: 1.8, y: 0, z: 3, vx: 0, vy: 0, angle: 0 });
  const before = a.vx * cars[0].mass + b.vx * cars[2].mass;
  collide(a, b);
  assert.ok(b.vx > 0);
  assert.ok(a.vx < 15);
  assert.ok(
    Math.abs(a.vx * cars[0].mass + b.vx * cars[2].mass - before) < 1e-9,
  );
  assert.ok(b.x - a.x > 1.8);
});
test('a fall recovers to the last road checkpoint without advancing a lap', () => {
  const road = new Road(presets[0]),
    b = spawn(driver(), road, 0);
  b.x = 80;
  b.y = 80;
  b.z = -6;
  advanceBody(b, idleInput(), road);
  assert.equal(b.falls, 1);
  assert.ok(b.z > 0);
  assert.equal(b.lap, 0);
  assert.ok(b.respawn > 0);
});
test('held throttle accelerates and brake dissipates speed', () => {
  const r = new Road(presets[1]),
    b = spawn(driver(), r, 0);
  for (let i = 0; i < 45; i++)
    advanceBody(b, { throttle: 1, steer: 0, brake: false, reset: false }, r);
  const speed = Math.hypot(b.vx, b.vy);
  assert.ok(speed > 7);
  for (let i = 0; i < 15; i++)
    advanceBody(b, { ...idleInput(), brake: true }, r);
  assert.ok(Math.hypot(b.vx, b.vy) < speed);
});
test('AI racers complete ordered laps on all supplied circuits', () => {
  for (const track of presets) {
    const r = new Road(track),
      w = createWorld(
        Array.from({ length: 6 }, (_, i) => ({
          id: `ai-${i}`,
          name: `Bot ${i}`,
          car: i % 4,
          ai: true,
        })),
        r,
      );
    w.countdown = 0;
    // A human spectator keeps the race timer alive without granting a finish.
    w.bodies[0].ai = false;
    w.bodies[0].id = 'spectator';
    for (let i = 0; i < 60 * Math.max(180, (r.length * 3) / 9 + 60); i++) {
      stepWorld(w, r, new Map());
      if (w.bodies.slice(1).every((b) => b.finished)) break;
    }
    console.log(
      track.name,
      w.bodies.slice(1).map((b) => ({
        lap: b.lap,
        time: Math.round(b.finished),
        falls: b.falls,
        progress: Math.round(b.progress),
      })),
    );
    assert.ok(
      w.bodies.slice(1).every((b) => b.finished > 0),
      `${track.name}: not every AI finished`,
    );
    assert.ok(
      w.bodies.every((b) => [b.x, b.y, b.z, b.vx, b.vy].every(Number.isFinite)),
    );
  }
});
test('race finish requires three ordered laps, not a teleport to the start', () => {
  const r = new Road(presets[1]),
    w = createWorld([driver()], r);
  w.countdown = 0;
  const b = w.bodies[0];
  b.progress = r.length * 3;
  b.nextGate = 0;
  stepWorld(w, r, new Map());
  assert.equal(b.finished, 0);
  assert.equal(w.ended, false);
});
test('simulation is repeatable with the same tick inputs', () => {
  const r = new Road(presets[1]),
    a = createWorld([driver()], r),
    b = createWorld([driver()], r);
  const inp = new Map([
    ['you', { throttle: 1, steer: 0.1, brake: false, reset: false }],
  ]);
  for (let i = 0; i < 360; i++) {
    stepWorld(a, r, inp);
    stepWorld(b, r, inp);
  }
  assert.equal(JSON.stringify(a), JSON.stringify(b));
  assert.equal(DT, 1 / 60);
});
test('peer validation rejects malformed states and unbounded controls', () => {
  const w = createWorld([driver()], new Road(presets[0]));
  assert.ok(validWorld(w));
  assert.equal(validWorld({ ...w, tick: NaN }), false);
  const bad = structuredClone(w);
  bad.bodies[0].x = Infinity;
  assert.equal(validWorld(bad), false);
  assert.equal(validWorld({ ...w, bodies: [w.bodies[0], w.bodies[0]] }), false);
  assert.ok(validInput(idleInput()));
  assert.equal(validInput({ ...idleInput(), steer: 5 }), false);
  assert.equal(validInput({ ...idleInput(), throttle: NaN }), false);
  assert.equal(
    validRoom({ room: 'r', name: 'x', capacity: 99, peers: [] }),
    false,
  );
});

test('malformed track neighbors are rejected without throwing', () => {
  const t = structuredClone(presets[0]);
  t.points[1] = null;
  assert.equal(validateTrack(t), false);
});

test('host rejects an invalid edited track before announcing the race', async () => {
  const { Multiplayer } = load('src/network.ts');
  const host = new Multiplayer();
  host.actor = host.authority = 'test-host';
  const track = structuredClone(presets[0]);
  track.points[1] = { ...track.points[0] };
  await assert.rejects(
    host.begin(track, createWorld([driver()], new Road(presets[0]))),
    /crowded track points/,
  );
  assert.equal(host.start, null);
});

test('earlier saved drafts upgrade width at every point without mutating the source', () => {
  const old = {
    version: 1,
    name: 'Earlier draft',
    width: 11,
    points: presets[0].points.map(({ x, y, z, wall }) => ({ x, y, z, wall })),
  };
  const upgraded = decodeTrack(old);
  assert.ok(validateTrack(upgraded));
  assert.ok(
    upgraded.points.every((p) => p.width === 14.3 && p.wallThickness === 0.6),
  );
  assert.equal(old.points[0].width, undefined);
  assert.equal(decodeTrack({ ...old, width: Infinity }), null);
  assert.equal(
    decodeTrack({ ...old, points: [null, ...old.points.slice(1)] }),
    null,
  );
  for (const property of ['width', 'wallThickness']) {
    const invalid = structuredClone(upgraded);
    invalid.points[1][property] = NaN;
    assert.equal(validateTrack(invalid), false);
  }
});

test('version 2 drafts widen once, retain section settings and enforce the new width ceiling', () => {
  const old = structuredClone(presets[0]);
  old.version = 2;
  old.points.forEach((p, i) => {
    p.width = i === 0 ? 16 : 7.5;
  });
  const before = JSON.stringify(old),
    upgraded = decodeTrack(old);
  assert.ok(validateTrack(upgraded));
  assert.equal(upgraded.version, 3);
  assert.equal(upgraded.points[0].width, 20.8);
  assert.equal(upgraded.points[1].width, 9.75);
  assert.equal(JSON.stringify(old), before);
  assert.equal(JSON.stringify(decodeTrack(upgraded)), JSON.stringify(upgraded));
  for (let i = 0; i < old.points.length; i++) {
    const { width: _, ...legacy } = old.points[i];
    const { width: __, ...current } = upgraded.points[i];
    assert.equal(JSON.stringify(current), JSON.stringify(legacy));
  }
  upgraded.points[0].width = MAX_POINT_WIDTH + 0.01;
  assert.equal(validateTrack(upgraded), false);
  for (const width of [4.99, 16.01, Infinity, NaN]) {
    old.points[0].width = width;
    assert.equal(decodeTrack(old), null);
  }
});

test('placement waits for a location, splits the clicked section and inherits its settings', () => {
  const { TrackEditor } = load('src/editor.ts');
  const track = structuredClone(presets[0]);
  track.points[3].width = 5;
  track.points[4].width = 15;
  track.points[3].wallThickness = 1.4;
  const editor = new TrackEditor({ addEventListener() {}, style: {} }, track);
  editor.setTool('add');
  assert.equal(track.points.length, 10);
  const middle = editor.road.samples.find((p) => p.u === 3.5);
  const xy = { x: middle.x - middle.ty * 2, y: middle.y + middle.tx * 2 }; // The click is deliberately off the old curve.
  assert.ok(editor.addAt(xy.x, xy.y));
  assert.equal(editor.selected, 4);
  assert.equal(track.points.length, 11);
  assert.ok(Math.abs(track.points[4].x - xy.x) < 1e-9);
  assert.ok(Math.abs(track.points[4].y - xy.y) < 1e-9);
  assert.ok(Math.abs(track.points[4].width - 10) < 0.1);
  assert.equal(track.points[4].wall, false);
  assert.equal(track.points[4].wallThickness, 1.4);
  assert.ok(validateTrack(track));
  assert.equal(editor.addAt(xy.x, xy.y), false);
  assert.equal(track.points.length, 11);
  editor.setTool('move');
  assert.equal(editor.tool, 'move');
});

function square(width = 10, thickness = 1) {
  return {
    version: 3,
    name: 'Square',
    points: [
      [-30, -30],
      [30, -30],
      [30, 30],
      [-30, 30],
    ].map(([x, y]) => ({
      x,
      y,
      z: 3,
      width,
      wall: true,
      wallThickness: thickness,
    })),
  };
}
test('road support uses local width, with safe single-file starts on narrow tracks', () => {
  const t = square();
  t.points[0].width = 5;
  t.points[2].width = 16;
  t.points.forEach((p) => (p.wall = false));
  const r = new Road(t);
  for (const [index, grounded] of [
    [0, false],
    [2, true],
  ]) {
    const p = r.samples.find((p) => p.u === index),
      xy = roadEdge(p, 4),
      b = spawn(driver(), r, 0);
    Object.assign(b, { ...xy, z: p.z + 0.65 });
    advanceBody(b, idleInput(), r);
    assert.equal(b.grounded, grounded);
  }
  const narrow = new Road(square(5, 2));
  const bodies = Array.from({ length: 6 }, (_, i) =>
    spawn({ ...driver(), id: String(i) }, narrow, i),
  );
  assert.ok(bodies.every((b) => !narrow.wallContact(b.x, b.y, b.z)));
  for (let i = 1; i < 6; i++)
    assert.ok(
      Math.hypot(bodies[i].x - bodies[i - 1].x, bodies[i].y - bodies[i - 1].y) >
        3,
    );
});
test('solid walls collide at inner/outer faces and section ends, with open gaps', () => {
  const t = square(10, 2);
  t.points[1].wall = false;
  const r = new Road(t),
    p = r.samples.find((p) => p.u === 0.5),
    inner = roadEdge(p, 3.8),
    outer = roadEdge(p, 6.2);
  const innerHit = r.wallContact(inner.x, inner.y, 3.65),
    outerHit = r.wallContact(outer.x, outer.y, 3.65);
  assert.ok(innerHit && outerHit);
  assert.ok(innerHit.nx * -p.ty + innerHit.ny * p.tx < -0.9);
  assert.ok(outerHit.nx * -p.ty + outerHit.ny * p.tx > 0.9);
  const thin = new Road(square(10, 0.3));
  assert.equal(thin.wallContact(inner.x, inner.y, 3.65), null);
  const end = r.samples.find((p) => p.u === 1),
    edge = roadEdge(end, 5);
  const capHit = r.wallContact(
    edge.x + end.tx * 0.3,
    edge.y + end.ty * 0.3,
    3.65,
  );
  assert.ok(capHit && capHit.nx * end.tx + capHit.ny * end.ty > 0.9);
  const gap = roadEdge(
    r.samples.find((p) => p.u === 1.5),
    5,
  );
  assert.equal(r.wallContact(gap.x, gap.y, 3.65), null);
  assert.equal(r.wallContact(inner.x, inner.y, 6), null);
  const b = spawn(driver(), r, 0);
  Object.assign(b, {
    ...inner,
    z: 3.65,
    vx: -p.ty * 4,
    vy: p.tx * 4,
    angle: Math.atan2(p.tx, -p.ty),
  });
  advanceBody(b, idleInput(), r);
  assert.ok(
    b.vx * -p.ty + b.vy * p.tx < 0,
    'impact bounces away from the wall',
  );
});

test('rear bumper offsets do not turn a forward push into a sideways shove', () => {
  const road = new Road(presets[0]);
  for (const offset of [-0.25, 0, 0.25]) {
    const a = spawn(driver(), road, 0),
      b = spawn({ ...driver(), id: 'ahead' }, road, 1);
    Object.assign(a, { x: 0, y: 0, z: 3.65, angle: 0, vx: 20, vy: 0, spin: 0 });
    Object.assign(b, {
      x: 1.9,
      y: offset,
      z: 3.65,
      angle: 0,
      vx: 8,
      vy: 0,
      spin: 0,
    });
    collide(a, b);
    assert.ok(b.vx > 8, 'the front car receives the push');
    assert.ok(
      Math.abs(a.vy) < 0.01 && Math.abs(b.vy) < 0.01,
      `rear offset ${offset}: lateral velocities ${a.vy}, ${b.vy}`,
    );
    assert.equal(a.angle, 0, 'a collision never teleports the heading');
  }
});
test('grazing bumper spin tends to zero with impact strength', () => {
  const road = new Road(presets[0]),
    a = spawn(driver(), road, 0),
    b = spawn({ ...driver(), id: 'ahead' }, road, 1);
  Object.assign(a, {
    x: 0,
    y: 0,
    z: 3.65,
    angle: 0,
    vx: 10.0001,
    vy: 3,
    spin: 0,
  });
  Object.assign(b, { x: 1.9, y: 0, z: 3.65, angle: 0, vx: 10, vy: 0, spin: 0 });
  collide(a, b);
  assert.ok(
    Math.abs(a.spin) < 0.005 && Math.abs(b.spin) < 0.005,
    `grazing spin ${a.spin}, ${b.spin}`,
  );
});
test('parked cars follow the starting grid slope', () => {
  const track = square();
  [10, 18, 10, 3].forEach((z, i) => (track.points[i].z = z));
  const road = new Road(track),
    b = spawn(driver(), road, 0),
    p = road.nearest(b.x, b.y, b.z - 0.65);
  assert.ok(Math.abs(p.slope) > 0.1, 'fixture is on a slope');
  assert.ok(
    Math.abs(b.pitch - Math.atan(p.slope)) < 0.06,
    `pitch ${b.pitch}, slope ${p.slope}`,
  );
});
test('a parked car facing across a slope rolls with the surface instead of pointing its nose uphill', () => {
  const track = square();
  track.points[1].z = 18;
  const road = new Road(track),
    p = road.samples.find((p) => p.u === 0.5),
    b = spawn(driver(), road, 0);
  Object.assign(b, {
    x: p.x,
    y: p.y,
    z: p.z + 0.65,
    angle: Math.atan2(p.ty, p.tx) + Math.PI / 2,
    vx: 0,
    vy: 0,
    vz: 0,
  });
  for (let i = 0; i < 30; i++) advanceBody(b, idleInput(), road);
  assert.ok(Math.abs(b.pitch) < 0.07, `cross-slope pitch ${b.pitch}`);
  assert.ok(Math.abs(b.roll) > 0.08, `cross-slope roll ${b.roll}`);
});

// Broad level asphalt isolates tire response from barriers and track geometry.
const asphalt = {
  length: 10000,
  nearest: (x, y) => ({
    x,
    y,
    z: 3,
    width: 500,
    left: 250,
    right: 250,
    offset: 0,
    tx: 1,
    ty: 0,
    slope: 0,
    distance: x,
  }),
  bounds: { minX: -1000, maxX: 1000, minY: -1000, maxY: 1000 },
  deck: { at: () => ({ z: 3, gx: 0, gy: 0 }) },
  wallContact: () => null,
};
function rollingCar(car = 0) {
  return Object.assign(spawn(driver(car), new Road(presets[0]), 0), {
    x: 0,
    y: 0,
    z: 3.65,
    vx: 18,
    vy: 0,
    vz: 0,
    angle: 0,
    pitch: 0,
    roll: 0,
  });
}
test('steering builds yaw through tire forces and does not redirect momentum instantly', () => {
  const b = rollingCar();
  advanceBody(b, { ...idleInput(), steer: 1 }, asphalt);
  assert.ok(b.steering > 0 && b.steering < 0.06);
  assert.ok(b.angle > 0 && b.angle < 0.005, `first-tick yaw ${b.angle}`);
  assert.ok(b.vx > 17.8 && Math.abs(b.vy) < 0.3);
  const parked = rollingCar();
  parked.vx = 0;
  for (let i = 0; i < 60; i++)
    advanceBody(parked, { ...idleInput(), steer: 1 }, asphalt);
  assert.equal(
    parked.angle,
    0,
    'steering alone cannot rotate a stationary car',
  );
});
test('handbrake initiates a momentum-carrying drift, with release and countersteer recovery', () => {
  const { slipAngle } = load('src/physics.ts');
  for (let car = 0; car < 4; car++) {
    const grip = rollingCar(car),
      drift = rollingCar(car);
    for (let i = 0; i < 45; i++) {
      advanceBody(grip, { ...idleInput(), throttle: 0.6, steer: 0.6 }, asphalt);
      advanceBody(
        drift,
        { ...idleInput(), throttle: 0.6, steer: 0.6, brake: true },
        asphalt,
      );
    }
    const slip = Math.abs(slipAngle(drift));
    assert.ok(
      slip > Math.abs(slipAngle(grip)) + 0.12,
      `${cars[car].name}: drift breaks rear grip`,
    );
    assert.ok(
      Math.hypot(drift.vx, drift.vy) > 10,
      'handbrake preserves useful momentum',
    );
    for (let i = 0; i < 120; i++)
      advanceBody(
        drift,
        {
          ...idleInput(),
          throttle: 0.5,
          steer: Math.max(-1, Math.min(1, slipAngle(drift) * 2)),
        },
        asphalt,
      );
    assert.ok(
      Math.abs(slipAngle(drift)) < 0.12,
      `${cars[car].name}: slide catches progressively`,
    );
    assert.ok(drift.handbrake < 0.001);
  }
});
test('slope pose follows heading at rest, in reverse, uphill and downhill', () => {
  const { surfacePose } = load('src/physics.ts');
  const p = { tx: 1, ty: 0, slope: 0.4 };
  assert.ok(Math.abs(surfacePose(p, 0).pitch - Math.atan(0.4)) < 1e-9);
  assert.ok(Math.abs(surfacePose(p, Math.PI).pitch + Math.atan(0.4)) < 1e-9);
  assert.ok(Math.abs(surfacePose(p, Math.PI / 2).roll + Math.atan(0.4)) < 1e-9);
  const t = square();
  [10, 18, 10, 3].forEach((z, i) => (t.points[i].z = z));
  const road = new Road(t),
    b = spawn(driver(), road, 0);
  for (let i = 0; i < 20; i++)
    advanceBody(b, { ...idleInput(), throttle: -1 }, road);
  assert.ok(b.pitch > 0, 'reversing does not flip the surface tilt');
});
test('suspension uses relative road velocity on fast climbs, then releases at a crest', () => {
  const b = rollingCar();
  b.vz = 9;
  const uphill = {
    ...asphalt,
    deck: { at: (x) => ({ z: 3 + x * 0.5, gx: 0.5, gy: 0 }) },
    nearest: (x, y) => ({
      ...asphalt.nearest(x, y),
      z: 3 + x * 0.5,
      slope: 0.5,
    }),
  };
  advanceBody(b, idleInput(), uphill);
  assert.equal(
    b.grounded,
    true,
    'uphill vertical speed greater than 5 remains supported',
  );
  b.vz = 8;
  advanceBody(b, idleInput(), asphalt);
  assert.equal(b.grounded, false, 'a crest can launch the chassis');
  assert.ok(b.vz < 8, 'airborne gravity acts');
});
test('rotated chassis contacts conserve momentum without manufacturing energy', () => {
  const { yawInertia } = load('src/contacts.ts');
  let seed = 872;
  const rand = () => {
    seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0;
    return seed / 4294967296;
  };
  for (let i = 0; i < 400; i++) {
    const a = rollingCar(i % 4),
      b = rollingCar((i + 1) % 4);
    b.id = 'other';
    Object.assign(a, {
      vx: rand() * 20 - 10,
      vy: rand() * 20 - 10,
      spin: rand() * 4 - 2,
      angle: rand() * 6.28,
    });
    Object.assign(b, {
      x: rand() * 2,
      y: rand() * 2,
      vx: rand() * 20 - 10,
      vy: rand() * 20 - 10,
      spin: rand() * 4 - 2,
      angle: rand() * 6.28,
    });
    const ma = cars[a.car].mass,
      mb = cars[b.car].mass;
    const energy = () =>
      (ma * (a.vx * a.vx + a.vy * a.vy)) / 2 +
      (mb * (b.vx * b.vx + b.vy * b.vy)) / 2 +
      (yawInertia(ma) * a.spin * a.spin) / 2 +
      (yawInertia(mb) * b.spin * b.spin) / 2;
    const initial = energy(),
      px = a.vx * ma + b.vx * mb,
      py = a.vy * ma + b.vy * mb;
    collide(a, b);
    assert.ok(Math.abs(a.vx * ma + b.vx * mb - px) < 1e-8);
    assert.ok(Math.abs(a.vy * ma + b.vy * mb - py) < 1e-8);
    assert.ok(
      energy() <= initial + 1e-8,
      `contact ${i} gained energy: ${initial} -> ${energy()}`,
    );
    assert.ok([a.x, a.y, b.x, b.y, a.spin, b.spin].every(Number.isFinite));
  }
});
test('race buildings are deterministic and keep their whole footprint clear of roads and neighbors', () => {
  const { raceBuildings } = load('src/scenery.ts');
  for (const t of [...presets, square(16, 2)]) {
    const road = new Road(t),
      buildings = raceBuildings(road);
    assert.ok(
      buildings.length >= 5,
      `${t.name}: ${buildings.length} buildings`,
    );
    assert.equal(
      JSON.stringify(buildings),
      JSON.stringify(raceBuildings(new Road(structuredClone(t)))),
    );
    assert.equal(new Set(buildings.map((b) => b.kind)).size, 3);
    buildings.forEach((b, i) => {
      for (const p of road.samples)
        assert.ok(
          Math.hypot(b.x - p.x, b.y - p.y) >
            b.radius + p.width / 2 + (p.wall ? p.wallThickness / 2 : 0) + 1.4,
        );
      for (const other of buildings.slice(i + 1))
        assert.ok(
          Math.hypot(b.x - other.x, b.y - other.y) >
            b.radius + other.radius + 2,
        );
    });
  }
});
test('steering and handbrake prediction state is required and bounded on the wire', () => {
  const world = createWorld([driver()], new Road(presets[0]));
  assert.equal(validWorld(world), true);
  for (const [key, value] of [
    ['steering', undefined],
    ['steering', 1.01],
    ['handbrake', NaN],
    ['handbrake', -0.01],
    ['handbrake', 1.01],
  ]) {
    const bad = structuredClone(world);
    bad.bodies[0][key] = value;
    assert.equal(validWorld(bad), false, `${key}: ${value}`);
  }
});

test('acceleration lifts the nose and service braking compresses the front suspension', () => {
  const accelerating = rollingCar(),
    braking = rollingCar();
  advanceBody(accelerating, { ...idleInput(), throttle: 1 }, asphalt);
  advanceBody(braking, { ...idleInput(), throttle: -1 }, asphalt);
  assert.ok(accelerating.pitch > 0, 'rear squat under acceleration');
  assert.ok(braking.pitch < 0, 'nose dive under braking');
});

const { ControllerDriving, mixDriving, steeringAt, pedalAt } =
  load('src/controls.ts');
function samplePad(index = 2, mapping = 'standard') {
  return {
    index,
    id: `Controller ${index}`,
    mapping,
    connected: true,
    axes: [0, 0],
    buttons: Array.from({ length: 17 }, () => ({ value: 0, pressed: false })),
  };
}
test('controller claim, analog input and one-shot actions use the selected browser slot', () => {
  const a = samplePad(2),
    b = samplePad(7),
    pads = [null, null, a, null, null, null, null, b];
  const controller = new ControllerDriving({
    read: () => pads,
    active: () => true,
  });
  a.buttons[0].value = 1;
  assert.equal(
    controller.poll().selected,
    null,
    'a held connection button is not a fresh claim',
  );
  a.buttons[0].value = 0;
  controller.poll();
  a.buttons[0].value = 1;
  assert.equal(controller.poll().selected, 2);
  a.buttons[0].value = 0;
  a.buttons[7].value = 0.7;
  a.buttons[6].value = 0.2;
  a.axes[0] = -0.6;
  b.buttons[0].value = 1;
  b.axes[0] = 1;
  const frame = controller.poll();
  assert.equal(frame.selected, 2, 'second controller cannot steal the car');
  assert.ok(Math.abs(frame.drive.throttle - 0.5) < 1e-8);
  assert.ok(frame.drive.steer > 0.4 && frame.drive.steer < 0.6);
  a.axes[0] = 0.1;
  assert.equal(controller.poll().drive.steer, 0, 'stick dead zone');
  a.buttons[1].value = 1;
  a.buttons[3].value = 1;
  a.buttons[9].value = 1;
  const first = controller.poll(),
    held = controller.poll();
  assert.ok(first.drive.brake && first.recover && first.pause);
  assert.ok(
    held.drive.brake && !held.recover && !held.pause,
    'held actions do not repeat edges',
  );
  controller.dispose();
  assert.equal(controller.poll().status, 'closed');
});
test('controller disconnect, replacement, focus loss and unknown mappings cannot leave stale drive', () => {
  let active = true;
  const a = samplePad(),
    pads = [a];
  const c = new ControllerDriving({ read: () => pads, active: () => active });
  c.poll();
  a.buttons[0].value = 1;
  c.poll();
  a.buttons[0].value = 0;
  a.buttons[7].value = 1;
  assert.equal(c.poll().drive.throttle, 1);
  active = false;
  assert.equal(c.poll().drive.throttle, 0);
  active = true;
  a.buttons[9].value = 1;
  assert.equal(c.poll().pause, false, 'focus return suppresses phantom pause');
  const replacement = samplePad();
  replacement.id = 'Different controller';
  replacement.buttons[0].value = 1;
  pads[0] = replacement;
  const lost = c.poll();
  assert.ok(lost.disconnected);
  assert.equal(lost.selected, null);
  assert.equal(lost.drive.throttle, 0);
  replacement.buttons[0].value = 0;
  c.poll();
  replacement.buttons[0].value = 1;
  assert.equal(c.poll().selected, 2);
  pads.length = 0;
  const empty = c.poll();
  assert.ok(empty.disconnected);
  assert.equal(empty.drive.throttle, 0);
  const unknown = samplePad(3, '');
  pads.push(unknown);
  c.poll();
  unknown.buttons[0].value = 1;
  const unmapped = c.poll();
  assert.ok(unmapped.unmapped);
  assert.equal(unmapped.selected, null);
  c.dispose();
  const missing = new ControllerDriving({
    read: () => {
      throw new Error('unavailable');
    },
    active: () => true,
  });
  assert.equal(missing.poll().status, 'unavailable');
  missing.dispose();
});
test('touch steering is proportional and the pedal pad supports one-thumb gas, brake and powered drift', () => {
  assert.equal(steeringAt(100, 20, 160), 0);
  assert.equal(steeringAt(20, 20, 160), 1);
  assert.equal(steeringAt(180, 20, 160), -1);
  assert.ok(steeringAt(125, 20, 160) > -1 && steeringAt(125, 20, 160) < -0.3);
  assert.equal(pedalAt(40, 20, 132, 110), 'go');
  assert.equal(pedalAt(30, 85, 132, 110), 'stop');
  assert.equal(pedalAt(105, 85, 132, 110), 'drift');
  assert.equal(pedalAt(-1, 30, 132, 110), '');
  assert.equal(
    mixDriving({ ...idleInput(), steer: 0.4 }, { ...idleInput(), steer: -0.4 })
      .steer,
    0,
  );
  assert.equal(
    mixDriving(
      { ...idleInput(), throttle: 0.7 },
      { ...idleInput(), throttle: -1 },
    ).throttle,
    -1,
  );
  assert.equal(
    mixDriving(
      { ...idleInput(), steer: 0.6 },
      { ...idleInput(), throttle: 1, brake: true },
    ).steer,
    0.6,
  );
});

test('tight elevated corners close without inverted road or thick-wall triangles', () => {
  const original = structuredClone(presets[2]);
  original.points.forEach((p) => {
    p.width = MAX_POINT_WIDTH;
    p.wall = true;
    p.wallThickness = 2;
  });
  const area = (a, b, c) =>
    (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
  for (const track of [original, ...presets]) {
    const road = new Road(track);
    for (let i = 0; i < road.samples.length - 1; i++) {
      const a = road.samples[i],
        b = road.samples[i + 1];
      assert.ok(
        area(a.rightEdge, b.rightEdge, b.leftEdge) >= -1e-8,
        `${track.name}: inverted road at ${a.u}`,
      );
      assert.ok(
        area(a.rightEdge, b.leftEdge, a.leftEdge) >= -1e-8,
        `${track.name}: inverted pocket at ${a.u}`,
      );
    }
    for (const {
      corners: [a, b, c, d],
    } of road.walls)
      assert.ok(
        area(a, b, c) * area(a, c, d) >= -1e-9,
        `${track.name}: folded thick wall`,
      );
  }
});

test('doubled workshop area roundtrips, drives and synchronizes beyond the old limits', () => {
  assert.equal(TRACK_HALF_SIZE, 130);
  const track = square();
  track.points.forEach((p) => {
    p.x = Math.sign(p.x) * 130;
    p.y = Math.sign(p.y) * 130;
  });
  assert.ok(validateTrack(track));
  assert.equal(JSON.stringify(decodeTrack(track)), JSON.stringify(track));
  const road = new Road(track),
    b = spawn(driver(), road, 0);
  const center = roadEdge(road.at(road.length * 0.3), 0);
  Object.assign(b, center, { z: center.z + 0.65, vx: 0, vy: 0 });
  advanceBody(b, idleInput(), road);
  assert.equal(b.falls, 0);
  assert.ok(b.grounded);
  assert.ok(validWorld({ ...createWorld([driver()], road), bodies: [b] }));
  track.points[0].x = 130.01;
  assert.equal(validateTrack(track), false);
  const { TrackEditor } = load('src/editor.ts');
  const editor = new TrackEditor(
    { clientWidth: 580, clientHeight: 580, addEventListener() {}, style: {} },
    presets[0],
  );
  editor.zoom = 1;
  assert.equal(editor.fromScreen(550, 30).x, 130);
  assert.equal(editor.fromScreen(550, 30).y, -130);
  assert.equal(editor.placement(131, 0).valid, false);
});

test('every authored course varies point widths, open edges and solid wall thickness', () => {
  assert.equal(presets.length, 7);
  for (const track of presets) {
    assert.ok(new Set(track.points.map((p) => p.width)).size >= 3, track.name);
    assert.ok(
      track.points.some((p) => p.wall) && track.points.some((p) => !p.wall),
      track.name,
    );
    assert.ok(
      new Set(track.points.filter((p) => p.wall).map((p) => p.wallThickness))
        .size >= 2,
      track.name,
    );
  }
  assert.ok(
    presets
      .slice(3)
      .every(
        (t) =>
          t.points.some((p) => Math.abs(p.x) > 65) &&
          t.points.some((p) => Math.abs(p.y) > 65),
      ),
  );
});

test('closed pockets are drivable and preserve authored wall thickness', () => {
  const t = structuredClone(presets[2]);
  t.points.forEach((p) => {
    p.width = MAX_POINT_WIDTH;
    p.wall = true;
    p.wallThickness = 2;
  });
  const road = new Road(t);
  assert.ok(road.closedSections.size > 0);
  for (const w of road.walls) {
    assert.ok(
      Math.abs(
        Math.hypot(
          w.corners[0].x - w.corners[3].x,
          w.corners[0].y - w.corners[3].y,
        ) - 2,
      ) < 1e-8,
    );
    assert.ok(
      Math.abs(
        Math.hypot(
          w.corners[1].x - w.corners[2].x,
          w.corners[1].y - w.corners[2].y,
        ) - 2,
      ) < 1e-8,
    );
  }
  t.points.forEach((p) => (p.wall = false));
  const open = new Road(t),
    p = open.samples.find(
      (p) =>
        Math.hypot(
          p.leftEdge.x - (p.x - (p.ty * p.width) / 2),
          p.leftEdge.y - (p.y + (p.tx * p.width) / 2),
        ) > 1,
    );
  assert.ok(p);
  const inside = roadBoundary(p, 1, -0.3),
    outside = roadBoundary(p, 1, 0.5);
  for (const [position, expected] of [
    [inside, true],
    [outside, false],
  ]) {
    const b = spawn(driver(), open, 0);
    Object.assign(b, position, {
      z: position.z + 0.65,
      vx: 0,
      vy: 0,
      respawn: 0,
    });
    advanceBody(b, idleInput(), open);
    assert.equal(b.grounded, expected);
  }
});

test('figure-eight crossing selects the correct deck and road sampling remains bounded', () => {
  const road = new Road(presets.find((t) => t.name === 'Skybridge Eight'));
  assert.ok(road.nearest(0, 0, 18).z > 16);
  assert.ok(road.nearest(0, 0, 3).z < 5);
  assert.ok(road.deck.at(0, 0, 18).z > 16);
  assert.ok(road.deck.at(0, 0, 3).z < 5);
  for (const track of presets) {
    const r = new Road(track);
    assert.ok(r.samples.length < 1300);
    for (let i = 0; i < r.samples.length - 1; i++) {
      const a = r.samples[i],
        b = r.samples[i + 1];
      assert.ok(Math.hypot(a.x - b.x, a.y - b.y) <= 1.501);
    }
  }
});