Back to Poly Dogfight
SOURCE / PINNED RELEASE

Made of little things.

Poly Dogfight

Release
b8511ee374e3…
Author-recorded commit
5579434b9d8b…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-44f63e5422b

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

tests/multiplayer.test.mjs
// Local multiplayer: controller roster, split-screen layout and mid-air collisions.
import assert from 'node:assert/strict';
import test from 'node:test';
import { Vector3 } from 'three';
import { mergeControls } from '../src/input.ts';
import { MAX_PLAYERS, planRoster, screenIndicator, splitLayout, steerToward } from '../src/players.ts';
import { PLANET_RADIUS } from '../src/sim/constants.ts';
import { collidePlanes, createFlightState, neutralInput, spawnAt, stepFlight } from '../src/sim/flight.ts';

const pads = (plan) => plan.map((slot) => slot.pad);

test('one controller flies pilot 1 alongside the keyboard', () => {
  assert.deepEqual(planRoster([null], []), [{ from: 0, pad: null }]);
  assert.deepEqual(planRoster([null], [0]), [{ from: 0, pad: 0 }]);
});

test('each extra controller adds a pilot, up to four', () => {
  assert.deepEqual(pads(planRoster([0], [0, 1])), [0, 1]);
  assert.deepEqual(pads(planRoster([null], [0, 1, 2])), [0, 1, 2]);
  const full = planRoster([0, 1, 2, 3], [0, 1, 2, 3, 4]);
  assert.equal(full.length, MAX_PLAYERS);
  assert.deepEqual(pads(full), [0, 1, 2, 3]);
});

test('a pilot leaves when their controller disconnects; pilot 1 stays', () => {
  const plan = planRoster([0, 1, 2], [0, 2]);
  assert.deepEqual(plan, [
    { from: 0, pad: 0 },
    { from: 2, pad: 2 },
  ]);
  assert.deepEqual(planRoster([0, 1], [1]), [
    { from: 0, pad: null },
    { from: 1, pad: 1 },
  ]);
  // A returning controller takes pilot 1's empty seat rather than adding a pilot.
  assert.deepEqual(pads(planRoster([null, 1], [0, 1])), [0, 1]);
});

test('split layout covers the frame without overlap', () => {
  for (const [w, h] of [[1600, 900], [900, 600], [320, 560], [200, 160]]) {
    for (let count = 1; count <= 4; count++) {
      const { views, spare } = splitLayout(count, w, h);
      assert.equal(views.length, count);
      const rects = spare ? [...views, spare] : views;
      const area = rects.reduce((sum, r) => sum + r.w * r.h, 0);
      assert.ok(Math.abs(area - 1) < 1e-9, `${count} pilots at ${w}×${h} cover ${area}`);
    }
  }
  // Two pilots: side by side on a wide frame, stacked on a tall one.
  assert.equal(splitLayout(2, 1600, 900).views[1].x, 0.5);
  assert.equal(splitLayout(2, 320, 560).views[1].y, 0.5);
});

test('keyboard and controller input merge with the strongest axis winning', () => {
  const merged = mergeControls(
    { ...neutralInput(), pitch: 0.3, boost: true },
    { ...neutralInput(), pitch: -0.8, roll: 0.5 },
  );
  assert.equal(merged.pitch, -0.8);
  assert.equal(merged.roll, 0.5);
  assert.equal(merged.boost, true);
});

const sky = { surfaceHeight: () => 0, terrainHeight: () => -60, hitsObstacle: () => false };

function planeAt(angle, heading) {
  const state = createFlightState();
  spawnAt(state, sky, new Vector3(Math.sin(angle), Math.cos(angle), 0), heading);
  state.position.setLength(PLANET_RADIUS + 150);
  return state;
}

test('two planes flying head-on collide and both crash', () => {
  const gap = 200 / (PLANET_RADIUS + 150);
  const a = planeAt(-gap / 2, new Vector3(1, 0, 0));
  const b = planeAt(gap / 2, new Vector3(-1, 0, 0));
  let hits = [];
  for (let i = 0; i < 6 * 120 && !hits.length; i++) {
    stepFlight(a, neutralInput(), sky, 1 / 120);
    stepFlight(b, neutralInput(), sky, 1 / 120);
    hits = collidePlanes([a, b]);
  }
  assert.deepEqual(hits, [[0, 1]]);
  assert.equal(a.alive, false);
  assert.equal(b.alive, false);
  assert.equal(a.crashCause, 'midair');
  assert.ok(a.crashTimer > 4.9);
});

