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.
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();
});
});
