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-correctness.mjs
import { instrument } from './edit-latency.mjs';
import {readFile,writeFile} from 'node:fs/promises';
const context = JSON.parse(await readFile('.napplet-space/soy-backend.json','utf8'));
const rendered = (p,n) => p.frame.waitForFunction(n => Number(document.querySelector('canvas').dataset.confirmedEdits) === n && document.querySelector('#app').dataset.pending === '0',n,{timeout:20000});
const key = async (p,code) => {
  await p.frame.locator('canvas').focus(); await p.page.keyboard.press(code);
};
export default async ({players,connectIdentity,approveBackendAccount,check,diagnostics}) => {
  const [a,b]=players;
  await connectIdentity(a);
  await a.frame.locator('#create').click();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();
  const [release,instance]=JSON.parse(Buffer.from(code.slice(4),'base64url'));
  const target={module:{napplet:context.napplet,name:'worlds'},release,instance};
  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(()=>document.querySelector('canvas').dataset.peerCount==='1')));
  await Promise.all(players.map(p=>instrument(p.frame,150)));
  const invoke=(p,operation,input)=>p.frame.evaluate(async ({target,operation,input})=>window.napplet.cvm.registry.call('soy.backends.v1','soy_backend_invoke',{target,operation,input,requestId:crypto.randomUUID(),expiresAt:Math.floor(Date.now()/1000)+240}),{target,operation,input});
  for(let i=0;i<12;i++)await key(b,i%2?'x':'Space');
  await b.frame.waitForFunction(()=>document.querySelector('canvas').dataset.edits==='12',null,{timeout:1000});
  check('rapid inputs render while saves are in flight',Number(await b.frame.locator('#app').getAttribute('data-pending'))>0);
  await Promise.all(players.map(p=>rendered(p,12)));
  const batches=await b.frame.evaluate(()=>window.editProbe.calls.filter(c=>c.args.operation==='editBlocks'));
  check('rapid edits are compact atomic batches',batches.length<12&&batches.some(c=>c.args.input.edits.length>1));
  check('provider confirms every accepted burst edit',(await invoke(a,'readWorld',{})).structuredContent.result.edits===12);

  // Hold a real background read at the SDK boundary. Input must still reach the canvas.
  await a.frame.evaluate(()=>{window.editProbe.holdReads=true;document.dispatchEvent(new Event('visibilitychange'));});
  await a.frame.waitForFunction(()=>typeof window.editProbe.releaseRead==='function');
  await key(a,'Space');
  await a.frame.waitForFunction(()=>document.querySelector('canvas').dataset.edits==='13');
  check('background read never disables building',await a.frame.locator('#place').isEnabled());
  await a.frame.evaluate(()=>{window.editProbe.holdReads=false;window.editProbe.releaseRead();});
  await Promise.all(players.map(p=>rendered(p,13)));

  await Promise.all(players.map(p=>key(p,'Space')));
  await Promise.all(players.map(p=>rendered(p,14)));
  const conflicts=await Promise.all(players.map(p=>p.frame.evaluate(()=>window.editProbe.calls.filter(c=>c.reply?.isError&&c.reply?.structuredContent?.error?.code==='CONFLICT').length)));
  check('simultaneous same-cell edits converge with one winner',conflicts.reduce((x,y)=>x+y,0)>=1&&(await invoke(a,'readWorld',{})).structuredContent.result.edits===14);

  // Failure before sending: queued predictions are visible, durable state stays unchanged.
  await b.frame.evaluate(()=>{window.editProbe.offline=true;});
  await key(b,'x');await key(b,'Space');await key(b,'x');
  await b.frame.waitForFunction(()=>document.querySelector('#app').dataset.sync==='retry');
  await b.page.locator('#stage iframe').screenshot({path:'.napplet-space/promotion/shared-edit-pending.png'});
  check('offline queue stays explicitly unconfirmed',await b.frame.locator('#app').getAttribute('data-pending')==='3');
  check('offline inputs do not masquerade as durable commits',(await invoke(a,'readWorld',{})).structuredContent.result.edits===14);
  await b.frame.evaluate(()=>{window.editProbe.offline=false;window.dispatchEvent(new Event('online'));});
  await Promise.all(players.map(p=>rendered(p,17)));
  const retryCalls=await b.frame.evaluate(()=>window.editProbe.calls.filter(c=>c.args.operation==='editBlocks'&&c.error==='offline').map(c=>c.args));
  const all=await b.frame.evaluate(()=>window.editProbe.calls.filter(c=>c.args.operation==='editBlocks'&&c.end).map(c=>c.args));
  check('reconnect retries the original request unchanged',retryCalls.some(c=>all.some(a=>JSON.stringify(a)===JSON.stringify(c))));

  // Commit succeeded, response lost. Reload must recover its original receipt, not replay it.
  await b.frame.evaluate(()=>{window.editProbe.loseNext=true;window.editProbe.offlineAfterLoss=true;});
  await key(b,'Space');
  await b.frame.waitForFunction(()=>document.querySelector('#app').dataset.sync==='retry');
  const lost=await b.frame.evaluate(()=>window.editProbe.calls.findLast(c=>c.error==='lost response').args);
  check('lost acknowledgement leaves a recoverable pending intent',Boolean(await b.frame.evaluate(async()=>JSON.parse(await window.napplet.storage.getItem('napcraft-edit-journal-v1'))?.active?.intent?.requestId)));
  await b.page.reload();
  b.frame=await(await b.page.locator('#stage iframe').elementHandle()).contentFrame();
  await b.frame.locator('#app[data-ready=true]').waitFor();
  await instrument(b.frame,150);
  await b.frame.locator('#recover').click();await b.frame.locator('#retrySave').click();
  await rendered(b,18);
  const recovered=await b.frame.evaluate(()=>window.editProbe.calls.find(c=>['editBlock','editBlocks'].includes(c.args.operation)).args);
  check('reload retries identical ID, expiry and payload',JSON.stringify(recovered)===JSON.stringify(lost));
  check('lost acknowledgement is committed exactly once',(await invoke(a,'readWorld',{})).structuredContent.result.edits===18);

  // Close peer transport: durable polling must still deliver changes.
  await b.frame.waitForFunction(()=>document.querySelector('canvas').dataset.peerCount==='1');
  const diag=await diagnostics();
  await b.frame.evaluate(id=>window.napplet.webrtc.close(id,'Test peer outage'),diag[1][0].session);
  await key(b,'x');
  await Promise.all(players.map(p=>rendered(p,19)));
  check('peer outage retains persistence and polling fallback',true);
  const unauthorized=await invoke(b,'setLocked',{locked:true});
  check('guest still cannot change owner permissions',unauthorized.isError===true);
  await a.frame.locator('#share').click();await a.frame.locator('#lock').click();
  await b.frame.waitForFunction(()=>document.querySelector('#saveStatus').textContent.includes('paused'));
  const denied=await invoke(b,'editBlocks',{after:19,edits:[{x:9,y:4,z:10,expected:0,block:5}]});
  check('paused world rejects shared edit batches',denied.isError===true);
  await a.frame.locator('#share').click();await a.frame.locator('#lock').click();
  await b.frame.waitForFunction(()=>!document.querySelector('#saveStatus').textContent.includes('paused'));
  await Promise.all(players.map(p=>p.page.reload()));
  b.frame=await(await b.page.locator('#stage iframe').elementHandle()).contentFrame();
  await b.frame.locator('#app[data-ready=true]').waitFor();
  await b.frame.locator('#join').click();await b.frame.locator('#code').fill(code);await b.frame.locator('#confirmJoin').click();
  await rendered(b,19);
  check('world survives all players leaving after recovery and conflicts',true);
  // Independent columns: instance-wide contention must not discard a still-valid edit.
  a.frame=await(await a.page.locator('#stage iframe').elementHandle()).contentFrame();
  await a.frame.locator('#app[data-ready=true]').waitFor();
  await a.frame.locator('#join').click();await a.frame.locator('#code').fill(code);await a.frame.locator('#confirmJoin').click();
  await rendered(a,19);
  const aim=async(p,x,z)=>{
    const point=await p.frame.evaluate(({x,z})=>{
      const canvas=document.querySelector('canvas'),w=canvas.clientWidth,h=canvas.clientHeight;
      const unit=Math.max(10,Math.min(w<600?w/26:w/43,h/29,33));
      return {x:w/2+(x-z)*unit,y:h*.48+(x+z+1-17)*unit*.51-3*unit*.94};
    },{x,z});
    await p.frame.locator('canvas').click({position:point,modifiers:['Shift']});
    await p.page.waitForTimeout(30);
    const selected=await p.frame.locator('canvas').getAttribute('data-selected');
    check('aimed independent column '+x+','+z,selected===`${x},${z}`);
  };
  await aim(a,2,8);await aim(b,3,8);
  await Promise.all(players.map(p=>key(p,'Space')));
  await Promise.all(players.map(p=>rendered(p,21)));
  check('simultaneous independent columns both persist after revalidation',true);
  // Simulate a fresh transport restoring host storage from a previous guest tab.
  // The provider's receipt key includes the principal: never blindly replay after ABA.
  await instrument(b.frame,150);
  await aim(b,4,8);
  await b.frame.evaluate(()=>{window.editProbe.loseNext=true;window.editProbe.offlineAfterLoss=true;});
  await key(b,'Space');
  await b.frame.waitForFunction(()=>document.querySelector('#app').dataset.sync==='retry');
  const oldJournal=await b.frame.evaluate(()=>window.napplet.storage.getItem('napcraft-edit-journal-v1'));
  let undone;
  for(let i=0;i<3;i++) {
    undone=await invoke(a,'editBlock',{x:4,y:3,z:8,expected:4,block:0});
    if(!undone.isError)break;
    await a.page.waitForTimeout(300);
  }
  check('another player can undo a committed edit whose acknowledgement was lost',!undone.isError);
  await a.frame.evaluate(value=>window.napplet.storage.setItem('napcraft-edit-journal-v1',value),oldJournal);
  await a.page.reload();
  a.frame=await(await a.page.locator('#stage iframe').elementHandle()).contentFrame();
  await a.frame.locator('#app[data-ready=true]').waitFor();await instrument(a.frame,150);
  await a.frame.locator('#recover').click();await a.frame.locator('#retrySave').click();await a.frame.locator('#share').waitFor();
  await rendered(a,23);
  check('changed transport refreshes without replaying an already-undone edit',
    await a.frame.evaluate(()=>!window.editProbe.calls.some(c=>['editBlock','editBlocks'].includes(c.args.operation))));
  const legacy=JSON.parse(oldJournal).active.intent;
  legacy.operation='editBlock';legacy.input={x:4,y:3,z:8,expected:0,block:4};
  await a.frame.evaluate(value=>window.napplet.storage.setItem('napcraft-pending-v1',value),JSON.stringify(legacy));
  await a.page.reload();a.frame=await(await a.page.locator('#stage iframe').elementHandle()).contentFrame();
  await a.frame.locator('#app[data-ready=true]').waitFor();await instrument(a.frame,150);
  await a.frame.locator('#recover').click();await a.frame.locator('#retrySave').click();await a.frame.locator('#share').waitFor();await rendered(a,23);
  check('legacy pending save without a transport identity is refreshed, never replayed',
    await a.frame.evaluate(()=>!window.editProbe.calls.some(c=>['editBlock','editBlocks'].includes(c.args.operation))));
  await writeFile('.napplet-space/edit-correctness.json',JSON.stringify({passed:true,edits:23,code},null,2));
};