Back to Sickman Survival
SOURCE / PINNED RELEASE

Made of little things.

Sickman Survival

Release
7335c9fdf0a8…
Author-recorded commit
cbd2eeafa70d…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-8804151df7f

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

tests/roster.test.mjs
import assert from 'node:assert/strict';
import test from 'node:test';
import { layoutPanes, Roster } from '../src/roster.ts';

const slots = (roster) => roster.players.map((p) => [p.slot, p.source]);

test('keyboard and a single pad share one solo player', () => {
  const roster = new Roster();
  assert.deepEqual(roster.sync([]), { added: [], removed: [] });
  assert.deepEqual(roster.sync([0]), { added: [], removed: [] });
  assert.equal(roster.mode, 'solo');
  assert.deepEqual(slots(roster), [[0, 'solo']]);
});

test('a second pad splits the screen without any setup; P1 keeps slot 0', () => {
  const roster = new Roster();
  roster.sync([0]);
  const change = roster.sync([0, 1]);
  assert.equal(roster.mode, 'split');
  assert.deepEqual(change, { added: [1], removed: [] });
  assert.deepEqual(slots(roster), [[0, 0], [1, 1]]);
});

test('two pads at once, and pads with index gaps', () => {
  const roster = new Roster();
  assert.deepEqual(roster.sync([3, 1]).added, [1]);
  assert.deepEqual(slots(roster), [[0, 1], [1, 3]]);
});

test('more pads fill to four players; a fifth waits for a free slot', () => {
  const roster = new Roster();
  roster.sync([0, 1, 2, 3, 4]);
  assert.deepEqual(slots(roster), [[0, 0], [1, 1], [2, 2], [3, 3]]);
  const change = roster.sync([0, 2, 3, 4]);
  assert.deepEqual(change, { added: [1], removed: [1] });
  assert.deepEqual(slots(roster), [[0, 0], [1, 4], [2, 2], [3, 3]], 'the waiting pad takes the freed slot');
});

test('unplugging removes that player; others keep their slots', () => {
  const roster = new Roster();
  roster.sync([0, 1, 2]);
  assert.deepEqual(roster.sync([1, 2]), { added: [], removed: [0] });
  assert.deepEqual(slots(roster), [[1, 1], [2, 2]]);
  // A new pad takes the lowest free slot.
  assert.deepEqual(roster.sync([1, 2, 5]).added, [0]);
});

test('back down to one pad returns to solo, with keyboard re-merged', () => {
  const roster = new Roster();
  roster.sync([0, 1]);
  assert.deepEqual(roster.sync([1]), { added: [], removed: [0] });
  assert.equal(roster.mode, 'solo');
  assert.deepEqual(slots(roster), [[1, 'solo']]);
});

test('if every pad leaves at once, the first player stays for the keyboard', () => {
  const roster = new Roster();
  roster.sync([0, 1, 2]);
  assert.deepEqual(roster.sync([]), { added: [], removed: [1, 2] });
  assert.equal(roster.mode, 'solo');
  assert.deepEqual(slots(roster), [[0, 'solo']]);
});

test('a different pad on the same index is a leave and a fresh join', () => {
  const roster = new Roster();
  roster.sync([0, 1, 2]);
  assert.deepEqual(roster.sync([0, 1, 2], 'ready', [1]), { added: [1], removed: [1] });
  assert.equal(roster.players.length, 3);
});

test('a blocked or missing gamepad API leaves the roster alone', () => {
  const roster = new Roster();
  roster.sync([0, 1]);
  assert.deepEqual(roster.sync([], 'blocked'), { added: [], removed: [] });
  assert.deepEqual(roster.sync([], 'unavailable'), { added: [], removed: [] });
  assert.equal(roster.players.length, 2);
});

test('panes tile the frame exactly at odd sizes', () => {
  for (const [w, h] of [[201, 161], [320, 560], [901, 599], [1920, 1080]]) {
    for (let count = 1; count <= 4; count++) {
      const rects = layoutPanes(count, w, h);
      assert.ok(rects.length >= count);
      let area = 0;
      for (const r of rects) {
        assert.ok(r.x >= 0 && r.y >= 0 && r.x + r.width <= w && r.y + r.height <= h);
        area += r.width * r.height;
      }
      assert.equal(area, w * h, `${count} panes cover ${w}×${h}`);
    }
  }
});

test('two players split along the axis that keeps panes closest to the game view', () => {
  const wide = layoutPanes(2, 1920, 600);
  assert.equal(wide[1].x, 960, 'very wide frames split side by side');
  const landscape = layoutPanes(2, 900, 600);
  assert.equal(landscape[1].x, 450, 'landscape frames split side by side');
  const square = layoutPanes(2, 600, 600);
  assert.equal(square[1].y, 300, 'square frames stack');
  const tall = layoutPanes(2, 320, 560);
  assert.equal(tall[1].y, 280, 'portrait frames stack');
  assert.equal(layoutPanes(3, 900, 600).length, 4, 'three players use a 2×2 grid with a spare cell');
});