test('planes in formation do not collide, and wrecks are ignored', () => {
  const a = planeAt(0, new Vector3(0, 0, -1));
  const b = planeAt(0, new Vector3(0, 0, -1));
  b.position.x += 30;
  assert.deepEqual(collidePlanes([a, b]), []);
  b.position.copy(a.position);
  b.alive = false;
  assert.deepEqual(collidePlanes([a, b]), []);
  assert.equal(a.alive, true);
});

test('a pilot in view gets a tag at their projected position', () => {
  const spot = screenIndicator({ x: 0, y: 0, z: -100 }, 1, 1, 800, 400, 16);
  assert.equal(spot.onScreen, true);
  assert.deepEqual([spot.x, spot.y], [400, 200]);
  const upRight = screenIndicator({ x: 50, y: 50, z: -100 }, 1, 1, 800, 400, 16);
  assert.equal(upRight.onScreen, true);
  assert.deepEqual([upRight.x, upRight.y], [600, 100]);
});

test('a pilot out of view is pinned to the edge on the side to turn toward', () => {
  const right = screenIndicator({ x: 500, y: 0, z: -100 }, 1, 1, 800, 400, 16);
  assert.equal(right.onScreen, false);
  assert.deepEqual([right.x, right.y], [784, 200]);
  assert.ok(Math.abs(right.angle) < 1e-12);
  // Behind and to the left still points left, not mirrored to the right.
  const behindLeft = screenIndicator({ x: -30, y: 0, z: 100 }, 1, 1, 800, 400, 16);
  assert.equal(behindLeft.onScreen, false);
  assert.equal(behindLeft.x, 16);
  // Above the view points up; dead astern points down ("turn around").
  const above = screenIndicator({ x: 0, y: 300, z: -100 }, 1, 1, 800, 400, 16);
  assert.deepEqual([above.x, above.y], [400, 16]);
  const astern = screenIndicator({ x: 0, y: 0, z: 100 }, 1, 1, 800, 400, 16);
  assert.deepEqual([astern.x, astern.y], [400, 384]);
  // Every edge position stays inside the view.
  for (let i = 0; i < 64; i++) {
    const a = (i / 64) * Math.PI * 2;
    const s = screenIndicator({ x: Math.cos(a) * 900, y: Math.sin(a) * 900, z: 50 }, 1.2, 0.7, 320, 180, 16);
    assert.ok(s.x >= 16 - 1e-9 && s.x <= 304 + 1e-9 && s.y >= 16 - 1e-9 && s.y <= 164 + 1e-9);
  }
});

test('far-away pilots are found along the horizon, not through the planet', () => {
  const R = PLANET_RADIUS + 100;
  const near = steerToward({ x: 0, y: R, z: 0 }, { x: 60, y: R, z: 0 });
  assert.deepEqual(near.aim, { x: 60, y: R, z: 0 });
  // A quarter of the way round: aim a short way along the great circle, at altitude.
  const far = steerToward({ x: 0, y: R, z: 0 }, { x: R, y: 0, z: 0 });
  assert.ok(Math.abs(Math.hypot(far.aim.x, far.aim.y, far.aim.z) - R) < 1e-6);
  assert.ok(far.aim.x > 0 && Math.abs(far.aim.z) < 1e-9);
  assert.ok(Math.abs(far.distance - (Math.PI / 2) * R) < 1e-6);
  // The far side of the planet still yields a usable aim point above the ground.
  const opposite = steerToward({ x: 0, y: R, z: 0 }, { x: 0, y: -R, z: 0 });
  assert.ok(Math.hypot(opposite.aim.x, opposite.aim.y, opposite.aim.z) > PLANET_RADIUS);
  assert.ok(Math.abs(opposite.distance - Math.PI * R) < 1e-6);
});