SOURCE / PINNED RELEASE
Made of little things.
Pickle Rustlers
- Release
- 039e51f55e34…
- Author-recorded commit
- 40608586a8a1…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-bad44e7ab35
Archive hash verified: f38e99afa0992052…. 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 { readFile } from 'node:fs/promises';
import { runInNewContext } from 'node:vm';
import ts from 'typescript';
// Execute the real TypeScript modules with only their shell boundary replaced.
async function load(path, imports = {}, globals = {}) {
const source = await readFile(new URL(path, import.meta.url), 'utf8');
const { outputText } = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
});
const exports = {};
runInNewContext(outputText, { exports, require: (name) => imports[name], structuredClone, Error, ...globals });
return exports;
}
const { createGamepadInput } = await load('../src/game/gamepad.ts');
const { createShellGamepadSource } = await load('../src/game/gamepad-source.ts', {
'@napplet/sdk': {}, '../domain-availability.js': {},
});
const bindings = { steer: [{ axis: 0 }], shoot: [{ button: 0 }], pause: [{ button: 9 }] };
const pad = (value = 0, axis = 0, index = 2, mapping = 'standard') => ({
index, id: 'controller', mapping, connected: true, timestamp: 0,
axes: [axis], buttons: Array.from({ length: 10 }, (_, i) => ({
value: i === 0 ? value : 0, pressed: i === 0 && value > 0.5, touched: false,
})),
});
function setup() {
let handler, closed = 0, reads = 0;
let mapper;
const source = createShellGamepadSource(() => mapper?.reset(), true, {
available: true, focused: false,
getGamepads() { reads++; return []; },
onChange(fn) { handler = fn; return { close() { closed++; } }; },
});
mapper = createGamepadInput(bindings, source);
return {
mapper, source, get closed() { return closed; }, get reads() { return reads; },
send(pads, focused = true, available = true, reason) { handler({ pads, focused, available, reason }); },
};
}
test('cached snapshots preserve sparse controller indices, analog dead zone and one-shot edges', () => {
const s = setup();
s.send([null, null, pad(0, 0.1)]);
let frame = s.mapper.poll();
assert.equal(frame.players[0].index, 2);
assert.equal(frame.players[0].actions.steer.value, 0);
s.send([null, null, pad(1, 0.59)]);
frame = s.mapper.poll();
assert.ok(Math.abs(frame.players[0].actions.steer.value - 0.5) < 0.0001);
assert.equal(frame.players[0].actions.shoot.pressed, true);
assert.equal(s.mapper.poll().players[0].actions.shoot.pressed, false);
assert.equal(s.reads, 1, 'rendering reads only the cached state');
});
test('focus loss is idle, and a held button on regain cannot create a press', () => {
const s = setup();
s.send([pad()]); s.mapper.poll();
s.send([pad(1)]); assert.equal(s.mapper.poll().players[0].actions.shoot.pressed, true);
s.send([pad()], false);
const idle = s.mapper.poll().players[0].actions.shoot;
assert.equal(idle.value, 0); assert.equal(idle.released, false);
s.send([pad(1)]);
assert.equal(s.mapper.poll().players[0].actions.shoot.pressed, false);
s.send([pad()]); s.mapper.poll();
s.send([pad(1)]); assert.equal(s.mapper.poll().players[0].actions.shoot.pressed, true);
});
test('focus loss and regain between frames still suppress a new held press', () => {
const s = setup();
s.send([pad()]); s.mapper.poll();
s.send([pad()], false); s.send([pad(1)]);
assert.equal(s.mapper.poll().players[0].actions.shoot.pressed, false);
});
test('disconnect, slot replacement and reset never synthesize presses', () => {
const s = setup();
s.send([pad()]); s.mapper.poll();
s.send([]); assert.equal(s.mapper.poll().disconnected[0], 2);
s.send([pad(1)]); assert.equal(s.mapper.poll().players[0].actions.shoot.pressed, false);
s.send([{ ...pad(1), id: 'replacement' }]);
const replacement = s.mapper.poll();
assert.equal(replacement.disconnected[0], 2);
assert.equal(replacement.players[0].actions.shoot.pressed, false);
s.mapper.reset();
assert.equal(s.mapper.poll().players[0].actions.shoot.pressed, false);
});
test('unknown layouts stay neutral; missing, blocked and failed optional domains remain safe', () => {
const s = setup();
s.send([pad(1, 1, 2, '')]); s.mapper.poll();
assert.equal(s.mapper.poll().players[0].actions.steer.value, 0);
s.send([], false, false, 'blocked'); assert.equal(s.mapper.poll().status, 'blocked');
// No shell domain and no browser Gamepad API either: nothing to read, still safe.
const absent = createShellGamepadSource(() => {}, false);
assert.equal(createGamepadInput(bindings, absent).poll().status, 'unavailable');
const broken = createShellGamepadSource(() => {}, true, {
get available() { throw new Error('denied'); },
});
assert.equal(createGamepadInput(bindings, broken).poll().status, 'unavailable');
});
test('teardown closes subscription once and ignores later snapshots', () => {
const s = setup();
s.source.dispose(); s.source.dispose();
assert.equal(s.closed, 1);
s.send([pad(1)]);
assert.equal(s.source.active(), false);
s.mapper.dispose(); assert.equal(s.mapper.poll().status, 'closed');
});
test('disconnect and reconnect in one slot between frames rebaseline held input', () => {
const s = setup();
s.send([pad()]); s.mapper.poll();
s.send([]); s.send([pad(1)]);
assert.equal(s.mapper.poll().players[0].actions.shoot.pressed, false);
});
test('hosts without the gamepad domain fall back to the browser Gamepad API', async () => {
let reads = 0;
const browser = {
navigator: { getGamepads() { reads++; return [pad(1)]; } },
document: { hidden: false, hasFocus: () => true },
};
const native = await load('../src/game/gamepad-source.ts', { '@napplet/sdk': {}, '../domain-availability.js': {} }, browser);
const source = native.createShellGamepadSource(() => {}, false);
assert.equal(source.active(), true);
assert.equal(source.read()[0].id, 'controller');
assert.equal(reads, 1);
source.dispose();
});
const { Input } = await load('../src/game/input.ts', {
'./gamepad.js': { createGamepadInput }, './gamepad-source.js': {}, './keyboard.js': {},
});
test('keyboard and touch still work alongside controllers and after domain loss', () => {
// Exercise Input.read without constructing DOM listeners.
const input = Object.create(Input.prototype);
Object.assign(input, {
keyboard: (() => {
let pending = new Set(['lasso']);
return {
down: (...names) => names.includes('w'),
read: () => { const result = pending; pending = new Set(); return result; },
};
})(),
stick: { id: -1, x: 0, y: 0 },
touchShoot: false, touchLasso: false,
gamepad: { poll: () => ({ players: [{ actions: {
moveX: { value: 0.6 }, moveY: { value: 0 }, gas: { value: 0.2 }, brake: { value: 0 },
shoot: { pressed: false }, lasso: { pressed: false }, pause: { pressed: true },
} }] }) },
});
let frame = input.read();
assert.equal(frame.throttle, 1); assert.equal(frame.steer, 0.6);
assert.equal(frame.lasso, true); assert.equal(frame.pause, true);
input.gamepad = { poll: () => ({ players: [] }) };
input.touchShoot = true;
frame = input.read();
assert.equal(frame.throttle, 1); assert.equal(frame.shoot, true); assert.equal(frame.confirm, true);
assert.equal(input.read().shoot, false);
});
