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.
import {
connectTestIdentity,
approveLocalPrompts,
} from './browser-support.mjs';
export default async ({ players, check, measure, diagnostics, connectIdentity, approveBackendAccount }) => {
const [a, b] = players;
for (const p of players)
await p.frame.evaluate(() => {
window.testRtc = [];
window.napplet.webrtc.onEvent((e) => window.testRtc.push(e));
});
const stopA = connectIdentity ? () => {} : approveLocalPrompts(a.page),
stopB = connectIdentity ? () => {} : approveLocalPrompts(b.page);
try {
if (connectIdentity) await connectIdentity(a);
else await connectTestIdentity(a.page);
await a.frame.locator('#create').click();
await a.frame.locator('#name').fill('Two friends test');
await a.frame.locator('#confirmCreate').click();
if (approveBackendAccount) await approveBackendAccount(a,'worlds');
await a.frame
.locator('#share')
.waitFor({ state: 'visible', timeout: 20000 });
await a.frame.locator('#share').click();
const code = await a.frame.locator('#shareCode').inputValue();
check('persistent world code produced', code.startsWith('NC1-'));
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' });
try {
await a.frame.waitForFunction(
() => document.querySelector('canvas')?.dataset.peerCount === '1',
{},
{ timeout: 30000 },
);
} catch (e) {
for (const p of players)
console.log(
'DEBUG',
await p.frame.evaluate(() => ({
events: window.testRtc,
body: document.body.innerText,
})),
);
console.log('DIAG', JSON.stringify(await diagnostics()));
throw e;
}
await b.frame.waitForFunction(
() => document.querySelector('canvas')?.dataset.peerCount === '1',
);
check(
'two independent players see each other',
(await diagnostics()).every((rows) =>
rows.some((p) => p.state === 'connected'),
),
);
const latency = await b.frame.evaluate(async () => {
const canvas = document.querySelector('canvas'),
before = canvas.dataset.localX,
start = performance.now();
canvas.dispatchEvent(
new KeyboardEvent('keydown', { code: 'KeyD', bubbles: true }),
);
await new Promise((resolve, reject) => {
const poll = () => {
if (canvas.dataset.localX !== before) resolve();
else if (performance.now() - start > 2000)
reject(Error('Player did not move'));
else requestAnimationFrame(poll);
};
requestAnimationFrame(poll);
});
canvas.dispatchEvent(
new KeyboardEvent('keyup', { code: 'KeyD', bubbles: true }),
);
return performance.now() - start;
});
measure('guest input to rendered movement', latency, 70);
const guestBefore = Number(await b.frame.locator('canvas').getAttribute('data-local-x'));
await b.frame.locator('canvas').focus();
await b.page.keyboard.down('ArrowRight');
await b.page.waitForTimeout(500);
await b.page.keyboard.up('ArrowRight');
await b.page.waitForTimeout(600);
check(
'host sees moved guest',
JSON.parse(
await a.frame.locator('canvas').getAttribute('data-peer-positions'),
)[0][1] > guestBefore + 0.5,
);
await b.frame.locator('#place').click();
await b.frame.waitForFunction(
() => document.querySelector('canvas').dataset.edits === '1',
);
await a.frame.waitForFunction(
() => document.querySelector('canvas').dataset.edits === '1',
);
check('guest block reaches creator', true);
await b.page.waitForTimeout(850);
await b.frame.locator('#remove').click();
await a.frame.waitForFunction(
() => document.querySelector('canvas').dataset.edits === '2',
);
check('guest removal reaches creator', true);
await a.frame.locator('#share').click();
await a.frame.locator('#lock').click();
await b.page.waitForTimeout(3000);
check(
'owner can pause building',
(await b.frame.locator('#saveStatus').innerText()).includes('paused'),
);
await a.frame.locator('#share').click();
await a.frame.locator('#lock').click();
await b.page.waitForTimeout(3000);
check(
'owner can resume building',
!(await b.frame.locator('#saveStatus').innerText()).includes('paused'),
);
// Leave both sessions, then rejoin with the code. Persistence is independent of peers.
await Promise.all([a.page.reload(), b.page.reload()]);
for (const p of [a, b]) {
await p.page.locator('iframe').waitFor();
p.frame = await (
await p.page.locator('iframe').elementHandle()
).contentFrame();
await p.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 b.frame.locator('#share').waitFor({ state: 'visible' });
check(
'world survives everyone leaving',
(await b.frame.locator('canvas').getAttribute('data-edits')) === '2',
);
} finally {
stopA();
stopB();
}
};
