Back to Napplet Machines V8
SOURCE / PINNED RELEASE

Made of little things.

Napplet Machines V8

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

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

tests/local-multiplayer.mjs
import { mkdir } from 'node:fs/promises';

export default async ({ players, check }) => {
  const { page, frame } = players[0], element = await frame.frameElement();
  await mkdir('.napplet-space/promotion', { recursive: true });
  const resize = async (w, h) => {
    await page.setViewportSize({ width: Math.max(320, w), height: Math.max(300, h) });
    await element.evaluate((el, { w, h }) => Object.assign(el.style, {
      position: 'fixed', top: '0', left: '0', width: `${w}px`, height: `${h}px`,
      minHeight: '0', minWidth: '0', maxWidth: 'none', border: '0', zIndex: '9999',
    }), { w, h });
    await frame.waitForFunction(({ w, h }) => innerWidth === w && innerHeight === h, { w, h });
  };
  const facts = () => frame.locator('#scene').evaluate(el => ({ ...el.dataset }));
  const start = async () => {
    await frame.locator('#raceButton').click();
    await frame.waitForFunction(() => document.querySelector('#scene').dataset.countdown === '0');
  };
  const inside = async () => frame.locator('#scene').evaluate(el => {
    const s = el.dataset, r = el.getBoundingClientRect();
    return [1, 2].every(i => Number(s[`player${i}ScreenX`]) > r.width * 0.08 &&
      Number(s[`player${i}ScreenX`]) < r.width * 0.92 && Number(s[`player${i}ScreenY`]) > r.height * 0.2 &&
      Number(s[`player${i}ScreenY`]) < r.height * 0.8);
  });
  await resize(900, 600);
  await frame.locator('#addPlayer').click();
  check('adding player creates separate keyboard schemes',
    await frame.locator('#inputPlayer1').inputValue() === 'wasd' && await frame.locator('#inputPlayer2').inputValue() === 'arrows');
  check('a keyboard scheme cannot be selected by both players',
    await frame.locator('#inputPlayer2 option[value=wasd]').evaluate(el => el.disabled));
  await frame.locator('#carPlayer2').selectOption('2');
  await frame.locator('#gridSize').selectOption('2');
  // Use the level, walled course to make boost/recovery measurements repeatable.
  await frame.locator('#trackSelect').selectOption('1');
  await frame.locator('#trackSelect').dispatchEvent('change');
  await start();
  check('two humans replace AI in the two-car grid', (await frame.locator('#raceOrder').innerText()).includes('Player 2') && await frame.locator('#raceOrder li').count() === 2);
  check('both players have a HUD and boost meter', await frame.locator('#playerTwoHud').isVisible() && await frame.locator('#boostMeterTwo').getAttribute('value') === '1');
  await page.waitForTimeout(900);
  const initialSpan = Number((await facts()).cameraSpan);
  await page.keyboard.down('w'); await page.keyboard.down('a'); await page.keyboard.down('ShiftLeft');
  await page.keyboard.down('ArrowDown'); await page.keyboard.down('ArrowRight');
  await page.waitForTimeout(450);
  let s = await facts();
  check('WASD and arrows drive different cars simultaneously', Number(s.inputThrottle) === 1 && Number(s.inputTwoThrottle) === -1 && Number(s.inputSteer) === 1 && Number(s.inputTwoSteer) === -1);
  check('left Shift drains only player one boost', Number(s.localBoost) < 0.95 && Number(s.playerTwoBoost) > 0.999 && s.inputBoost === 'true' && s.inputTwoBoost === 'false');
  await page.keyboard.up('a'); await page.keyboard.up('ArrowRight');
  await page.waitForTimeout(1500);
  s = await facts();
  check('shared camera zooms out as players separate', s.cameraMode === 'shared' && Number(s.cameraSpan) > initialSpan + 2);
  check('separated players stay within padded screen bounds', await inside());
  await element.screenshot({ path: '.napplet-space/promotion/local-two-keyboards.png' });
  await page.keyboard.up('ShiftLeft'); await page.keyboard.up('w'); await page.keyboard.up('ArrowDown');
  const empty = Number((await facts()).localBoost);
  await frame.waitForFunction(empty => Number(document.querySelector('#scene').dataset.localBoost) > empty + 0.1, empty);
  check('released boost recharges', Number((await facts()).localBoost) > empty + 0.1);
  await page.keyboard.down('ArrowUp'); await page.keyboard.down('ShiftRight'); await page.keyboard.down('Enter');
  await page.waitForTimeout(100);
  s = await facts();
  check('arrow player drift and boost buttons do not affect WASD player', s.inputTwoBrake === 'true' && s.inputTwoBoost === 'true' && s.inputBrake === 'false' && s.inputBoost === 'false');
  await page.keyboard.up('Enter'); await page.keyboard.up('ShiftRight'); await page.keyboard.up('ArrowUp');
  await page.keyboard.press('Escape');
  check('Escape pauses both cars', await frame.locator('#pauseButton').innerText() === 'Resume');
  check('pause clears both input assignments', Number((await facts()).inputThrottle) === 0 && Number((await facts()).inputTwoThrottle) === 0);
  await frame.locator('#exitRace').click();

  // Standard gamepad samples are synthetic; this does not certify physical hardware.
  await frame.locator('#inputPlayer2').selectOption('controller');
  await frame.locator('#scene').focus();
  await frame.evaluate(() => {
    window.__pads = [];
    window.__pad = index => ({ index, id: `Local test pad ${index}`, mapping: 'standard', connected: true,
      axes: [0, 0], buttons: Array.from({ length: 17 }, () => ({ value: 0, pressed: false })) });
    window.__pads[3] = window.__pad(3);
    Object.defineProperty(navigator, 'getGamepads', { configurable: true, value: () => window.__pads });
  });
  const padButton = async (slot, button, value) => {
    await frame.evaluate(({ slot, button, value }) => { window.__pads[slot].buttons[button].value = value; }, { slot, button, value });
    await page.waitForTimeout(80);
  };
  const tap = async (slot, button) => { await padButton(slot, button, 1); await padButton(slot, button, 0); };
  await page.waitForTimeout(100); await tap(3, 0);
  check('controller joins only the player assigned to controller', await frame.locator('#inputPlayer1').inputValue() === 'wasd' && (await frame.locator('.local-player[data-player="2"]').innerText()).includes('Pad 4 connected'));
  await start();
  await padButton(3, 7, 0.75); await padButton(3, 2, 1);
  await page.keyboard.down('w');
  await page.waitForTimeout(300);
  s = await facts();
  check('keyboard and controller race simultaneously with separate boost', Number(s.inputThrottle) === 1 && Math.abs(Number(s.inputTwoThrottle) - 0.75) < 0.01 && s.inputBoost === 'false' && s.inputTwoBoost === 'true' && Number(s.playerTwoBoost) < 1);
  await page.keyboard.up('w');
  await frame.evaluate(() => { window.__pads[3] = null; });
  await page.waitForTimeout(100);
  check('controller loss pauses the shared race and clears both cars', await frame.locator('#pauseButton').innerText() === 'Resume' && Number((await facts()).inputTwoThrottle) === 0 && Number((await facts()).inputThrottle) === 0);
  await frame.evaluate(() => { window.__pads[3] = window.__pad(3); window.__pads[3].buttons[0].value = 1; });
  await page.waitForTimeout(120);
  check('replacement controller held button cannot reclaim the car', !(await frame.locator('.local-player[data-player="2"]').innerText()).includes('connected'));
  await padButton(3, 0, 0); await tap(3, 0);
  check('fresh press explicitly rejoins the same player', (await frame.locator('.local-player[data-player="2"]').innerText()).includes('Pad 4 connected'));
  await tap(3, 9);
  check('rejoined controller can resume both cars', await frame.locator('#pauseButton').innerText() === 'Pause');
  await frame.locator('#exitRace').click();
  await frame.locator('#inputPlayer1').selectOption('controller');
  await frame.locator('#scene').focus();
  await frame.evaluate(() => { window.__pads[7] = window.__pad(7); });
  await page.waitForTimeout(100); await tap(7, 0);
  await start();
  await padButton(7, 7, 0.6); await padButton(3, 7, 0.9);
  s = await facts();
  check('two controllers use distinct non-contiguous slots', Math.abs(Number(s.inputThrottle) - 0.6) < 0.01 && Math.abs(Number(s.inputTwoThrottle) - 0.9) < 0.01);
  await padButton(7, 7, 0); await padButton(3, 7, 0);
  await frame.locator('#exitRace').click();
  await frame.locator('#inputPlayer1').selectOption('wasd');
  await frame.locator('#inputPlayer2').selectOption('arrows');

  for (const [theme, colors] of Object.entries({ light: { background: '#fafafa', text: '#111111' }, dark: { background: '#111111', text: '#fafafa' } })) {
    await element.evaluate((el, colors) => el.contentWindow.postMessage({ type: 'theme.changed', theme: { colors } }, '*'), colors);
    for (const [w, h] of [[200, 160], [320, 560], [900, 600], [2400, 1200]]) {
      await resize(w, h);
      const overflow = await frame.evaluate(() => ({ width: innerWidth, scroll: document.documentElement.scrollWidth, elements: [...document.querySelectorAll('body *')].filter(el => el.getBoundingClientRect().right > innerWidth + 1).map(el => ({ id: el.id, tag: el.tagName, class: el.className, right: el.getBoundingClientRect().right })).slice(0, 15) }));
      if (overflow.scroll > overflow.width) { console.log('Overflow', overflow); await element.screenshot({ path: '.napplet-space/promotion/local-overflow.png' }); }
      check(`${theme} ${w}×${h} setup fits exact frame`, await frame.evaluate(() => document.documentElement.scrollWidth === innerWidth));
      if (w < 280) {
        check(`${theme} tiny frame shows resize notice`, await frame.locator('.too-small').isVisible());
        continue;
      }
      await frame.locator('#inputPlayer2').scrollIntoViewIfNeeded();
      check(`${theme} ${w}×${h} second player's setup is reachable`, await frame.locator('#inputPlayer2').isVisible());
      await start(); await page.waitForTimeout(400);
      check(`${theme} ${w}×${h} both HUDs fit without overlapping`, await frame.evaluate(() => {
        const a = document.querySelector('.hud-primary').getBoundingClientRect(), b = document.querySelector('#playerTwoHud').getBoundingClientRect();
        return a.left >= 0 && b.right <= innerWidth && a.right + 4 <= b.left;
      }));
      check(`${theme} ${w}×${h} shared camera keeps both cars visible`, await inside());
      check(`${theme} theme leaves the app-owned palette intact`, await frame.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--bg').trim() === '#282044'));
      await element.screenshot({ path: `.napplet-space/promotion/local-${theme}-${w}x${h}.png` });
      await frame.locator('#exitRace').click();
    }
  }
  await resize(900, 600);
  await frame.locator('#removePlayer').click();
  check('removing player returns to solo setup', await frame.locator('.local-player').count() === 1 && !(await frame.locator('#addPlayer').isDisabled()));
  await frame.locator('#inputPlayer1').selectOption('touch');
  const cdp = await page.context().newCDPSession(page);
  await cdp.send('Emulation.setTouchEmulationEnabled', { enabled: true, maxTouchPoints: 5 });
  for (const [w, h] of [[390, 844], [360, 640], [844, 390]]) {
    await resize(w, h); await start();
    const boost = await frame.locator('#touchBoost').boundingBox(), steering = await frame.locator('#steeringPad').boundingBox();
    check(`touch boost has a comfortable target ${w}×${h}`, boost.width >= 44 && boost.height >= 44);
    await cdp.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [
      { x: boost.x + boost.width / 2, y: boost.y + boost.height / 2, id: 0 },
      { x: steering.x + steering.width * 0.7, y: steering.y + steering.height * 0.6, id: 1 },
    ] });
    await page.waitForTimeout(250);
    s = await facts();
    check(`two thumbs steer and boost with forward gas ${w}×${h}`, Number(s.inputThrottle) === 1 && Number(s.inputSteer) < -0.1 && s.inputBoost === 'true' && Number(s.localBoost) < 1);
    await cdp.send('Input.dispatchTouchEvent', { type: 'touchCancel', touchPoints: [] });
    await page.waitForTimeout(100);
    check(`touch cancellation clears boost and steering ${w}×${h}`, (await facts()).inputBoost === 'false' && Number((await facts()).inputSteer) === 0 && await frame.locator('#touchBoost').getAttribute('aria-pressed') === 'false');
    await element.screenshot({ path: `.napplet-space/promotion/local-touch-${w}x${h}.png` });
    await frame.locator('#exitRace').click();
  }
  await cdp.detach();
};