Back to super-platformer
SOURCE / PINNED RELEASE

Made of little things.

super-platformer

Release
b61593abc2c6…
Author-recorded commit
bbd35ca510f6…
License
LICENSE
Author’s source reference
nostr://npub16uawymqtmdg4j6zcl6snt5apzfp4ek2g8lpasnqa75tspdpglcqsgpatvc/wss%3A%2F%2Fgit.napplet.soy%2F/n-01288b1ab38

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

tests/controller.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import ts from 'typescript';
const js = ts.transpileModule(readFileSync(new URL('../src/controller.ts', import.meta.url), 'utf8'), {
  compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext },
}).outputText;
const { InputSources, ControllerInput } = await import(`data:text/javascript;base64,${Buffer.from(js).toString('base64')}`);
const pad = (buttons = [], axis = 0, mapping = 'standard') => ({
  index: 0, connected: true, mapping, axes: [axis],
  buttons: Array.from({ length: 16 }, (_, i) => ({ pressed: buttons.includes(i) })),
});
function setup() {
  const held = new Set();
  const presses = [];
  const input = new InputSources({ press: a => { held.add(a); presses.push(a); }, release: a => held.delete(a) });
  let playing = true;
  const commands = [];
  const controller = new ControllerInput(input, {
    playing: () => playing,
    primary: () => { commands.push('primary'); playing = true; },
    pause: () => { commands.push('pause'); playing = false; },
    restart: () => commands.push('restart'),
  });
  controller.update([pad()]);
  return { input, controller, held, presses, commands };
}
test('stick dead zone, D-pad, held jump edges and fire', () => {
  const { controller, held, presses } = setup();
  controller.update([pad([], 0.2)]);
  assert.equal(held.size, 0);
  controller.update([pad([0, 2], -0.8)]);
  controller.update([pad([0, 2], -0.8)]);
  assert.deepEqual([...held].sort(), ['fire', 'jump', 'left']);
  assert.equal(presses.filter(a => a === 'jump').length, 1);
  controller.update([pad([15])]);
  assert.deepEqual([...held], ['right']);
  controller.update([pad()]);
  controller.update([pad([0])]);
  assert.equal(presses.filter(a => a === 'jump').length, 2);
});
test('disconnect releases only controller-owned input', () => {
  const { input, controller, held } = setup();
  input.set('keyboard', ['left']);
  controller.update([pad([14, 0])]);
  controller.update([]);
  assert.deepEqual([...held], ['left']);
  input.clear();
  assert.equal(held.size, 0);
});
test('menu commands fire once, and confirmation never leaks a jump', () => {
  const { controller, commands, held } = setup();
  controller.update([pad([9])]);
  controller.update([pad([9])]);
  assert.deepEqual(commands, ['pause']);
  controller.update([pad()]);
  controller.update([pad([0])]);
  controller.update([pad([0])]);
  assert.deepEqual(commands, ['pause', 'primary']);
  assert.equal(held.size, 0);
  controller.update([pad()]);
  controller.update([pad([0])]);
  assert.ok(held.has('jump'));
});
test('focus reset requires neutral input; unknown mappings are ignored', () => {
  const { controller, held } = setup();
  controller.update([pad([15])]);
  controller.reset();
  controller.update([pad([15])]);
  assert.equal(held.size, 0);
  controller.update([pad()]);
  controller.update([pad([15])]);
  assert.ok(held.has('right'));
  assert.equal(controller.update([pad([15], 0, '')]), 'unsupported');
  assert.equal(held.size, 0);
});