Back to Poly Dogfight
SOURCE / PINNED RELEASE

Made of little things.

Poly Dogfight

Release
ad11a55ec88c…
Author-recorded commit
483e0e0c5000…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.shakespeare.diy%2F/poly-dogfight

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

tests/nap-input.test.mjs
import assert from 'node:assert/strict';
import test from 'node:test';
import { createInput } from '../src/input.ts';

const sample = (index) => ({ index, id: `pad-${index}`, mapping: 'standard', connected: true, timestamp: 1, axes: [0, 0, 0, 0], buttons: Array.from({ length: 17 }, () => ({ value: 0, pressed: false, touched: false })) });
const elements = (Element) => Object.fromEntries(['stick', 'stickKnob', 'throttle', 'boost', 'yawLeft', 'yawRight', 'fire', 'rocket'].map(key => [key, new Element()]));

/** A browser-like global with an injected `window.napplet`; native controller reads throw, as a denying shell makes them. */
function withRuntime(napplet, run) {
  const originals = ['window', 'document', 'navigator'].map(key => [key, Object.getOwnPropertyDescriptor(globalThis, key)]);
  class Element extends EventTarget { style = {}; }
  const doc = new Element(); doc.hidden = false; doc.hasFocus = () => false;
  const win = new Element(); win.document = doc; win.napplet = napplet;
  Object.defineProperty(globalThis, 'window', { configurable: true, value: win });
  Object.defineProperty(globalThis, 'document', { configurable: true, value: doc });
  Object.defineProperty(globalThis, 'navigator', { configurable: true, value: { getGamepads: () => { const e = new Error('denied'); e.name = 'SecurityError'; throw e; } } });
  try { return run({ win, doc, Element }); } finally {
    for (const [key, descriptor] of originals) { if (descriptor) Object.defineProperty(globalThis, key, descriptor); else delete globalThis[key]; }
  }
}

function fakeGamepad() {
  const handlers = new Set();
  const api = {
    state: { available: true, focused: false, pads: [] },
    subscribed: 0,
    get available() { return this.state.available; },
    get focused() { return this.state.focused; },
    getGamepads() { return this.state.pads; },
    onChange(handler) { api.subscribed++; handlers.add(handler); return { close: () => handlers.delete(handler) }; },
    push(state) { api.state = state; for (const handler of handlers) handler(state); },
    get handlers() { return handlers.size; },
  };
  return api;
}

test('NAP-GAMEPAD snapshots drive pilots by slot, honour shell focus and never replay a held button', () => {
  const gamepad = fakeGamepad();
  withRuntime({ gamepad }, ({ Element }) => {
    const input = createInput(elements(Element));
    assert.equal(gamepad.subscribed, 1);
    const pad = sample(2);
    gamepad.push({ available: true, focused: false, pads: [null, null, pad] });
    // Connected but unfocused: the seat exists, values stay neutral.
    let frame = input.poll(1 / 60);
    assert.deepEqual(frame.activePads, [2]);
    assert.equal(frame.padAny, false);
    // Accept (A) already held when the shell grants focus is not a new press.
    pad.buttons[0] = { value: 1, pressed: true, touched: true };
    pad.axes[0] = 1;
    gamepad.push({ available: true, focused: true, pads: [null, null, pad] });
    frame = input.poll(1 / 60);
    assert.equal(frame.pads[0].accept, false);
    assert.ok(frame.pads[0].control.roll > 0.9, 'live stick values arrive with focus, despite no DOM focus');
    pad.buttons[0] = { value: 0, pressed: false, touched: false }; input.poll(1 / 60);
    pad.buttons[0] = { value: 1, pressed: true, touched: true };
    assert.equal(input.poll(1 / 60).pads[0].accept, true);
    // Blocked runtime access releases controls but keeps the seat.
    gamepad.push({ available: false, reason: 'blocked', focused: false, pads: [] });
    frame = input.poll(1 / 60);
    assert.deepEqual(frame.activePads, [2]);
    assert.equal(frame.pads[0].control.roll, 0);
    input.dispose();
    assert.equal(gamepad.handlers, 0);
  });
});

test('NAP-KEYS actions register with hints, fire once per binding and unregister on dispose', () => {
  const registered = [], unregistered = [], handlers = new Map();
  const keys = {
    registerAction: async (action) => { registered.push(action); return { actionId: action.id, binding: action.defaultKey }; },
    unregisterAction: (id) => unregistered.push(id),
    onAction: (id, callback) => { handlers.set(id, callback); return { close: () => handlers.delete(id) }; },
  };
  withRuntime({ keys }, ({ win, Element }) => {
    const input = createInput(elements(Element));
    assert.deepEqual(registered.map(a => [a.id, a.defaultKey]), [['game.pause', 'p'], ['camera.cycle', 'c'], ['audio.mute', 'm']]);
    assert.ok(registered.every(a => a.defaultKey !== 'Escape' && a.label));
    // The injected binding prevents default and runs the action; the raw key must not fire it again.
    const bound = new Event('keydown', { cancelable: true }); bound.code = 'KeyP'; bound.repeat = false;
    bound.preventDefault(); handlers.get('game.pause')();
    win.dispatchEvent(bound);
    let frame = input.poll(1 / 60);
    assert.equal(frame.pause, true);
    assert.equal(input.poll(1 / 60).pause, false);
    // Built-in keys keep working when the shell leaves an action unbound or rebinds it.
    const raw = new Event('keydown', { cancelable: true }); raw.code = 'KeyM'; raw.repeat = false;
    win.dispatchEvent(raw);
    assert.equal(input.poll(1 / 60).mute, true);
    handlers.get('camera.cycle')();
    assert.equal(input.poll(1 / 60).localCamera, true);
    input.dispose();
    assert.deepEqual(unregistered, ['game.pause', 'camera.cycle', 'audio.mute']);
    assert.equal(handlers.size, 0);
  });
});

test('without the optional domains the native Gamepad API and plain keys still work', () => {
  withRuntime(undefined, ({ win, Element }) => {
    const input = createInput(elements(Element));
    const frame = input.poll(1 / 60);
    assert.deepEqual(frame.activePads, []);
    const key = new Event('keydown', { cancelable: true }); key.code = 'KeyP'; key.repeat = false;
    win.dispatchEvent(key);
    assert.equal(input.poll(1 / 60).pause, true);
    input.dispose();
  });
});