Back to Napcraft
SOURCE / PINNED RELEASE

Made of little things.

Napcraft

Release
ee47ac6a25df…
Author-recorded commit
410dea87e109…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-52f9e22f5ce

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

tests/capture.mjs
import {
  chromium,
  executable,
  frameOf,
  fitFrame,
  connectTestIdentity,
  approveLocalPrompts,
} from './browser-support.mjs';
import { startLocalRelay, useLocalRelay, browserArgs } from './local-relay.mjs';
import { mkdir, writeFile, readFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import { blockPoint } from './direct-building.mjs';
const artifactHash = createHash('sha256')
  .update(await readFile('dist/index.html'))
  .digest('hex');
const dir = '.napplet-space/promotion';
await mkdir(dir, { recursive: true });
const relay = await startLocalRelay();
let browser;
try {
  browser = await chromium.launch({
    executablePath: executable,
    headless: true,
    args: browserArgs,
  });
  const players = [];
  for (let i = 0; i < 2; i++) {
    const ctx = await browser.newContext({
      viewport: { width: 960, height: 600 },
      deviceScaleFactor: 1,
      ...(i === 0
        ? {
            recordVideo: {
              dir: `${dir}/raw`,
              size: { width: 960, height: 600 },
            },
          }
        : {}),
    });
    await useLocalRelay(ctx, relay.config, 0, 0);
    const page = await ctx.newPage();
    await page.goto(process.env.NAPCRAFT_URL || 'http://127.0.0.1:51696/');
    const stop = approveLocalPrompts(page);
    players.push({ page, frame: await frameOf(page), ctx, stop });
  }
  const [a, b] = players;
  await connectTestIdentity(a.page);
  await a.frame.locator('#create').click();
  await a.frame.locator('#name').fill('Cloudberry Club');
  await a.frame.locator('#confirmCreate').click();
  await a.frame.locator('#share').waitFor({ state: 'visible' });
  await a.frame.locator('#share').click();
  const code = await a.frame.locator('#shareCode').inputValue();
  await a.frame.locator('#close').click();
  await b.frame.locator('#join').click();
  await b.frame.locator('#code').fill(code);
  await b.frame.locator('#confirmJoin').click();
  await b.frame.locator('#share').waitFor({ state: 'visible' });
  await a.frame.waitForFunction(
    () => document.querySelector('canvas').dataset.peerCount === '1',
    {},
    { timeout: 30000 },
  );
  await b.frame.locator('canvas').focus();
  await b.page.keyboard.down('ArrowLeft');
  await b.page.waitForTimeout(650);
  await b.page.keyboard.up('ArrowLeft');
  await a.frame.getByRole('button', { name: 'Coral', exact: true }).click();
  await a.frame.locator('canvas').click({position:await blockPoint(a.frame,7,11)});
  await a.page.waitForTimeout(850);
  await a.frame.locator('canvas').click({position:await blockPoint(a.frame,7,11,4)});
  await a.page.waitForTimeout(900);
  await fitFrame(a.page, 1200, 750);
  await a.page.waitForTimeout(500);
  await a.page
    .locator('#stage iframe')
    .screenshot({ path: `${dir}/napcraft-cover.png` });
  await fitFrame(a.page, 390, 844);
  const touch = await a.ctx.newCDPSession(a.page);
  await touch.send('Emulation.setTouchEmulationEnabled', { enabled: true });
  await a.page.waitForTimeout(600);
  await a.page
    .locator('#stage iframe')
    .screenshot({ path: `${dir}/napcraft-portrait.png` });
  await touch.send('Emulation.setTouchEmulationEnabled', { enabled: false });
  await touch.detach();
  await fitFrame(a.page, 960, 600);
  await a.page.waitForTimeout(300);
  // The final seven seconds are the published clip candidate, all real gameplay.
  const start = Date.now();
  await a.frame.locator('canvas').focus();
  await a.page.keyboard.down('ArrowDown');
  await a.page.waitForTimeout(420);
  await a.page.keyboard.up('ArrowDown');
  await a.frame.getByRole('button', { name: 'Butter', exact: true }).click();
  await a.frame.locator('canvas').click({position:await blockPoint(a.frame,6,10)});
  await a.page.waitForTimeout(900);
  await a.frame.locator('canvas').click({position:await blockPoint(a.frame,6,10,4)});
  await a.page.waitForTimeout(900);
  await a.frame.getByRole('button', { name: 'Glacier', exact: true }).click();
  await a.frame.locator('canvas').click({position:await blockPoint(a.frame,6,10,5)});
  await b.frame.locator('canvas').focus();
  await b.page.keyboard.down('ArrowDown');
  await b.page.waitForTimeout(500);
  await b.page.keyboard.up('ArrowDown');
  await b.frame.getByRole('button', { name: 'Coral', exact: true }).click();
  await b.frame.locator('#place').click();
  await a.page.waitForTimeout(900);
  await a.frame.locator('canvas').click({position:await blockPoint(a.frame,6,10,6),button:'right'});
  await a.page.waitForTimeout(Math.max(0, 7000 - (Date.now() - start)));
  if (
    createHash('sha256')
      .update(await readFile('dist/index.html'))
      .digest('hex') !== artifactHash
  )
    throw Error('Build changed during capture');
  const video = a.page.video();
  for (const p of players) p.stop();
  await a.ctx.close();
  const raw = await video.path();
  await writeFile(
    `${dir}/capture.json`,
    JSON.stringify(
      {
        raw,
        artifactHash,
        trimLastSeconds: 7,
        viewport: [960, 600],
        world: 'Cloudberry Club',
        players: 2,
        network: 'local authenticated TURN, real soyLI host',
        caption:
          'A little island for big ideas. Wander, stack, and build together.',
      },
      null,
      2,
    ),
  );
  console.log(
    'Captured landscape cover, portrait image and raw two-player gameplay',
    raw,
  );
} finally {
  await browser?.close();
  await relay.close();
}