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/edit-latency.mjs
// Real hosted UI + provider. Delays/faults wrap the SDK only in this trusted test.
import { writeFile } from 'node:fs/promises';
export async function instrument(frame, delay = 150) {
  await frame.evaluate((delay) => {
    const clock = () => performance.timeOrigin + performance.now();
    const wait = (ms) => new Promise(r => setTimeout(r, ms));
    const registry = window.napplet.cvm.registry;
    const original = registry.call.bind(registry);
    window.editProbe = { delay, offline: false, loseNext: false, calls: [], inputs: [], renders: [] };
    registry.call = async (family, tool, args, options) => {
      const p = window.editProbe;
      const call = { tool, args: structuredClone(args), start: clock() };
      p.calls.push(call);
      if (p.holdReads && ['readWorld','readUpdates'].includes(args.operation)) await new Promise(r => { p.releaseRead = r; });
      await wait(p.delay);
      if (p.offline) { call.error = 'offline'; throw Error('Test connection unavailable'); }
      const reply = await original(family, tool, args, options);
      call.providerAt = clock();
      call.reply = structuredClone(reply);
      await wait(p.delay);
      if (p.loseNext && ['editBlock', 'editBlocks'].includes(args.operation) && !reply.isError) {
        p.loseNext = false;
        if (p.offlineAfterLoss) p.offline = true;
        call.error = 'lost response';
        throw Error('Test lost reply after durable commit');
      }
      call.end = clock();
      return reply;
    };
    const canvas = document.querySelector('canvas');
    new MutationObserver(() => {
      const edits = Number(canvas.dataset.edits);
      const p = window.editProbe;
      if (p.renders.at(-1)?.edits !== edits) p.renders.push({ edits, at: clock() });
    }).observe(canvas, { attributes: true, attributeFilter: ['data-edits'] });
    const input = event => {
      if (event.type === 'keydown' && !['Space', 'KeyX'].includes(event.code)) return;
      if (event.type === 'pointerdown' && !['place', 'remove'].includes(event.target.closest('button')?.id)) return;
      window.editProbe.inputs.push({ at: clock(), type: event.type });
    };
    document.addEventListener('keydown', input, true);
    document.addEventListener('pointerdown', input, true);
  }, delay);
}
export default async ({ players, connectIdentity, approveBackendAccount, check, diagnostics }) => {
  const [a, b] = players;
  const cpuRate = Number(process.env.NAPCRAFT_CPU_RATE || 1);
  if (cpuRate !== 1) {
    const cdp = await b.page.context().newCDPSession(b.page);
    await cdp.send('Emulation.setCPUThrottlingRate',{rate:cpuRate});
  }
  await connectIdentity(a);
  await a.frame.locator('#create').click();
  await a.frame.locator('#name').fill('Responsiveness lab');
  await a.frame.locator('#confirmCreate').click();
  await approveBackendAccount(a, 'worlds');
  await a.frame.locator('#share').waitFor();
  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();
  await Promise.all(players.map(p => p.frame.waitForFunction(() => Number(document.querySelector('canvas').dataset.peerCount) === 1)));
  check('two independent players connected', true);
  await Promise.all(players.map(p => instrument(p.frame)));
  // Compare idle-network input in BOTH versions. Background-read overlap has its
  // own correctness/feedback test; the old app disabled these inputs entirely.
  await b.page.waitForTimeout(1000);
  const samples = [];
  let edits = 0;
  for (const mobile of [false, true]) {
    if (mobile) {
      await b.page.setViewportSize({ width: 390, height: 844 });
      await b.page.addStyleTag({content:'body>header,body>#status,body>#media{display:none!important}#stage{margin:0!important;padding:0!important;width:390px!important;height:844px!important}#stage iframe{width:390px!important;height:844px!important;min-height:0!important;border:0!important}'});
      await b.frame.waitForFunction(() => innerWidth === 390 && innerHeight === 844);
      const cdp = await b.page.context().newCDPSession(b.page);
      await cdp.send('Emulation.setTouchEmulationEnabled', { enabled: true });
    }
    for (let i = 0; i < 4; i++) {
      const remove = i % 2 === 1;
      await Promise.all(players.map(p => p.frame.waitForFunction(() => window.editProbe.calls.every(c => c.end !== undefined || c.error))));
      await b.frame.waitForFunction(() => !document.querySelector('#place').disabled);
      await b.frame.locator('canvas').focus();
      if (mobile) {
        const rect = await b.frame.locator(remove ? '#remove' : '#place').boundingBox();
        const cdp = await b.page.context().newCDPSession(b.page);
        await cdp.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:rect.x+rect.width/2,y:rect.y+rect.height/2}]});
        await cdp.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});
        await cdp.detach();
      } else await b.page.keyboard.press(remove ? 'x' : 'Space');
      edits++;
      await Promise.all(players.map(p => p.frame.waitForFunction(n => Number(document.querySelector('canvas').dataset.edits) === n, edits, {timeout:12000})));
      const local = await b.frame.evaluate(n => {
        const p = window.editProbe, input = p.inputs.at(-1).at;
        const command = p.calls.findLast(c => ['editBlock', 'editBlocks'].includes(c.args.operation));
        return {input, localMs:p.renders.find(r => r.edits === n).at-input, confirmedMs:command.end ? command.end-input : null,resultBytes:command.reply ? new TextEncoder().encode(JSON.stringify(command.reply.structuredContent.result)).length : null};
      }, edits);
      const remote = await a.frame.evaluate(n => window.editProbe.renders.find(r => r.edits === n).at, edits);
      // Optimistic rendering can precede confirmation: collect final acknowledgement too.
      await b.frame.waitForFunction(() => window.editProbe.calls.filter(c => ['editBlock','editBlocks'].includes(c.args.operation)).at(-1)?.end !== undefined);
      local.confirmedMs = await b.frame.evaluate(start => window.editProbe.calls.filter(c => ['editBlock','editBlocks'].includes(c.args.operation)).at(-1).end-start, local.input);
      samples.push({mobile,action:remove?'erase':'build',localMs:local.localMs,confirmedMs:local.confirmedMs,remoteMs:remote-local.input,resultBytes:local.resultBytes});
      console.log(JSON.stringify(samples.at(-1)));
      await b.page.waitForTimeout(900);
    }
  }
  const label = process.env.NAPCRAFT_BASELINE ? 'before' : `after${cpuRate === 1 ? '' : '-cpu'+cpuRate}`;
  await writeFile(`.napplet-space/edit-latency-${label}.json`, JSON.stringify({delayPerCvmLegMs:150,cpuRate,samples,diagnostics:await diagnostics()},null,2));
  check('local block feedback under 80 ms', samples.every(s => s.localMs < 80));
  check('remote committed blocks under 1300 ms', samples.every(s => s.remoteMs < 1300));
};