Back to Napplet Machines V8
SOURCE / PINNED RELEASE

Made of little things.

Napplet Machines V8

Release
b3f614e739f0…
Author-recorded commit
534f19acb4ba…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-b5572362d4a

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

tests/controllers.mjs
import { mkdir } from 'node:fs/promises';

export default async ({ players, check }) => {
  const { page, frame } = players[0],
    element = await frame.frameElement();
  await page.setViewportSize({ width: 1200, height: 750 });
  await element.evaluate((el) =>
    Object.assign(el.style, {
      position: 'fixed',
      top: 0,
      left: 0,
      width: '1200px',
      height: '750px',
      minHeight: 0,
      border: 0,
      zIndex: 9999,
    }),
  );
  await frame.waitForFunction(() => innerWidth === 1200 && innerHeight === 750);
  await frame.evaluate(() => document.fonts.ready);
  await mkdir('.napplet-space/promotion', { recursive: true });
  await element.screenshot({
    path: '.napplet-space/promotion/island-overview.png',
  });
  const native = await frame.evaluate(() => {
    try {
      return (
        typeof navigator.getGamepads === 'function' &&
        Array.isArray(navigator.getGamepads())
      );
    } catch {
      return false;
    }
  });
  check('native Gamepad API is callable in the opaque hosted frame', native);
  await frame.locator('#scene').focus();
  await frame.evaluate(() => {
    window.__originalGamepads = navigator.getGamepads.bind(navigator);
    window.__pads = [];
    window.__pad = (index, mapping = 'standard') => ({
      index,
      id: `Synthetic pad ${index}`,
      mapping,
      connected: true,
      axes: [0, 0],
      buttons: Array.from({ length: 17 }, () => ({
        value: 0,
        pressed: false,
        touched: false,
      })),
    });
    window.__pads[3] = window.__pad(3);
    Object.defineProperty(navigator, 'getGamepads', {
      configurable: true,
      value: () => window.__pads,
    });
  });
  const button = async (index, value, slot = 3) => {
    await frame.evaluate(
      ({ index, value, slot }) => {
        window.__pads[slot].buttons[index].value = value;
      },
      { index, value, slot },
    );
    await page.waitForTimeout(65);
  };
  const tap = async (index, slot = 3) => {
    await button(index, 1, slot);
    await button(index, 0, slot);
  };
  await page.waitForTimeout(100);
  await tap(0);
  check(
    'explicit controller claim uses browser slot 3',
    (await frame.locator('#controllerState').innerText()) === 'PAD 4',
  );
  check(
    'claim does not accidentally start a race',
    await frame.locator('#raceButton').isVisible(),
  );
  await tap(5);
  check(
    'controller chooses a car in the garage',
    (await frame.locator('.car-name').innerText()) === 'Bumble',
  );
  await tap(4);
  await tap(13);
  check(
    'controller chooses a course',
    (await frame.locator('#trackSelect').inputValue()) === '1',
  );
  await tap(12);
  await frame.evaluate(() => {
    window.__pads[1] = window.__pad(1);
  });
  await page.waitForTimeout(80);
  await tap(0, 1);
  check(
    'second controller cannot steal the active slot',
    (await frame.locator('#controllerState').innerText()) === 'PAD 4',
  );
  await frame.locator('#gridSize').selectOption('2');
  await frame.locator('#scene').focus();
  await tap(0);
  await frame.waitForFunction(
    () => document.querySelector('#scene').dataset.countdown === '0',
  );
  check('controller starts a race', true);
  await frame.evaluate(() => {
    window.__pads[3].axes[0] = -0.5;
    window.__pads[3].buttons[7].value = 0.65;
  });
  await frame.waitForFunction(() => {
    const s = document.querySelector('#scene').dataset;
    return (
      Number(s.inputSteer) > 0.3 &&
      Number(s.inputSteer) < 0.5 &&
      Math.abs(Number(s.inputThrottle) - 0.65) < 0.01 &&
      Number(s.localSpeed) > 1
    );
  });
  check('analog stick and trigger drive the actual simulation', true);
  await button(1, 1);
  await frame.waitForFunction(
    () => document.querySelector('#scene').dataset.inputBrake === 'true',
  );
  check('controller face button engages drift', true);
  await button(1, 0);
  await frame.evaluate(() => {
    window.__pads[3].axes[0] = 0.08;
    window.__pads[3].buttons[7].value = 0;
    window.__pads[3].buttons[6].value = 0.45;
  });
  await frame.waitForFunction(() => {
    const s = document.querySelector('#scene').dataset;
    return (
      Number(s.inputSteer) === 0 &&
      Math.abs(Number(s.inputThrottle) + 0.45) < 0.01
    );
  });
  check('stick dead zone and analog service braking work in the sandbox', true);
  await button(9, 1);
  await page.waitForTimeout(300);
  check(
    'held Menu pauses only once',
    (await frame.locator('#pauseButton').innerText()) === 'Resume',
  );
  await button(9, 0);
  await tap(9);
  check(
    'fresh Menu press resumes',
    (await frame.locator('#pauseButton').innerText()) === 'Pause',
  );
  await frame.evaluate(() => {
    window.__pads[3] = null;
  });
  await frame.waitForFunction(
    () => document.querySelector('#pauseButton').textContent === 'Resume',
  );
  check(
    'disconnect neutralizes input and pauses local play',
    Number(
      await frame.locator('#scene').getAttribute('data-input-throttle'),
    ) === 0,
  );
  await page.keyboard.press('Escape');
  await page.keyboard.press('Escape');
  await frame.waitForFunction(
    () => document.querySelector('#countdown').textContent === 'PAUSED',
  );
  check(
    'keyboard can take over pause after controller loss',
    (await frame.locator('#pauseButton').innerText()) === 'Resume',
  );
  await frame.evaluate(() => {
    window.__pads[3] = window.__pad(3);
    window.__pads[3].buttons[0].value = 1;
  });
  await page.waitForTimeout(100);
  check(
    'reconnected held button cannot claim or resume',
    (await frame.locator('#controllerState').innerText()) !== 'PAD 4' &&
      (await frame.locator('#pauseButton').innerText()) === 'Resume',
  );
  await button(0, 0);
  await tap(0);
  await tap(9);
  check(
    'deliberate rejoin and resume work',
    (await frame.locator('#controllerState').innerText()) === 'PAD 4' &&
      (await frame.locator('#pauseButton').innerText()) === 'Pause',
  );
  await tap(9);
  await tap(1);
  await frame.waitForFunction(
    () => document.querySelector('#scene').dataset.racing === 'false',
  );
  check('controller can exit a paused race to the garage', true);
  await frame.evaluate(() => {
    window.__pads = [];
    window.__pads[2] = window.__pad(2, '');
  });
  await page.waitForTimeout(100);
  await tap(0, 2);
  check(
    'unknown mappings keep keyboard and touch fallback',
    (await frame.locator('#controllerState').innerText()) === 'UNMAPPED PAD' &&
      (await frame.locator('#raceButton').isEnabled()),
  );
  await frame.evaluate(() =>
    Object.defineProperty(navigator, 'getGamepads', {
      configurable: true,
      value: () => {
        throw new DOMException('Not permitted', 'SecurityError');
      },
    }),
  );
  await page.waitForTimeout(100);
  check(
    'blocked gamepad API degrades without a crash',
    (await frame.locator('#controllerState').innerText()) === 'PAD BLOCKED' &&
      (await frame.locator('#raceButton').isEnabled()),
  );
  await frame.evaluate(() =>
    Object.defineProperty(navigator, 'getGamepads', {
      configurable: true,
      value: window.__originalGamepads,
    }),
  );
};